*/ 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], 'whatsapp' => ['enabled' => false, 'daily_limit' => 0, 'scheduled_updates' => 0], 'sms' => ['enabled' => false, 'daily_limit' => 0], 'ai_predictions' => false, 'price_threshold' => false, 'score_alerts' => false, ], ]); } 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', ]; } }