Solved

Automated bKash, Nagad & Rocket Payment Gateway Webhook Integration with Laravel Invoicing

โ€ข 12-Sep-2026 10:39 AM (3 weeks ago)
2 Replies
227 Views

Official Verified Solution

Solved by Tanvir Ahmed

Jump to Answer ↓

Always use database transaction locking (DB::transaction with lockForUpdate) and store the gateway paymentID / trxID in a unique column on the invoice_payments table.

DB::transaction(function () use ($invoice, $trxId, $amount) {
    $lockedInvoice = Invoice::query()->where('id', $invoice->id)->lockForUpdate()->first();
    
    if ($lockedInvoice->status === 'Paid') {
        return; // Idempotent skip
    }
    
    InvoicePayment::query()->create([
        'invoice_id' => $lockedInvoice->id,
        'transaction_id' => $trxId,
        'amount' => $amount,
        'payment_method' => 'bKash',
    ]);
    
    $lockedInvoice->status = 'Paid';
    $lockedInvoice->save();
});

This completely eliminates race conditions.

A
Afsana Mimi @afsana_mimi Author
12-Sep-2026 10:39 AM โ€ข 40 Karma

Hi developers,

We are building automated checkout billing for our online client portal in Bangladesh. We want to support bKash Tokenized Checkout, Nagad, and Rocket.

What is the best architectural approach for handling callback timeouts or cases where customers close the browser before the return URL redirects back? How do you ensure idempotent payment capture without double-crediting customer invoices?

T
13-Sep-2026 01:39 PM โ€ข 55 Karma

Always use database transaction locking (DB::transaction with lockForUpdate) and store the gateway paymentID / trxID in a unique column on the invoice_payments table.

DB::transaction(function () use ($invoice, $trxId, $amount) {
    $lockedInvoice = Invoice::query()->where('id', $invoice->id)->lockForUpdate()->first();
    
    if ($lockedInvoice->status === 'Paid') {
        return; // Idempotent skip
    }
    
    InvoicePayment::query()->create([
        'invoice_id' => $lockedInvoice->id,
        'transaction_id' => $trxId,
        'amount' => $amount,
        'payment_method' => 'bKash',
    ]);
    
    $lockedInvoice->status = 'Paid';
    $lockedInvoice->save();
});

This completely eliminates race conditions.

F
14-Sep-2026 06:39 PM โ€ข 30 Karma

In addition to @tanvir.ahmed's solution, configure a recurring scheduler command in routes/console.php that queries bKash Query Payment API (/v1.2.0-beta/tokenized/checkout/payment/query) for any invoice created in the last 60 minutes that remains in Unpaid status. If bKash confirms transactionStatus == 'Completed', mark it as paid automatically.

Join the Discussion

Please log in to your client account to reply or participate in this conversation.

Log in to Reply
0%