From e8bc5fd7866afab83dc0b807fdda8f6b3a0b1cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Thu, 5 Mar 2026 11:58:04 +0000 Subject: [PATCH] fix(billing): create Stripe customer before redirecting to billing portal (#206) ## Summary - Users without a Stripe customer ID (`stripe_id = null`) would hit an `InvalidCustomer` exception when visiting the billing portal - Added a `hasStripeId()` check before calling `redirectToBillingPortal()`, creating the Stripe customer on-the-fly if needed - Added two tests covering both branches (with and without an existing Stripe customer ID) --- .../Controllers/SubscriptionController.php | 8 ++++- tests/Feature/SubscriptionTest.php | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/SubscriptionController.php b/app/Http/Controllers/SubscriptionController.php index 2bd12f2f..b719f668 100644 --- a/app/Http/Controllers/SubscriptionController.php +++ b/app/Http/Controllers/SubscriptionController.php @@ -136,6 +136,12 @@ class SubscriptionController extends Controller ->withErrors(['demo' => 'Billing management is not available on the demo account.']); } - return $request->user()->redirectToBillingPortal(route('settings.billing')); + $user = $request->user(); + + if (! $user->hasStripeId()) { + $user->createAsStripeCustomer(); + } + + return $user->redirectToBillingPortal(route('settings.billing')); } } diff --git a/tests/Feature/SubscriptionTest.php b/tests/Feature/SubscriptionTest.php index 3768c0e2..0831408e 100644 --- a/tests/Feature/SubscriptionTest.php +++ b/tests/Feature/SubscriptionTest.php @@ -1,11 +1,13 @@ where('canUseFreePlan', false) ); }); + +test('billing portal creates stripe customer when user has no stripe id', function () { + $user = Mockery::mock(User::class)->shouldIgnoreMissing(); + $user->shouldReceive('isDemoAccount')->andReturn(false); + $user->shouldReceive('hasStripeId')->once()->andReturn(false); + $user->shouldReceive('createAsStripeCustomer')->once(); + $user->shouldReceive('redirectToBillingPortal') + ->with(route('settings.billing')) + ->once() + ->andReturn(new RedirectResponse(route('settings.billing'))); + + $this->withoutMiddleware(HandleInertiaRequests::class); + $this->actingAs($user); + + $this->get(route('settings.billing.portal'))->assertRedirect(); +}); + +test('billing portal skips stripe customer creation when user already has a stripe id', function () { + $user = Mockery::mock(User::class)->shouldIgnoreMissing(); + $user->shouldReceive('isDemoAccount')->andReturn(false); + $user->shouldReceive('hasStripeId')->once()->andReturn(true); + $user->shouldNotReceive('createAsStripeCustomer'); + $user->shouldReceive('redirectToBillingPortal') + ->with(route('settings.billing')) + ->once() + ->andReturn(new RedirectResponse(route('settings.billing'))); + + $this->withoutMiddleware(HandleInertiaRequests::class); + $this->actingAs($user); + + $this->get(route('settings.billing.portal'))->assertRedirect(); +});