Fix the design · Incident triage
Payment Double-Charge: The Service That Charged Customers Twice
During a Diwali sale week on Razorpay checkout, shoppers who saw Payment failed and tapped Retry were charged twice for the same order — about 2% of checkouts — and support tickets jumped about 4×.
You are on Razorpay’s merchant checkout team during Diwali sale week. Shoppers submit payment, see Payment failed after a timeout, tap Retry — and about 2% are charged twice for the same order.
We need your help. Identify the bottlenecks and failure modes in the current design, then redesign the path from “shopper taps Pay” → “order is charged once” so a timeout is treated as unknown, Retry cannot start a second capture, and a unique payment id makes a repeated attempt return the same result.
Problem
Redesign Razorpay’s existing checkout payment path — it already sends card and UPI captures to an external payment provider for merchant checkouts.
Incident summary
- The payment provider timed out with no clear success or failure on the first attempt.
- Checkout showed Payment failed and offered Retry; shoppers who retried were often charged twice.
- About 2% of checkouts were double-charged — the first capture often already succeeded.
- Support tickets jumped about 400% in two hours.
- Extra payment attempts pushed the provider error rate to 18%.
- A nightly reconciliation job found about $240k in duplicate captures.
Assumptions made by the team
- If the payment page times out, it is safe to show Payment failed and offer Retry.
- Shoppers will only retry when the first payment truly failed.
- Checking payment status before Retry is unnecessary — the gateway will not charge twice.
- A database transaction alone is enough to prevent double charges.
- Duplicate charges are rare and easy to refund later.
Impacted services
- Checkout apps (critical) — Showed Payment failed on timeout and let Retry start a second payment.
- Payment API (critical) — Accepted the retry as a new capture with no status check or idempotency.
- Payment provider (degraded) — Timed out under load and returned unclear answers on the first attempt.
- Customers (critical) — About 2% were charged twice for the same order after tapping Retry.
- Support (critical) — Ticket volume jumped about 400% in two hours.
Triage questions
- Why did tapping Retry after Payment failed charge customers twice when the provider timed out?
- How would you redesign checkout so Retry never charges the same order twice?
- When the provider times out, what should happen before the app shows Payment failed or enables Retry?
- Where should the status check and duplicate prevention live — on the Retry button, in the API, or both?