A simple Laravel package for integrating the bKash Tokenized Payment Gateway into your application. With built-in payment flow and full control via manual methods, this package supports payment creation, execution, status queries, refunds, and token management.
- Overview
 - Requirements
 - Installation
 - Configuration
 - Usage
 - Built-in Payment Flow
 - Error Handling
 - Customization
 - Test Credentials
 - Testing
 - Contributing
 - Credits and License
 
The Laravel bKash package simplifies integrating bKash’s tokenized payment gateway into your Laravel projects. It provides:
- Quick installation and configuration.
 - Built-in controllers, routes, and views for out-of-the-box payment flow.
 - Manual methods for complete control.
 - Detailed error handling through custom exceptions.
 
- PHP: 8.0 or higher
 - Laravel: 8.x or later
 - cURL Extension: Enabled
 
Install the package via Composer:
composer require theihasan/laravel-bkashPublish the migrations
php artisan vendor:publish --tag=bkash-migrationsMigrate the database
php artisan migrateThen, run the setup command to test the connection and publish assets:
php artisan bkash:setup --test --publish-views --publish-controllersAlternatively, publish individual assets as needed:
- Configuration:
php artisan vendor:publish --tag="bkash-config" 
After publishing, update the config/bkash.php file with your bKash credentials and settings:
return [
    'sandbox' => env('BKASH_SANDBOX', true),
    'credentials' => [
        'app_key'    => env('BKASH_APP_KEY', ''),
        'app_secret' => env('BKASH_APP_SECRET', ''),
        'username'   => env('BKASH_USERNAME', ''),
        'password'   => env('BKASH_PASSWORD', ''),
    ],
    'sandbox_base_url' => env('SANDBOX_BASE_URL', 'https://tokenized.sandbox.bka.sh'),
    'live_base_url'    => env('LIVE_BASE_URL', 'https://tokenized.pay.bka.sh'),
    'version' => 'v1.2.0-beta',
    'cache' => [
        'token_lifetime' => 3600,
    ],
    'default_currency' => 'BDT',
    'default_intent'   => 'sale',
    'redirect_urls' => [
        'success' => '/payment/success',
        'failed'  => '/payment/failed',
    ],
    'routes' => [
        'enabled' => true,
    ],
    
        /*
    |--------------------------------------------------------------------------
    | Event Configuration
    |--------------------------------------------------------------------------
    | Configure whether to fire events on successful payments
    */
    'events' => [
        'payment_success' => env('BKASH_FIRE_PAYMENT_SUCCESS_EVENT', true),
    ],
    
        /*
    |--------------------------------------------------------------------------
    | Database Configuration
    |--------------------------------------------------------------------------
    */
    'database' => [
        'table_prefix' => env('BKASH_TABLE_PREFIX', 'bkash_'),
    ],
];Also, add the necessary variables to your .env file:
BKASH_SANDBOX=true
BKASH_APP_KEY='0vWQuCRGiUX7EPVjQDr0EUAYtc'
BKASH_APP_SECRET='jcUNPBgbcqEDedNKdvE4G1cAK7D3hCjmJccNPZZBq96QIxxwAMEx'
BKASH_USERNAME='01770618567'
BKASH_PASSWORD='D7DaC<*E*eG'
SANDBOX_BASE_URL=https://tokenized.sandbox.bka.sh
LIVE_BASE_URL=https://tokenized.pay.bka.shYou can use the package with its built-in payment flow or build a custom process.
Use the provided createPayment method to start a payment:
use Ihasan\Bkash\Facades\Bkash;
public function initiatePayment(Request $request)
{
    $paymentData = [
        'amount'                  => '100', // Payment amount in BDT
        'payer_reference'         => 'customer123',
        'callback_url'            => route('bkash.callback'), //If you use this built in route then this package will handle your callback automatically otherwise you have to implement your own callback logic. So don't change this to use automatic callback handling
        'merchant_invoice_number' => 'INV-123456',
    ];
    try {
        $response = Bkash::createPayment($paymentData);
        // Redirect to the bKash payment page
        return redirect()->away($response['bkashURL']);
    } catch (\Exception $e) {
        return back()->with('error', $e->getMessage());
    }
}Starting from version 1.3.0, the package supports multi-tenant applications. This is useful when you have multiple tenants (organizations, businesses, etc.) using the same application but with different bKash credentials.
use Ihasan\Bkash\Facades\Bkash;
public function initiatePayment(Request $request)
{
    $paymentData = [
        'amount'                  => '100', // Payment amount in BDT
        'payer_reference'         => 'customer123',
        'callback_url'            => route('bkash.callback'), //If you use this built in route then this package will handle your callback automatically otherwise you have to implement your own callback logic. So don't change this to use automatic callback handling
        'merchant_invoice_number' => 'INV-123456',
    ];
    try {
        $response = Bkash::forTenant($tenantId)->createPayment($paymentData);
        // Redirect to the bKash payment page
        return redirect()->away($response['bkashURL']);
    } catch (\Exception $e) {
        return back()->with('error', $e->getMessage());
    }
}After payment, bKash will redirect to your callback URL:
use Ihasan\Bkash\Facades\Bkash;
public function handleCallback(Request $request)
{
    if ($request->input('status') === 'success') {
        try {
            $response = Bkash::executePayment($request->input('paymentID'));
            return redirect()->route('payment.success', ['transaction_id' => $response['trxID']]);
        } catch (\Exception $e) {
            return redirect()->route('payment.failed')->with('error', $e->getMessage());
        }
    }
    return redirect()->route('payment.failed')->with('error', 'Payment was not successful');
}Check a payment’s status using:
use Ihasan\Bkash\Facades\Bkash;
public function queryPaymentStatus($paymentId)
{
    try {
        $response = Bkash::queryPayment($paymentId);
        $status = $response['transactionStatus'];
        return response()->json([
            'success' => $status === 'Completed',
            'message' => 'Payment is ' . $status,
            'data'    => $response,
        ]);
    } catch (\Exception $e) {
        return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
    }
}use Ihasan\Bkash\Facades\Bkash;
public function queryPaymentStatus($paymentId)
{
    try {
        $response = Bkash::forTenant($tenantId)->queryPayment($paymentId);
        $status = $response['transactionStatus'];
        return response()->json([
            'success' => $status === 'Completed',
            'message' => 'Payment is ' . $status,
            'data'    => $response,
        ]);
    } catch (\Exception $e) {
        return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
    }
}Initiate a refund (partial or full) with:
use Ihasan\Bkash\Facades\Bkash;
public function refundPayment(Request $request)
{
    $refundData = [
        'payment_id' => $request->input('payment_id'),
        'trx_id'     => $request->input('trx_id'),
        'amount'     => $request->input('amount'),
        'reason'     => $request->input('reason'),
    ];
    try {
        $response = Bkash::refundPayment($refundData);
        return response()->json(['success' => true, 'message' => 'Refund processed successfully', 'data' => $response]);
    } catch (\Exception $e) {
        return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
    }
}use Ihasan\Bkash\Facades\Bkash;
public function refundPayment(Request $request)
{
    $refundData = [
        'payment_id' => $request->input('payment_id'),
        'trx_id'     => $request->input('trx_id'),
        'amount'     => $request->input('amount'),
        'reason'     => $request->input('reason'),
    ];
    try {
        $response = Bkash::forTenant($tenantId)->refundPayment($refundData);
        return response()->json(['success' => true, 'message' => 'Refund processed successfully', 'data' => $response]);
    } catch (\Exception $e) {
        return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
    }
}For manual token operations:
// Get a token
$token = Bkash::getToken();
// Refresh a token
$token = Bkash::refreshToken();Tokens are tenant-specific, so you can manage them for each tenant:
use Ihasan\Bkash\Facades\Bkash;
// Get a token for a tenant
$token = Bkash::forTenant($tenantId)->getToken();
// Refresh a token for a tenant
$token = Bkash::forTenant($tenantId)->refreshToken();By default, the package registers these routes:
- GET /bkash/callback – Payment callback handling.
 - GET /bkash/success – Payment success page.
 - GET /bkash/failed – Payment failure page.
 
