π Table of Contents
- How Razorpay Ensures Your Payment Succeeds Even If Your Internet Drops After You Click "Pay Now"
- π© The Real World Problem
- π§© Step 1: Identifying the Core Problem
- βοΈ Step 2: The Reliable Payment Flow β End to End
- π§ Step 3: Key Engineering Concepts
- ποΈ Step 4: System Architecture Diagram
- π§© Step 5: UML Sequence Diagram
- π§± Step 6: Suggested Tech Stack
- π Step 7: Handling Failures Gracefully
- π Step 8: Security and Compliance
- π§ Step 9: Final Takeaways
- Razorpays Reliability Recipe
- π Final Summary
How Razorpay Ensures Your Payment Succeeds β Even If Your Internet Drops After You Click "Pay Now"
π© The Real World Problem
Imagine this:
Youβre buying something online, click βPay Nowβ, and suddenly your internet disconnects.
Now youβre stuck wondering β
- βDid my payment go through?β
- βWill I get charged twice if I retry?β
- βHow does the app even know what happened?β
This situation occurs millions of times a day, and yet companies like Razorpay, Stripe, or PayPal handle it gracefully β without double-charging users or losing transactions.
Letβs see how they design for reliability, idempotency, and consistency even when your network vanishes mid-payment.
π§© Step 1: Identifying the Core Problem
When you initiate a payment:
- Your app sends the payment request.
- Razorpay talks to your bank.
- But your client may drop offline before getting the result.
Without protection:
- The app might show βPayment Failedβ even though the amount is debited.
- The user might retry and get charged twice.
Hence, we need a fault-tolerant payment flow that ensures:
Every payment request is processed exactly once, and the final state is always recoverable β even if the user disappears.
βοΈ Step 2: The Reliable Payment Flow β End to End
Letβs walk through what happens behind the scenes.
π§± Client β Merchant Backend (Create Order)
Every transaction starts by creating a unique Order.
This acts as an idempotency key β so retries never create duplicates.
Request:
POST /api/payments/create-order Content-Type: application/json { "orderId": "ORD_12345", "amount": 49900, "currency": "INR" } Backend Implementation (Node.js):
app.post("/api/payments/create-order", async (req, res) => { const { orderId, amount, currency } = req.body; const response = await axios.post("https://api.razorpay.com/v1/orders", { amount, currency, receipt: orderId, payment_capture: 1 }, { auth: { username: process.env.RAZORPAY_KEY, password: process.env.RAZORPAY_SECRET } }); await db.orders.insert({ orderId, razorpayOrderId: response.data.id, status: "CREATED", amount }); res.json({ razorpayOrderId: response.data.id, key: process.env.RAZORPAY_KEY }); }); β What happens:
- Your backend creates an order in Razorpay.
- The unique
razorpayOrderIdensures idempotency. - Status = βCREATEDβ is stored in the DB.
π³ Client β Razorpay Checkout
Your app opens Razorpayβs secure Checkout window.
Frontend Example:
const options = { key: RAZORPAY_KEY, amount: order.amount, currency: "INR", order_id: order.razorpayOrderId, handler: async function (response) { await verifyPayment(response); } }; const rzp = new Razorpay(options); rzp.open(); β οΈ Important:
This handler() runs only if the browser is alive.
If the internet drops here, Razorpay continues processing the payment in the background β but the app wonβt know immediately.
π¦ Razorpay β Bank / UPI Network
Razorpay securely forwards your payment request to the bank or UPI system over PCI-DSS compliant channels.
The bank:
- Processes the payment.
- Sends the result (success/failure) back to Razorpay.
This happens completely independent of your deviceβs internet.
π Razorpay β Merchant Backend (Webhook)
Once Razorpay gets the bankβs result, it triggers a server-to-server webhook to your backend.
Webhook Example:
POST /api/payments/webhook Content-Type: application/json { "event": "payment.captured", "payload": { "payment": { "id": "pay_29QQoUBi66xm2f", "entity": "payment", "order_id": "order_DBJOWzybf0sJbb", "amount": 49900, "status": "captured", "method": "upi" } } } Webhook Handler:
app.post("/api/payments/webhook", async (req, res) => { const secret = process.env.RAZORPAY_WEBHOOK_SECRET; const signature = req.headers["x-razorpay-signature"]; const expected = crypto.createHmac("sha256", secret) .update(JSON.stringify(req.body)) .digest("hex"); if (expected !== signature) return res.status(403).send("Invalid signature"); const payment = req.body.payload.payment.entity; await db.orders.updateOne( { razorpayOrderId: payment.order_id }, { $set: { status: payment.status, paymentId: payment.id } } ); res.status(200).send("OK"); }); β Why this matters:
- Webhook ensures Razorpay β Merchant communication doesnβt depend on the userβs browser.
- Even if the user vanishes, your backend receives the final truth.
π Client Reconnects β Merchant Backend
When the user reopens the app:
GET /api/payments/status?orderId=ORD_12345 Backend:
app.get("/api/payments/status", async (req, res) => { const order = await db.orders.findOne({ orderId: req.query.orderId }); res.json({ status: order.status }); }); β
Result:
Even after a crash, disconnect, or timeout β the app can re-fetch the confirmed payment status directly from the server.
π§ Step 3: Key Engineering Concepts
| Concept | Why Itβs Needed |
|---|---|
| Idempotency | Ensures retries donβt cause double charges. |
| Event-driven architecture | Webhooks asynchronously notify merchants of results. |
| Atomic DB Transactions | Payment + order update happen together. |
| Retries with Exponential Backoff | Handles transient failures safely. |
| Queue-based Delivery (Kafka/SQS) | Guarantees webhook/event delivery. |
| Caching (Redis) | Enables quick status lookups for reconnecting users. |
| Audit Logging | Every payment event is traceable for reconciliation. |
ποΈ Step 4: System Architecture Diagram
βββββββββββββββββββββββββββββββββ β User Client β β (Web / Mobile App / Checkout) β ββββββββββββββββ¬βββββββββββββββββ β β (1) Create Order βΌ βββββββββββββββββββββββββββββββββ β Merchant Backend β β (Spring Boot / Node / Django) β βββββββββββββββββββββββββββββββββ€ β Generates order, stores in DB β β & calls Razorpay API β ββββββββββββββββ¬βββββββββββββββββ β β (2) Payment Init βΌ βββββββββββββββββββββββββββββββββ β Razorpay API β β Connects securely with Bank β ββββββββββββββββ¬βββββββββββββββββ β β (3) Process Payment βΌ βββββββββββββββββββββββββββββββββ β Bank / UPI Network β β Processes & sends result β ββββββββββββββββ¬βββββββββββββββββ β β (4) Webhook βΌ βββββββββββββββββββββββββββββββββ β Merchant Backend Webhook β β Updates DB, Publishes Kafka β ββββββββββββββββ¬βββββββββββββββββ β β (5) User Reconnects βΌ βββββββββββββββββββββββββββββββββ β User Client β β Fetches final payment state β βββββββββββββββββββββββββββββββββ π§© Step 5: UML Sequence Diagram
sequenceDiagram participant User participant ClientApp participant MerchantBackend participant RazorpayAPI participant Bank participant WebhookHandler participant DB User->>ClientApp: Click "Pay Now" ClientApp->>MerchantBackend: POST /create-order MerchantBackend->>RazorpayAPI: Create Order (idempotent) RazorpayAPI-->>MerchantBackend: razorpayOrderId MerchantBackend-->>ClientApp: Send Order ID ClientApp->>RazorpayAPI: Start Payment via Checkout RazorpayAPI->>Bank: Process Payment Bank-->>RazorpayAPI: Success RazorpayAPI-->>WebhookHandler: POST /webhook WebhookHandler->>WebhookHandler: Verify signature WebhookHandler->>DB: Update order & payment WebhookHandler->>Kafka: Publish payment.captured event Note right of WebhookHandler: Happens even if<br>user is offline ClientApp-->>User: User reconnects later ClientApp->>MerchantBackend: GET /payment-status MerchantBackend->>DB: Query latest status DB-->>MerchantBackend: status = "captured" MerchantBackend-->>ClientApp: Send final confirmation ClientApp-->>User: Show "Payment Successful β
" π§± Step 6: Suggested Tech Stack
| Layer | Recommended Tools |
|---|---|
| Frontend | React / Angular / Flutter / Android SDK |
| Backend | Node.js (Express), Spring Boot, or Django |
| Database | PostgreSQL / MongoDB |
| Cache | Redis (for idempotency + status caching) |
| Message Queue | Kafka / RabbitMQ / AWS SQS |
| API Gateway | Nginx / Kong / AWS API Gateway |
| Monitoring | Prometheus + Grafana / ELK Stack |
| Security | HMAC validation, HTTPS, JWT Auth |
π Step 7: Handling Failures Gracefully
| Scenario | Solution |
|---|---|
| Client disconnects | Webhook ensures backend gets final result |
| User retries βPay Nowβ | Same order ID β idempotency prevents double charge |
| Webhook fails | Retries via Kafka / Dead Letter Queue |
| Bank timeout | Razorpay retries safely using internal transaction queue |
| DB crash | Atomic transaction + durable logs ensure replay recovery |
π Step 8: Security and Compliance
- All API traffic is over HTTPS / TLS 1.2+
- HMAC-SHA256 signature validates webhook authenticity
- No card or UPI info stored β PCI-DSS compliance
- JWT tokens for clientβmerchant authentication
- Vault/KMS for secret key rotation
π§ Step 9: Final Takeaways
Even if your internet fails right after βPay Nowβ:
- Razorpay continues the transaction with your bank.
- The merchantβs backend receives final confirmation via server webhook.
- When you come back online, your app simply checks your order ID.
- Because of idempotency + event-driven design, thereβs:
- No duplicate charge
- No missed confirmation
- A fully auditable, consistent payment flow
Razorpays Reliability Recipe
| Ingredient | Role |
|---|---|
| Idempotency Keys | Prevent double payments |
| Server-to-Server Webhooks | Reliable final status |
| Atomic DB Updates | Consistent state |
| Kafka/Redis Queues | Guaranteed delivery |
| HMAC Signatures | Secure verification |
| Retry + Backoff Policies | Network fault recovery |
π Final Summary
| Event | Trigger | Ensures |
|---|---|---|
Create Order | User initiates payment | Unique ID for idempotency |
Payment Initiated | Client connects to Razorpay | Secure checkout session |
Webhook Received | Razorpay confirms with backend | Reliable confirmation |
Status Fetch | User reconnects | Final truth retrieval |
β
In short:
Razorpayβs system is not βclient-dependentβ β itβs server-driven, idempotent, and event-consistent.
Thatβs how your payment succeeds β even if your phone doesnβt.
More Details:
Get all articles related to system design
Hastag: SystemDesignWithZeeshanAli
Git: https://github.com/ZeeshanAli-0704/SystemDesignWithZeeshanAli
Top comments (0)