From ac3f429958fcefe53a1761a29f200267a2fe341e Mon Sep 17 00:00:00 2001 From: nexxo Date: Sun, 26 Jul 2026 09:27:02 +0200 Subject: [PATCH] fix(billing): round the recurring figure the same way as the total MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The note rounded VAT after aggregating while the total rounded per order, so cent-level prices made the two disagree — and a customer who spots that stops trusting every other number on the page. Co-Authored-By: Claude Opus 4.8 --- resources/views/livewire/billing.blade.php | 8 +++++--- tests/Feature/CartTest.php | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/resources/views/livewire/billing.blade.php b/resources/views/livewire/billing.blade.php index 129bc45..0d5e63e 100644 --- a/resources/views/livewire/billing.blade.php +++ b/resources/views/livewire/billing.blade.php @@ -72,7 +72,9 @@ @php $net = $pending->sum('amount_cents'); $gross = $pending->sum(fn ($o) => $o->grossCents()); - $recurringNet = $pending->filter->isRecurring()->sum('amount_cents'); + // Summed from the same per-order rounding the total uses: rounding + // after aggregating would let the two figures disagree by a cent. + $recurringGross = $pending->filter->isRecurring()->sum(fn ($o) => $o->grossCents()); @endphp
@@ -87,11 +89,11 @@ {{ __('billing.cart.total_gross') }} {{ $eur($gross) }}
- @if ($recurringNet > 0) + @if ($recurringGross > 0) {{-- The number that actually matters every month, kept apart from a one-off traffic top-up in the same cart. --}}

- {{ __('billing.cart.recurring_note', ['amount' => $eur((int) round($recurringNet * (1 + \App\Models\Order::taxRate())))]) }} + {{ __('billing.cart.recurring_note', ['amount' => $eur($recurringGross)]) }}

@endif
diff --git a/tests/Feature/CartTest.php b/tests/Feature/CartTest.php index c5471e6..9274a14 100644 --- a/tests/Feature/CartTest.php +++ b/tests/Feature/CartTest.php @@ -132,3 +132,20 @@ it('separates what recurs from a one-off top-up', function () { Livewire\Livewire::actingAs($user)->test(Billing::class)->assertSee($expected); }); + +it('keeps the recurring figure consistent with the total to the cent', function () { + [$customer, $user] = cartCustomer(); + config()->set('provisioning.tax.rate_percent', 20); + + // Cent-level prices: rounding after aggregating instead of per order makes + // the two figures disagree, and a customer who spots that stops trusting + // every other number on the page. + Order::factory()->for($customer)->create(['type' => 'storage', 'amount_cents' => 3, 'status' => 'pending']); + Order::factory()->for($customer)->create(['type' => 'storage', 'amount_cents' => 3, 'status' => 'pending']); + + $expected = Illuminate\Support\Number::currency(0.08, in: 'EUR', locale: 'de'); + + Livewire\Livewire::actingAs($user)->test(Billing::class) + ->assertSee($expected) // total gross + ->assertSee(__('billing.cart.recurring_note', ['amount' => $expected])); // and the note +});