Skip to content

Commit e378c6b

Browse files
simonhampclaude
andauthored
Add post-purchase flow with sync license creation (#276)
* Add post-purchase flow with sync license creation and success banner Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Redirect course checkout to cart success page for webhook-based purchase confirmation Instead of synchronously creating licenses on the /course page after Stripe checkout, redirect to the existing cart/success page that polls for webhook confirmation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3f8061c commit e378c6b

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

resources/views/course.blade.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<x-layout title="The NativePHP Masterclass">
22
<div class="mx-auto max-w-5xl">
3+
@if (session('error'))
4+
<div class="mt-6 rounded-2xl border border-red-200 bg-red-50 p-4 text-center text-red-700 dark:border-red-800 dark:bg-red-950/50 dark:text-red-300">
5+
{{ session('error') }}
6+
</div>
7+
@endif
8+
39
{{-- Hero Section --}}
410
<section
511
class="mt-12 md:mt-20"

routes/web.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
],
107107
'quantity' => 1,
108108
]],
109-
'success_url' => route('course').'?purchased=1',
109+
'success_url' => route('cart.success').'?session_id={CHECKOUT_SESSION_ID}',
110110
'cancel_url' => route('course'),
111111
'customer' => $user->stripe_id,
112112
'metadata' => $metadata,

tests/Feature/CoursePageTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Tests\Feature;
44

5+
use App\Models\User;
56
use Illuminate\Foundation\Testing\RefreshDatabase;
67
use PHPUnit\Framework\Attributes\Test;
78
use Tests\TestCase;
@@ -48,4 +49,61 @@ public function course_checkout_redirects_guests_to_login(): void
4849
->post(route('course.checkout'))
4950
->assertRedirect(route('customer.login'));
5051
}
52+
53+
#[Test]
54+
public function course_checkout_redirects_to_stripe_with_cart_success_url(): void
55+
{
56+
$user = User::factory()->create(['stripe_id' => 'cus_test123']);
57+
58+
$stripeSessionUrl = 'https://checkout.stripe.com/test-session';
59+
$capturedParams = null;
60+
61+
$mockCheckoutSessions = new class($stripeSessionUrl, $capturedParams)
62+
{
63+
public function __construct(
64+
private string $url,
65+
private &$capturedParams,
66+
) {}
67+
68+
public function create(array $params): object
69+
{
70+
$this->capturedParams = $params;
71+
72+
return (object) [
73+
'id' => 'cs_test123',
74+
'url' => $this->url,
75+
];
76+
}
77+
};
78+
79+
$mockCheckout = new \stdClass;
80+
$mockCheckout->sessions = $mockCheckoutSessions;
81+
82+
$mockCustomers = new class
83+
{
84+
public function retrieve(): \Stripe\Customer
85+
{
86+
return \Stripe\Customer::constructFrom([
87+
'id' => 'cus_test123',
88+
'name' => 'Test User',
89+
'email' => 'test@example.com',
90+
]);
91+
}
92+
};
93+
94+
$mockStripeClient = $this->createMock(\Stripe\StripeClient::class);
95+
$mockStripeClient->checkout = $mockCheckout;
96+
$mockStripeClient->customers = $mockCustomers;
97+
98+
$this->app->bind(\Stripe\StripeClient::class, fn () => $mockStripeClient);
99+
100+
$this
101+
->actingAs($user)
102+
->post(route('course.checkout'))
103+
->assertRedirect($stripeSessionUrl);
104+
105+
$this->assertNotNull($capturedParams, 'Stripe checkout session should have been created');
106+
$this->assertStringContainsString(route('cart.success'), $capturedParams['success_url']);
107+
$this->assertStringContainsString('{CHECKOUT_SESSION_ID}', $capturedParams['success_url']);
108+
}
51109
}

0 commit comments

Comments
 (0)