feat: update checkout and API resources for cycle-aware pricing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Dev
2026-03-14 23:38:01 -04:00
parent d1df4dd8b2
commit e0e38e47c6
4 changed files with 26 additions and 5 deletions

View File

@@ -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'),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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',