@php $loc = app()->getLocale(); $eur = fn ($c) => Number::currency($c / 100, in: 'EUR', locale: $loc); @endphp

{{ __('billing.title') }}

{{ __('billing.subtitle') }}

{{-- Ganz oben, vor allem anderen: wer hier landet, weil eine Abbuchung gescheitert ist, soll nicht erst an Paketen vorbeiscrollen. Darunter die zwei Bauteile, die das Problem tatsächlich lösen — offene Rechnungen begleichen und die Karte tauschen. --}} @if ($pastDue)

{{ __('billing.past_due_title') }}

{{ __('billing.past_due_body') }}

@endif @if ($hasStripeCustomer)
@livewire('open-invoices') @livewire('payment-method')
@endif {{-- purchase() refuses before touching anything when the active mode has no Stripe key — a sentence here, not a 500 page, and not a cart entry nobody can ever pay for. OUTSIDE the contract branch below on purpose: the customer most likely to meet this is the one buying for the FIRST time, who has no contract yet. Inside the @else it would be invisible to exactly them. --}} @error('purchase') {{ $message }} @enderror {{-- Nothing booked. Said plainly instead of falling back to the first plan and rendering it as "AKTUELLES PAKET — Status Aktiv": that is a contract nobody entered into, on the page a customer opens to find out what they pay, and the dashboard said the opposite about the same account. Everything below needs a contract to mean anything — an "Upgrade" from nothing is a purchase, and that has its own page. --}} @if (! $hasContract)

{{ __('billing.none_title') }}

{{ __('billing.none_body') }}

{{ __('billing.none_cta') }} {{ __('dashboard.nav.support') }}
@else {{-- Current plan --}}

{{ __('billing.current_plan') }}

{{ __('billing.plan.'.$currentKey) }}

@if ($current['granted'] ?? false) {{-- Not "free", not struck through — just the service. A later conversion to paid must not read as a negotiation. --}}

{{ __('billing.granted_plan') }}

@else

{{ $eur($current['price_cents'] ?? 0) }}

{{ __('billing.net_per_month') }}

{{-- A yearly contract is charged once a year; the figure above is per month whatever the term, so without this line a yearly customer read a number that never appears on a statement. --}} @if (($current['term'] ?? null) === App\Models\Subscription::TERM_YEARLY)

{{ __('billing.term_yearly_note', ['total' => $eur($current['term_total_cents'] ?? 0)]) }} @if ($current['renews_at'] ?? null) {{-- R19: stored in UTC, read on the wall clock. --}} · {{ __('billing.renews_at', ['date' => $current['renews_at']->local()->isoFormat('D. MMM YYYY')]) }} @endif

@endif @if ($totalMonthlyCents !== null && $totalMonthlyCents !== ($current['price_cents'] ?? 0)) {{-- The plan alone is not the bill once modules are booked, and every part of it is frozen at what was agreed. --}}

{{ __('billing.total_with_addons', ['total' => $eur($totalMonthlyCents)]) }}

@endif @endif
{{-- Storage is the figure Nextcloud actually enforces: the package plus every booked pack. Printing the package alone told a customer with two packs that they had 500 GB while their own cloud was giving them 700. --}} @foreach ([ ['billing.storage', ($current['storage_gb'] ?? 0).' GB'], ['billing.seats', $current['seats'] ?? '—'], ['billing.performance', __('billing.perf.'.($current['performance'] ?? 'standard'))], ['billing.status', __('billing.status_active')], ] as [$label, $value])

{{ __($label) }}

{{ $value }}

@endforeach
{{-- Where the storage above comes from, once it is no longer only the package. A customer who books packs should be able to see them in the figure they are enforced in, not only as a line on the bill. --}} @if (($current['storage_packs'] ?? 0) > 0)

{{ __('billing.storage_includes_packs', [ 'count' => $current['storage_packs'], 'gb' => $current['storage_pack_gb'] ?? 0, ]) }}

@endif {{-- A change already booked, on the card that states what they have. A contract quietly due to shrink at the end of the term is not something a customer should have to remember on their own — and it is announced here rather than only in the cart, because the cart entry goes the moment it is paid for and the booking does not. --}} @if ($pendingChange)

