Home / Node.js / aamarPay Stable
aamarPay
Also for Laravel: https://github.com/byteminus/laravel-bdpaygate/blob/main/docs/gateways/aamarpay.md
Status: stable · Driver: aamarpay · Refunds: no API (refund from the merchant panel) · Sync: yes
aamarPay is a hosted checkout from a Bangladesh Bank licensed payment system operator. One integration covers:
- Cards: Visa, Mastercard, UnionPay, Nexus.
- Mobile wallets: bKash, Nagad, Rocket (DBBL), Upay, Tap, OK Wallet, SureCash, Qcash.
- Net banking.
That makes it a way to accept Rocket, OK Wallet and other wallets that have no public merchant API of their own. aamarPay notes that the card option only shows for amounts of 10 BDT or more.
Setup
AAMARPAY_STORE_ID=aamarpaytest
AAMARPAY_SIGNATURE_KEY=dbb74894e82415a2f7ff0ec3a97e4183
AAMARPAY_SANDBOX=true
These are read by gatewaysFromEnv(process.env). Or configure the gateway in code:
gateways: {
aamarpay: {
storeId: process.env.AAMARPAY_STORE_ID,
signatureKey: process.env.AAMARPAY_SIGNATURE_KEY,
sandbox: true,
},
}
- The values above are aamarPay's public sandbox credentials. Live credentials come from aamarPay support once your merchant account is set up.
sandboxdefaults totrue. Set it tofalsefor live payments.- Hosts:
https://sandbox.aamarpay.com(sandbox) andhttps://secure.aamarpay.com(live). Override withbaseUrl. Sandbox credentials on the live host (or the reverse) fail with a misleading "Invalid Store ID". - Currency must be
BDTorUSD.
IPN
aamarPay can POST a JSON notification for successful payments to a URL you give their technical support during onboarding. Give them:
https://your-app.com/bdpaygate/aamarpay/ipn
The IPN is optional: pay.syncPending() settles payments whose return never arrived.
How a payment is verified
-
pay.charge()posts to/jsonpost.php. The attempt'sgatewayReferenceis sent astran_id, your own reference inopt_aand the payment id inopt_b. The session id in the returnedpayment_url(track=AAM…) is aamarPay'spg_txnid, stored aspayment.gatewayPaymentId. -
aamarPay POSTs the customer's browser to
/bdpaygate/aamarpay/successor/fail, withmer_txnid,pg_txnid,status_codeand more. The cancel button returns the customer to/bdpaygate/aamarpay/cancel?tran_id=…with no payload. None of these are signed. -
A callback whose
pg_txniddiffers from the stored one is rejected at once. -
Every callback, cancel and IPN is then confirmed with the Search Transaction API (
/api/v1/trxcheck/request.php) bytran_id. The answer must carry the samemer_txnidandpg_txnid. Thenstatus_codedecides:status_codeMeaning Result 2Successful Completed, once the amount and currency match and pay_statusis "Successful" (bank_trxidstored aspayment.gatewayTransactionId)7Failed Failed 3Expired Failed 0Initiated, not paid yet Stays pending anything else Unknown Stays pending A fail or cancel for a payment aamarPay reports as paid completes it.
-
If aamarPay cannot be reached, or answers in plain text instead of JSON, the payment stays pending and the customer goes to your
pendingredirect.pay.syncPending()settles it later.
For BDT payments, amount is compared. For USD payments, amount is converted to BDT, so amount_currency and currency_merchant are compared instead.
A risk_level above 0 in the search result completes the payment but sets payment.requiresReview = true. Check it in your payment.completed listener before delivering.
Refunds
aamarPay has no public refund API. Refund from the aamarPay merchant panel or through aamarPay support. pay.refund() throws UnsupportedOperationError for this gateway, and pay.for(payment).capabilities.refunds is false.
Limits and quirks
- A reused
tran_idis accepted. aamarPay then creates a second session and Search Transaction only reports the newest one. The package sends a fresh attempt id on every charge and cross-checkspg_txnid, so an answer about another session never completes a payment. - Errors come with HTTP 200. Init errors are
{"result": false, "errors": [...]}or{"result": false, "message": "..."}. Success is"result": "true", a string. A wrong key on search returns plain text ("Store_id & signature key not matched"). All of these are handled. - Missing values are placeholders. Fields hold
"Not-Available"or"Not Available"instead of null; the package treats both as empty. - The signature key travels in the search query string. If you pass a custom
fetchthat logs requests, redact it. - The return is a cross-site POST. aamarPay's own troubleshooting page suggests excluding the callback from CSRF and loosening session cookies. Mount the bdpaygate handler before any CSRF, session or auth middleware and you don't need to: customers are not logged out, and your landing page receives a normal GET. SvelteKit and Astro reject cross-site form POSTs by default; allow aamarPay's checkout origin there.
tran_idmay be at most 32 characters; attempt ids are 20.- Customer email is required by aamarPay; when the customer has none, a placeholder is sent.
- The IPN's signature scheme is not documented, so the IPN is treated like any other claim and confirmed with Search Transaction.
Sandbox
Use the public sandbox credentials above with AAMARPAY_SANDBOX=true. aamarPay's own samples note that the sandbox may only offer bKash; the run below was paid with Nagad, so what it offers can vary.
This Node package was run end to end in the aamarPay sandbox on 2026-10-15:
- A payment was paid via Nagad.
- Syncing an unpaid payment left it pending.