To define your own routes, simply disable the built-in ones in config/bkash.php by setting:
'routes' => [
    'enabled' => false,
],The package provides clear exception classes to help you handle errors:
- TokenGenerationException: When token generation fails.
 - RefreshTokenException: When token refresh fails.
 - PaymentCreationException: When payment creation fails.
 - PaymentExecutionException: When executing payment fails.
 - PaymentQueryException: When payment status query fails.
 - RefundException: When refund processing fails.
 
Handle exceptions as shown in the usage examples above.
Customize the built-in views and controllers to match your needs:
- 
Views:
php artisan bkash:setup --publish-views
Files will be copied to
resources/views/vendor/bkash/. - 
Controllers:
php artisan bkash:setup --publish-controllers
Controllers will appear in
app/Http/Controllers/Vendor/Bkash/. Adjust namespaces as needed. 
Starting from version 1.1.0, you can customize the database table prefix used by the package. This is useful when you want to avoid table name conflicts or organize your database schema.
By default, all tables created by this package use the bkash_ prefix. You can change this by updating your .env file:
BKASH_TABLE_PREFIX=custom_prefix_Or by directly modifying the config/bkash.php file:
'database' => [
    'table_prefix' => env('BKASH_TABLE_PREFIX', 'bkash_'),
],If you're updating from a previous version and want to use a custom table prefix:
- 
Publish the new migration file:
php artisan vendor:publish --tag="bkash-migrations" - 
Set your desired prefix in the
.envfile or config file. - 
Run the migration to create new tables with your prefix and migrate existing data:
php artisan migrate
 
