*/ use HasFactory; protected $fillable = [ 'name', 'stripe_price_id_monthly', 'stripe_price_id_annual', 'features', 'active', ]; /** * Resolve the active plan for a user. * Falls back to the free plan when no active Cashier subscription exists. */ public static function resolveForUser(User $user): self { $cache = Cache::supportsTags() ? Cache::tags(['plans']) : Cache::store(); $planId = $cache->remember( "plan_for_user_{$user->id}", 3600, function () use ($user): ?int { $priceId = null; if (method_exists($user, 'subscriptions')) { $subscription = $user->subscriptions()->active()->first(); $priceId = $subscription?->stripe_price ?? null; } if ($priceId) { $plan = static::where(fn ($q) => $q ->where('stripe_price_id_monthly', $priceId) ->orWhere('stripe_price_id_annual', $priceId)) ->where('active', true) ->first(); if ($plan) { return $plan->id; } } return static::where('name', PlanTier::Free->value)->value('id'); } ); if ($planId !== null) { $plan = static::find($planId); if ($plan !== null) { return $plan; } } // Fallback for tests / partially-seeded environments: return a free-tier stub. return new self([ 'name' => PlanTier::Free->value, 'features' => [ 'fuel_types' => ['max' => 1], 'email' => ['enabled' => true, 'frequency' => 'weekly_digest'], 'push' => ['enabled' => false, 'frequency' => 'none'], 'whatsapp' => ['enabled' => false, 'daily_limit' => 0, 'scheduled_updates' => 0], 'sms' => ['enabled' => false, 'daily_limit' => 0], 'ai_predictions' => false, 'price_threshold' => false, 'score_alerts' => false, ], ]); } /** * Resolve the active subscription cadence for a user. * Returns 'monthly' | 'annual', or null if the user has no paid subscription. */ public static function resolveCadenceForUser(User $user): ?string { if (! method_exists($user, 'subscriptions')) { return null; } $priceId = $user->subscriptions()->active()->value('stripe_price'); if ($priceId === null) { return null; } $plan = static::where('stripe_price_id_monthly', $priceId) ->orWhere('stripe_price_id_annual', $priceId) ->first(); if ($plan === null) { return null; } if ($plan->stripe_price_id_monthly === $priceId) { return 'monthly'; } if ($plan->stripe_price_id_annual === $priceId) { return 'annual'; } return null; } protected static function booted(): void { static::saved(function (): void { if (Cache::supportsTags()) { Cache::tags(['plans'])->flush(); } }); } protected function casts(): array { return [ 'features' => 'array', 'active' => 'boolean', ]; } /** User-facing display label for this plan (e.g. basic → "Daily"). */ public function displayName(): string { $tier = PlanTier::tryFrom((string) $this->name) ?? PlanTier::Free; return $tier->label(); } }