From e0e38e47c64580ece58407a9a5be1d65013aefbeb7194f41e8d8bc957ad66051 Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Sat, 14 Mar 2026 23:38:01 -0400 Subject: [PATCH] feat: update checkout and API resources for cycle-aware pricing Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Account/CheckoutController.php | 2 +- .../app/Http/Resources/ServiceResource.php | 3 +++ .../Http/Resources/SubscriptionResource.php | 3 +++ website/resources/ts/Pages/Checkout/Show.vue | 23 +++++++++++++++---- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/website/app/Http/Controllers/Account/CheckoutController.php b/website/app/Http/Controllers/Account/CheckoutController.php index 32e20c7..35f6f46 100644 --- a/website/app/Http/Controllers/Account/CheckoutController.php +++ b/website/app/Http/Controllers/Account/CheckoutController.php @@ -69,7 +69,7 @@ class CheckoutController extends Controller } return Inertia::render('Checkout/Show', [ - 'plan' => $plan, + 'plan' => $plan->load('prices'), 'paymentMethods' => $stripeService->getPaymentMethods($user), 'intent' => $user->hasStripeId() ? $user->createSetupIntent() : null, 'stripeKey' => config('cashier.key'), diff --git a/website/app/Http/Resources/ServiceResource.php b/website/app/Http/Resources/ServiceResource.php index e6d5ce6..e56ea25 100644 --- a/website/app/Http/Resources/ServiceResource.php +++ b/website/app/Http/Resources/ServiceResource.php @@ -28,6 +28,9 @@ class ServiceResource extends JsonResource 'name' => $this->plan->name, 'price' => $this->plan->price, 'billing_cycle' => $this->plan->billing_cycle, + 'prices' => $this->plan->prices?->mapWithKeys(fn ($p) => [ + $p->billing_cycle => $p->price, + ]) ?? [], ]), 'provisioned_at' => $this->provisioned_at?->toIso8601String(), 'created_at' => $this->created_at?->toIso8601String(), diff --git a/website/app/Http/Resources/SubscriptionResource.php b/website/app/Http/Resources/SubscriptionResource.php index 4f53109..b71edad 100644 --- a/website/app/Http/Resources/SubscriptionResource.php +++ b/website/app/Http/Resources/SubscriptionResource.php @@ -22,6 +22,9 @@ class SubscriptionResource extends JsonResource 'plan' => $this->when($this->plan_name !== null, fn () => [ 'name' => $this->plan_name, 'price' => $this->plan_price, + 'prices' => $this->plan?->prices?->mapWithKeys(fn ($p) => [ + $p->billing_cycle => $p->price, + ]) ?? [], ]), 'billing_cycle' => $this->plan_billing_cycle ?? null, 'current_period_end' => $this->current_period_end?->toIso8601String(), diff --git a/website/resources/ts/Pages/Checkout/Show.vue b/website/resources/ts/Pages/Checkout/Show.vue index 72e9e09..b2e3ac0 100644 --- a/website/resources/ts/Pages/Checkout/Show.vue +++ b/website/resources/ts/Pages/Checkout/Show.vue @@ -41,8 +41,10 @@ const couponApplied = ref(false) const couponDiscount = ref(0) const couponError = ref('') -// Billing cycle selection -const billingCycle = ref<'monthly' | 'quarterly' | 'semi_annual' | 'annual'>('monthly') +// Billing cycle selection — pre-select from URL query param if present +const urlParams = new URLSearchParams(window.location.search) +const initialCycle = (urlParams.get('cycle') || 'monthly') as 'monthly' | 'quarterly' | 'semi_annual' | 'annual' +const billingCycle = ref<'monthly' | 'quarterly' | 'semi_annual' | 'annual'>(initialCycle) const billingCycles = [ { value: 'monthly', label: 'Monthly', months: 1, discount: 0, popular: false }, @@ -66,8 +68,21 @@ const selectedCycle = computed(() => const monthlyPrice = computed(() => parseFloat(props.plan.price)) -// Calculate price with billing cycle discount +// Get price for the selected billing cycle from server-provided plan.prices +function getSelectedPrice(): number { + const planPrice = props.plan.prices?.find( + (p: { billing_cycle: string }) => p.billing_cycle === billingCycle.value + ) + return planPrice ? parseFloat(planPrice.price) : monthlyPrice.value +} + +// Calculate price with billing cycle — use server prices if available, fall back to client-side discount const basePriceWithCycle = computed(() => { + if (props.plan.prices && props.plan.prices.length > 0) { + const cyclePrice = getSelectedPrice() + return cyclePrice * selectedCycle.value.months + } + // Fallback: client-side discount calculation const monthly = monthlyPrice.value const cycle = selectedCycle.value const discountedMonthly = monthly * (1 - cycle.discount) @@ -115,7 +130,7 @@ const form = useForm({ gateway: 'stripe', payment_method_id: props.paymentMethods?.[0]?.id || '', coupon_code: '', - billing_cycle: 'monthly', + billing_cycle: initialCycle, configuration: { os_template_id: props.osTemplates[0]?.id || null, auth_method: 'password' as 'password' | 'ssh',