A test card number passes your form and then gets rejected the moment it reaches a payment provider. That is expected behaviour, and understanding exactly where the rejection happens is useful — both for developers debugging a sandbox setup, and for anyone wondering whether a generated number could ever go through.
The short version: your form validates the number’s shape. The payment provider validates its existence. Those are entirely different checks, performed by entirely different systems, and only the second one determines whether money moves.
The four layers of card validation
Every card number passes through up to four independent checks. They run in different places, answer different questions, and fail for different reasons.
| Layer | Where it runs | What it checks | Can a generated number pass? |
|---|---|---|---|
| 1. Format | Browser (JavaScript) | Length, digits only, input mask | Yes |
| 2. Checksum | Browser or server | Luhn check digit | Yes |
| 3. BIN validation | Gateway or processor | Prefix exists in a real BIN table, network supported | Sometimes |
| 4. Authorisation | Issuing bank | Account exists, is active, has funds, code matches, not blocked | Never |
Layer 1 — format. Is this 13 to 19 digits, with separators stripped, matching the expected pattern for the detected brand? Pure string handling, no knowledge of payments required. Any generated number passes by construction.
Layer 2 — checksum. Does the final digit satisfy the Luhn formula? Also arithmetic, also local, and also something a generator satisfies deliberately — computing that digit is the last step of generating the number. Worth remembering that roughly one in ten random strings passes this check anyway.
Layer 3 — BIN validation. Now the number leaves your application. The gateway reads the leading digits and asks whether they fall in a range assigned to a network it supports and an issuer it can route to. This is the interesting layer, because a generated number can pass it: prefixes are public, and a generator that uses real network ranges produces numbers whose first six digits genuinely belong to somebody. What a BIN identifies is an institution, not an account — so passing here means only that the number is addressed correctly.
Layer 4 — authorisation. The request reaches the issuing bank, which looks the number up in its own records. There is no record. The transaction is declined, and no amount of formatting can change that, because the check is a database lookup at an institution that never issued the card.
Layers 1 and 2 are yours. Layer 3 is your gateway’s. Layer 4 belongs to a bank you have no relationship with, and it is the only one that decides whether money moves.
That split determines where your effort belongs. The first two layers exist to save a customer a round trip when they mistype a digit — they are a user-experience feature, and treating them as a security control is the mistake behind most of the confusion on this page. The last two are the actual controls, and you do not implement either of them. Which means the practical question in any integration is not “how thoroughly do I validate a card number”, but “how gracefully do I handle the answer somebody else gives me” — a question almost entirely about error paths, and the reason the failure-path section below is longer than it looks like it should be.
Where each provider draws the line
The rejection looks slightly different depending on who is processing it:
- Stripe —
Stripe.jsvalidates format client-side. Once the number reaches the API through a PaymentIntent, you getinvalid_numberor acard_declinedwith a decline code. The decline codes reference lists which are retriable. - PayPal — adding a card triggers a verification authorisation against the issuer, which fails immediately. Errors surface through the Orders API error reference.
- Adyen — checks against its own BIN data and returns
Refusedwith a refusal reason; the refusal reasons list maps each to a cause and a recommended action. - Braintree — distinguishes
processor_declinedfromgateway_rejected, which is a useful split: the first came from the bank, the second never left Braintree. The authorisation responses reference covers both. - Google Play and the App Store — run a verification authorisation when a payment method is added, on top of account-level fraud checks. Google documents the common causes of a rejected payment method.
Different names, one mechanism. Every provider eventually asks a bank, and the bank’s answer is the same in all five cases.
A rejected attempt still leaves a trace
This is the part most people get wrong, and it matters more than the decline itself.
A declined attempt is not a non-event. Payment providers log every attempt with the card number, IP address, device fingerprint, and account. Repeated failed attempts from one source are the signature of a card testing attack — the automated probing of stolen card lists — and providers respond to that pattern automatically.
The consequences are usually account-level: rate limiting, a review flag, a requirement to re-verify identity, or suspension. On some platforms the block attaches to the device fingerprint and the payment method as well, which means it follows you to a new account.
Note what this means for the intent behind the search. Someone trying generated numbers on a real checkout produces exactly the traffic pattern that fraud systems are built to detect, at exactly the endpoints that are monitored most closely. The outcome is not a successful payment; it is a flagged account and an automated report that nothing generated here can ever complete anyway.
Common developer causes of rejection
If you are debugging rather than experimenting, the cause is usually in this table:
| Symptom | Likely cause |
|---|---|
| Test card declined in sandbox | A generated number instead of the gateway’s own test card |
| Works in sandbox, fails in production | Live keys with a test card — expected; use a real card |
invalid_number on a valid-looking card | Length or brand not enabled on that gateway account |
| Card accepted, charge fails later | Deferred authorisation; the number never existed |
| Amex rejected everywhere | American Express not enabled on the merchant account |
| 2-series Mastercard rejected | Brand detection regex still only covers 51–55 |
| Random failures on long numbers | A 19-digit PAN silently truncated by a VARCHAR(16) column |
The last row is the nastiest, because nothing errors. The row saves, the number is quietly wrong, and the failure surfaces later as an unexplained decline for a subset of customers — usually the ones on UnionPay and some Discover products. The card number structure guide covers the length range that column has to accommodate.
The second row is worth stating plainly too: a test card failing in production is not a bug to fix. It is the sandbox boundary working. The fix is a real card and a small amount.
What to use instead
- Layers 1 and 2 — your own form. Generated numbers are exactly right, because those layers never contact anyone. The generator covers every network, and the validator shows what a checksum check does and does not prove.
- Layers 3 and 4 — the gateway. Use your provider’s published sandbox cards. The test card reference collects them across processors, and the Stripe set is documented in full.
- Production verification. A real card, a small amount, refunded afterwards. There is no synthetic substitute for this last step, and skipping it is how integrations ship with a broken capture path.
Testing the failure paths
Most teams test that a payment succeeds. Almost none test what happens when it does not, which is where real users end up:
- The message shown after a decline — “Your bank declined this payment” is enough; never surface the raw decline code, which is often wrong and occasionally unsafe.
- Whether the cart survives a failed payment, or the customer has to start again.
- Retry logic and its rate limit, so a legitimate retry is possible and a loop is not.
- Network timeouts, and whether your idempotency key prevents a double charge.
- Partial success: authorisation succeeded, capture failed.
- Webhooks arriving late, twice, or out of order.
The payment form testing checklist covers the assertions for each, and the 3-D Secure guide covers the authentication paths that sit between layers 3 and 4.
The gateway behaviour and error codes above were last checked against provider documentation on . Providers do change what they publish — the official link beside each claim is authoritative.