Deploy Ladill Link / deploy (push) Successful in 1m11s
Use the shared Inline sheet, hold busy until payment UI opens, and avoid locking page scroll during the handoff.
154 lines
7.4 KiB
PHP
154 lines
7.4 KiB
PHP
@props([
|
|
'id',
|
|
'title' => 'Add funds',
|
|
'description' => '',
|
|
'topupAction',
|
|
'minTopup' => 5,
|
|
'suggestedAmount' => 10,
|
|
'ladillWalletBalance' => 0,
|
|
'serviceBalance' => null,
|
|
'serviceBalanceLabel' => 'Wallet balance',
|
|
'pricePerAction' => null,
|
|
'actionNoun' => 'action',
|
|
'openOnLoad' => false,
|
|
'returnUrl' => null,
|
|
])
|
|
|
|
@php
|
|
// Single-wallet (siloing step 2): there is one Ladill wallet, so "pay from
|
|
// wallet into service credits" is a meaningless round-trip — top up the one
|
|
// wallet directly (Paystack). Transfer option removed.
|
|
$canPayFromWallet = false;
|
|
|
|
// Balance shown in the explainer — prefer the service balance, fall back to
|
|
// the central wallet figure.
|
|
$shownBalance = $serviceBalance !== null ? (float) $serviceBalance : (float) $ladillWalletBalance;
|
|
@endphp
|
|
|
|
<x-modal :name="$id" :show="$openOnLoad" maxWidth="md">
|
|
<div x-data="{ step: 1 }"
|
|
x-on:open-modal.window="String($event.detail) === @js($id) ? step = 1 : null"
|
|
class="flex min-h-full flex-col sm:min-h-0">
|
|
|
|
{{-- Header with step indicator --}}
|
|
<div class="border-b border-slate-100 px-5 py-4 pr-12 sm:px-6 sm:pr-14">
|
|
<div class="flex items-center gap-1.5">
|
|
<span class="h-1.5 w-6 rounded-full transition-colors" :class="step >= 1 ? 'bg-indigo-600' : 'bg-slate-200'"></span>
|
|
<span class="h-1.5 w-6 rounded-full transition-colors" :class="step >= 2 ? 'bg-indigo-600' : 'bg-slate-200'"></span>
|
|
<span class="ml-2 text-xs font-medium text-slate-400" x-text="'Step ' + step + ' of 2'"></span>
|
|
</div>
|
|
<h2 class="mt-2 text-lg font-semibold text-slate-900"
|
|
x-text="step === 1 ? 'How billing works' : @js($title)">How billing works</h2>
|
|
<p x-show="step === 2 && @js((bool) $description)" x-cloak class="mt-1 text-sm text-slate-500">{{ $description }}</p>
|
|
</div>
|
|
|
|
{{-- STEP 1 — billing explainer --}}
|
|
<div x-show="step === 1" class="flex flex-1 flex-col p-5 sm:p-6">
|
|
<p class="text-sm leading-6 text-slate-600">
|
|
Ladill is <span class="font-medium text-slate-800">pay-as-you-go</span>. One wallet funds
|
|
every Ladill app, and you're only charged when you perform an action.
|
|
</p>
|
|
|
|
<div class="mt-4 space-y-2.5 rounded-xl border border-slate-200 bg-slate-50 p-4">
|
|
@if($pricePerAction !== null)
|
|
<div class="flex items-center justify-between text-sm">
|
|
<span class="text-slate-500">Each {{ $actionNoun }} costs</span>
|
|
<span class="font-semibold text-slate-900">GHS {{ number_format((float) $pricePerAction, 2) }}</span>
|
|
</div>
|
|
@endif
|
|
<div class="flex items-center justify-between text-sm">
|
|
<span class="text-slate-500">{{ $serviceBalanceLabel }}</span>
|
|
<span class="font-semibold text-slate-900">GHS {{ number_format($shownBalance, 2) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="mt-4 text-xs leading-5 text-slate-500">
|
|
Top up any amount — unused funds stay in your wallet for next time. You can withdraw
|
|
them whenever you like.
|
|
</p>
|
|
|
|
<div class="mt-6 flex flex-col gap-2 sm:mt-auto sm:flex-row sm:justify-end">
|
|
<button type="button" @click="$dispatch('close-modal', '{{ $id }}')"
|
|
data-close-modal="{{ $id }}"
|
|
class="hidden rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 sm:inline-flex">
|
|
Not now
|
|
</button>
|
|
<button type="button" @click="step = 2" class="btn-primary w-full sm:w-auto">
|
|
Add funds
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- STEP 2 — amount + payment --}}
|
|
<form x-show="step === 2" x-cloak action="{{ $topupAction }}" method="POST" class="flex flex-1 flex-col p-5 sm:p-6"
|
|
x-data="{
|
|
method: 'paystack',
|
|
showSheet: false,
|
|
checkoutUrl: '',
|
|
loading: false,
|
|
errorMsg: '',
|
|
async submitTopup(event) {
|
|
if (this.method !== 'paystack') return;
|
|
|
|
event.preventDefault();
|
|
this.loading = true; window.LadillPayCheckout?.prepare?.();
|
|
this.errorMsg = '';
|
|
|
|
try {
|
|
const res = await fetch(event.target.action, {
|
|
method: 'POST',
|
|
headers: { 'Accept': 'application/json' },
|
|
body: new FormData(event.target),
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok || data.error || !data.checkout_url) {
|
|
this.errorMsg = data.error || 'Unable to start payment. Please try again.';
|
|
this.loading = false;
|
|
return;
|
|
}
|
|
|
|
this.checkoutUrl = data.checkout_url;
|
|
this.showSheet = true; // loading cleared when payment UI opens (ladill-pay-opened)
|
|
} catch (error) {
|
|
window.LadillPayCheckout?.cancel?.(); this.errorMsg = 'Network error. Please try again.';
|
|
this.loading = false;
|
|
}
|
|
},
|
|
}"
|
|
@submit="submitTopup($event)">
|
|
@csrf
|
|
@if($returnUrl)
|
|
<input type="hidden" name="return_url" value="{{ $returnUrl }}">
|
|
@endif
|
|
|
|
<div x-show="errorMsg" x-cloak class="mb-4 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700" x-text="errorMsg"></div>
|
|
|
|
<div>
|
|
<label class="text-sm font-medium text-slate-700">Amount (GHS)</label>
|
|
<input type="number" name="amount" min="{{ $minTopup }}" max="5000" step="0.01"
|
|
value="{{ $suggestedAmount }}"
|
|
class="mt-1.5 block w-full rounded-xl border border-slate-200 px-3 py-2.5 text-sm focus:border-indigo-400 focus:ring-1 focus:ring-indigo-400"
|
|
required>
|
|
<p class="mt-1.5 text-xs text-slate-500">Minimum GHS {{ number_format($minTopup, 2) }}</p>
|
|
</div>
|
|
|
|
<input type="hidden" name="payment_method" value="paystack">
|
|
|
|
<p class="mt-4 text-xs text-slate-500">Pay securely with Paystack.</p>
|
|
|
|
<div class="mt-6 flex flex-col gap-2 sm:mt-auto sm:flex-row sm:justify-end">
|
|
<button type="button" @click="step = 1"
|
|
class="hidden rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 sm:inline-flex">
|
|
Back
|
|
</button>
|
|
<button type="submit" :disabled="loading"
|
|
class="btn-primary w-full sm:w-auto">
|
|
<span x-text="loading ? 'Processing…' : 'Continue to payment'">Continue to payment</span>
|
|
</button>
|
|
</div>
|
|
|
|
@include('partials.paystack-sheet')
|
|
</form>
|
|
</div>
|
|
</x-modal>
|