diff --git a/app/Listeners/DowngradeUserOnSubscriptionDeleted.php b/app/Listeners/DowngradeUserOnSubscriptionDeleted.php deleted file mode 100644 index eaee68c..0000000 --- a/app/Listeners/DowngradeUserOnSubscriptionDeleted.php +++ /dev/null @@ -1,37 +0,0 @@ -payload['type'] ?? null) !== 'customer.subscription.deleted') { - return; - } - - $stripeCustomerId = $event->payload['data']['object']['customer'] ?? null; - - if (! $stripeCustomerId) { - return; - } - - $user = User::where('stripe_id', $stripeCustomerId)->first(); - - if (! $user) { - return; - } - - UserNotificationPreference::query() - ->where('user_id', $user->id) - ->whereIn('channel', ['whatsapp', 'sms']) - ->update(['enabled' => false]); - - Cache::tags(['plans'])->forget("plan_for_user_{$user->id}"); - } -} diff --git a/app/Listeners/HandleStripeWebhook.php b/app/Listeners/HandleStripeWebhook.php index d206929..85b86ac 100644 --- a/app/Listeners/HandleStripeWebhook.php +++ b/app/Listeners/HandleStripeWebhook.php @@ -3,6 +3,7 @@ namespace App\Listeners; use App\Models\User; +use App\Models\UserNotificationPreference; use Illuminate\Support\Facades\Cache; use Laravel\Cashier\Events\WebhookReceived; @@ -26,10 +27,23 @@ final class HandleStripeWebhook match ($type) { 'customer.subscription.created', 'customer.subscription.updated' => $this->bustPlanCache($user), + 'customer.subscription.deleted' => $this->handleSubscriptionDeleted($user), default => null, }; } + private function handleSubscriptionDeleted(User $user): void + { + UserNotificationPreference::query() + ->where('user_id', $user->id) + ->whereIn('channel', ['whatsapp', 'sms']) + ->update(['enabled' => false]); + + $user->forceFill(['grace_period_until' => null])->save(); + + $this->bustPlanCache($user); + } + private function bustPlanCache(User $user): void { Cache::tags(['plans'])->forget("plan_for_user_{$user->id}"); diff --git a/tests/Feature/Payments/DowngradeUserOnSubscriptionDeletedTest.php b/tests/Feature/Payments/DowngradeUserOnSubscriptionDeletedTest.php deleted file mode 100644 index 0aedc7a..0000000 --- a/tests/Feature/Payments/DowngradeUserOnSubscriptionDeletedTest.php +++ /dev/null @@ -1,42 +0,0 @@ -create(['stripe_id' => 'cus_test_123']); - - UserNotificationPreference::query()->insert([ - ['user_id' => $user->id, 'channel' => 'whatsapp', 'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()], - ['user_id' => $user->id, 'channel' => 'sms', 'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()], - ['user_id' => $user->id, 'channel' => 'email', 'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()], - ]); - - (new DowngradeUserOnSubscriptionDeleted)->handle(new WebhookReceived([ - 'type' => 'customer.subscription.deleted', - 'data' => ['object' => ['customer' => 'cus_test_123']], - ])); - - expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'whatsapp')->value('enabled'))->toBeFalse(); - expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'sms')->value('enabled'))->toBeFalse(); - expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'email')->value('enabled'))->toBeTrue(); -}); - -it('ignores non-deletion webhook events', function (): void { - $user = User::factory()->create(['stripe_id' => 'cus_test_456']); - UserNotificationPreference::query()->insert([ - ['user_id' => $user->id, 'channel' => 'whatsapp', 'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()], - ]); - - (new DowngradeUserOnSubscriptionDeleted)->handle(new WebhookReceived([ - 'type' => 'customer.subscription.updated', - 'data' => ['object' => ['customer' => 'cus_test_456']], - ])); - - expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'whatsapp')->value('enabled'))->toBeTrue(); -}); diff --git a/tests/Feature/Payments/HandleStripeWebhookTest.php b/tests/Feature/Payments/HandleStripeWebhookTest.php index 3f441d6..e491689 100644 --- a/tests/Feature/Payments/HandleStripeWebhookTest.php +++ b/tests/Feature/Payments/HandleStripeWebhookTest.php @@ -2,6 +2,7 @@ use App\Listeners\HandleStripeWebhook; use App\Models\User; +use App\Models\UserNotificationPreference; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Cache; use Laravel\Cashier\Events\WebhookReceived; @@ -40,3 +41,29 @@ it('busts the plan cache on customer.subscription.updated', function (): void { expect(Cache::tags(['plans'])->get("plan_for_user_{$user->id}"))->toBeNull(); }); + +it('on customer.subscription.deleted disables whatsapp+sms prefs, clears grace, busts cache', function (): void { + $user = User::factory()->create([ + 'stripe_id' => 'cus_deleted_1', + 'grace_period_until' => now()->addDays(5), + ]); + + UserNotificationPreference::query()->insert([ + ['user_id' => $user->id, 'channel' => 'whatsapp', 'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()], + ['user_id' => $user->id, 'channel' => 'sms', 'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()], + ['user_id' => $user->id, 'channel' => 'email', 'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()], + ]); + + Cache::tags(['plans'])->put("plan_for_user_{$user->id}", 'stale', 3600); + + (new HandleStripeWebhook)->handle(new WebhookReceived([ + 'type' => 'customer.subscription.deleted', + 'data' => ['object' => ['customer' => 'cus_deleted_1']], + ])); + + expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'whatsapp')->value('enabled'))->toBeFalse(); + expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'sms')->value('enabled'))->toBeFalse(); + expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'email')->value('enabled'))->toBeTrue(); + expect($user->fresh()->grace_period_until)->toBeNull(); + expect(Cache::tags(['plans'])->get("plan_for_user_{$user->id}"))->toBeNull(); +});