Deploy Ladill Events / deploy (push) Successful in 42s
Cut ticket checkouts off Ladill Pay, settle to merchant gateways at 0% platform fee, and mirror Invoice freemium pricing (GHS 49 / 149). Co-authored-by: Cursor <cursoragent@cursor.com>
184 lines
13 KiB
PHP
184 lines
13 KiB
PHP
<x-user-layout>
|
||
<x-slot name="title">Payments & Payouts</x-slot>
|
||
@php
|
||
$fmt = fn ($m) => 'GHS '.number_format($m / 100, 2);
|
||
$pa = is_array($payoutAccount ?? null) ? $payoutAccount : null;
|
||
$hasPayoutErrors = $errors->hasAny(['account_type', 'account_name', 'account_number', 'bank_code', 'bank_name']);
|
||
@endphp
|
||
<div class="mx-auto max-w-5xl space-y-6">
|
||
@foreach(['success', 'error'] as $flash)
|
||
@if(session($flash))
|
||
<div class="rounded-xl border px-4 py-3 text-sm {{ $flash === 'success' ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-red-200 bg-red-50 text-red-700' }}">
|
||
{{ session($flash) }}
|
||
</div>
|
||
@endif
|
||
@endforeach
|
||
|
||
<div>
|
||
<h1 class="text-xl font-semibold text-slate-900">Payments & Payouts</h1>
|
||
<p class="mt-1 text-sm text-slate-500">Paid tickets and contributions settle directly to your connected gateway (0% Ladill fee). This wallet is for app subscriptions and older balances only.</p>
|
||
</div>
|
||
|
||
<div class="grid gap-4 sm:grid-cols-2">
|
||
<div class="rounded-2xl border border-slate-200 bg-white p-6">
|
||
<p class="text-xs font-medium uppercase tracking-wide text-slate-400">Total event revenue (net)</p>
|
||
<p class="mt-2 text-3xl font-semibold text-slate-900">{{ $fmt($revenueMinor) }}</p>
|
||
</div>
|
||
<div class="rounded-2xl border border-indigo-100 bg-indigo-50/60 p-6">
|
||
<p class="text-xs font-medium uppercase tracking-wide text-indigo-600">Available in wallet</p>
|
||
<p class="mt-2 text-3xl font-semibold text-slate-900">{{ $fmt($balanceMinor) }}</p>
|
||
<a href="{{ route('account.wallet') }}" class="mt-4 inline-block text-sm font-medium text-indigo-600 hover:text-indigo-800">Full wallet →</a>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Withdraw --}}
|
||
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white" x-data="{ open: {{ $pa ? 'true' : 'false' }} }">
|
||
<button type="button" @click="open = !open" class="flex w-full items-center justify-between gap-3 px-5 py-4 text-left">
|
||
<div>
|
||
<h2 class="text-sm font-semibold text-slate-900">Withdraw to bank or MoMo</h2>
|
||
<p class="mt-0.5 text-xs text-slate-500">
|
||
@if($pa)
|
||
To {{ $pa['bank_name'] }} · {{ $pa['account_number'] }}
|
||
@else
|
||
Add a payout account to withdraw from your wallet.
|
||
@endif
|
||
</p>
|
||
</div>
|
||
<svg class="h-4 w-4 shrink-0 text-slate-400 transition" :class="open && 'rotate-180'" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/></svg>
|
||
</button>
|
||
|
||
<div x-show="open" x-cloak class="space-y-4 border-t border-slate-100 px-5 py-4">
|
||
@if($pa)
|
||
<div class="flex items-start gap-3 rounded-xl border border-slate-100 bg-slate-50/80 p-4">
|
||
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-indigo-100">
|
||
<svg class="h-5 w-5 text-indigo-600" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"/></svg>
|
||
</div>
|
||
<div class="min-w-0 flex-1">
|
||
<p class="text-[11px] font-semibold uppercase tracking-widest text-slate-400">{{ ($pa['account_type'] ?? '') === 'mobile_money' ? 'Mobile Money' : 'Bank Account' }}</p>
|
||
<p class="text-sm font-semibold text-slate-900">{{ $pa['account_name'] }}</p>
|
||
<p class="text-xs text-slate-500">{{ $pa['bank_name'] }} · {{ $pa['account_number'] }}</p>
|
||
</div>
|
||
<button type="button" @click="$dispatch('open-modal', 'events-payout-account')" class="shrink-0 text-xs font-semibold text-indigo-600 hover:text-indigo-800">Change</button>
|
||
</div>
|
||
|
||
<form action="{{ route('events.payouts.withdraw') }}" method="POST" class="space-y-3">
|
||
@csrf
|
||
<div>
|
||
<label class="block text-xs font-semibold text-slate-600">Amount (GHS)</label>
|
||
<div class="relative mt-1.5">
|
||
<span class="absolute inset-y-0 left-0 flex items-center pl-3.5 text-sm text-slate-400">GHS</span>
|
||
<input type="number" name="amount" min="1" max="{{ $balanceGhs }}" step="0.01" value="{{ old('amount') }}" placeholder="10.00"
|
||
class="block w-full rounded-xl border border-slate-200 py-2.5 pl-12 pr-4 text-sm text-slate-900 placeholder-slate-400 focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30" required>
|
||
</div>
|
||
@error('amount') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
|
||
</div>
|
||
<button type="submit" class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-slate-800">Request withdrawal</button>
|
||
</form>
|
||
@else
|
||
<div class="flex flex-col items-center gap-3 py-2 text-center">
|
||
<p class="text-sm text-slate-600">Add a payout account to withdraw from your wallet.</p>
|
||
<button type="button" @click="$dispatch('open-modal', 'events-payout-account')" class="btn-primary">Add payout account</button>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<x-modal name="events-payout-account" :show="$hasPayoutErrors" maxWidth="md" focusable>
|
||
<div class="border-b border-slate-100 px-5 py-4 pr-12 sm:px-6 sm:pr-14">
|
||
<h3 class="text-base font-semibold text-slate-900">Payout account</h3>
|
||
<p class="mt-1 text-sm text-slate-500">Mobile money or bank account for withdrawals.</p>
|
||
</div>
|
||
<div class="px-5 py-4 sm:px-6 sm:py-5">
|
||
@include('events.partials.payout-account-form', ['payoutAccount' => $pa])
|
||
</div>
|
||
</x-modal>
|
||
|
||
{{-- Event payments --}}
|
||
<div class="rounded-2xl border border-slate-200 bg-white">
|
||
<div class="border-b border-slate-100 px-5 py-4">
|
||
<h2 class="text-sm font-semibold text-slate-900">Event payments</h2>
|
||
<p class="mt-0.5 text-xs text-slate-500">Legacy wallet credits from before BYO gateway settlement. New ticket revenue does not land here.</p>
|
||
</div>
|
||
|
||
@if($transactions->isEmpty())
|
||
<div class="px-6 py-10 text-center">
|
||
<svg class="mx-auto h-10 w-10 text-slate-300" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18.75a60.07 60.07 0 0 1 15.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 0 1 3 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 0 0-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 0 1-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 0 0 3 15h-.75M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm3 0h.008v.008H18V10.5Zm-12 0h.008v.008H6V10.5Z"/></svg>
|
||
<h3 class="mt-3 text-sm font-semibold text-slate-900">No payments yet</h3>
|
||
<p class="mt-1 text-sm text-slate-500">Paid registrations will appear here once attendees complete checkout.</p>
|
||
</div>
|
||
@else
|
||
<div class="overflow-x-auto">
|
||
<table class="min-w-full text-left text-sm">
|
||
<thead class="border-b border-slate-100 bg-slate-50/80 text-xs uppercase tracking-wide text-slate-500">
|
||
<tr>
|
||
<th class="px-5 py-3 font-medium">Event</th>
|
||
<th class="px-5 py-3 font-medium">Attendee</th>
|
||
<th class="px-5 py-3 font-medium">Type</th>
|
||
<th class="px-5 py-3 font-medium text-right">Gross</th>
|
||
<th class="px-5 py-3 font-medium text-right">Fee</th>
|
||
<th class="px-5 py-3 font-medium text-right">Net</th>
|
||
<th class="px-5 py-3 font-medium">Paid</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-slate-100">
|
||
@foreach($transactions as $tx)
|
||
@php
|
||
$eventName = $tx->qrCode?->content()['name'] ?? $tx->qrCode?->label ?? 'Event';
|
||
$isContribution = ($tx->qrCode?->content()['mode'] ?? 'ticketing') === 'contributions';
|
||
@endphp
|
||
<tr class="hover:bg-slate-50/50">
|
||
<td class="px-5 py-3 font-medium text-slate-900">{{ $eventName }}</td>
|
||
<td class="px-5 py-3 text-slate-600">{{ $tx->attendee_name }}</td>
|
||
<td class="px-5 py-3 text-slate-600">{{ $tx->tier_name }}</td>
|
||
<td class="px-5 py-3 text-right text-slate-600">{{ $fmt($tx->amount_minor) }}</td>
|
||
<td class="px-5 py-3 text-right text-slate-400">−{{ $fmt($tx->platformFeeMinor()) }}</td>
|
||
<td class="px-5 py-3 text-right font-medium text-slate-900">{{ $fmt($tx->netAmountMinor()) }}</td>
|
||
<td class="px-5 py-3 text-slate-500">
|
||
{{ $tx->paid_at?->format('M j, Y g:i A') ?? '—' }}
|
||
@if($tx->payment_reference)
|
||
<span class="block text-[11px] text-slate-400">{{ $tx->payment_reference }}</span>
|
||
@endif
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Withdrawal history --}}
|
||
@if(!empty($withdrawals))
|
||
<div class="rounded-2xl border border-slate-200 bg-white">
|
||
<div class="border-b border-slate-100 px-5 py-4">
|
||
<h2 class="text-sm font-semibold text-slate-900">Withdrawal history</h2>
|
||
<p class="mt-0.5 text-xs text-slate-500">Recent requests to your bank or mobile money account.</p>
|
||
</div>
|
||
<div class="divide-y divide-slate-100">
|
||
@foreach($withdrawals as $withdrawal)
|
||
@php
|
||
$status = (string) ($withdrawal['status'] ?? 'pending');
|
||
$statusClass = match ($status) {
|
||
'completed', 'success' => 'text-emerald-700 bg-emerald-50',
|
||
'failed' => 'text-red-700 bg-red-50',
|
||
default => 'text-amber-700 bg-amber-50',
|
||
};
|
||
@endphp
|
||
<div class="flex flex-wrap items-center justify-between gap-3 px-5 py-3">
|
||
<div>
|
||
<p class="text-sm font-medium text-slate-900">GHS {{ number_format((float) ($withdrawal['amount_ghs'] ?? 0), 2) }}</p>
|
||
<p class="text-xs text-slate-500">
|
||
@if(!empty($withdrawal['created_at']))
|
||
{{ \Illuminate\Support\Carbon::parse($withdrawal['created_at'])->format('M j, Y g:i A') }}
|
||
@endif
|
||
</p>
|
||
</div>
|
||
<span class="rounded-full px-2.5 py-0.5 text-xs font-medium capitalize {{ $statusClass }}">{{ $status }}</span>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</x-user-layout>
|