route('app.calendar'); } $account = $request->user()->account; if ($account && $account->hasActiveSubscription()) { return redirect()->route('app.billing.index'); } return Inertia::render('billing/Subscribe', [ 'plans' => Plan::active()->orderBy('sort')->get(), 'trialDays' => config('cashier.trial_days'), ]); } public function checkout(Request $request, Plan $plan): SymfonyResponse|RedirectResponse { if (config('trypost.self_hosted')) { return redirect()->route('app.calendar'); } $user = $request->user(); $account = $user->account; abort_unless($user->isAccountOwner(), SymfonyResponse::HTTP_FORBIDDEN); $request->validate([ 'price_id' => ['required', 'string'], ]); $priceId = $request->input('price_id'); abort_unless( $priceId === $plan->stripe_monthly_price_id || $priceId === $plan->stripe_yearly_price_id, 422, 'Invalid price for this plan', ); $account->createOrGetStripeCustomer([ 'email' => $account->stripeEmail(), 'name' => $account->stripeName(), ]); $subscription = $account->newSubscription(Account::SUBSCRIPTION_NAME, $priceId) ->allowPromotionCodes() ->trialDays(config('cashier.trial_days')); $checkoutSession = $subscription->checkout([ 'success_url' => route('app.billing.processing'), 'cancel_url' => route('app.subscribe'), ]); $account->update(['plan_id' => $plan->id]); return Inertia::location($checkoutSession->url); } public function processing(Request $request): Response|RedirectResponse { if (config('trypost.self_hosted')) { return redirect()->route('app.calendar'); } $account = $request->user()->account; return Inertia::render('billing/Processing', [ 'subscriptionActive' => $account && $account->subscribed(Account::SUBSCRIPTION_NAME), ]); } public function index(Request $request): Response|RedirectResponse { if (config('trypost.self_hosted')) { return redirect()->route('app.calendar'); } $account = $request->user()->account; abort_unless($request->user()->isAccountOwner(), SymfonyResponse::HTTP_FORBIDDEN); $subscription = $account->subscription(Account::SUBSCRIPTION_NAME); return Inertia::render('settings/account/Billing', [ 'hasSubscription' => $account->subscribed(Account::SUBSCRIPTION_NAME), 'onTrial' => $subscription?->onTrial() ?? false, 'trialEndsAt' => $subscription?->trial_ends_at?->toFormattedDateString(), 'subscription' => $subscription?->only([ 'stripe_status', 'ends_at', ]), 'plan' => $account->plan, 'plans' => Plan::active()->orderBy('sort')->get(), 'invoices' => $account->invoices()->map(fn ($invoice) => [ 'id' => $invoice->id, 'date' => $invoice->date()->toFormattedDateString(), 'total' => $invoice->total(), 'status' => $invoice->status, 'invoice_pdf' => $invoice->invoice_pdf, ]), 'defaultPaymentMethod' => $account->defaultPaymentMethod()?->card?->only([ 'brand', 'last4', 'exp_month', 'exp_year', ]), ]); } public function swap(Request $request, Plan $plan): RedirectResponse { if (config('trypost.self_hosted')) { return redirect()->route('app.calendar'); } $account = $request->user()->account; abort_unless($request->user()->isAccountOwner(), SymfonyResponse::HTTP_FORBIDDEN); abort_unless($account->subscribed(Account::SUBSCRIPTION_NAME), 422, 'No active subscription'); $request->validate([ 'price_id' => ['required', 'string'], ]); $priceId = $request->input('price_id'); $subscription = $account->subscription(Account::SUBSCRIPTION_NAME); abort_unless( $priceId === $plan->stripe_monthly_price_id || $priceId === $plan->stripe_yearly_price_id, 422, 'Invalid price for this plan', ); $currentPlan = $account->plan; $isOnYearly = $currentPlan && $subscription->stripe_price === $currentPlan->stripe_yearly_price_id; $isTargetMonthly = $priceId === $plan->stripe_monthly_price_id; abort_if( $isOnYearly && $isTargetMonthly, 422, 'Cannot downgrade from yearly to monthly billing.', ); $subscription->swap($priceId); $account->update(['plan_id' => $plan->id]); return redirect()->route('app.billing.index') ->with('flash.success', __('billing.flash.plan_changed', ['plan' => $plan->name])); } public function portal(Request $request): RedirectResponse { if (config('trypost.self_hosted')) { return redirect()->route('app.calendar'); } $account = $request->user()->account; abort_unless($request->user()->isAccountOwner(), SymfonyResponse::HTTP_FORBIDDEN); return $account->redirectToBillingPortal( route('app.billing.index') ); } }