| @@ -51,8 +51,15 @@ | ||
| 51 | 51 | */ |
| 52 | 52 | const OVERFLOW_BAND = '1000+'; |
| 53 | 53 | |
| 54 | 54 | /** |
| 55 | + * Pro-fit thresholds — see pro_fit_segment() for where the numbers come from. | |
| 56 | + */ | |
| 57 | + const MULTI_REGISTER_MIN_POS_USERS = 4; | |
| 58 | + const MULTI_GATEWAY_MIN_GATEWAYS = 3; | |
| 59 | + const HIGH_VOLUME_ORDER_COUNT_BANDS = array( '101-1000', '1000+' ); | |
| 60 | + | |
| 61 | + /** | |
| 55 | 62 | * Classes that indicate a multi-currency plugin is running. |
| 56 | 63 | * |
| 57 | 64 | * WooCommerce core has no multi-currency concept, so presence has to be |
| 58 | 65 | * inferred from the well-known implementations. This is a best-effort |
| @@ -86,9 +93,16 @@ | ||
| 86 | 93 | 'wp_version' => get_bloginfo( 'version' ), |
| 87 | 94 | 'wc_version' => $this->get_wc_version(), |
| 88 | 95 | 'mysql_version' => $this->get_mysql_version(), |
| 89 | 96 | 'wcpos_version' => PLUGIN_VERSION, |
| 90 | - 'wcpos_edition' => class_exists( '\WCPOS\WooCommercePOSPro\WooCommercePOSPro' ) ? 'pro' : 'free', | |
| 97 | + 'wcpos_edition' => wcpos_is_pro_active() ? 'pro' : 'free', | |
| 98 | + 'pro_license_active' => \WCPOS\WooCommercePOS\Templates::is_pro_license_active(), | |
| 99 | + 'pro_fit_segment' => self::pro_fit_segment( | |
| 100 | + wcpos_is_pro_active(), | |
| 101 | + (int) ( $metrics['pos_user_count'] ?? 0 ), | |
| 102 | + \count( (array) ( $metrics['active_gateways'] ?? array() ) ), | |
| 103 | + self::band( (int) ( $metrics['order_count'] ?? 0 ) ) | |
| 104 | + ), | |
| 91 | 105 | |
| 92 | 106 | // Locale and market. |
| 93 | 107 | 'wc_country' => $this->get_base_country(), |
| 94 | 108 | 'wc_currency' => function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : '', |
| @@ -191,8 +205,41 @@ | ||
| 191 | 205 | } |
| 192 | 206 | } |
| 193 | 207 | |
| 194 | 208 | return self::OVERFLOW_BAND; |
| 209 | + } | |
| 210 | + | |
| 211 | + /** | |
| 212 | + * Classify a store by how closely it resembles the stores that buy Pro. | |
| 213 | + * | |
| 214 | + * One label per site, strongest signal wins. It exists so PostHog can | |
| 215 | + * split free stores into a handful of named segments without anyone | |
| 216 | + * re-deriving the cut-offs from raw properties in every insight. | |
| 217 | + * | |
| 218 | + * Thresholds come from a 2026-09-04 ClickHouse analysis of free-plugin sites | |
| 219 | + * with a known domain (about 190 with profile data). Share of sites holding a | |
| 220 | + * Pro license: 57-100% at pos_user_count >= 4 vs 11-18% below; 85-100% at | |
| 221 | + * gateway_count >= 3 vs 24% at 2; 41-53% at order_count_band 101-1000 / | |
| 222 | + * 1000+ vs 11-30% below. Baseline across all sites was about 9-17%. | |
| 223 | + * | |
| 224 | + * @param bool $is_pro Whether WCPOS Pro is active. | |
| 225 | + * @param int $pos_user_count Number of POS users. | |
| 226 | + * @param int $gateway_count Number of active gateways. | |
| 227 | + * @param string $order_count_band Banded order count. | |
| 228 | + * | |
| 229 | + * @return string One of pro, multi_register, multi_gateway, high_volume, starter. | |
| 230 | + */ | |
| 231 | + public static function pro_fit_segment( bool $is_pro, int $pos_user_count, int $gateway_count, string $order_count_band ): string { | |
| 232 | + if ( $is_pro ) { | |
| 233 | + return 'pro'; | |
| 234 | + } | |
| 235 | + if ( $pos_user_count >= self::MULTI_REGISTER_MIN_POS_USERS ) { | |
| 236 | + return 'multi_register'; | |
| 237 | + } | |
| 238 | + if ( $gateway_count >= self::MULTI_GATEWAY_MIN_GATEWAYS ) { | |
| 239 | + return 'multi_gateway'; | |
| 240 | + } | |
| 241 | + return \in_array( $order_count_band, self::HIGH_VOLUME_ORDER_COUNT_BANDS, true ) ? 'high_volume' : 'starter'; | |
| 195 | 242 | } |
| 196 | 243 | |
| 197 | 244 | /** |
| 198 | 245 | * Get the WooCommerce version, or an empty string when WC is unavailable. |