
Ask a team whether they've tested payments and the answer is always yes. Ask whether they've tested a card that gets declined after 3D Secure succeeds, and the room goes quiet.
The successful payment is one scenario. In production traffic, somewhere between 5 and 15 percent of payment attempts fail - and every failure lands somewhere in your code. If you haven't tested where, your customers are running those tests for you, live, with their own money.
This is the failure list we run on every checkout audit. Twelve scenarios, ordered roughly by how often we find them broken.
1. The plain decline. Insufficient funds, generic refusal. The baseline failure — and teams do usually handle it. What they get wrong is the message. "Payment failed, try again" tells the customer nothing. Did the bank refuse? Is the card expired? Should they retry or use a different card? Test that the decline reason your provider returns actually reaches the screen in words a customer can act on.
2. The soft decline that succeeds on retry. Some declines are temporary — the bank wants another attempt or a step-up check. If your checkout treats every decline as final, you're losing orders that would have completed one click later. Watch what the retry path looks like: is the cart intact, or does the customer rebuild it from scratch?
3. The decline after authorization. The card authorizes, then capture fails later — expired auth, insufficient funds at capture time. This one happens hours after the customer left. Does anything notify them? Does the order sit in a phantom "paid" state? We've found orders shipped against captures that never completed.
4. The abandoned 3DS challenge. The bank's verification popup appears; the customer closes it. No payment, no error, just a vanished window. Check what state the checkout is left in — frozen spinner, silent failure, or a clear "verification was cancelled, try again"?
5. The 3DS timeout. The customer waits too long on the bank's page. The session expires mid-verification. Where do they land when they come back, and is the cart still there?
6. 3DS succeeds, payment fails anyway. Authentication passing doesn't guarantee the charge clears. The customer did everything right, jumped through the bank's hoop, and still failed. This is the moment your error message matters most — and the scenario where we most often see a raw error code shown to a human.
7. The double-click. Two rapid clicks on the pay button. The single most reliable way to find a missing idempotency check. Best case: one charge. Worst case we've documented: two charges, one order, and a support ticket. Test it on a slow connection where the button stays clickable for longer.
8. The back-button replay. Pay, land on confirmation, hit back, click pay again. Related to the double-click but takes a different code path — the first payment already completed. A correct implementation recognizes the order is paid. A broken one charges again.
9. The refresh mid-payment. F5 while the spinner runs. Depending on timing, the charge may or may not have gone through. What does the customer see when the page reloads — and can they tell whether they paid?
10. The dropped connection. Network dies right after the pay click. We covered the order-creation side of this in the checkout bug pattern piece; here the question is the customer-facing half. When they reconnect, does the site know what happened, or does it invite them to pay again for an order that may already exist?
11. The expired session. The customer fills the cart, walks away, comes back forty minutes later, and pays. Session-based carts and saved payment states rot in interesting ways. We've seen expired sessions charge successfully and then create the order against a logged-out user — an order no account can see.
12. The wallet cancel. Apple Pay or Google Pay sheet opens; the customer dismisses it. Wallet flows run through different code than card forms, and the cancel event is easy to mishandle. Verify the checkout returns to a usable state instead of a dead button.
You don't need production cards. Every serious provider ships test cards that trigger specific failures — Stripe alone has designated numbers for declines, insufficient funds, 3DS challenges, and capture failures. The scenarios that can't be triggered by a card number (network drops, double-clicks, refreshes) take nothing but dev tools and a willingness to be rude to your own checkout.
Budget half a day for the full list. Log four things per scenario: what the customer saw, whether a charge exists, whether an order exists, and whether the two agree.
That last column is the one that matters. Most payment-failure bugs aren't crashes - they're disagreements between what the customer was told and what the system recorded. Every disagreement is either a lost sale or a future refund.
Notice that almost none of these are exotic. Double-clicks, back buttons, closed popups, impatient refreshes - this is just how people use the internet. The failure scenarios most teams skip aren't rare events; they're ordinary behavior that only looks rare because nobody's logging what happens when it hits the payment flow.
We run this full list, plus the reconciliation and webhook checks, in every audit. If you want to know how your checkout scores against all twelve before your customers find out - that's a 48-hour turnaround for us. Get in touch.
Want us to catch bugs like these before your customers do? Get a free mini-audit or see our services.