Skip to content

Commit 05868ea

Browse files
simonhampclaude
andcommitted
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>
1 parent 345a4c1 commit 05868ea

11 files changed

Lines changed: 1741 additions & 691 deletions

File tree

.cursor/rules/laravel-boost.mdc

Lines changed: 120 additions & 172 deletions
Large diffs are not rendered by default.

.github/copilot-instructions.md

Lines changed: 120 additions & 172 deletions
Large diffs are not rendered by default.

.junie/guidelines.md

Lines changed: 120 additions & 172 deletions
Large diffs are not rendered by default.

AGENTS.md

Lines changed: 375 additions & 0 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 120 additions & 172 deletions
Large diffs are not rendered by default.

app/Http/Controllers/CustomerLicenseController.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ public function index(): View
3131
// Get subscription plan name
3232
$subscriptionName = null;
3333
if ($activeSubscription) {
34-
try {
35-
$subscriptionName = \App\Enums\Subscription::fromStripePriceId($activeSubscription->stripe_price)->name();
36-
} catch (\RuntimeException) {
34+
if ($activeSubscription->stripe_price) {
35+
try {
36+
$subscriptionName = \App\Enums\Subscription::fromStripePriceId($activeSubscription->stripe_price)->name();
37+
} catch (\RuntimeException) {
38+
$subscriptionName = ucfirst($activeSubscription->type);
39+
}
40+
} else {
3741
$subscriptionName = ucfirst($activeSubscription->type);
3842
}
3943
}

boost.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"agents": [
3+
"claude_code",
4+
"copilot",
5+
"cursor",
6+
"opencode",
7+
"phpstorm"
8+
],
9+
"guidelines": []
10+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
use App\Models\Product;
4+
use App\Models\ProductPrice;
5+
use Illuminate\Database\Migrations\Migration;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
$product = Product::create([
15+
'name' => 'The NativePHP Masterclass',
16+
'slug' => 'nativephp-masterclass',
17+
'description' => 'Go from zero to published app. Learn to build native mobile and desktop applications using the PHP and Laravel skills you already have.',
18+
'is_active' => true,
19+
'published_at' => now(),
20+
]);
21+
22+
ProductPrice::create([
23+
'product_id' => $product->id,
24+
'tier' => 'regular',
25+
'amount' => 9900,
26+
'currency' => 'USD',
27+
'is_active' => true,
28+
]);
29+
}
30+
31+
/**
32+
* Reverse the migrations.
33+
*/
34+
public function down(): void
35+
{
36+
$product = Product::where('slug', 'nativephp-masterclass')->first();
37+
38+
if ($product) {
39+
$product->prices()->delete();
40+
$product->delete();
41+
}
42+
}
43+
};

resources/views/course.blade.php

Lines changed: 713 additions & 0 deletions
Large diffs are not rendered by default.

routes/web.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,68 @@
6464
Route::view('/', 'welcome')->name('welcome');
6565
Route::redirect('pricing', 'blog/nativephp-for-mobile-is-now-free')->name('pricing');
6666
Route::view('alt-pricing', 'alt-pricing')->name('alt-pricing')->middleware('signed');
67+
Route::view('course', 'course')->name('course');
68+
69+
Route::post('course/checkout', function (\Illuminate\Http\Request $request) {
70+
$user = $request->user();
71+
72+
if (! $user) {
73+
session(['url.intended' => route('course')]);
74+
75+
return to_route('customer.login')
76+
->with('message', 'Please log in or create an account to complete your purchase.');
77+
}
78+
79+
$product = \App\Models\Product::where('slug', 'nativephp-masterclass')->firstOrFail();
80+
81+
if ($product->isOwnedBy($user)) {
82+
return to_route('course')->with('error', 'You already own this course.');
83+
}
84+
85+
$cartService = resolve(\App\Services\CartService::class);
86+
$cart = $cartService->getCart($user);
87+
$cartService->addProduct($cart, $product);
88+
89+
$cart->load('items.product');
90+
$item = $cart->items->where('product_id', $product->id)->first();
91+
92+
$user->createOrGetStripeCustomer();
93+
94+
$metadata = ['cart_id' => (string) $cart->id];
95+
96+
$session = \Laravel\Cashier\Cashier::stripe()->checkout->sessions->create([
97+
'mode' => 'payment',
98+
'line_items' => [[
99+
'price_data' => [
100+
'currency' => strtolower($item->currency),
101+
'unit_amount' => $item->product_price_at_addition,
102+
'product_data' => [
103+
'name' => $product->name,
104+
'description' => $product->description,
105+
],
106+
],
107+
'quantity' => 1,
108+
]],
109+
'success_url' => route('course').'?purchased=1',
110+
'cancel_url' => route('course'),
111+
'customer' => $user->stripe_id,
112+
'metadata' => $metadata,
113+
'allow_promotion_codes' => true,
114+
'billing_address_collection' => 'required',
115+
'invoice_creation' => [
116+
'enabled' => true,
117+
'invoice_data' => [
118+
'description' => 'NativePHP Masterclass Purchase',
119+
'metadata' => $metadata,
120+
],
121+
],
122+
]);
123+
124+
$cart->update(['stripe_checkout_session_id' => $session->id]);
125+
126+
return redirect($session->url);
127+
})->name('course.checkout');
128+
67129
Route::view('wall-of-love', 'wall-of-love')->name('wall-of-love');
68130
Route::view('brand', 'brand')->name('brand');
69131
Route::get('showcase/{platform?}', [App\Http\Controllers\ShowcaseController::class, 'index'])

0 commit comments

Comments
 (0)