Home / Node.js / Moneybag Stable
Moneybag
Also for Laravel: https://github.com/byteminus/laravel-bdpaygate/blob/main/docs/gateways/moneybag.md
Status: stable · Driver: moneybag · Refunds: no public API · Sync: no public API (see below)
Moneybag is a Bangladeshi hosted checkout (cards, mobile wallets, EMI). Its public merchant API is small: create a checkout, and verify a transaction.
Setup
MONEYBAG_API_KEY=your-merchant-api-key
MONEYBAG_SANDBOX=true
# MONEYBAG_BASE_URL=https://api.moneybag.com.bd # optional: override the API origin
These are read by gatewaysFromEnv(process.env). Or configure the gateway in code:
gateways: {
moneybag: {
apiKey: process.env.MONEYBAG_API_KEY,
sandbox: true,
// baseUrl: process.env.MONEYBAG_BASE_URL, // override the API origin
},
}
sandbox defaults to true. Set it to false for live payments.
| API origin | |
|---|---|
| Sandbox | https://sandbox.api.moneybag.com.bd |
| Live | https://api.moneybag.com.bd |
Sandbox accounts are self-serve: create one at https://developers.moneybag.com.bd/sandbox (or https://sandbox.moneybag.com.bd). The merchant API key is shown once, so store it straight away. Production keys need a separate review and approval by Moneybag. The public contract lists only the sandbox origin; the live origin above comes from Moneybag's own SDK and plugin releases. Confirm it with Moneybag at go-live, and set baseUrl if they give you another. Docs: https://developers.moneybag.com.bd/docs.
How a payment is verified
pay.charge()creates a checkout (POST /api/v2/payments/checkout, headerX-Merchant-API-Key). This attempt'sgatewayReferenceis sent asorder_id, and your own reference goes inmetadata.reference. The returnedsession_idis stored inpayment.gatewayPaymentId, and the customer is sent topayment.checkoutUrl.- Moneybag returns the customer to
/bdpaygate/moneybag/{success|fail|cancel}?ref=…, addingtransaction_idandstatus(SUCCESS,FAILEDorCANCELLED). - Neither parameter is trusted. The driver calls
GET /api/v2/payments/verify/{transaction_id}and checks:- that the transaction's
order_idis this attempt'sgatewayReference; - for a payment, that
statusisSUCCESSandverifiedistrue, and thatamountandcurrencymatch.
- that the transaction's
- A verified
SUCCESScompletes the payment, even on the fail or cancel URL. A verifiedFAILEDorCANCELLEDfails or cancels it. Anything else stays pending: an unknown transaction, a non-success answer, orverified: false. - The Moneybag
transaction_idis stored inpayment.gatewayTransactionId.
No sync, no refunds
The public API can only verify by transaction_id. There is no lookup by order or session, and no refund API: Moneybag lists refunds, payouts and transaction history as merchant-portal features outside the public API. So:
-
pay.for(payment).capabilitiesis{ sync: false, refunds: false }.pay.sync()andpay.refund()throwUnsupportedOperationError, andpay.syncPending()skips Moneybag payments. Refund from the Moneybag merchant portal. -
If a customer pays but never comes back, find the transaction id in the merchant portal and settle the payment yourself:
import type { MoneybagGateway } from 'bdpaygate'; const updated = await (pay.for(payment) as MoneybagGateway).verifyTransaction(payment, 'txn…');This runs the same checks as a return, and fires the usual events.
-
If the customer cancels before a transaction exists, the return has no
transaction_id. The payment stays pending, and the customer lands on yourpendingredirect.
Limits and quirks
- Amount must be 10.00 to 1000000.00 BDT. Checked before the API call.
order_idmust be 10 to 36 characters; attempt ids are 20.customer.emailis required by Moneybag; the driver falls back tonoemail@example.com. Passcustomer.postcode(ormeta.postcode) to send a postcode.- Validation errors are HTTP 422 with
{"detail":[{"loc","msg","type"}]}; they are included in theGatewayErrormessage. - Webhooks are not handled yet. Moneybag signs webhooks (
X-Webhook-Signature: sha256=HMAC(secret, "{timestamp}.{raw body}")), but the payment event body is not in the public contract, and webhook endpoints are configured in the merchant portal. The driver sends noipn_url. - Still unverified: the non-terminal
statusvalues returned by verify. OnlySUCCESS,FAILEDandCANCELLEDare documented; anything else leaves the payment pending.
Hosts (checked 2026-10-15)
Both https://sandbox.api.moneybag.com.bd (this driver's sandbox default) and https://staging.api.moneybag.com.bd (the official SDK's default) are live and require authentication. https://api.moneybag.com.bd is production. Override any of them with baseUrl. A merchant API key is issued from the Moneybag panel; there is no public demo key.
Sandbox runs
The Laravel package was verified against the real Moneybag sandbox through a Laravel 13 app with a self-serve sandbox account (developer portal → sandbox onboarding issues a merchant ID and a one-time-display API key). A 120 BDT payment paid with bKash completed after GET /api/v2/payments/verify/{transaction_id}; a simulated failure was confirmed as FAILED and marked failed. The verify response reports the order amount in amount and Moneybag's service charge separately in charge_amount (the customer saw 121.20), so the amount check correctly compares amount.
This Node package was also run end to end in the Moneybag sandbox on 2026-10-15:
- A payment was paid via Upay. Moneybag showed its service charge separately, and the order amount was the one checked.
- There is no status API, so no sync was run.