223 lines
14 KiB
PHP
223 lines
14 KiB
PHP
{{--
|
||
The order, before it is placed.
|
||
|
||
Two columns, and which is which matters: the LEFT one is what you are buying
|
||
and what will happen, the RIGHT one is what it costs and the button that
|
||
spends the money. The summary sticks, so on a long left column the total and
|
||
the button stay in view — the shape every enterprise checkout has, for the
|
||
reason that the figure you are agreeing to should never be scrolled away from
|
||
the agreement.
|
||
|
||
The terms acceptance sits at the BOTTOM of the right column, immediately
|
||
above that button. It used to be at the top of the package list, as a
|
||
condition of looking at prices; it is a condition of buying, and this is the
|
||
moment of buying.
|
||
--}}
|
||
@php
|
||
use Illuminate\Support\Number;
|
||
|
||
$eur = fn (int $cents) => Number::currency($cents / 100, in: $lines['currency'] ?? 'EUR', locale: app()->getLocale());
|
||
@endphp
|
||
|
||
<div class="space-y-6">
|
||
|
||
<header class="flex flex-wrap items-end gap-x-6 gap-y-4 border-b border-line pb-5 animate-rise">
|
||
<div class="min-w-0 flex-1">
|
||
<p class="lbl">{{ __('checkout.eyebrow') }}</p>
|
||
<h1 class="mt-[7px] text-[23px] font-bold leading-[1.12] tracking-[-0.03em] text-ink min-[901px]:text-[30px]">
|
||
{{ __('checkout.title') }}
|
||
</h1>
|
||
</div>
|
||
<a href="{{ route('order') }}" wire:navigate
|
||
class="inline-flex items-center gap-2 text-sm font-semibold text-muted transition-colors hover:text-ink">
|
||
<x-ui.icon name="arrow-left" class="size-4" />{{ __('checkout.back_to_plans') }}
|
||
</a>
|
||
</header>
|
||
|
||
@if ($contract)
|
||
<x-ui.alert variant="info">
|
||
{{ __('order.already_customer') }}
|
||
<a href="{{ route('billing') }}" class="font-semibold underline">{{ __('order.to_billing') }}</a>
|
||
</x-ui.alert>
|
||
@elseif ($package === null)
|
||
{{-- A package key nobody knows. Reached from a retyped address or from a
|
||
package taken off sale between the two pages. --}}
|
||
<x-ui.alert variant="warning">
|
||
{{ __('checkout.unknown_plan') }}
|
||
<a href="{{ route('order') }}" class="font-semibold underline">{{ __('checkout.back_to_plans') }}</a>
|
||
</x-ui.alert>
|
||
@else
|
||
@error('plan')<x-ui.alert variant="error">{{ $message }}</x-ui.alert>@enderror
|
||
@error('term')<x-ui.alert variant="error">{{ $message }}</x-ui.alert>@enderror
|
||
@error('terms_accepted')<x-ui.alert variant="error">{{ $message }}</x-ui.alert>@enderror
|
||
@error('checkout')<x-ui.alert variant="error">{{ $message }}</x-ui.alert>@enderror
|
||
|
||
<div class="grid gap-5 lg:grid-cols-[minmax(0,1fr)_400px] lg:items-start">
|
||
|
||
{{-- ── What you are buying ──────────────────────────────────────── --}}
|
||
<div class="space-y-5">
|
||
<section class="overflow-hidden rounded-lg border border-line bg-surface shadow-xs animate-rise">
|
||
<div class="flex flex-wrap items-baseline gap-x-3 gap-y-1 border-b border-line px-6 py-4">
|
||
<h2 class="text-md font-bold tracking-[-0.01em] text-ink">{{ $package['name'] }}</h2>
|
||
<span class="rounded-pill border border-line bg-surface-2 px-2.5 py-0.5 text-xs font-semibold text-body">
|
||
{{ __('order.term_'.$term) }}
|
||
</span>
|
||
@if ($package['audience'] ?? '')
|
||
<span class="text-sm text-muted">{{ $package['audience'] }}</span>
|
||
@endif
|
||
</div>
|
||
|
||
<dl class="grid grid-cols-2 gap-px bg-line sm:grid-cols-4">
|
||
@foreach ([
|
||
__('order.storage') => $package['quota_gb'] >= 1000 ? ($package['quota_gb'] / 1000).' TB' : $package['quota_gb'].' GB',
|
||
__('order.traffic') => $package['traffic_gb'] >= 1000 ? ($package['traffic_gb'] / 1000).' TB' : $package['traffic_gb'].' GB',
|
||
__('order.seats') => __('order.up_to', ['count' => $package['seats']]),
|
||
__('checkout.performance') => __('billing.perf.'.($package['performance'] ?? 'standard')),
|
||
] as $label => $value)
|
||
<div class="bg-surface px-6 py-4">
|
||
<dt class="text-xs text-muted">{{ $label }}</dt>
|
||
<dd class="mt-1 font-mono text-sm tabular-nums text-ink">{{ $value }}</dd>
|
||
</div>
|
||
@endforeach
|
||
</dl>
|
||
|
||
<div class="border-t border-line px-6 py-4">
|
||
<x-ui.delivery :state="$package['delivery']" />
|
||
</div>
|
||
</section>
|
||
|
||
{{-- What happens after the button. Three steps, because "you are
|
||
redirected to a payment provider" is the point at which a
|
||
first-time buyer wants to know what they are getting into. --}}
|
||
<section class="rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:60ms]">
|
||
<h2 class="text-sm font-bold text-ink">{{ __('checkout.next_title') }}</h2>
|
||
<ol class="mt-4 space-y-4">
|
||
@foreach (['pay', 'build', 'credentials'] as $i => $step)
|
||
<li class="flex gap-3.5">
|
||
<span class="mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-pill bg-ink text-xs font-bold text-bg">{{ $i + 1 }}</span>
|
||
<span class="min-w-0">
|
||
<b class="block text-sm font-semibold text-ink">{{ __('checkout.step_'.$step.'_title') }}</b>
|
||
<span class="mt-0.5 block text-xs leading-relaxed text-muted">{{ __('checkout.step_'.$step.'_body') }}</span>
|
||
</span>
|
||
</li>
|
||
@endforeach
|
||
</ol>
|
||
</section>
|
||
|
||
{{-- The three standing facts about the sale. Same band as the
|
||
order page, so the two pages read as one flow. --}}
|
||
<div class="grid gap-px overflow-hidden rounded-lg border border-line bg-line shadow-xs sm:grid-cols-3 animate-rise [animation-delay:120ms]">
|
||
@foreach ([
|
||
['icon' => 'refresh', 'title' => __('order.note_cancel_title'), 'body' => __('order.note_cancel_body')],
|
||
['icon' => 'lock', 'title' => __('order.note_payment_title'), 'body' => __('order.note_payment_body')],
|
||
['icon' => 'shield', 'title' => __('checkout.note_withdrawal_title'), 'body' => __('checkout.note_withdrawal_body')],
|
||
] as $note)
|
||
<div class="bg-surface p-5">
|
||
<p class="flex items-center gap-2 text-sm font-semibold text-ink">
|
||
<x-ui.icon :name="$note['icon']" class="size-4 text-accent-text" />{{ $note['title'] }}
|
||
</p>
|
||
<p class="mt-2 text-xs leading-relaxed text-muted">{{ $note['body'] }}</p>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ── What it costs, and the button ────────────────────────────── --}}
|
||
<form method="POST" action="{{ route('checkout.start') }}"
|
||
x-data="{ terms: false }"
|
||
class="rounded-lg border border-line bg-surface shadow-md animate-rise [animation-delay:40ms] lg:sticky lg:top-6">
|
||
@csrf
|
||
<input type="hidden" name="plan" value="{{ $package['key'] }}" />
|
||
<input type="hidden" name="term" value="{{ $term }}" />
|
||
<input type="hidden" name="terms_accepted" x-bind:value="terms ? '1' : ''" />
|
||
|
||
<div class="border-b border-line px-6 py-4">
|
||
<h2 class="text-sm font-bold text-ink">{{ __('checkout.summary_title') }}</h2>
|
||
</div>
|
||
|
||
<dl class="space-y-3 px-6 py-5 text-sm">
|
||
<div class="flex items-baseline justify-between gap-4">
|
||
<dt class="text-muted">
|
||
{{ $package['name'] }} ·
|
||
{{ $lines['yearly'] ? __('order.per_year_label') : __('order.per_month_label') }}
|
||
</dt>
|
||
<dd class="font-mono tabular-nums text-ink">{{ $eur($lines['recurring_net']) }}</dd>
|
||
</div>
|
||
|
||
@if ($lines['yearly'] && $lines['free_months'] > 0)
|
||
<div class="flex items-baseline justify-between gap-4 text-success">
|
||
<dt>{{ trans_choice('order.free_months_hint', $lines['free_months'], ['count' => $lines['free_months']]) }}</dt>
|
||
<dd class="font-mono tabular-nums">− {{ $eur($lines['twelve_months_gross'] - $lines['recurring_gross']) }}</dd>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($lines['setup_net'] > 0)
|
||
<div class="flex items-baseline justify-between gap-4">
|
||
<dt class="text-muted">{{ __('checkout.setup_line') }}</dt>
|
||
<dd class="font-mono tabular-nums text-ink">{{ $eur($lines['setup_net']) }}</dd>
|
||
</div>
|
||
@endif
|
||
|
||
<div class="flex items-baseline justify-between gap-4 border-t border-line pt-3">
|
||
<dt class="text-muted">{{ __('checkout.tax_line', ['rate' => $vat]) }}</dt>
|
||
<dd class="font-mono tabular-nums text-ink">{{ $eur($lines['today_tax']) }}</dd>
|
||
</div>
|
||
|
||
{{-- The one figure that will actually be charged, and it is
|
||
the largest thing in the panel. --}}
|
||
<div class="flex items-baseline justify-between gap-4 border-t border-line pt-3">
|
||
<dt class="text-sm font-semibold text-ink">{{ __('checkout.total_today') }}</dt>
|
||
<dd class="font-mono text-xl font-bold tabular-nums tracking-[-0.02em] text-ink">{{ $eur($lines['today_gross']) }}</dd>
|
||
</div>
|
||
|
||
{{-- And what comes after it, because the total above includes
|
||
a one-off and the next charge will not. --}}
|
||
<p class="text-xs leading-relaxed text-muted">
|
||
{{ __($lines['yearly'] ? 'checkout.then_yearly' : 'checkout.then_monthly', [
|
||
'amount' => $eur($lines['recurring_gross']),
|
||
]) }}
|
||
@if ($lines['yearly'])
|
||
· {{ __('order.per_month_equivalent', ['amount' => $eur($lines['per_month_gross'])]) }}
|
||
@endif
|
||
</p>
|
||
</dl>
|
||
|
||
{{-- ── The acceptance, directly above the button ─────────────
|
||
Not at the top of the previous page as a condition of
|
||
looking at prices: it is a condition of buying, and this is
|
||
the button that buys. The server rule is the real gate
|
||
(CheckoutController validates `accepted`), so with
|
||
JavaScript off the field arrives empty and the checkout is
|
||
refused with the sentence explaining why. --}}
|
||
<div class="border-t border-line bg-surface-2 px-6 py-5">
|
||
<label class="flex cursor-pointer items-start gap-3">
|
||
<input type="checkbox" x-model="terms"
|
||
class="mt-0.5 size-4 shrink-0 rounded-sm border-line-strong text-accent-active focus:ring-accent-active" />
|
||
<span class="min-w-0 text-sm leading-relaxed text-body">
|
||
{{-- A new tab: following the link must not lose the
|
||
page with the order on it. --}}
|
||
{!! __('order.terms_label', [
|
||
'link' => '<a href="'.route('legal.agb').'" target="_blank" rel="noopener"
|
||
class="font-semibold text-accent-text underline underline-offset-4">'
|
||
.e(__('order.terms_link')).'</a>',
|
||
]) !!}
|
||
</span>
|
||
</label>
|
||
<p class="mt-2 pl-7 text-xs leading-relaxed text-muted">{{ __('order.terms_why') }}</p>
|
||
|
||
<button type="submit"
|
||
class="mt-5 inline-flex w-full items-center justify-center gap-2 rounded bg-ink px-4 py-3 text-sm font-semibold text-bg transition-colors hover:bg-accent-text"
|
||
x-bind:disabled="!terms"
|
||
x-bind:class="terms ? '' : 'cursor-not-allowed opacity-50'">
|
||
<x-ui.icon name="lock" class="size-4" />{{ __('checkout.pay_cta') }}
|
||
</button>
|
||
<p class="mt-2 text-center text-[11px] leading-snug text-muted" x-show="!terms" x-cloak>
|
||
{{ __('order.terms_first') }}
|
||
</p>
|
||
<p class="mt-3 text-center text-[11px] leading-snug text-muted">{{ __('checkout.stripe_note') }}</p>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
@endif
|
||
</div>
|