{{ __('billing.pending_change_note', ['plan' => $pendingChange['plan'], 'date' => $pendingChange['at']]) }}

@endif
@endif {{-- Cart: "5 purchases pending" told nobody what they had ordered, and offered no way to change their mind. Outside the contract gate on purpose: a customer can have something in their cart before they have any contract at all, and hiding it there would hide the thing they came to pay for. --}} @if ($pending->isNotEmpty())

{{ __('billing.cart.title') }}

{{ __('billing.cart.subtitle') }}
@php $net = $pending->sum('amount_cents'); $gross = $pending->sum(fn ($o) => $o->grossCents()); // 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
{{ __('billing.cart.subtotal_net') }} {{ $eur($net) }}
{{ $tax->reverseCharge ? __('billing.cart.reverse_charge') : __('billing.cart.vat', ['percent' => $tax->percentLabel()]) }} {{ $eur($gross - $net) }}
{{ __('billing.cart.total_gross') }} {{ $eur($gross) }}
@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($recurringGross)]) }}

@endif
@endif @if ($hasContract) {{-- Upgrades --}} @if (count($upgrades) > 0)

{{ __('billing.upgrade_title') }}

@foreach ($upgrades as $key) @php $p = $plans[$key]; @endphp

{{ __('billing.plan.'.$key) }}

{{ __('billing.perf.'.($p['performance'] ?? 'standard')) }}

{{ $p['quota_gb'] }} GB · {{ __('billing.seats_count', ['count' => $p['seats'] ?? 0]) }}

    @foreach (array_slice($features[$key] ?? [], 0, 4) as $f)
  • {{ __('billing.feature.'.$f) }}
  • @endforeach

{{ $eur($p['price_cents']) }} / {{ __('billing.month_short') }}

{{ $tax->reverseCharge ? __('billing.net_reverse_charge') : __('billing.net_hint', ['percent' => $tax->percentLabel()]) }}

