Payment System Design
The money should be debited only once, reach the right account, and every rupee should be accounted for; that is the job of a payment system.
40.1 Why is it used?
- A mistake in payments means real money loss, double charge or fraud, so correctness matters more than speed.
- A payment involves many parties: merchant, payment gateway, card network/NPCI, issuer bank, acquirer bank.
- On a network timeout the status is unknown, so idempotency, status polling and webhooks are essential.
- Regulations like RBI and PCI-DSS impose rules on card data storage and the audit trail.
- Every day, settlement and reconciliation must confirm that your records match the bank records.
40.2 How it works
- Flow: create Order, create Payment intent (with an idempotency key), user authorizes via UPI/card, gateway callback, confirm order.
- Payment state machine: CREATED, PENDING, AUTHORIZED, CAPTURED, FAILED, REFUNDED; allow only valid transitions.
- Webhooks: the gateway sends an async event (payment.captured); verify the signature (HMAC), handle it idempotently, return 2xx quickly.
- Status polling fallback: if a webhook is missed, periodically check the status of pending payments through the gateway API.
- Double-entry ledger: every transaction has both debit and credit entries, total debits = total credits; entries are append-only, never updated or deleted.
- Reconciliation: every day match the gateway/bank settlement file with your ledger; alert on and fix mismatches (missing, amount difference).
- Tokenization: do not store card numbers yourself; use a gateway/network token to reduce PCI scope.
- Store amounts as integers (paise), never float; keep the currency in a separate column.
- Refunds and chargebacks: make refunds through the gateway API with their own idempotency key and add a reversal entry in the ledger (do not change the old entry).
- 1.Create Order, payment status CREATED
- 2.Create Payment intent (with an idempotency key)
- 3.User authorizes via UPI / card (NPCI / bank)
- 4.Gateway webhook payment.captured (verify HMAC signature)
- 5.One DB transaction: payment CAPTURED + ledger debit/credit entries
- 6.Kafka event: confirm order + notification
If a webhook is missed, a poller checks the status through the gateway API; the next day the settlement file is reconciled against the ledger.
40.3 Tools you can use
| Tool | Type | How it is used |
|---|---|---|
| Razorpay | Payment gateway (India) | UPI, cards, netbanking, wallets; Orders API, webhooks, Route/settlements for Indian merchants. |
| Stripe | Payment gateway (global) | PaymentIntents API, Idempotency-Key, webhooks with signature; global cards and subscriptions. |
| UPI (NPCI) | Real-time payment rail | Instant bank-to-bank transfer via VPA; integrate through PSP apps (PhonePe, Google Pay, Paytm) and gateways. |
| PayU / Cashfree / Juspay | Indian PG / orchestration | Alternative gateways; orchestrators like Juspay do smart routing across multiple gateways. |
| PostgreSQL / MySQL | Relational DB (ACID) | ACID transactions, unique constraints and audit tables for the ledger and payment state. |
| Kafka | Event streaming | Reliably deliver events like payment.captured to the order, notification and accounting services. |
| TigerBeetle | Financial ledger DB | A purpose-built high-throughput ledger database for double-entry accounting. |
40.4 Comparison: Razorpay vs Stripe vs Direct UPI (NPCI via bank/PSP)
| Point | Razorpay | Stripe | Direct UPI integration |
|---|---|---|---|
| Market | India focused | Global, limited in India | India only |
| Payment methods | UPI, cards, netbanking, wallets, EMI | Cards, wallets, global local methods | UPI only |
| Integration effort | Low, SDK + checkout | Low, best-in-class APIs/docs | High, bank partnership + NPCI compliance |
| Cost | Per transaction MDR fees | Per transaction fees | Cheap at scale, but a big setup cost |
| Control | Medium | Medium | Full control |
| Webhooks / idempotency | Webhooks with signature | Webhooks + Idempotency-Key header | You have to build it yourself |
| Best for | Indian startups and e-commerce | Global SaaS, international cards | Big players at the scale of PhonePe, Flipkart |
40.5 When to choose which
- Razorpay / Cashfree / PayU: launching in India, need UPI + cards quickly.
- Stripe: international customers, subscriptions, global cards.
- Direct UPI / multiple gateways: when volume is so large that fees and routing control matter.
- At large scale keep payment orchestration (multiple gateways) so that if one gateway is down you route through another.
40.6 Real-world example
Such a scenario: a UPI payment for a Swiggy-style order via Razorpay
- The user places an order of Rs 450; the backend creates an order_id with the Razorpay Orders API and the payment status in the DB is CREATED.
- The app opens Razorpay checkout, the user enters the PIN in a UPI app to pay; money is debited from the bank through NPCI.
- Razorpay sends the webhook payment.captured; the backend verifies the signature and ignores a duplicate webhook with a unique check on payment_id.
- In the same DB transaction: payment status CAPTURED and ledger entries: debit Customer/Gateway receivable Rs 450, credit Order revenue Rs 450.
- Through the Kafka event payment.captured the order service confirms the order and a notification goes out.
- If the webhook does not arrive for 10 minutes, a poller checks the status through the Razorpay API; if still pending, the auto-refund/fail flow runs.
- The next day the settlement report is reconciled with the ledger; any mismatch alerts the finance team.
40.7 Interview tips
- Treating payment as successful on a client-side redirect; always verify through a server-side webhook/API.
- Storing the amount as float causes rounding errors; keep paise as an integer.
- Updating ledger rows; if there is a mistake add a reversal entry and do not change the old entry.
- Interview tip: cover these 5 points: idempotency key, state machine, webhook + polling, double-entry ledger and reconciliation.