Transfer: two-step add-funds modal on insufficient balance
Deploy Ladill Transfer / deploy (push) Successful in 48s
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:
co-authored by
Claude Opus 4.8
parent
c9994129dc
commit
a0a706093b
@@ -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);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\Billing\BillingClient;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* In-app wallet top-up. Posts to the central billing API to start a Paystack
|
||||
* checkout against the one Ladill wallet, then hands the user to checkout. Backs
|
||||
* the two-step "Add funds" modal so users never leave for the wallet page when a
|
||||
* transfer can't be afforded.
|
||||
*/
|
||||
class WalletTopupController extends Controller
|
||||
{
|
||||
public function store(Request $request, BillingClient $billing): JsonResponse|RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'amount' => ['required', 'numeric', 'min:5', 'max:5000'],
|
||||
'return_url' => ['nullable', 'url', 'max:2048'],
|
||||
]);
|
||||
|
||||
$account = ladill_account();
|
||||
if ($account === null) {
|
||||
return $request->expectsJson()
|
||||
? response()->json(['error' => 'Unauthenticated.'], 401)
|
||||
: redirect()->route('sso.connect');
|
||||
}
|
||||
|
||||
$returnUrl = $validated['return_url'] ?? (string) (url()->previous() ?: route('transfer.transfers.create'));
|
||||
|
||||
try {
|
||||
$checkoutUrl = $billing->topup((string) $account->public_id, (float) $validated['amount'], $returnUrl);
|
||||
} catch (\Throwable) {
|
||||
$checkoutUrl = '';
|
||||
}
|
||||
|
||||
if ($checkoutUrl === '') {
|
||||
return $request->expectsJson()
|
||||
? response()->json(['error' => 'Unable to start payment. Please try again.'], 422)
|
||||
: back()->with('error', 'Unable to start payment. Please try again.');
|
||||
}
|
||||
|
||||
return $request->expectsJson()
|
||||
? response()->json(['checkout_url' => $checkoutUrl])
|
||||
: redirect()->away($checkoutUrl);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user