{{ __('billing.upgrade_cta') }}
@endforeach
@endif {{-- ── Downgrade ────────────────────────────────────────────────────── Smaller plans are listed even when they cannot be taken, with the reason in the customer's own numbers. A plan that quietly disappears reads as "no longer offered"; a greyed-out button that does not say what is in the way is the thing people ring about. --}} @if ($downgrades !== [])

{{ __('billing.downgrade') }}

{{ __('billing.downgrade_hint') }}

@foreach ($downgrades as $key => $plan)

{{ $plan['name'] ?? $key }}

{{ $plan['quota_gb'] ?? 0 }} GB · {{ __('billing.seats_count', ['count' => $plan['seats'] ?? 0]) }}

{{ $eur($plan['price_cents'] ?? 0) }} {{ __('billing.per_month') }}

{{-- What the move would take away, above the button that makes it — being told afterwards that an own domain has been switched off is the same failure as a greyed-out button with no reason on it. --}} @if ($plan['check']->consequences !== [])

{{ __('billing.downgrade_consequence_title') }}

    @foreach ($plan['check']->consequences as $consequence)
  • {{ __('billing.downgrade_consequence.'.$consequence['key'], $consequence['replace']) }}
  • @endforeach
@endif @if ($plan['check']->allowed) {{ __('billing.downgrade_to', ['plan' => $plan['name'] ?? $key]) }} @else

{{ __('billing.downgrade_blocked') }}

    @foreach ($plan['check']->blockers as $blocker)
  • {{ __('billing.downgrade_blocker.'.$blocker['key'], [ 'current' => $blocker['current'], 'limit' => $blocker['limit'], ]) }}
  • @endforeach
{{-- "No" with a way out of it. A customer blocked by their own data has exactly two: buy the room, or delete the files and have us look again — and being told to wait for tonight's reading after clearing 40 GB is the same dead end as a greyed-out button. --}} @if ($remedy = $plan['check']->storageRemedy)

{{ __('billing.downgrade_escape.intro', [ 'used' => $remedy['used'], 'limit' => $remedy['limit'], 'over' => $remedy['over'], ]) }}

@if ($remedy['packs'] > 0) {{ __('billing.downgrade_escape.book', ['count' => $remedy['packs'], 'gb' => $remedy['packs'] * $remedy['pack_gb']]) }} @endif {{ __('billing.downgrade_escape.remeasure') }}
@if ($storageMeasuredAt)

{{ __('billing.downgrade_escape.measured_at', ['when' => $storageMeasuredAt->local()->isoFormat('LLL')]) }}

@endif
@endif
@endif
@endforeach
@endif {{-- Extra storage + Add-ons --}}
{{-- Storage --}}

{{ __('billing.storage_title') }}

{{ __('billing.storage_body', ['gb' => $storage['gb'], 'price' => $eur($storage['price_cents'])]) }}

{{ __('billing.net_per_month') }}

{{ __('billing.storage_cta', ['gb' => $storage['gb']]) }}
{{-- Traffic --}}

{{ __('billing.traffic_title') }}

{{ __('billing.traffic_body', ['gb' => $trafficAddon['gb'], 'price' => $eur($trafficAddon['price_cents'])]) }}

{{ __('billing.net_once') }}

@if ($trafficMeter)

{{ __('billing.traffic_used', [ 'used' => \App\Support\Bytes::human($trafficMeter->usedBytes), 'quota' => \App\Support\Bytes::human($trafficMeter->quotaBytes), ]) }}

@endif {{ __('billing.traffic_cta', ['gb' => $trafficAddon['gb']]) }}
{{-- Add-ons --}} @foreach ($addons as $key => $addon)

{{ __('billing.addon.'.$key.'.name') }}

{{ __('billing.addon.'.$key.'.desc') }}

@if ($addon['included'] ?? false) {{-- Part of the package they already pay for. Neither a price nor a button belongs here: both would be selling a customer something they have. --}}

{{ __('billing.addon_included') }}

@elseif ($addon['granted'] ?? false) {{-- Not "free", not struck through — just the service. --}}

{{ __('billing.granted_plan') }}

@else {{-- What they actually pay for it once booked (packs and all), and today's price while it is still a sale to be made. --}}

{{ $eur($addon['booked'] ? $addon['monthly_cents'] : (int) $addon['price_cents']) }} / {{ __('billing.month_short') }} @if ($addon['booked'] && $addon['quantity'] > 1) · {{ __('billing.addon_packs', ['count' => $addon['quantity']]) }} @endif

{{ $tax->reverseCharge ? __('billing.net_reverse_charge') : __('billing.net_hint', ['percent' => $tax->percentLabel()]) }}

@endif @if ($addon['booked'] && ($addon['cancelling'] ?? false)) {{-- Cancelled and still running. The customer has to be able to see both halves of that: the date it actually stops, and that they can still take it back until then. What is NOT offered is a second cancellation — the card that says "gekündigt" must not also say "kündigen". --}}

{{ __('billing.addon_runs_until', ['date' => $addon['cancels_at']?->local()->isoFormat('LL')]) }}

@elseif ($addon['booked']) {{-- Already theirs, at the price they booked it for — which is why this card does not show today's. --}}

{{ __('billing.addon_booked') }}

@elseif ($addon['in_cart'] ?? false) {{-- Placed and waiting to be paid for. Saying so is what stops the second click: an unchanged card invites one, and a second order for the same module is a second charge for one thing. --}}

{{ __('billing.addon_in_cart_badge') }}

@endif {{-- The button appears only where the purchase would actually go through. A module the package includes has nothing to sell; one already booked is a second charge for the same thing, unless it is sold in packs, where a second one is genuinely a second helping (see `sold_as` in config/provisioning.php); and one already in the cart is that second charge one step earlier. An offer that would be refused is worse than no offer. --}} @if (! ($addon['included'] ?? false) && ! ($addon['in_cart'] ?? false) && ! ($addon['booked'] && ($addon['entitlement'] ?? true))) {{ __('billing.addon_cta') }} @endif {{-- The way out, which the page did not have at all. The owner's rule is that a module can be cancelled monthly, and until now the only way to exercise it was to write to us. Not offered on a granted module: nothing is being charged for it, so there is no recurring cost to escape, and letting a customer "cancel" a gift would take away something they were given rather than stopping a payment. --}} @if ($addon['booked'] && ! ($addon['granted'] ?? false)) @if ($addon['cancelling'] ?? false) @else @endif @endif
@endforeach
@endif

{{ __('billing.mock_note') }}