This content originally appeared on DEV Community and was authored by ZeeshanAli-0704
π 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
- 1οΈβ£ Client β Merchant Backend (Create Order)
- 2οΈβ£ Client β Razorpay Checkout
- 3οΈβ£ Razorpay β Bank / UPI Network
- 4οΈβ£ Razorpay β Merchant Backend (Webhook)
-
5οΈβ£ Client Reconnects β Merchant Backend
- π§ 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 & Compliance
- π§ Step 9: Final Takeaways
- π TL;DR β Razorpayβs Reliability Recipe
- π Final Summary
- π More Details β #SystemDesignWithZeeshanAli
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.
π§± 1. 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.
π³ 2. 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.
π¦ 3. 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.
π 4. 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.
π 5. 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 & 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
π TL;DR β Razorpayβs 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
This content originally appeared on DEV Community and was authored by ZeeshanAli-0704
ZeeshanAli-0704 | Sciencx (2025-10-19T08:01:42+00:00) How Razorpay Ensures Your Payment Succeeds β Even If Your Internet Drops. Retrieved from https://www.scien.cx/2025/10/19/how-razorpay-ensures-your-payment-succeeds-even-if-your-internet-drops/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.