rayhan-bapari / bkash-payment
A production-ready Laravel package for bKash Tokenized Checkout Payment Gateway — with token caching, webhook/IPN support, refund, search, and more. Supports Laravel 9–12 and PHP 8.1–8.5.
Requires
- php: ^8.1
- ext-curl: *
- ext-json: *
- ext-openssl: *
- illuminate/database: ^9.0|^10.0|^11.0|^12.0
- illuminate/http: ^9.0|^10.0|^11.0|^12.0
- illuminate/log: ^9.0|^10.0|^11.0|^12.0
- illuminate/routing: ^9.0|^10.0|^11.0|^12.0
- illuminate/support: ^9.0|^10.0|^11.0|^12.0
Requires (Dev)
- mockery/mockery: ^1.5
- orchestra/testbench: ^7.0|^8.0|^9.0|^10.0
- phpunit/phpunit: ^9.0|^10.0|^11.0
README
A production-ready Laravel package for bKash Tokenized Checkout payment gateway integration.
Features:
- ✅ Create, execute, query, and verify payments
- ✅ Refund & refund status
- ✅ Transaction search by TrxID
- ✅ Database token caching (ID token + refresh token)
- ✅ IPN / Webhook support (Amazon SNS format with subscription confirmation)
- ✅ Optional HMAC-SHA256 webhook signature verification
- ✅ AWS SNS signature verification (RSA + SHA1)
- ✅ Full webhook payload logging to database
- ✅ Laravel Facade & dependency injection support
- ✅ Laravel 9 / 10 / 11 / 12 · PHP 8.1 / 8.2 / 8.3 / 8.4 / 8.5
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.1 |
| Laravel | ^9.0 | ^10.0 | ^11.0 | ^12.0 |
| ext-curl | * |
| ext-json | * |
| ext-openssl | * |
Installation
composer require rayhan-bapari/bkash-payment
Publish config & migrations
php artisan vendor:publish --tag=bkash-config php artisan vendor:publish --tag=bkash-migrations php artisan migrate
Configuration
Add the following variables to your .env file:
# --- Environment --- BKASH_SANDBOX=true # true = sandbox, false = production # --- Merchant credentials (provided by bKash during onboarding) --- BKASH_USERNAME=your_username BKASH_PASSWORD=your_password BKASH_APP_KEY=your_app_key BKASH_APP_SECRET=your_app_secret # --- Payment defaults --- BKASH_CALLBACK_URL=https://yourdomain.com/bkash/callback # --- Webhook / IPN --- BKASH_WEBHOOK_ENABLED=true BKASH_WEBHOOK_PATH=bkash/webhook BKASH_WEBHOOK_SECRET= # optional extra HMAC layer BKASH_WEBHOOK_VERIFY_SSL=true BKASH_WEBHOOK_LOG_PAYLOADS=true
Usage
Via Facade
use RayhanBapari\BkashPayment\Facades\BkashPayment;
Via Dependency Injection
use RayhanBapari\BkashPayment\Contracts\BkashPaymentInterface; public function __construct(protected BkashPaymentInterface $bkash) {}
1. Create Payment
$response = BkashPayment::createPayment([ 'amount' => '100.00', 'merchantInvoiceNumber' => 'INV-' . time(), 'callbackURL' => route('bkash.callback'), // or omit to use config default 'payerReference' => '01712345678', // optional ]); // Redirect user to bKash checkout if (isset($response['bkashURL'])) { return redirect($response['bkashURL']); }
Response:
{
"statusCode": "0000",
"statusMessage": "Successful",
"paymentID": "TR0011abc123",
"bkashURL": "https://sandbox.payment.bkash.com/?paymentId=...",
"callbackURL": "https://yourdomain.com/bkash/callback",
"amount": "100.00",
"currency": "BDT",
"intent": "sale",
"merchantInvoiceNumber": "INV-1718449546"
}
2. Handle Callback (Execute & Verify)
After the user completes payment on the bKash page, bKash redirects back to your callbackURL with paymentID and status query parameters.
// routes/web.php Route::get('/bkash/callback', [BkashController::class, 'callback'])->name('bkash.callback');
// BkashController.php public function callback(Request $request): RedirectResponse { $paymentID = $request->query('paymentID'); $status = $request->query('status'); if ($status === 'cancel') { return redirect()->route('checkout')->with('error', 'Payment cancelled.'); } if ($status === 'failure') { return redirect()->route('checkout')->with('error', 'Payment failed.'); } // status === 'success' — verify the payment $result = BkashPayment::verifyPayment($paymentID); if ($result['success']) { // Payment confirmed — store the TrxID in your orders table // $result['trxID'], $result['amount'] return redirect()->route('order.success')->with('trxID', $result['trxID']); } return redirect()->route('checkout')->with('error', $result['message']); }
3. Execute Payment (manual)
$response = BkashPayment::executePayment('TR0011abc123');
4. Query Payment Status
$response = BkashPayment::queryPayment('TR0011abc123');
5. Search Transaction by TrxID
$response = BkashPayment::searchTransaction('BK0011XYZ');
6. Refund
$response = BkashPayment::refund([ 'paymentID' => 'TR0011abc123', 'trxID' => 'BK0011XYZ', 'amount' => '50.00', 'reason' => 'Customer requested refund', 'sku' => 'ITEM-001', // optional ]);
Refund response:
{
"statusCode": "0000",
"statusMessage": "Successful",
"originalTrxID": "BK0011XYZ",
"refundTrxID": "BK0099REF",
"transactionStatus": "Completed",
"amount": "50.00",
"currency": "BDT"
}
7. Refund Status
$response = BkashPayment::refundStatus('TR0011abc123', 'BK0011XYZ');
Webhook / IPN
bKash delivers payment event notifications as Amazon SNS HTTP messages to your endpoint.
How it works
- You share your webhook URL with the bKash technical team during onboarding.
- bKash sends a
SubscriptionConfirmation— the package automatically visits theSubscribeURLto activate the subscription. - After confirmation, bKash POSTs
Notificationmessages for each completed transaction.
Default webhook URL
POST https://yourdomain.com/bkash/webhook
Override via BKASH_WEBHOOK_PATH in .env.
Listening to webhook events in your app
The package fires a Laravel event bkash.webhook.notification for every notification. Register a listener in your EventServiceProvider:
// app/Providers/EventServiceProvider.php protected $listen = [ 'bkash.webhook.notification' => [ App\Listeners\HandleBkashWebhook::class, ], ];
// app/Listeners/HandleBkashWebhook.php namespace App\Listeners; class HandleBkashWebhook { public function handle(array $payload): void { $trxID = $payload['trxID']; $status = $payload['transactionStatus']; // "Completed" $amount = $payload['amount']; $invoice= $payload['merchantInvoiceNumber']; // Update your order/transaction record here } }
Notification payload fields
| Field | Description |
|---|---|
trxID |
bKash transaction ID |
transactionStatus |
Completed | Initiated | etc. |
amount |
Actual deducted amount |
saleAmount |
Original sale amount (before coupon) |
currency |
BDT |
dateTime |
Transaction datetime (YYYYMMDDHHmmss) |
debitMSISDN |
Customer's bKash wallet number |
merchantInvoiceNumber |
Your invoice number |
transactionReference |
Payer reference value |
couponAmount |
Coupon discount (if any) |
CSRF Exclusion
Add the webhook path to your CSRF exclusions if using web middleware:
// app/Http/Middleware/VerifyCsrfToken.php protected $except = [ 'bkash/webhook', ];
Optional: Extra HMAC verification
Set BKASH_WEBHOOK_SECRET in .env if your setup sends an X-Bkash-Signature header alongside requests for an extra validation layer.
Database Tables
| Table | Purpose |
|---|---|
bkash_tokens |
Caches ID & refresh tokens per environment |
bkash_webhook_logs |
Stores raw + parsed IPN payloads |
Error Handling
The package throws typed exceptions:
| Exception | When |
|---|---|
BkashAuthException |
Token grant or refresh fails |
BkashPaymentException |
Empty/invalid response from bKash API |
BkashWebhookException |
Invalid/untrusted webhook payload |
use RayhanBapari\BkashPayment\Exceptions\BkashAuthException; use RayhanBapari\BkashPayment\Exceptions\BkashPaymentException; try { $result = BkashPayment::createPayment([...]); } catch (BkashAuthException $e) { // Credentials or token issue } catch (BkashPaymentException $e) { // API communication issue }
Sandbox Testing
Set BKASH_SANDBOX=true and use your sandbox credentials. The package routes all requests to:
https://tokenized.sandbox.bka.sh/v1.2.0-beta
License
MIT © Rayhan Bapari