CVV test data comparison: what you are actually comparing
Test CVV data does not move between providers. Every payment gateway publishes its own sandbox card numbers, and each number is tied to a fixed CVV, expiry, and postal code that triggers a specific response. Take the CVV from one provider's test set and pair it with another provider's test card, and the decline you get back is an artifact of your test data, not of your integration. That is the whole reason a comparison matters.
So the comparison is not "which CVV is correct." It is a mapping exercise: for each gateway, capture the card number, the brand, the CVV length, the expiry the sandbox accepts, and the outcome that combination is designed to produce.
CVV Test Data Software Buying Guide
Why the values differ per provider
Gateways carve out reserved test BIN ranges so their sandboxes never touch the real card networks. Stripe, Adyen, PayPal, and Checkout.com each own a different slice of those ranges, which is why the card numbers look unrelated. The CVV values are equally arbitrary. Some providers fix a single CVV across the whole test set. Others vary it to select a result, so a different three digits on the same card number returns a different response code. Card brands add a second layer: Amex uses a four digit CID instead of a three digit CVV, so an Amex row in your matrix needs its own column.
What belongs in a comparison matrix
- Provider and sandbox environment, since test mode and live mode use separate credentials and separate data sets
- Card number, brand, and whether the number passes a Luhn check
- CVV or CID value, plus the expected digit count for that brand
- Expiry that the sandbox accepts, and whether an expired test date is a valid case
- Postal code or address verification value, when the gateway checks it
- Expected outcome: approval, specific decline reason, or a 3DS challenge
Details that break a test run
Some sandboxes ignore the CVV entirely and return an approval no matter what you send. If your matrix assumes a CVV mismatch will decline, you will chase a bug that is not there. Others reject a test card whose CVV does not match the documented pair, which looks like a validation failure in your own code.
Two more traps. First, several providers route certain test cards straight into an authentication challenge, so a card that approves in one sandbox will stall in another until you handle the redirect. Second, expiry validation differs: a gateway may accept any future date, or only the exact date printed in its docs.
Keeping the matrix usable
Store the data as a single source of truth in your repo, keyed by provider and test scenario, and assert against it in integration tests rather than typing numbers into forms by hand. Providers retire and change test cards without much notice, so re-check the published reference pages on a schedule and treat a broken test as a data problem first, a code problem second.
The payoff is boring and useful: when a payment test fails, you know within a minute whether the cause is your code or the card row you copied from the wrong table.