
Most payment integrations ship with the same gap: the happy path is tested thoroughly, and almost everything else gets deferred to "we'll handle it if it comes up." It always comes up. Usually as a customer who got charged twice, or an order that never fulfilled even though the bank confirmed the payment.
A successful payment isn't one scenario
If your gateway supports 3D Secure, there are at least two distinct success flows. In the first, the card is approved immediately and the redirect fires. In the second, the user gets sent to their bank's authentication page, completes a challenge, then comes back to your site. These flows have different timing and different redirect behaviour — and they need separate test cases. An integration that only tests the immediate approval flow will break on a subset of international cards in production.
Decline errors aren't all the same
Insufficient funds is what most developers test. But the cases that generate the most support tickets are usually different: an expired card (the user can fix this immediately if you tell them), a bank-issued decline (the gateway processed the request fine — the cardholder's bank said no), and a fraud flag (the gateway's own risk engine blocked it before it reached the bank). Each returns a different error code. If your application shows a generic "payment failed" message for all of them, you're making the user guess at what to do next.
Three webhook problems that will happen eventually
Duplicate delivery: gateways retry when your endpoint doesn't respond within their timeout window — usually five to ten seconds. If your handler processes the same event twice and produces two fulfilment records or two confirmation emails, that's a support ticket and a manual fix. Store each incoming event ID and check for it before doing any work. Return 200 immediately; do the real processing in a background job.
Out-of-order delivery: events for the same transaction don't always arrive chronologically. A refund event can precede the payment confirmation on a slow network. Check current order state before applying any transition — never assume events arrive in sequence.
Late delivery: webhooks can arrive hours after the user already completed the redirect flow and the order was fulfilled. Handle this by checking whether the order is already complete before doing anything — don't create duplicates or throw errors that trigger a retry loop.
The problem with testing these against a real sandbox
Most provider sandboxes don't give you direct control over these conditions. Forcing a specific decline type requires the right test card number. Triggering a duplicate delivery means waiting for a real retry or manually replaying the event. Simulating a late webhook requires workarounds that usually get skipped under deadline pressure.
A mock gateway where you pick the outcome directly — decline type, event replay, webhook timing — makes every scenario here testable in a normal development cycle rather than something discovered post-launch. MockGateway's playground supports all of this across Stripe, PayPal, Razorpay, and other templates.
Tagged with
Found this helpful?
Share it with your network.


