@php
/** @var \App\Models\Job $job */
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
/* ---------- helpers ---------- */
$toCents = function ($v): int {
if (is_null($v)) return 0;
if (is_int($v)) return $v;
if (is_numeric($v) && str_contains((string) $v, '.')) return (int) round(((float) $v) * 100);
return (int) $v;
};
$currency = $job->currency ?? optional($job->flow)->currency ?? 'NZD';
$formatMoney = function (int $cents, string $cur = 'NZD'): string {
$amount = number_format($cents / 100, 2);
$symbol = match (strtoupper($cur)) {
'NZD' => 'NZ$', 'AUD' => 'A$', 'USD' => '$', 'GBP' => '£', 'EUR' => '€',
default => ''
};
return $symbol ? $symbol.$amount : ($amount.' '.strtoupper($cur));
};
/* ---------- totals ---------- */
$totalCents = $job->charge_cents
?? $job->total_cents
?? $toCents($job->charge_amount ?? $job->total_amount ?? 0);
$paidCents = (int) ($job->paid_cents ?? 0);
if ($paidCents === 0 && method_exists($job, 'payments')) {
try {
$col = Schema::hasColumn('payments', 'amount_cents') ? 'amount_cents'
: (Schema::hasColumn('payments', 'amount') ? 'amount' : null);
if ($col) {
$paidCents = (int) $job->payments()
->whereIn('status', ['succeeded','captured','paid','completed'])
->where(function ($q) {
$q->whereNull('purpose')->orWhereNotIn('purpose', ['hold','bond_hold']);
})
->sum($col);
}
} catch (\Throwable $e) { /* ignore */ }
}
$remainingCents = max(0, (int)$totalCents - (int)$paidCents);
/* ---------- hold (job → flow fallbacks) ---------- */
$holdCents = (function () use ($job, $toCents) {
$candidates = [
$job->hold_amount_cents ?? null,
$job->hold_cents ?? null,
optional($job->flow)->hold_cents ?? null,
$toCents($job->hold_amount ?? null),
$toCents(optional($job->flow)->hold_amount ?? null),
$job->bond_cents ?? null,
optional($job->flow)->bond_cents ?? null,
optional($job->flow)->deposit_cents ?? null,
optional($job->flow)->hold_amount_cents ?? null,
];
foreach ($candidates as $v) if (is_numeric($v) && (int)$v > 0) return (int)$v;
return 0;
})();
/* ---------- mode (adds hold-only handling like the React version) ---------- */
$serverMode = $mode ?? null; // allow controller override
$computedMode = ($remainingCents > 0 && $holdCents > 0) ? 'charge+hold'
: (($remainingCents > 0) ? 'charge'
: (($holdCents > 0) ? 'hold' : 'none'));
$mode = $serverMode ?: $computedMode;
/* ---------- dates ---------- */
$tz = $job->timezone ?? config('app.timezone', 'UTC');
$startAt = $job->start_at ? Carbon::parse($job->start_at)->timezone($tz) : null;
$endAt = $job->end_at ? Carbon::parse($job->end_at)->timezone($tz) : null;
$fmt = fn (Carbon $c) => $c->isoFormat('ddd D MMM, HH:mm');
$durationLabel = null;
if ($startAt && $endAt && $endAt->greaterThan($startAt)) {
$mins = $endAt->diffInMinutes($startAt);
$d = intdiv($mins, 1440); $h = intdiv($mins % 1440, 60); $m = $mins % 60;
$parts = []; if ($d) $parts[] = $d.'d'; if ($h) $parts[] = $h.'h'; if (!$d && !$h && $m) $parts[] = $m.'m';
$durationLabel = implode(' ', $parts);
}
$tzShort = $startAt?->format('T') ?? $endAt?->format('T') ?? strtoupper($tz);
/* ---------- urls / keys ---------- */
$stripeKey = config('services.stripe.key');
$bundleUrl = route('portal.pay.bundle.job', ['job' => $job->getKey()]);
$successUrl = route('portal.pay.job.complete', ['job' => $job->getKey()]);
$recordPaidUrl = route('portal.pay.recordPaid', ['job' => $job->getKey()]);
$customerEmail = optional($job->customer)->email ?? ($job->customer_email ?? '');
/* ---------- CTA text (prebuilt) ---------- */
$ctaPayText = $remainingCents > 0
? 'Pay ' . $formatMoney((int)$remainingCents, $currency)
: ($mode === 'hold' ? 'Authorise ' . $formatMoney((int)$holdCents, $currency) . ' hold' : 'Confirm');
$ctaHoldText = ($mode === 'charge+hold' && $holdCents > 0)
? ' + authorise ' . $formatMoney((int)$holdCents, $currency) . ' hold'
: '';
/* ---------- booking ref ---------- */
$jobRef = $job->external_reference
?? $job->reference
?? $job->job_reference
?? $job->ref
?? ('#'.$job->id);
/* ---------- GA4 ---------- */
$gaItem = [
'item_id' => (string)($jobRef ?? $job->id),
'item_name' => (string)($job->title ?? 'Booking'),
'price' => (float) number_format(($remainingCents ?? 0)/100, 2, '.', ''),
'quantity' => 1,
];
/* ---------- controller may pass $clientSecret for hold-only ---------- */
$clientSecret = $clientSecret ?? null;
@endphp
Secure Payment • {{ config('app.name') }}
@php
$header = $mode === 'hold' ? 'Authorise your refundable security hold'
: ($mode === 'charge+hold' ? 'Complete your payment & authorise hold'
: ($mode === 'charge' ? 'Complete your payment' : 'Nothing to pay'));
@endphp
{{ $header }}
Reservation {{ $jobRef }} • {{ $job->title ?? 'Booking' }}
@if($startAt || $endAt)
Rental:
@if($startAt) {{ $fmt($startAt) }} @else TBC @endif
→
@if($endAt) {{ $fmt($endAt) }} @else TBC @endif
@if($durationLabel) ({{ $durationLabel }}) @endif • {{ $tzShort }}
@endif
{{-- Only show a red banner when there is TRULY nothing to do --}}
@if($mode === 'none')
Heads up:
Nothing to chargeeee.
@endif
Heads up:
Something went wrong.
What you’ll authorise now
@if($remainingCents > 0)
- Amount due now: {{ $formatMoney((int)$remainingCents, $currency) }}
@endif
@if($holdCents > 0)
- Refundable security hold: {{ $formatMoney((int)$holdCents, $currency) }} will be authorised (manual capture).
@endif
- Save card for off-session: we may save your card for permitted adjustments.