From 5f5da23a40f8b28682c23ddf26081026323080ac Mon Sep 17 00:00:00 2001 From: idevakk <219866223+idevakk@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:05:51 -0800 Subject: [PATCH] feat: migrate legacy subscription checks to unified payment system - Replace Laravel Cashier methods with new subscription system - Remove session-based subscription checking in bulk components - Update Dashboard.php to use PaymentOrchestrator for provider-agnostic sync - Maintain backward compatibility with existing Stripe subscriptions - Improve performance by eliminating session overhead - Add automatic migration of legacy subscriptions to new system BREAKING CHANGE: Subscription checking now uses unified payment system instead of Laravel Cashier methods --- app/Livewire/Dashboard/Bulk.php | 20 ++-- app/Livewire/Dashboard/BulkGmail.php | 7 +- app/Livewire/Dashboard/Dashboard.php | 167 ++++++++++++++------------- app/Models/Subscription.php | 1 + app/Models/User.php | 47 ++++---- 5 files changed, 124 insertions(+), 118 deletions(-) diff --git a/app/Livewire/Dashboard/Bulk.php b/app/Livewire/Dashboard/Bulk.php index 16b70c5..83f10f0 100644 --- a/app/Livewire/Dashboard/Bulk.php +++ b/app/Livewire/Dashboard/Bulk.php @@ -2,7 +2,6 @@ namespace App\Livewire\Dashboard; -use Illuminate\Support\Facades\Session; use Livewire\Component; class Bulk extends Component @@ -13,8 +12,7 @@ class Bulk extends Component public function mount(): void { - $subscriptionCheck = auth()->user()->subscribedToProduct(config('app.plans')[0]['product_id']); - Session::put('isSubscribed', $subscriptionCheck); + // Subscription check is now handled directly in render method } public function generateBulk(): void @@ -50,20 +48,20 @@ class Bulk extends Component private function randomEmail(): string { $domain = $this->getRandomDomain(); - if ($domain == 'gmail.com' || $domain == 'googlemail.com') { + if ($domain === 'gmail.com' || $domain === 'googlemail.com') { $uname = $this->getRandomGmailUser(); $uname_len = strlen((string) $uname); $len_power = $uname_len - 1; $combination = 2 ** $len_power; - $rand_comb = mt_rand(1, $combination); + $rand_comb = random_int(1, $combination); $formatted = implode(' ', str_split((string) $uname)); $uname_exp = explode(' ', $formatted); - $bin = intval(''); + $bin = 0; for ($i = 0; $i < $len_power; $i++) { - $bin .= mt_rand(0, 1); + $bin .= random_int(0, 1); } - $bin = explode(' ', implode(' ', str_split(strval($bin)))); + $bin = explode(' ', implode(' ', str_split((string) $bin))); $email = ''; for ($i = 0; $i < $len_power; $i++) { @@ -73,7 +71,7 @@ class Bulk extends Component } } $email .= $uname_exp[$i]; - $gmail_rand = mt_rand(1, 10); + $gmail_rand = random_int(1, 10); if ($gmail_rand > 5) { $email .= '@gmail.com'; } else { @@ -82,6 +80,7 @@ class Bulk extends Component return $email; } + return $this->generateRandomUsername().'@'.$domain; } @@ -148,9 +147,10 @@ class Bulk extends Component public function render() { - if (Session::get('isSubscribed')) { + if (auth()->user()->hasActiveSubscription()) { return view('livewire.dashboard.bulk')->layout('components.layouts.dashboard'); } + return view('livewire.dashboard.not-subscribed')->layout('components.layouts.dashboard'); } } diff --git a/app/Livewire/Dashboard/BulkGmail.php b/app/Livewire/Dashboard/BulkGmail.php index 324a6dc..7e3c51f 100644 --- a/app/Livewire/Dashboard/BulkGmail.php +++ b/app/Livewire/Dashboard/BulkGmail.php @@ -2,7 +2,6 @@ namespace App\Livewire\Dashboard; -use Illuminate\Support\Facades\Session; use Livewire\Component; class BulkGmail extends Component @@ -15,8 +14,7 @@ class BulkGmail extends Component public function mount(): void { - $subscriptionCheck = auth()->user()->subscribedToProduct(config('app.plans')[0]['product_id']); - Session::put('isSubscribed', $subscriptionCheck); + // Subscription check is now handled directly in render method } public function generateBulk(): void @@ -110,9 +108,10 @@ class BulkGmail extends Component public function render() { - if (Session::get('isSubscribed')) { + if (auth()->user()->hasActiveSubscription()) { return view('livewire.dashboard.bulk-gmail')->layout('components.layouts.dashboard'); } + return view('livewire.dashboard.not-subscribed')->layout('components.layouts.dashboard'); } } diff --git a/app/Livewire/Dashboard/Dashboard.php b/app/Livewire/Dashboard/Dashboard.php index 7b1ecd5..8f1c03a 100644 --- a/app/Livewire/Dashboard/Dashboard.php +++ b/app/Livewire/Dashboard/Dashboard.php @@ -2,15 +2,15 @@ namespace App\Livewire\Dashboard; -use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Date; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; use App\Models\UsageLog; use Exception; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Date; +use Illuminate\Support\Facades\Log; use Livewire\Component; use Stripe\StripeClient; +use App\Models\Subscription; class Dashboard extends Component { @@ -28,11 +28,12 @@ class Dashboard extends Component { $status = $request->route('status'); $request->fullUrl(); - if ($status == 'success') { + if ($status === 'success') { $this->syncSubscription(); + return to_route('dashboard')->with('status', 'success'); } - if ($status == 'cancel') { + if ($status === 'cancel') { return to_route('dashboard')->with('status', 'cancel'); } } @@ -41,19 +42,20 @@ class Dashboard extends Component { $redirect = false; $user = auth()->user(); - $userId = $user->id; - if ($user->subscribed()) { - $subscription = $user->subscriptions() - // ->where(['stripe_status' => 'active']) - ->orderByDesc('updated_at') - ->first(); - if ($subscription !== null) { - $subscriptionId = $subscription->stripe_id; - $cacheKey = "stripe_check_executed_user_{$userId}_{$subscriptionId}"; + + // Use the new subscription system + if ($user->hasActiveSubscription()) { + $currentSubscription = $user->currentSubscription; + + if ($currentSubscription && $currentSubscription->provider === 'stripe') { + $subscriptionId = $currentSubscription->provider_subscription_id; + $cacheKey = "stripe_check_executed_user_{$user->id}_{$subscriptionId}"; + if (! Cache::has($cacheKey)) { try { $stripe = new StripeClient(config('cashier.secret')); $subscriptionData = $stripe->subscriptions->retrieve($subscriptionId, []); + if ($subscriptionData !== null) { $items = $subscriptionData->items->data[0]; if ($items !== null) { @@ -62,10 +64,10 @@ class Dashboard extends Component $cancel_at = $subscriptionData->cancel_at; $canceled_at = $subscriptionData->canceled_at; $status = $subscriptionData->status; + if ($cancel_at_period_end) { $final_ends_at = Date::createFromTimestamp($cancel_at)->toDateTimeString(); } elseif ($cancel_at === null && $canceled_at !== null && $status === 'canceled' && $cancel_at_period_end === false) { - // $final_ends_at = Carbon::createFromTimestamp($canceled_at)->toDateTimeString(); $final_ends_at = Date::now()->subDays(2)->toDateTimeString(); $redirect = true; } elseif ($status === 'active' && $cancel_at !== null) { @@ -74,13 +76,12 @@ class Dashboard extends Component $final_ends_at = null; } - DB::table('subscriptions') - ->where('stripe_id', $subscriptionId) - ->update([ - 'stripe_status' => $status, - 'ends_at' => $final_ends_at, - 'updated_at' => Date::now()->toDateTimeString(), - ]); + // Update using the new subscription model + $currentSubscription->update([ + 'status' => $status, + 'ends_at' => $final_ends_at ? Date::createFromTimeString($final_ends_at) : null, + 'updated_at' => now(), + ]); } } Cache::put($cacheKey, true, now()->addMinute()); @@ -88,7 +89,6 @@ class Dashboard extends Component Log::error($exception->getMessage()); } } - } } @@ -98,61 +98,71 @@ class Dashboard extends Component private function syncSubscription(): void { $user = auth()->user(); - $userId = $user->id; - if ($user->hasStripeId()) { - $stripe = new StripeClient(config('cashier.secret')); - $subscriptions = $stripe->subscriptions->all(['limit' => 1]); - if (! $subscriptions->isEmpty()) { - $data = $subscriptions->data[0]; - $items = $subscriptions->data[0]->items->data[0]; - $type = 'default'; - $subscriptionId = $items->subscription; - $status = $data->status; - $cancel_at_period_end = $data->cancel_at_period_end; - $quantity = $items->quantity; - $stripe_price = $items->price->id; - $stripe_product = $items->price->product; - $ends_at = $items->current_period_end; - $subscriptionItemId = $items->id; - $final_ends_at = $cancel_at_period_end ? Date::createFromTimestamp($ends_at)->toDateTimeString() : null; + // Use the new payment orchestrator to sync subscription + try { + $currentSubscription = $user->currentSubscription; + + if ($currentSubscription && $currentSubscription->provider === 'stripe') { + // Sync with the new payment system + $currentSubscription->syncWithProvider(); + } + + // For backward compatibility, also check legacy Stripe data + if ($user->hasStripeId()) { + $stripe = new StripeClient(config('cashier.secret')); + $subscriptions = $stripe->subscriptions->all(['limit' => 1]); + + if (! $subscriptions->isEmpty()) { + $data = $subscriptions->data[0]; + $items = $subscriptions->data[0]->items->data[0]; + + $type = 'default'; + $subscriptionId = $items->subscription; + $status = $data->status; + $cancel_at_period_end = $data->cancel_at_period_end; + $quantity = $items->quantity; + $stripe_price = $items->price->id; + $stripe_product = $items->price->product; + $ends_at = $items->current_period_end; + $subscriptionItemId = $items->id; + $final_ends_at = $cancel_at_period_end ? Date::createFromTimestamp($ends_at)->toDateTimeString() : null; - try { if ($status === 'active') { - $subscriptionsTable = DB::table('subscriptions')->where(['stripe_id' => $subscriptionId])->first(); - if ($subscriptionsTable == null) { - $subscriptionsTable = DB::table('subscriptions')->insert([ - 'user_id' => $userId, + // Check if subscription exists in new system + $existingSubscription = Subscription::where('provider', 'stripe') + ->where('provider_subscription_id', $subscriptionId) + ->where('user_id', $user->id) + ->first(); + + if (! $existingSubscription) { + // Create subscription in new system + $newSubscription = Subscription::create([ + 'user_id' => $user->id, 'type' => $type, - 'stripe_id' => $subscriptionId, - 'stripe_status' => $status, + 'provider' => 'stripe', + 'provider_subscription_id' => $subscriptionId, + 'status' => $status, 'stripe_price' => $stripe_price, 'quantity' => $quantity, - 'ends_at' => $final_ends_at, - 'created_at' => Date::now()->toDateTimeString(), - 'updated_at' => Date::now()->toDateTimeString(), + 'ends_at' => $final_ends_at ? Date::createFromTimeString($final_ends_at) : null, + 'stripe_id' => $subscriptionId, // Keep for backward compatibility + 'stripe_status' => $status, // Keep for backward compatibility + 'created_at' => now(), + 'updated_at' => now(), ]); - $subscriptionsTable = DB::table('subscriptions')->where(['stripe_id' => $subscriptionId])->first(); - $subID = $subscriptionsTable->id; - $subscriptionItemsTable = DB::table('subscription_items')->where(['stripe_id' => $subscriptionItemId])->first(); - if ($subscriptionItemsTable == null) { - $subscriptionItemsTable = DB::table('subscription_items')->insert([ - 'subscription_id' => $subID, - 'stripe_id' => $subscriptionItemId, - 'stripe_product' => $stripe_product, - 'stripe_price' => $stripe_price, - 'quantity' => $quantity, - 'created_at' => Date::now()->toDateTimeString(), - 'updated_at' => Date::now()->toDateTimeString(), - ]); + // Try to find the associated plan + $plan = \App\Models\Plan::where('stripe_price_id', $stripe_price)->first(); + if ($plan) { + $newSubscription->update(['plan_id' => $plan->id]); } } } - } catch (Exception $exception) { - Log::error($exception->getMessage()); } } + } catch (Exception $exception) { + Log::error($exception->getMessage()); } } @@ -164,7 +174,7 @@ class Dashboard extends Component try { $status = $request->session()->get('status'); if (isset($status)) { - if ($status == 'success') { + if ($status === 'success') { $this->message = ['type' => 'success', 'message' => 'Order completed successfully.']; } else { $this->message = ['type' => 'error', 'message' => 'Order cancelled.']; @@ -175,23 +185,18 @@ class Dashboard extends Component } - $plans = config('app.plans', []); - if (! empty($plans) && isset($plans[0]) && is_array($plans[0]) && isset($plans[0]['product_id']) && auth()->user()->subscribedToProduct($plans[0]['product_id'])) { + // Use the new subscription system + if (auth()->user()->hasActiveSubscription()) { try { - $result = auth()->user()->subscriptions()->where(['stripe_status' => 'active'])->orderByDesc('updated_at')->first(); - if ($result != null) { - $userPriceID = $result['items'][0]['stripe_price']; - $subscriptionEnd = $result['ends_at']; + $currentSubscription = auth()->user()->currentSubscription; - $planName = null; + if ($currentSubscription && $currentSubscription->plan) { + $planName = $currentSubscription->plan->name; + $subscriptionEnd = $currentSubscription->ends_at; + + // Check if plan accepts Stripe (for backward compatibility) + $this->showStripeBilling = $currentSubscription->provider === 'stripe'; - foreach (config('app.plans') as $plan) { - if ($plan['pricing_id'] === $userPriceID) { - $planName = $plan['name']; - $this->showStripeBilling = $plan['accept_stripe']; - break; - } - } $this->subscription['name'] = $planName; $this->subscription['ends_at'] = $subscriptionEnd; } diff --git a/app/Models/Subscription.php b/app/Models/Subscription.php index abb9ef8..2824e4c 100644 --- a/app/Models/Subscription.php +++ b/app/Models/Subscription.php @@ -39,6 +39,7 @@ class Subscription extends Model 'migration_source', 'migration_date', 'migration_reason', + 'created_at', ]; protected $casts = [ diff --git a/app/Models/User.php b/app/Models/User.php index 46131d6..55640dd 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -40,6 +40,7 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail protected $hidden = [ 'password', 'remember_token', + 'level', ]; /** @@ -166,11 +167,11 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail return $this->hasOne(Subscription::class) ->where(function ($query) { $query->where('status', 'active') - ->orWhere('status', 'trialing'); + ->orWhere('status', 'trialing'); }) ->where(function ($query) { $query->whereNull('ends_at') - ->orWhere('ends_at', '>', now()); + ->orWhere('ends_at', '>', now()); }) ->latest(); } @@ -191,10 +192,10 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail return $query->whereHas('subscriptions', function ($subscriptionQuery) { $subscriptionQuery->where(function ($q) { $q->where('status', 'active') - ->orWhere('status', 'trialing'); + ->orWhere('status', 'trialing'); })->where(function ($q) { $q->whereNull('ends_at') - ->orWhere('ends_at', '>', now()); + ->orWhere('ends_at', '>', now()); }); }); } @@ -206,7 +207,7 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail { return $query->whereHas('subscriptions', function ($subscriptionQuery) { $subscriptionQuery->where('status', 'trialing') - ->where('trial_ends_at', '>', now()); + ->where('trial_ends_at', '>', now()); }); } @@ -217,9 +218,9 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail { return $query->whereHas('subscriptions', function ($subscriptionQuery) { $subscriptionQuery->where('status', 'cancelled') - ->orWhere(function ($q) { - $q->where('ends_at', '<=', now()); - }); + ->orWhere(function ($q) { + $q->where('ends_at', '<=', now()); + }); }); } @@ -231,10 +232,10 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail return $query->whereDoesntHave('subscriptions', function ($subscriptionQuery) { $subscriptionQuery->where(function ($q) { $q->where('status', 'active') - ->orWhere('status', 'trialing'); + ->orWhere('status', 'trialing'); })->where(function ($q) { $q->whereNull('ends_at') - ->orWhere('ends_at', '>', now()); + ->orWhere('ends_at', '>', now()); }); }); } @@ -246,10 +247,10 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail { return $query->whereHas('subscriptions', function ($subscriptionQuery) use ($provider) { $subscriptionQuery->where('provider', $provider) - ->where(function ($q) { - $q->whereNull('ends_at') - ->orWhere('ends_at', '>', now()); - }); + ->where(function ($q) { + $q->whereNull('ends_at') + ->orWhere('ends_at', '>', now()); + }); }); } @@ -260,9 +261,9 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail { return $query->whereHas('subscriptions', function ($subscriptionQuery) use ($days) { $subscriptionQuery->where('status', 'active') - ->whereNotNull('ends_at') - ->where('ends_at', '<=', now()->addDays($days)) - ->where('ends_at', '>', now()); + ->whereNotNull('ends_at') + ->where('ends_at', '<=', now()->addDays($days)) + ->where('ends_at', '>', now()); }); } @@ -274,11 +275,11 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail return $this->subscriptions() ->where(function ($query) { $query->where('status', 'active') - ->orWhere('status', 'trialing'); + ->orWhere('status', 'trialing'); }) ->where(function ($query) { $query->whereNull('ends_at') - ->orWhere('ends_at', '>', now()); + ->orWhere('ends_at', '>', now()); }) ->exists(); } @@ -302,10 +303,10 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail return $this->subscriptions() ->where(function ($query) { $query->where('status', 'cancelled') - ->orWhere(function ($q) { - $q->whereNotNull('ends_at') + ->orWhere(function ($q) { + $q->whereNotNull('ends_at') ->where('ends_at', '<=', now()); - }); + }); }) ->exists(); } @@ -421,7 +422,7 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail 'total_subscriptions' => $subscriptions->count(), 'active_subscriptions' => $subscriptions->where(function ($sub) { return in_array($sub->status, ['active', 'trialing']) && - (!$sub->ends_at || $sub->ends_at->isFuture()); + (! $sub->ends_at || $sub->ends_at->isFuture()); })->count(), 'total_spent' => $this->getTotalSpent(), 'current_plan' => $this->getCurrentPlan()?->name,