Integrating bKash Tokenized Checkout in Laravel

December 24, 2025 10 min read

A Developer’s Practical Guide for Secure Payments in Bangladesh

If you are building an e-commerce, ticketing, or booking system in Bangladesh, integrating bKash Tokenized Checkout is almost mandatory.

This guide explains the correct and secure payment flow using Laravel, Inertia.js, and Vue 3, with special focus on security pitfalls that many developers get wrong.


Understanding the Payment Flow

⚠️ Critical Misconception

Many developers assume a payment is completed when bKash redirects to the callback URL.
This assumption is dangerous and incorrect.

👉 Callback URLs can be forged.
👉 Money transfers only during the Execute step.


The Complete bKash Tokenized Checkout Flow

Payment Lifecycle Overview

  1. Get Token (Background – Server Only)

  2. Create Payment (Initiated – No money moved)

  3. User Authorization (OTP & PIN on bKash)

  4. Callback Redirect (Untrusted)

  5. Execute Payment (Money transfers here)

  6. Fulfill Order

Easy Formula to Remember

Create → Approve → Execute → Deliver


Step-by-Step Implementation Guide


Step 1: Token Authentication

This is a server-side background process.

Laravel sends merchant credentials to bKash and receives an authentication token, valid for 1 hour.

Best Practice: Cache the Token (59 Minutes)

Cache::remember('bkash_token', 3540, function () {
    $response = Http::post("{$endpoint}/token/grant", [
        'app_key'    => $appKey,
        'app_secret' => $appSecret,
    ]);

    return $response->json()['id_token'];
});

Why cache the token?

  • Improves performance

  • Prevents unnecessary API calls

  • Avoids rate-limit issues


Step 2: Create Payment

Your server prepares the payment request and sends:

  • Amount

  • Unique invoice number

  • Callback URL

Frontend Example (Vue / Inertia)

const response = await fetch('/bkash/create', {
  method: 'POST',
  body: JSON.stringify({
    amount: 500,
    invoice: 'INV-' + Date.now(),
  })
});

const data = await response.json();
window.location.href = data.bkashURL;

📌 Payment Status: Initiated
💰 Money Moved: ❌ No


Step 3: User Authorization

The user is now on bKash’s servers.

They enter:

  • Phone number

  • OTP

  • PIN

At this stage:

  • Money is reserved

  • Money is NOT transferred

Sandbox Test Credentials

  • Phone: 01770618575

  • OTP: 123456

  • PIN: 12121


Step 4: Callback Redirect (Untrusted)

bKash redirects back to your application:

yoursite.com/callback?status=success&paymentID=xxx

🚫 Never Fulfill Orders Here

Attackers can manually fake this URL:

yoursite.com/callback?status=success&paymentID=fake123

👉 The callback is NOT proof of payment


Step 5: Execute Payment (MOST IMPORTANT)

This is the only step where money actually transfers.

Your server must call the Execute API to verify and complete the transaction.

Backend Example (Laravel)

$response = Http::post("{$endpoint}/checkout/execute", [
    'paymentID' => $request->payment_id,
]);

if ($response->json()['transactionStatus'] === 'Completed') {
    $transaction->update([
        'status' => 'completed',
        'trx_id' => $response->json()['trxID'],
    ]);
}

Why trxID Matters

  • Official proof of payment

  • Required for reconciliation

  • Essential for dispute handling


Step 6: Fulfill the Order

Only after Execute returns Completed, you may:

  • Generate tickets / receipts

  • Send confirmation emails

  • Update order or booking status

  • Trigger post-payment workflows


Common Mistakes to Avoid

❌ Wrong

✅ Correct

Trust callback parameters

Always verify via Execute API

Fulfill order in callback

Fulfill after Execute only

Generate token per request

Cache token for 59 minutes

Reuse invoice numbers

Generate unique invoices

Skip trxID storage

Always save trxID


Recommended Database Structure

transactions Table

Store full payment data for auditing and debugging:

  • id

  • user_id

  • invoice_number (unique)

  • amount

  • status (pending / completed / failed)

  • payment_id (from Create API)

  • trx_id (from Execute API)

  • metadata (JSON – full API responses)

  • created_at, updated_at


Quick Implementation Checklist

Configuration

  • Add bKash credentials to .env

  • Create config/bkash.php

  • Use correct sandbox / production endpoint

Backend (Laravel)

  • Create transaction migration & model

  • Implement token caching

  • Create payment session

  • Handle callback (redirect only)

  • Execute payment verification

Frontend (Vue / Inertia)

  • Payment form (amount, invoice)

  • Redirect to bKash URL

  • Callback page auto-executes payment

  • Show success / failure messages


Key Takeaways

  • bKash handles authorization

  • Your server verifies the truth

The Golden Rule

Money transfers ONLY in Execute

Security Best Practices

  1. Never trust callback URLs

  2. Always generate unique invoices

  3. Cache tokens for 59 minutes

  4. Store trxID securely

  5. Log full API responses

  6. Use HTTPS with valid SSL


Testing Your Integration

Sandbox Environment

Endpoint:
https://tokenized.sandbox.bka.sh/v1.2.0-beta

Test Scenarios

  • Successful payment

  • User cancellation

  • Duplicate invoice attempts

  • Invalid amounts


Production Readiness Checklist

  • Switch to production endpoint

  • Use real merchant credentials

  • Update bKash script URLs

  • Test with small real payments

  • Monitor logs closely


Final Reminder

The callback is NOT proof of payment.
Always execute before delivering.

Md Yaqub Ajgori

Yaqub Ajgori

Full Stack Developer specializing in Laravel, Vue.js, PHP, and MySQL. Building scalable web solutions for businesses worldwide.