Skip to content

Commit c173464

Browse files
simonhampclaude
andcommitted
Improve upgrade modal pricing breakdown with proration handling
- Separate plan price from Stripe proration line items so "New plan" shows the actual charge, not inflated totals - Calculate "Due today" from displayed charge minus credit (clamped to 0) - Show "(pro-rated)" label when using a prorated price - Show remaining credit note when unused time exceeds the new plan cost - Fix undefined route: customer.dashboard → dashboard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 540a81f commit c173464

File tree

3 files changed

+102
-19
lines changed

3 files changed

+102
-19
lines changed

app/Livewire/MobilePricing.php

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class MobilePricing extends Component
2020
#[Url]
2121
public string $interval = 'month';
2222

23-
/** @var array{amount_due: string, raw_amount_due: int, credit: string, new_charge: string}|null */
23+
/** @var array{amount_due: string, raw_amount_due: int, new_charge: string, is_prorated: bool, credit: string|null, remaining_credit: string|null}|null */
2424
public ?array $upgradePreview = null;
2525

2626
#[Locked]
@@ -112,24 +112,33 @@ public function previewUpgrade(): void
112112
$invoice = $subscription->previewInvoice($newPriceId);
113113

114114
$currency = $invoice->asStripeInvoice()->currency;
115-
$credit = 0;
116-
$newCharge = 0;
115+
$newPlanCharge = 0;
116+
$prorationCredit = 0;
117+
$prorationCharge = 0;
117118

118119
foreach ($invoice->invoiceLineItems() as $item) {
119-
$amount = $item->asStripeInvoiceLineItem()->amount;
120+
$raw = $item->asStripeInvoiceLineItem();
120121

121-
if ($amount < 0) {
122-
$credit += abs($amount);
122+
if (! $raw->proration) {
123+
$newPlanCharge += $raw->amount;
124+
} elseif ($raw->amount < 0) {
125+
$prorationCredit += abs($raw->amount);
123126
} else {
124-
$newCharge += $amount;
127+
$prorationCharge += $raw->amount;
125128
}
126129
}
127130

131+
$displayedCharge = $prorationCharge ?: $newPlanCharge;
132+
$amountDue = max(0, $displayedCharge - $prorationCredit);
133+
$remainingCredit = max(0, $prorationCredit - $displayedCharge);
134+
128135
$this->upgradePreview = [
129-
'amount_due' => $invoice->amountDue(),
130-
'raw_amount_due' => $invoice->rawAmountDue(),
131-
'credit' => Cashier::formatAmount($credit, $currency),
132-
'new_charge' => Cashier::formatAmount($newCharge, $currency),
136+
'amount_due' => Cashier::formatAmount($amountDue, $currency),
137+
'raw_amount_due' => $amountDue,
138+
'new_charge' => Cashier::formatAmount($displayedCharge, $currency),
139+
'is_prorated' => $prorationCharge > 0,
140+
'credit' => $prorationCredit > 0 ? Cashier::formatAmount($prorationCredit, $currency) : null,
141+
'remaining_credit' => $remainingCredit > 0 ? Cashier::formatAmount($remainingCredit, $currency) : null,
133142
];
134143
} catch (\Exception $e) {
135144
Log::error('Failed to preview upgrade invoice', ['error' => $e->getMessage()]);
@@ -159,7 +168,7 @@ public function upgradeSubscription(): mixed
159168

160169
$subscription->skipTrial()->swapAndInvoice($newPriceId);
161170

162-
return redirect(route('customer.dashboard'))->with('success', 'Your subscription has been upgraded to Ultra!');
171+
return redirect(route('dashboard'))->with('success', 'Your subscription has been upgraded to Ultra!');
163172
}
164173

165174
private function findOrCreateUser(string $email): User

