intermediate · investigation

The Customer Charged Twice

A customer's card shows two identical charges 12 seconds apart for one checkout. Support finds a handful of similar cases, all from the mobile app, all on poor connections.

Evidence

10:41:02 POST /payments amount=4999 device=ios   -> (response lost: client timeout at 10s)
10:41:14 POST /payments amount=4999 device=ios   -> 200 charge_id=ch_2                    <- automatic retry

ledger: ch_1 created 10:41:07 (original request DID succeed, response never arrived)
API docs, "Retries" section: (does not exist)
request headers: no Idempotency-Key; retry sent identical body

Investigate

Inspect Client retry behavior
The mobile HTTP client retries timed-out requests once — reasonable per its docs, and nothing in the API contract told it POST /payments was unsafe to retry.
Inspect Timeline reconstruction
The first request succeeded at 10:41:07, after the client gave up at 10s — the classic ambiguous outcome: the response was lost, not the operation.
Inspect Idempotency support
The payments endpoint has no idempotency-key mechanism at all: two syntactically identical POSTs are two charges, by design.
Inspect Server-side dedup heuristics
A “same card + same amount within 60s” warning was proposed and rejected last year as too false-positive-prone; nothing replaced it.