Note: The migration will automatically copy your existing data to the new tables with your custom prefix. Your original tables will remain untouched, so you can verify the data before removing the old tables if needed.
- Changing the table prefix after you've already been using the package will create new tables with the new prefix.
 - The package will automatically use the tables with the configured prefix.
 - If you're using direct database queries in your application that reference these tables, make sure to update those queries to use the new table names.
 
Starting from version 1.1.0, the package can fire Laravel events when certain actions occur. You can listen for these events to perform additional actions in your application.
This event is fired when a payment is successfully executed:
Ihasan\Bkash\Events\PaymentSuccessfulThe event contains:
$payment- The BkashPayment model instance$paymentData- The raw payment data from bKash
By default, all events are enabled. You can disable specific events in your .env file:
BKASH_FIRE_PAYMENT_SUCCESS_EVENT=falseOr in your config/bkash.php file:
'events' => [
    'payment_success' => false,
],You can listen for these events in your EventServiceProvider:
protected $listen = [
    \Ihasan\Bkash\Events\PaymentSuccessful::class => [
        \App\Listeners\HandleSuccessfulPayment::class,
    ],
];Or if you are using Laravel 11 or higher Laravel will automatically register the listener. Just run this command for listener
php artisan make:listener SendBkashPaymentNotification --event=PaymentSuccessfulExample listener:
namespace App\Listeners;
use Ihasan\Bkash\Events\PaymentSuccessful;
use Illuminate\Contracts\Queue\ShouldQueue;
class HandleSuccessfulPayment implements ShouldQueue
{
    public function handle(PaymentSuccessful $event)
    {
        $payment = $event->payment;
        $paymentData = $event->paymentData;
        
        // Your custom logic here
        // For example, update order status, send notification, etc.
    }
}For sandbox testing, you may use these credentials (or update your .env accordingly):
- Testing Numbers:
- 01929918378
 - 01619777283
 - 01619777282
 - 01823074817
 
 - OTP: 123456
 - PIN: 12121
 
Run package tests with:
composer testEnsure your testing environment is set up as required by your Laravel configuration.
Contributions are welcome. When submitting a pull request:
- Follow PSR-4 coding standards.
 - Include tests for new features or bug fixes.
 - Update the documentation as needed.
 
Credits:
- Developed by Abul Hassan
 - Special thanks to:
- Ahmed Shamim Hassan Shaon for his invaluable guidance in package development.
 - Anis Uddin Ahmed for his valuable insights and support.
 
 
License:
Licensed under the MIT License.
