Transfer: two-step add-funds modal on insufficient balance
Deploy Ladill Transfer / deploy (push) Successful in 48s

Creating a transfer that the wallet can't cover used to flash a bare error.
Detect the insufficient-balance case and open an in-app two-step modal (billing
explainer -> amount -> Paystack) on the create page instead, with an 'Add funds'
button to top up on demand.

- BillingClient::topup -> POST /api/billing/topup (Transfer already holds its key)
- WalletTopupController + route POST /wallet/topup (user.wallet.topup)
- store() flashes open_topup_modal on TransferWalletErrors::isInsufficientBalance
- service-topup-modal made two-step (uses <x-modal>, plain POST -> checkout)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
isaacclad
2026-06-26 07:15:38 +00:00
co-authored by Claude Opus 4.8
parent c9994129dc
commit a0a706093b
6 changed files with 168 additions and 87 deletions
@@ -4,10 +4,12 @@ namespace App\Http\Controllers\Transfer;
use App\Http\Controllers\Controller;
use App\Models\Transfer;
use App\Services\Billing\BillingClient;
use App\Services\Qr\QrImageGeneratorService;
use App\Services\Qr\QrPdfExporter;
use App\Services\Transfer\TransferService;
use App\Services\Upload\ChunkedUploadService;
use App\Support\TransferWalletErrors;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
@@ -59,12 +61,21 @@ class TransferController extends Controller
return view('transfer.transfers.index', compact('transfers', 'search', 'status', 'sort'));
}
public function create(): View
public function create(BillingClient $billing): View
{
$account = ladill_account();
$balanceCedis = 0.0;
try {
$balanceCedis = $billing->balanceMinor((string) $account->public_id) / 100;
} catch (\Throwable) {
// Balance is informational on this page; ignore lookup failures.
}
return view('transfer.transfers.create', [
'maxFiles' => (int) config('transfer.max_files_per_transfer', 20),
'pricePerGb' => (float) config('transfer.price_per_gb_month', 0.30),
'gracePeriodDays' => (int) config('transfer.grace_period_days', 15),
'balanceCedis' => $balanceCedis,
]);
}
@@ -108,7 +119,12 @@ class TransferController extends Controller
try {
$transfer = $this->transfers->create($account, $data, $files);
} catch (RuntimeException $e) {
return back()->withInput()->with('error', $e->getMessage());
$redirect = back()->withInput()->with('error', $e->getMessage());
if (TransferWalletErrors::isInsufficientBalance($e)) {
$redirect->with('open_topup_modal', 'transfer');
}
return $redirect;
} finally {
foreach ($uploadIds as $uploadId) {
$this->chunkedUploads->cleanup($uploadId);