@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') }}
🔒
Secured by
{{ config('app.name') }} (via Stripe)
@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

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.