| @@ -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() : '', |
| @@ -160,9 +174,11 @@ | ||
| 160 | 174 | 'default_customer_is_cashier' => $settings->default_customer_is_cashier(), |
| 161 | 175 | 'restore_stock_on_delete' => $settings->restore_stock_on_delete_enabled(), |
| 162 | 176 | 'storefront_receipt_enabled' => wp_validate_boolean( $settings->get_settings( 'general', 'storefront_receipt_enabled' ) ), |
| 163 | 177 | 'barcode_field' => $defaults['barcode_field'] === $settings->barcode_field() ? 'default' : 'custom', |
| 164 | - 'tracking_consent' => $settings->tracking_consent(), | |
| 178 | + // Report the persisted answer, so the property cannot describe a | |
| 179 | + // consent state the merchant never chose. | |
| 180 | + 'tracking_consent' => $settings->raw_tracking_consent(), | |
| 165 | 181 | 'enabled_gateway_count' => \count( (array) ( ( new Landing_Profile() )->get_metrics()['active_gateways'] ?? array() ) ), |
| 166 | 182 | ); |
| 167 | 183 | |
| 168 | 184 | /** |
| @@ -189,8 +205,41 @@ | ||
| 189 | 205 | } |
| 190 | 206 | } |
| 191 | 207 | |
| 192 | 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'; | |
| 193 | 242 | } |
| 194 | 243 | |
| 195 | 244 | /** |
| 196 | 245 | * Get the WooCommerce version, or an empty string when WC is unavailable. |