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:
@@ -69,7 +69,7 @@ class CheckoutController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Inertia::render('Checkout/Show', [
|
return Inertia::render('Checkout/Show', [
|
||||||
'plan' => $plan,
|
'plan' => $plan->load('prices'),
|
||||||
'paymentMethods' => $stripeService->getPaymentMethods($user),
|
'paymentMethods' => $stripeService->getPaymentMethods($user),
|
||||||
'intent' => $user->hasStripeId() ? $user->createSetupIntent() : null,
|
'intent' => $user->hasStripeId() ? $user->createSetupIntent() : null,
|
||||||
'stripeKey' => config('cashier.key'),
|
'stripeKey' => config('cashier.key'),
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ class ServiceResource extends JsonResource
|
|||||||
'name' => $this->plan->name,
|
'name' => $this->plan->name,
|
||||||
'price' => $this->plan->price,
|
'price' => $this->plan->price,
|
||||||
'billing_cycle' => $this->plan->billing_cycle,
|
'billing_cycle' => $this->plan->billing_cycle,
|
||||||
|
'prices' => $this->plan->prices?->mapWithKeys(fn ($p) => [
|
||||||
|
$p->billing_cycle => $p->price,
|
||||||
|
]) ?? [],
|
||||||
]),
|
]),
|
||||||
'provisioned_at' => $this->provisioned_at?->toIso8601String(),
|
'provisioned_at' => $this->provisioned_at?->toIso8601String(),
|
||||||
'created_at' => $this->created_at?->toIso8601String(),
|
'created_at' => $this->created_at?->toIso8601String(),
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class SubscriptionResource extends JsonResource
|
|||||||
'plan' => $this->when($this->plan_name !== null, fn () => [
|
'plan' => $this->when($this->plan_name !== null, fn () => [
|
||||||
'name' => $this->plan_name,
|
'name' => $this->plan_name,
|
||||||
'price' => $this->plan_price,
|
'price' => $this->plan_price,
|
||||||
|
'prices' => $this->plan?->prices?->mapWithKeys(fn ($p) => [
|
||||||
|
$p->billing_cycle => $p->price,
|
||||||
|
]) ?? [],
|
||||||
]),
|
]),
|
||||||
'billing_cycle' => $this->plan_billing_cycle ?? null,
|
'billing_cycle' => $this->plan_billing_cycle ?? null,
|
||||||
'current_period_end' => $this->current_period_end?->toIso8601String(),
|
'current_period_end' => $this->current_period_end?->toIso8601String(),
|
||||||
|
|||||||
@@ -41,8 +41,10 @@ const couponApplied = ref(false)
|
|||||||
const couponDiscount = ref(0)
|
const couponDiscount = ref(0)
|
||||||
const couponError = ref('')
|
const couponError = ref('')
|
||||||
|
|
||||||
// Billing cycle selection
|
// Billing cycle selection — pre-select from URL query param if present
|
||||||
const billingCycle = ref<'monthly' | 'quarterly' | 'semi_annual' | 'annual'>('monthly')
|
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 = [
|
const billingCycles = [
|
||||||
{ value: 'monthly', label: 'Monthly', months: 1, discount: 0, popular: false },
|
{ value: 'monthly', label: 'Monthly', months: 1, discount: 0, popular: false },
|
||||||
@@ -66,8 +68,21 @@ const selectedCycle = computed(() =>
|
|||||||
|
|
||||||
const monthlyPrice = computed(() => parseFloat(props.plan.price))
|
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(() => {
|
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 monthly = monthlyPrice.value
|
||||||
const cycle = selectedCycle.value
|
const cycle = selectedCycle.value
|
||||||
const discountedMonthly = monthly * (1 - cycle.discount)
|
const discountedMonthly = monthly * (1 - cycle.discount)
|
||||||
@@ -115,7 +130,7 @@ const form = useForm({
|
|||||||
gateway: 'stripe',
|
gateway: 'stripe',
|
||||||
payment_method_id: props.paymentMethods?.[0]?.id || '',
|
payment_method_id: props.paymentMethods?.[0]?.id || '',
|
||||||
coupon_code: '',
|
coupon_code: '',
|
||||||
billing_cycle: 'monthly',
|
billing_cycle: initialCycle,
|
||||||
configuration: {
|
configuration: {
|
||||||
os_template_id: props.osTemplates[0]?.id || null,
|
os_template_id: props.osTemplates[0]?.id || null,
|
||||||
auth_method: 'password' as 'password' | 'ssh',
|
auth_method: 'password' as 'password' | 'ssh',
|
||||||
|
|||||||
Reference in New Issue
Block a user