3,000
customers charged twice a day at 0.01%
30 million payments a day at about ₹1,200 each is ₹3,600 crore of other people’s money. Correctness comes before availability.
Two Pay requests arrive 20 ms apart
Bad: Check, then charge
Both read “not paid yet”, and both charge.
Good: Key + unique constraint
One insert wins. A duplicate gets 409 while it runs, then the saved response.
Two keys, one charge
The caller’s key K1 dedupes retries to your API. Your attempt ID att_9, committed before the call, dedupes the charge at the PSP.
Your charge times out after 8 seconds. Never arrived? Declined? Succeeded, reply lost? All three look identical.
The trick
A timeout is an unknown, not a failure. Never retry blindly: ask the PSP about att_9.
Retry on PSP B, and one order costs ₹2,499
PSP B has never heard of PSP A, so no key helps. Ask PSP A first and you charge once, about ten seconds later.
UPI: the customer pays in another app
- Get a upi://pay link from the PSP
- The customer enters their UPI PIN
- A signed webhook, not the app, brings the answer
- Your app long-polls for the result
You marked att_15 failed. The PSP’s file says it was paid.
Each morning, match attempts to settlement rows and batch totals to bank credits. Rules reinstate the order or refund late successes; people get the rest.
Remember
Key every call with an ID you committed first. A timeout is an unknown: ask, never guess. Then reconcile against files you didn’t write.