Skip to content

Commit 8f12e25

Browse files
simonhampclaude
andauthored
Improve post-purchase flow (#277)
* Add NativePHP Masterclass landing page with Stripe checkout - Create /course landing page with hero, curriculum, audience, pricing, and email signup sections - Wire up Stripe checkout for one-time $99 early bird payment using Cart/Product infrastructure - Add migration to seed Masterclass product and price in the database - Integrate Mailcoach for email waitlist signup via direct form POST - Fix null stripe_price crash in CustomerLicenseController dashboard - Add feature tests for course page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Learn link to main nav, mobile menu, and footer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update masterclass price to $101 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Auto-submit checkout after login/registration flow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add availability timeline section for Summer/Fall 2026 launch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add New pill to Learn links and fix mobile menu on large screens Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * 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> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4a2d34f commit 8f12e25

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

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)