> */ public function plans(): array { $plans = config('academy_billing.plans', []); return is_array($plans) ? $plans : []; } public function normalizePlanKey(?string $planKey): string { return Str::of((string) $planKey) ->trim() ->lower() ->replace('-', '_') ->value(); } /** * @return array|null */ public function plan(?string $planKey): ?array { $normalized = $this->normalizePlanKey($planKey); if ($normalized === '') { return null; } $plan = Arr::get($this->plans(), $normalized); if (! is_array($plan)) { return null; } $plan['key'] = $normalized; $plan['tier'] = $this->normalizeTier((string) ($plan['tier'] ?? 'free')); $plan['interval'] = Str::lower(trim((string) ($plan['interval'] ?? 'monthly'))); $plan['amount'] = trim((string) ($plan['amount'] ?? '')); $plan['currency'] = Str::upper(trim((string) ($plan['currency'] ?? config('cashier.currency', 'EUR')))); $plan['stripe_price_id'] = trim((string) ($plan['stripe_price_id'] ?? '')); $plan['configured'] = $plan['stripe_price_id'] !== ''; $plan['price_id_valid'] = $this->isValidPriceId($plan['stripe_price_id']); $plan['price_display'] = $plan['amount'] !== '' ? $plan['amount'].' '.$plan['currency'] : null; return $plan; } /** * @return array|null */ public function planForPriceId(?string $priceId): ?array { $priceId = trim((string) $priceId); if ($priceId === '') { return null; } foreach (array_keys($this->plans()) as $planKey) { $plan = $this->plan((string) $planKey); if ($plan !== null && ($plan['stripe_price_id'] ?? null) === $priceId) { return $plan; } } return null; } /** * @return array */ public function missingPriceIds(?string $planKey = null): array { if ($planKey !== null) { $plan = $this->plan($planKey); return $plan !== null && ! ($plan['configured'] ?? false) ? [$this->normalizePlanKey($planKey)] : []; } return collect(array_keys($this->plans())) ->filter(fn (string $key): bool => ! ((bool) ($this->plan($key)['configured'] ?? false))) ->values() ->all(); } public function assertConfigured(?string $planKey = null): void { if (app()->environment(['local', 'testing'])) { return; } $missingPlans = $this->missingPriceIds($planKey); if ($missingPlans === []) { return; } throw new RuntimeException('Academy billing price IDs are missing for: '.implode(', ', $missingPlans)); } public function normalizeTier(string $tier): string { return match (Str::lower(trim($tier))) { 'admin' => 'admin', 'pro' => 'pro', 'creator', 'premium' => 'creator', default => 'free', }; } public function isValidPriceId(?string $priceId): bool { $priceId = trim((string) $priceId); if ($priceId === '') { return false; } return preg_match('/^price_[A-Za-z0-9]+$/', $priceId) === 1; } }