Fix the design · Incident triage

Double Submit: The Payment Handler That Charged Twice on Retry

Replaying the same Pay request in QA produced two charges for one order because each request calls the payment gateway without a safe way to detect a retry.

In a low-level design interview, the interviewer explains that QA sent the same Pay request twice and got two charges for one order, then asks you to walk through what the code does on retry.

We need your help. Identify what goes wrong when the same payment request is sent twice, then fix the path from “shopper taps Pay” → “one charge for the order” so a retry or double-tap cannot create a second capture.

Problem

Fix the team’s existing Pay charge path — it already sends every tap to the payment gateway and writes a ledger row for each successful call.

Incident summary

  • QA replayed the same Pay request twice and saw two charges for one order.
  • A mobile retry after a slow network can create a second charge for the same order.
  • The ledger shows duplicate payment rows that share the same orderId.
  • The team assumed shoppers would not tap twice and that the gateway would prevent duplicates on its own.

Assumptions made by the team

  • Shoppers will not double-tap the Pay button.
  • The payment gateway deduplicates charges automatically.
  • Duplicate charges can be cleaned up later with manual refunds.
  • Duplicate-safe retries are only needed for webhooks, not for the Pay button path.

Impacted services

  • processPayment (critical) — Not safe under retry or double-submit.
  • Payment gateway (degraded) — Called twice for one user action.
  • Ledger (critical) — Duplicate success rows allowed.
  • Support (degraded) — Manual refunds for duplicate charges.

Triage questions

  1. What reliability issue do you notice if the same charge request is sent twice (double-tap or retry)?
  2. How would you make sure posting the same charge twice only bills once? Name one concrete approach.
  3. Why must payment handlers be safe when the network retries? Why should the server own duplicate protection even if the app tries to prevent double-tap?
  4. If a retry arrives while the first charge is still running, what do you do — and when is a unique index enough on its own?

← All design challenges

Loading scenario…