created_at === null) { return self::LEGACY; } if ($user->created_at->lt(CarbonImmutable::parse($startedAt))) { return self::LEGACY; } return crc32('price:'.$user->getKey()) % 2 === 0 ? self::CONTROL : self::HIGH; } /** * The plans config with the user's variant applied: price, original_price and * Stripe lookup key. Feeds both the shared pricing prop and checkout, so what * is shown is always what is charged. Guests get the control config. * * @return array> */ public static function plansFor(?User $user): array { $plans = (array) config('subscriptions.plans', []); if ($user === null) { return $plans; } $overrides = (array) config('subscriptions.price_experiment.variants.'.self::variantFor($user), []); foreach ($overrides as $planKey => $override) { if (! isset($plans[$planKey])) { continue; } $plans[$planKey]['price'] = $override['price']; $plans[$planKey]['original_price'] = $override['original_price'] ?? null; $plans[$planKey]['stripe_lookup_key'] = $override['lookup']; } return $plans; } /** * Stripe lookup key to charge for a plan, resolved from the user's variant * server-side and never from the request, so nobody can pick the cheap price. */ public static function lookupKeyFor(User $user, string $planKey): string { return (string) (self::plansFor($user)[$planKey]['stripe_lookup_key'] ?? ''); } }