Idempotency is not optional in payments
A double-tap should never become a double-charge. How Collect makes payment endpoints safe to retry — by design, not by luck.
A double-tap should never become a double-charge. How Collect makes payment endpoints safe to retry — by design, not by luck.
See how Collect runs billing, payments, and collections in one place.
The scariest bug in a payments system isn't a crash. It's a double charge — the one that happens when a client taps "Pay" twice, or a network blip triggers a retry, and your server dutifully runs the charge a second time. Crashes get noticed. Double charges get disputed.
Every payment-creating endpoint in Collect requires an Idempotency-Key. The rules are spelled out, not improvised:
The keys are scoped per business, so two merchants can never collide.
Two concurrent requests with the same key race to insert a row. A unique index on (businessId, key) means exactly one wins; the loser reads the in-flight response and converges instead of charging again.
const existing = await idempotency.claim({ businessId, key, requestHash })
if (existing.status === "completed") return existing.response
if (existing.status === "in_flight") throw new TooEarly()
// we won the race — safe to call the processor exactly onceNone of this is glamorous. Nobody buys software because the idempotency contract is airtight. But the absence of it is the kind of thing that ends trust in a payments product overnight.
That's why it's built in from the first commit — typed, validated, and tested — not bolted on after the first incident.