When you build a checkout form, you need card numbers to test it. The usual shortcut is to type 4111 1111 1111 1111 or make up some digits. That works until it doesn't, so here is what actually matters.
Random digits fail for two different reasons
1. Luhn check. Almost every card number passes the Luhn checksum. Your front-end validation (and the payment processor) rejects numbers that don't. If you type random digits, about 9 out of 10 fail before your real code even runs, and you end up debugging the wrong thing.
Here is the check in a few lines:
function isLuhnValid(input) {
const value = input.replace(/\D/g, '') // strips spaces and dashes
if (!value) return false
let sum = 0
let double = false
for (let i = value.length - 1; i >= 0; i--) {
let digit = Number(value[i])
if (double) {
digit *= 2
if (digit > 9) digit -= 9
}
sum += digit
double = !double
}
return sum % 10 === 0
}
isLuhnValid('4242424242424242') // true
isLuhnValid('4242 4242 4242 4242') // true
isLuhnValid('4242-4242-4242-4242') // true
isLuhnValid('4242424242424243') // false
2. A Luhn-valid number is not a test number. A random number that passes Luhn can still belong to a real card issuer. In a sandbox the processor usually only accepts its own published test numbers. And
sending someone else's real card number is exactly what you don't want to do.
So the safe approach is: use the published test numbers, not generated ones.
The numbers worth keeping
These are widely published by payment providers for testing. They pass Luhn and don't ma
| Brand | Test number |
|---|---|
| Visa | 4242 4242 4242 4242 |
| Visa | 4111 1111 1111 1111 |
| Mastercard | 5555 5555 5555 4444 |
| American Express | 3782 822463 10005 |
| Discover | 6011 1111 1111 1117 |
| Diners Club | 3056 930902 5904 |
| JCB | 3530 1113 3330 0000 |
Notes that save time:
- Amex uses 15 digits and a 4-digit security code. A form that assumes 16 digits and a 3-digit CVV breaks here. Test it.
- Diners Club uses 14 digits.
- Check your provider's docs for failure cases (declined, insufficient funds, 3-D Secure). Those use special numbers and differ per provider.
- Expiry can be any future date, and the CVV any 3 or 4 digits, unless your provider says otherwise.
What to test besides "it works"
- Spaces and dashes in the number (
4242 4242 4242 4242vs4242-4242-4242-4242). - Brand detection from the first digits as the user types.
- 15-digit Amex and its 4-digit code.
- An expired date, and a date with a wrong format.
- Pasting a number with a trailing space.
A small tool I built for this
I got tired of looking these numbers up each time, so I built a test card generator. It gives you a card with a number from the fixed list above, plus a random name, expiry and security code. It doesn't generate real card numbers and it's not for anything except testing forms.
It runs in the browser, no sign-up: Test Card Generator
I wrote a longer version of this guide on my site: Test card numbers for sandbox testing
What other cases do you test in your checkout forms? I'd like to add the ones I missed.
Top comments (2)
Great question! For the happy path (standard successful checkout), any valid future date (like 2–5 years out) will pass across almost all major sandboxes (Stripe, PayPal, Braintree, etc.) since gateways generally just check expiry_year/month > current_date.
However, sandboxes can still reject or flag a future date in a couple of specific scenarios:
Testing Failure Flows: If you're explicitly trying to test how your form handles expired cards (to trigger a card_expired error from the gateway), a standard future date will fail because the processor expects a past date.
Provider-Specific Mock Logic: Some strict test environments have specific test dates or card combinations tied to specific error codes (e.g., certain gateways use specific months/years to simulate issuer declines or 3-D Secure triggers).
How the generator handles it:
By default, it generates a safe, rolling future date (usually 2 to 4 years ahead) so your standard tests pass without friction. If you're testing error handling for expired cards, you'll still want to manually override the year to something in the past.
What gateway are you usually testing against? If they have quirky date rules, I can look into adding a toggle for it!