BDPay৳Gatefor Laravel

Laravel payment gateway for Bangladesh

Every Bangladeshi payment gateway. One Laravel API.

Accept bKash, Nagad, SSLCommerz, Upay, ShurjoPay, aamarPay, PortPos, PayStation, UddoktaPay, Moneybag, EPS and DGePay with the same three lines of code. Every payment is confirmed with the gateway before your order is marked paid.

composer require byteminus/laravel-bdpaygate

12 gateways7 proven on real sandboxes401 testsLaravel 11–13 · PHP 8.2–8.5MIT licensed

payment BPWR5VKNM2O9৳120.00 · bkash
  1. charge created, customer sent to bKash
  2. callback says "success" · not trusted yet
  3. signature matches the one bKash issued
  4. execute → bKash: Completed, trxID DJF00PPRDK
  5. amount ৳120.00 = ৳120.00, currency BDT
  6. completed · PaymentCompleted fired once
From a real bKash sandbox run. A forged callback for the same payment was rejected.

Built for the payment that goes wrong

Most integrations work on the happy path. Money goes missing on the other ones.

Forged callbacks

No callback is trusted

Redirects, IPNs and webhooks are claims. The package asks the gateway's own API, checks the amount and currency, and only then changes the payment.

Double fulfilment

Paid exactly once

The IPN and the customer's redirect often arrive together. Status changes are atomic, so PaymentCompleted fires once.

Timeouts

A timeout is not a failure

If the gateway doesn't answer, the payment stays pending and bdpaygate:sync settles it later. A customer who paid is never marked failed.

Retries

Retries can't double charge

Each retry is a new attempt with its own gateway id. If a customer pays two of them, you get PaymentDuplicated to refund, not a second order.

Late success

Failed can still become paid

When a gateway confirms the money moved after a failure was reported, the payment completes. A fake "cancelled" can't strand a paid order.

Logouts

Customers stay signed in

Gateways return customers with a cross-site POST that wipes Laravel sessions. The callback routes run without sessions, so nobody gets logged out at checkout.

12 gateways, one interface

bKash, Nagad, SSLCommerz and the rest all use the same charge, callback, sync and refund.

GatewayStatusRefundsSandbox run
bKashTokenized Checkout V2, v1.2StableFull and partialPaid, refunded twice, forged callback rejected
SSLCommerzCards, banks, walletsStableFull and partialPaid, risk flag, partial refund
ShurjoPayCards, wallets, banksStableMerchant panelPaid and declined
aamarPayCards, wallets, banksStableMerchant panelPaid and declined
PayStationCards, wallets, banksStableMerchant panelPaid via bKash
MoneybagCards, walletsStableMerchant panelPaid and failed
UddoktaPaySelf-hostedStableMerchant panelPaid, forged invoice rejected
NagadMobile walletBetaMerchant panelNeeds merchant onboarding
UpayMobile walletBetaFullNeeds merchant onboarding
PortPosFormerly PortWalletBetaFull and partialSelf-serve sandbox
EPSEasy Payment SystemExperimentalMerchant panelDocs on request
DGePayAggregatorExperimentalMerchant panelDocs on request

Rocket, OK Wallet and CellFin have no public merchant API of their own. Accept them through SSLCommerz, ShurjoPay or aamarPay. "Merchant panel" means the gateway publishes no refund API, so refunds are made from its dashboard.

What happens to every payment

The same steps for all 12 gateways. Nothing the browser sends can mark an order paid.

  1. You call charge()

    The package records a pending attempt with a fresh gateway id and returns the checkout URL to redirect to.

  2. The customer pays on the gateway's page

    bKash, Nagad, a card or a bank. Your app does nothing here.

  3. The gateway calls back, and is not believed

    The redirect or IPN only says which payment to check. Its status, amount and ids are ignored.

  4. The package asks the gateway's own API

    Validation, execute or status query, server to server. The answer must name this attempt, and the amount and currency must match.

  5. One atomic update, one event

    PaymentCompleted, PaymentFailed or PaymentCancelled fires once. If the gateway can't be reached, the payment stays pending for bdpaygate:sync.

The whole integration

Install, add your keys to .env, and write these.

use ByteMinus\BdPayGate\Facades\BdPayGate;
use ByteMinus\BdPayGate\Data\Customer;

$payment = BdPayGate::driver('bkash')->charge(
    amount: 1500,
    reference: 'ORD-1001',
    customer: new Customer(name: 'Rahim Uddin', phone: '01711000000'),
);

return redirect($payment->checkout_url);

Questions developers ask

Which Bangladeshi payment gateways does it support?

bKash, Nagad, SSLCommerz, Upay, ShurjoPay, aamarPay, PortPos (formerly PortWallet), PayStation, UddoktaPay, Moneybag, EPS and DGePay. Rocket, OK Wallet and CellFin work through SSLCommerz, ShurjoPay or aamarPay.

Which Laravel and PHP versions are supported?

Laravel 11, 12 and 13 on PHP 8.2 to 8.5. Laravel 13 needs PHP 8.3 or newer.

Can I refund a bKash payment from Laravel?

Yes. bKash supports full and partial refunds, up to 10 per transaction, and the package tracks the refunded amount. SSLCommerz, Upay and PortPos also have refund APIs. The other gateways only allow refunds from their merchant panel.

What happens if the customer closes the browser after paying?

The gateway's IPN still completes the payment, and bdpaygate:sync settles any payment whose callback never arrived. Do your fulfilment in a PaymentCompleted listener, not on the thank-you page.

Can I use two SSLCommerz stores in one app?

Yes. Each entry in the config is a separate gateway name with its own credentials, routes and payments, so two stores of the same gateway work side by side.

How do I test without real payments?

Use BdPayGate::fake() in your tests: no network and no credentials. For a real end-to-end check, use the gateway's sandbox keys in a local app.

Is it free?

Yes. BDPayGate is open source under the MIT license.

Take payments in ৳ without trusting the browser.

composer require byteminus/laravel-bdpaygate