Payment gateway testing checklist
Fifteen checks across the two halves of a payment integration that fail for different reasons. Money arithmetic fails quietly: a rounding rule applied in the wrong place produces an amount that looks right on every screen and is wrong by a cent per transaction. Webhook handling fails loudly but late, because the provider retries, and a handler that is not idempotent turns one event into several charges while every individual request returns 200. The order below runs signature verification first, since a handler that accepts an unsigned request makes every check after it meaningless, then delivery, then the arithmetic that survives all of it. Run these against the provider's test mode or a local fixture; a test-mode key is still a real key.
15 checksabout 147 min end to end
Prove the request came from the provider
- How to check webhook signature
A handler that parses the body before verifying it has already acted on input it cannot vouch for, and the order of those two steps is the whole control. - Webhook signature verification failed
Any middleware that parses and re-serialises JSON changes the bytes, and the signature is over the bytes, which is why verification fails on a payload that is semantically identical. - Stripe webhook signature verification
A scheme that signs a timestamp and the body together needs both reconstructed in the right order, and getting it almost right produces the same failure as an attack. - Github webhook signature verification
A second scheme is worth running because it shows which parts of your verification code were about HMAC and which were about one provider's formatting. - Webhook replay attack
A signature stays valid forever unless something rejects old ones, so a captured request replayed a week later verifies perfectly.
Survive how the provider actually delivers
- Webhook idempotency
Providers retry on timeout, so the same event arrives more than once by design, and the handler has to be safe against that rather than assume it is rare. - How to test webhook retries
The retry schedule decides how long a broken handler keeps receiving traffic and how long a fixed one takes to catch up, and neither is obvious from a single delivery. - Webhook timeout
Work done inside the request is work the provider is timing, and a handler that finishes after the deadline is retried even though it succeeded. - Webhook ordering
Events do not arrive in the order they happened, so a state machine that assumes they do will process a refund before the charge it refers to. - Webhook dead letter queue
After the retries are exhausted the event is gone, and without somewhere for it to land the only record of the lost payment is on the provider's dashboard.
The arithmetic
- How to test duplicate payment prevention
A double-submitted checkout and a retried webhook are the same bug from two directions, and an idempotency key is what makes both safe. - Zero decimal currency
Not every currency has two decimal places, so a conversion that multiplies by 100 is wrong for the ones that have none and for the one that has three. - How to check currency rounding
Where the rounding happens decides the answer, and a total rounded per line disagrees with the same total rounded once by an amount that grows with the basket. - Price tampering
Any amount that arrives from the client is a suggestion, and the check is whether the server recomputed it rather than whether the form was hard to edit. - Luhn check
The check digit rejects typos and nothing else, so treating it as validation means a well-formed number that belongs to no card reaches the provider as a decline.
Take it with you
The file is the same list as Markdown checkboxes, ready to paste into a release ticket or a pull request description.
Ticks are kept in this browser only. They are not sent anywhere and other people do not see them.