Engineering4 min read

Idempotency in payment code: what I learned wrapping bKash and SSLCommerz

By Mitu Akter Monita · September 24, 2026

A payment request can arrive twice for very boring reasons. Here is what my SDK does about it.

A payment request can arrive twice for boring reasons. A customer taps the pay button twice. A mobile connection drops after the request leaves but before the answer comes back, so the app tries again. A gateway sends the same webhook more than once because it did not hear your response quickly enough. None of these are attacks, and any of them can charge someone twice or ship an order twice if your code is not ready.

When I wrote bd-payment-sdk, a Node.js and TypeScript wrapper around bKash Tokenized Checkout and SSLCommerz v4, four ideas ended up carrying most of the weight.

The first is idempotency. Every operation that moves money gets a key, and the result is stored under that key. Ask for the same operation again with the same key and you get the stored result back instead of a second charge.

The second is locking. A stored result only helps if two identical requests do not run at the same moment, because both would find nothing stored and both would go ahead. So each key takes a lock while it is being processed. I put the lock behind a Redis-style interface, which means production can use a real distributed lock while local development uses something simple.

The third is a retry policy that knows what is safe to retry. Asking for the status of a payment twice is harmless. Creating a payment twice without a key is how people get charged twice. Backoff is useful, but only once you are certain a retry cannot do damage.

The fourth is webhook validation. A webhook is just an HTTP request from the internet, so anyone can send one that looks right. The SDK checks the signature before it trusts a payload, and even then the safer habit is to confirm the payment with the provider before marking an order as paid.

I wrote around 150 adversarial tests for this: duplicate requests, replayed webhooks, steps that fail halfway on purpose. If you take one thing from this post, write the ugly-case tests before the happy path feels finished.