The Idempotency Test Every Payment Flow Needs
← All resources
CHECKOUT QA Signals · 9 Aug 2026 · 5 min read

The Idempotency Test Every Payment Flow Needs

Here is a sequence we can reproduce on more checkouts than we would like: a customer taps Pay, the spinner hangs, they tap again, and two charges land on their card. The payment provider did nothing wrong. The card processed both requests because the application asked it to. That gap is an idempotency bug, and it is one of the most expensive ones a checkout can ship because the person who finds it is a paying customer.

Idempotency means that sending the same request twice has the same effect as sending it once. For payments, that is not a nice-to-have. It is the difference between a retry that is safe and a retry that costs a refund, a chargeback, and a support ticket.

Why double charges happen even with a good payment provider

Stripe, Adyen, Braintree, and the rest all support idempotency keys. The bug is almost never in the provider. It is in the three or four lines of your own code that decide whether to generate a new key, reuse an old one, or forget to send one at all. We see the same three failure modes over and over.

1. No idempotency key at all

The charge call is fired without a key, so every retry, every double-click, every automatic client-side reattempt creates a fresh charge. This is the default state of a lot of hand-rolled integrations. It works perfectly in the demo because the demo has a fast network and a patient tester.

2. A key that regenerates on retry

The key exists, but it is generated at the moment the request is sent rather than tied to the checkout attempt. So the first click and the second click each mint their own key, and to the provider they look like two legitimate, distinct payments. This one is sneaky because a code review sees a key and moves on.

3. A key that never changes

The opposite mistake. The key is derived from the cart or the user and reused across genuinely separate purchases, so the customer's second order silently returns the first order's result and never actually pays. Now you have shipped goods you were not paid for.

How we test it

You cannot find these by clicking through a happy path. You have to force the conditions that trigger them.

  1. Rapid double submission. Fire the same submit twice within a few hundred milliseconds. Automate it so the timing is tighter than a human could manage. Expect exactly one charge and one order.
  2. Retry after timeout. Throttle the network so the response never arrives, let the client retry, then restore the connection. The provider should recognise the repeated key and return the original result, not a new charge.
  3. Same key, different amount. Send the same idempotency key with a changed cart total. A correct implementation rejects the mismatch rather than charging the old amount. This catches keys that are too sticky.
  4. Distinct purchases, distinct keys. Two real orders from the same user should never collide. Confirm both charges succeed and both orders exist.

Run each of these against the actual charge endpoint, not a mock. Mocks are where idempotency bugs hide, because a mock cheerfully returns success for whatever you send it.

What "passing" actually looks like

The result you want is boring: one authorisation, one captured charge, one order record, and a second request that returns the first request's response verbatim. If your reconciliation report at the end of the day shows the same number of charges as orders, your idempotency is holding. If those two numbers ever drift apart, you have a leak, and finance will find it before you do.

Idempotency is the one payment test where the correct behaviour is that nothing new happens. That is exactly why it gets skipped, and exactly why it belongs in your regression suite.

If you run a checkout and you are not certain which of the three failure modes you are exposed to, that uncertainty is the finding. It is a ten-minute test to answer, and a very bad week to answer it in production.

Want us to catch bugs like these before your customers do? Get a free mini-audit or see our services.

← Back to blogs