The Checkout Bug Pattern We Find in Almost Every Audit
← All resources
CHECKOUT QA Signals · 18 Jul 2026 · 5 min read

The Checkout Bug Pattern We Find in Almost Every Audit

There's a bug we've stopped being surprised by. It shows up in early-stage products and in checkouts doing serious monthly volume. The symptom is always the same: the customer's card gets charged, the confirmation screen appears, and no order exists anywhere in the system.

Support finds out first. Then finance. Then someone opens the payment dashboard, sees a successful charge with no matching order record, and the awkward internal thread begins.

We flag some version of this in the majority of first audits we run. Not because the developers were careless — the code usually looks fine. It fails for a quieter reason.

Why the charge and the order come apart

Most checkouts treat payment and order creation as one step. They aren't. The charge happens on your payment provider's infrastructure. The order gets written by your own backend, a moment later, usually triggered by a callback, a webhook, or the next line of code after the payment response.

Between those two moments, real life happens:

Each of those produces the same outcome: money moved, state didn't. The payment provider says success. Your database says nothing happened.

The happy path hides all of this. When the network is fast and nothing interrupts, charge and order land together every time — which is exactly how it behaves on the developer's machine and in staging. The bug only exists in the gap, and nobody tests the gap.

What it actually costs

One client was losing roughly 2% of completed payments to this. At their volume that was a few thousand dollars a month in refunds, before counting the support time — each incident generated two to three emails, sometimes a chargeback when the customer gave up waiting.

Chargebacks are the expensive version. A refund costs you the sale. A chargeback costs the sale, a fee, and a mark against your merchant account. Enough of them and your payment provider starts a conversation you don't want to have.

The trust damage is harder to measure. A customer who got charged for nothing rarely tries again.

The three tests that expose it

We run these on every checkout audit. They take under an hour and they catch the pattern reliably.

1. Kill the network at the worst moment

Complete a checkout, and cut the connection immediately after pressing the pay button — before the confirmation renders. Browser dev tools can simulate offline; on mobile, airplane mode works.

Then check three things: Was the card charged? Does an order exist? What did the customer see?

A sound implementation either fails the charge cleanly or recovers the order once connectivity returns. A broken one charges the card and shows an error — or worse, shows success over an order that was never written.

2. Reconcile payments against orders

Pull a day of transactions from your payment provider and match them against your orders table. Every charge should map to exactly one order.

Any charge without an order is this bug already happening in production. We've run this query during audits and watched the room go quiet. If you do nothing else from this article, run the reconciliation. It's one query, and it tells you whether the problem is theoretical or already costing you money.

3. Replay the webhook out of order

If your order creation depends on webhooks, test the ugly cases: deliver the webhook twice, deliver it late, deliver events out of sequence. Providers like Stripe let you resend events from the dashboard.

Duplicate webhooks should not create duplicate orders. A late webhook should still create the order — minutes late is fine, missing is not. If your handler assumes events arrive once, in order, on time, it will eventually be wrong on all three.

What a fix looks like

The reliable pattern is boring and well-known: make order creation idempotent, key it to the payment intent, and run a reconciliation job that sweeps for charges without orders every few minutes. When the sweep finds one, it creates the order and fires the confirmation email late rather than never.

Teams sometimes resist the reconciliation job because it feels like admitting the main flow can fail. It can. Every payment flow can. The difference between a mature checkout and a fragile one isn't whether the gap exists — it's whether anything is watching it.

Before your next release

Run the reconciliation query first. Then do the network-kill test on desktop and mobile. Then replay a webhook twice. Three checks, less than an hour, and you'll know whether the most expensive bug we find is sitting in your checkout right now.

If you'd rather have hostile eyes do it properly — this is literally the first thing we test in every audit. Send us your product and we'll tell you within 48 hours.

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

← Back to blogs