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

  1. Why did tapping Retry after Payment failed charge customers twice when the provider timed out?
  2. How would you redesign checkout so Retry never charges the same order twice?
  3. When the provider times out, what should happen before the app shows Payment failed or enables Retry?
  4. Where should the status check and duplicate prevention live — on the Retry button, in the API, or both?

← All design challenges

Loading scenario…