resources/views/livewire/mobile-pricing.blade.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -353,18 +353,25 @@ class="rounded-full px-4 py-1.5 text-sm font-medium transition"
353353
@if($upgradePreview)
354354
<div class="space-y-2 text-sm">
355355
<div class="flex items-baseline justify-between">
356-
<span class="text-gray-600 dark:text-gray-400">New plan (Ultra)</span>
356+
<span class="text-gray-600 dark:text-gray-400">New plan (Ultra)@if($upgradePreview['is_prorated']) <span class="text-gray-400 dark:text-gray-500">(pro-rated)</span>@endif</span>
357357
<span class="font-medium text-gray-900 dark:text-white">{{ $upgradePreview['new_charge'] }}</span>
358358
</div>
359-
<div class="flex items-baseline justify-between">
360-
<span class="text-gray-600 dark:text-gray-400">Credit for unused {{ $currentPlanName }} time</span>
361-
<span class="font-medium text-emerald-600 dark:text-emerald-400">-{{ $upgradePreview['credit'] }}</span>
362-
</div>
359+
@if($upgradePreview['credit'])
360+
<div class="flex items-baseline justify-between">
361+
<span class="text-gray-600 dark:text-gray-400">Credit for unused {{ $currentPlanName }} time</span>
362+
<span class="font-medium text-emerald-600 dark:text-emerald-400">-{{ $upgradePreview['credit'] }}</span>
363+
</div>
364+
@endif
363365
<div class="border-t border-gray-200 pt-2 dark:border-zinc-700">
364366
<div class="flex items-baseline justify-between">
365367
<span class="font-medium text-gray-900 dark:text-white">Due today</span>
366368
<span class="text-lg font-semibold text-gray-900 dark:text-white">{{ $upgradePreview['amount_due'] }}</span>
367369
</div>
370+
@if($upgradePreview['remaining_credit'])
371+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
372+
{{ $upgradePreview['remaining_credit'] }} will be credited to your next invoice.
373+
</p>
374+
@endif
368375
</div>
369376
</div>
370377
@else

tests/Feature/MobilePricingTest.php

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,14 +311,81 @@ public function upgrade_modal_shows_proration_breakdown_when_preview_loaded()
311311
->set('upgradePreview', [
312312
'amount_due' => '$28.50',
313313
'raw_amount_due' => 2850,
314-
'credit' => '$6.50',
315314
'new_charge' => '$35.00',
315+
'is_prorated' => false,
316+
'credit' => '$6.50',
317+
'remaining_credit' => null,
316318
])
317319
->assertSee('Due today')
318320
->assertSee('$28.50')
319321
->assertSee('$6.50')
320322
->assertSee('$35.00')
321-
->assertSee('Credit for unused');
323+
->assertSee('Credit for unused')
324+
->assertDontSee('pro-rated')
325+
->assertDontSee('credited to your next invoice');
326+
}
327+
328+
#[Test]
329+
public function upgrade_modal_shows_prorated_label_when_charge_is_prorated()
330+
{
331+
$user = User::factory()->create(['stripe_id' => 'cus_'.uniqid()]);
332+
Auth::login($user);
333+
334+
$subscription = Cashier::$subscriptionModel::factory()
335+
->for($user)
336+
->active()
337+
->create(['stripe_price' => self::PRO_PRICE_ID]);
338+
339+
Cashier::$subscriptionItemModel::factory()
340+
->for($subscription, 'subscription')
341+
->create(['stripe_price' => self::PRO_PRICE_ID]);
342+
343+
Livewire::test(MobilePricing::class)
344+
->set('upgradePreview', [
345+
'amount_due' => '$200.00',
346+
'raw_amount_due' => 20000,
347+
'new_charge' => '$250.00',
348+
'is_prorated' => true,
349+
'credit' => '$50.00',
350+
'remaining_credit' => null,
351+
])
352+
->assertSee('New plan (Ultra)')
353+
->assertSee('pro-rated')
354+
->assertSee('$250.00')
355+
->assertSee('Credit for unused')
356+
->assertSee('$50.00')
357+
->assertSee('$200.00')
358+
->assertDontSee('credited to your next invoice');
359+
}
360+
361+
#[Test]
362+
public function upgrade_modal_shows_remaining_credit_note_when_credit_exceeds_charge()
363+
{
364+
$user = User::factory()->create(['stripe_id' => 'cus_'.uniqid()]);
365+
Auth::login($user);
366+
367+
$subscription = Cashier::$subscriptionModel::factory()
368+
->for($user)
369+
->active()
370+
->create(['stripe_price' => self::PRO_PRICE_ID]);
371+
372+
Cashier::$subscriptionItemModel::factory()
373+
->for($subscription, 'subscription')
374+
->create(['stripe_price' => self::PRO_PRICE_ID]);
375+
376+
Livewire::test(MobilePricing::class)
377+
->set('upgradePreview', [
378+
'amount_due' => '$0.00',
379+
'raw_amount_due' => 0,
380+
'new_charge' => '$35.00',
381+
'is_prorated' => false,
382+
'credit' => '$50.00',
383+
'remaining_credit' => '$15.00',
384+
])
385+
->assertSee('$0.00')
386+
->assertSee('$35.00')
387+
->assertSee('$50.00')
388+
->assertSee('$15.00 will be credited to your next invoice');
322389
}
323390

324391
#[Test]

0 commit comments

Comments
 (0)