diff --git a/app/Http/Controllers/SubscriptionController.php b/app/Http/Controllers/SubscriptionController.php index 46f4c3d..d84e561 100644 --- a/app/Http/Controllers/SubscriptionController.php +++ b/app/Http/Controllers/SubscriptionController.php @@ -19,8 +19,8 @@ public function checkout(Request $request) $plan = $request->input('plan', 'monthly'); $priceId = $plan === 'yearly' - ? env('STRIPE_PRICE_YEARLY') - : env('STRIPE_PRICE_MONTHLY'); + ? config('services.stripe.price_yearly') + : config('services.stripe.price_monthly'); return $planner->newSubscription('default', $priceId) ->checkout([ @@ -57,6 +57,41 @@ public function success(Request $request): RedirectResponse return redirect()->route('dashboard')->with('success', 'Subscription activated!'); } + public function billing(Request $request) + { + $planner = $request->user(); + $subscription = $planner->subscription(); + + if (! $subscription) { + return redirect()->route('subscription.index'); + } + + $planType = match ($subscription->stripe_price) { + config('services.stripe.price_yearly') => 'Yearly', + config('services.stripe.price_monthly') => 'Monthly', + default => 'Unknown', + }; + + $nextBillingDate = null; + if ($subscription->stripe_status === 'active') { + try { + $stripeSubscription = Cashier::stripe()->subscriptions->retrieve($subscription->stripe_id); + $nextBillingDate = $stripeSubscription->current_period_end + ? now()->setTimestamp($stripeSubscription->current_period_end) + : null; + } catch (\Exception $e) { + // Stripe API error - continue without next billing date + } + } + + return view('billing.index', [ + 'subscription' => $subscription, + 'planner' => $planner, + 'planType' => $planType, + 'nextBillingDate' => $nextBillingDate, + ]); + } + public function cancel(Request $request): RedirectResponse { $planner = $request->user(); diff --git a/app/Http/Middleware/RequireSaasMode.php b/app/Http/Middleware/RequireSaasMode.php new file mode 100644 index 0000000..0950eb6 --- /dev/null +++ b/app/Http/Middleware/RequireSaasMode.php @@ -0,0 +1,19 @@ +alias([ 'subscription' => RequireSubscription::class, + 'saas' => RequireSaasMode::class, ]); // Exclude Stripe webhook from CSRF verification diff --git a/config/services.php b/config/services.php index 66bade1..cf3ce61 100644 --- a/config/services.php +++ b/config/services.php @@ -41,6 +41,8 @@ 'webhook' => [ 'secret' => env('STRIPE_WEBHOOK_SECRET'), ], + 'price_monthly' => env('STRIPE_PRICE_MONTHLY'), + 'price_yearly' => env('STRIPE_PRICE_YEARLY'), ], ]; diff --git a/resources/views/billing/index.blade.php b/resources/views/billing/index.blade.php new file mode 100644 index 0000000..c41163f --- /dev/null +++ b/resources/views/billing/index.blade.php @@ -0,0 +1,49 @@ + +
+
+

BILLING

+ +
+

Subscription Details

+ +
+
+ Plan + {{ $planType }} +
+ +
+ Status + + {{ ucfirst($subscription->stripe_status) }} + +
+ + @if($nextBillingDate) +
+ Next billing date + {{ $nextBillingDate->format('F j, Y') }} +
+ @endif + + @if($subscription->ends_at) +
+ Access until + {{ $subscription->ends_at->format('F j, Y') }} +
+ @endif + + @if($planner->pm_last_four) +
+ Payment method + + {{ ucfirst($planner->pm_type ?? 'Card') }} + •••• {{ $planner->pm_last_four }} + +
+ @endif +
+
+
+
+
diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php index c007beb..d893086 100644 --- a/resources/views/components/layouts/app.blade.php +++ b/resources/views/components/layouts/app.blade.php @@ -65,6 +65,11 @@ class="inline-flex items-center px-3 py-2 text-sm font-medium {{ request()->rout x-transition class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-gray-700 ring-1 ring-secondary">
+ @if(is_mode_saas()) + + Billing + + @endif
@csrf