Test Cards
Use these card numbers in test mode to simulate different payment outcomes. Any future expiry date and any 3-digit CVC will work (Amex takes a 4-digit CID).
These are the canonical industry test numbers, and they drive the outcomes below in the Vonpay-owned sandbox.
If your merchant is boarded onto a real processor's sandbox (rather than the Vonpay-owned one), the table below does not apply. That processor decides the outcome, and these numbers usually carry no special meaning to it — a card listed here as a decline will typically be approved, with no error and nothing to indicate the test did not do what you expected.
That failure is quiet by nature: you get a successful payment, which looks like your integration working. Use the test cards published by whichever processor your sandbox is boarded onto. If you are not sure which kind of sandbox you have, ask us.
For the hosted-checkout sandbox today (mock gateway), outcomes are chosen by session amount, not by card number — see Sandbox & Test Mode → Sandbox outcomes.
For the embedded-checkout sandbox (sandbox iframe via vora.js), outcomes are chosen by card number per the table below. The sandbox iframe locks down to these numbers only — any other input is rejected, both as a real-card safety lock and to keep outcomes deterministic. Embedded fields are collected and submitted through the canonical collection path — const elements = vora.elements.create(); const card = elements.create("card", {}); card.mount("#card-element"); const result = await elements.submit(); — not a single-field tokenize() call.
Card Numbers
| Card | Brand | Outcome | Webhook event | Use for |
|---|---|---|---|---|
4242 4242 4242 4242 | Visa | Success | payment_intent.succeeded | Happy path |
5555 5555 5555 4444 | Mastercard | Success | payment_intent.succeeded | Mastercard testing |
3782 822463 10005 | Amex | Success | payment_intent.succeeded | Amex testing |
4000 0000 0000 0002 | Visa | Generic decline | payment_intent.failed (failure_code: card_declined) | Generic error handling |
4000 0000 0000 9995 | Visa | Insufficient funds | payment_intent.failed (failure_code: insufficient_funds) | Buyer-fundable error path |
4000 0000 0000 0069 | Visa | Expired card | payment_intent.failed (failure_code: expired_card) | Re-prompt UX |
4000 0000 0000 0119 | Visa | Processing error | payment_intent.failed (failure_code: processing_error) | Transient-failure UX |
4000 0027 6000 3184 | Visa | 3DS required → success | payment_intent.succeeded | Happy-path 3DS |
4000 0084 0000 0029 | Visa | 3DS required → fail (fraud) | payment_intent.failed (failure_code: fraudulent) | 3DS challenge rejection |
Synthetic token shape (embedded sandbox)
When the embedded sandbox submits a test card and the charge succeeds, the result carries a final synthetic vp_pmt_test_* token (tokenIsFinal: true) with the outcome encoded in the middle segment. The sandbox iframe mints this synthetic token on every success branch — it does not emulate the production binder's guest charge-only path. Still read the submit result defensively as the 3-way { token } | { charged: true } | { error } union, never result.token unconditionally, so the same handler works against a live binder:
| Card outcome | Token shape |
|---|---|
| Success | vp_pmt_test_success_<random> |
| Decline | vp_pmt_test_decline_<reason>_<random> (e.g. vp_pmt_test_decline_insufficient_funds_abc123) |
| 3DS required | vp_pmt_test_3ds_success_<random> or vp_pmt_test_3ds_fail_<random> |
The encoded outcome means the server-side mock binder recognises the intended payment-intent result from the token prefix alone — no DB lookup, no card-number round-trip. The canonical map is in the @vonpay/test-cards workspace package; both the iframe and the server import from there so the list cannot drift.
vp_pmt_test_* tokens are sandbox-only — a test token used with a live-mode key is rejected with HTTP 400 payment_method_mode_mismatch (the cross-mode guard). (Don't confuse this with payment_method_inactive, which is HTTP 422 and means the token was revoked.)
Lifecycle ops with synthetic tokens
Every existing payment-intent lifecycle op works against a synthetic success token. The same mock binder that handles hosted sandbox today extends to embedded via the token-prefix recognition:
| Operation | Sandbox behavior with vp_pmt_test_success_* | Webhook fired |
|---|---|---|
POST /v1/payment_intents (auto-capture) | → succeeded | payment_intent.succeeded then charge.succeeded |
POST /v1/payment_intents (capture_method=manual) | → authorized | payment_intent.succeeded (the authorization; capture is separate) |
POST /v1/payment_intents/:id/capture | → succeeded | charge.succeeded |
POST /v1/payment_intents/:id/void | → voided | payment_intent.cancelled |
POST /v1/refunds | → refund settled | charge.refunded |
Access via MCP
AI agents can retrieve test cards using the vonpay_checkout_list_test_cards tool. See MCP Server for setup.