π 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
razorpayOrderId
ensures 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)