Integrating bKash Tokenized Checkout in Laravel
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
Get Token (Background – Server Only)
Create Payment (Initiated – No money moved)
User Authorization (OTP & PIN on bKash)
Callback Redirect (Untrusted)
Execute Payment (Money transfers here)
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:
01770618575OTP:
123456PIN:
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:
iduser_idinvoice_number(unique)amountstatus(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
.envCreate
config/bkash.phpUse 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
Never trust callback URLs
Always generate unique invoices
Cache tokens for 59 minutes
Store trxID securely
Log full API responses
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.
Yaqub Ajgori
Full Stack Developer specializing in Laravel, Vue.js, PHP, and MySQL. Building scalable web solutions for businesses worldwide.