Skip to content
On this page

Developers › Get started

Test cards

Use these with a key beginning sk_test_. Nothing here moves real money.

Explain this to your team →

View as Markdown

A test payment ends on one of two pages. If it has buttons saying what should happen, press one: there is no card to enter. If it asks for a card, use a number from the second table.

ButtonWhat happens
The payment worksSucceeds
It works, and the money is ready at onceSucceeds, and skips the wait before it can be paid out
Declined: not enough moneyFails, with decline_code insufficient_funds
Declined: the customer’s bank says noFails, with decline_code generic_decline
The customer leaves without payingExpires, with no decline_code
The customer’s bank asks them to confirmWaits at requires_action, then they confirm, fail or give up
It works, then the customer disputes itSucceeds, then opens a test dispute. Answer it from your dashboard: evidence containing losing_evidence loses, any other evidence wins, and no answer by the deadline loses
NumberExpiryCVCWhat happens
555555555555559912/34123Succeeds
411111111111111112/26123Succeeds
516849489505578012/26123Declined
The cards, their expiry dates and what each one does are the bank’s, from its sandbox documentation. Use CVC 123 for all of them. If the bank’s page does something other than what the table says, tell us and quote the payment’s id.

What a decline looks like

The customer is shown “The payment did not go through” on our result page, with a button to try another card — they are not sent to your cancel_url. The payment goes back to requires_payment_method and carries a last_payment_error with a decline_code, and you get payment_intent.payment_failed. The checkout session stays open, so they can try another card without you creating anything new.

Leaving the bank’s page without paying is not a decline. The bank expires the page about twenty minutes at most after it opened — sooner if the checkout session had less time left than that — and the payment goes back to requires_payment_method with last_payment_error.code payment_intent_payment_attempt_expired and no decline_code.

Which decline_code the decline card in the second table produces is up to the bank’s sandbox, and we have not pinned it down; do not build a test that expects a particular one from it. A reason we cannot map arrives as generic_decline. The raw code the bank sent is on the payment at gateway.action_code, for support — branch on decline_code, never on that.

What authentication looks like

The payment sits at requires_action while the cardholder’s bank asks them to confirm. It is not a failure and it is not finished — wait for the webhook rather than reading the status once and deciding. To see it on purpose, press “The customer’s bank asks them to confirm”. The bank does not document a test card that always asks for authentication, so a page that asks for a card may or may not show you this step; your integration should handle it either way.

Running the same test twice

Our default risk rules apply in test mode too, and one of them refuses a payment after five attempts from the same email address within an hour. If you re-run a test with a fixed receipt_email or customer_email, the sixth press of Pay by card in an hour is refused — and so is every press after it, because a refusal counts as an attempt too. Use a fresh address per run, leave the email off, or add your test address to your allow list in test mode. The risk page says exactly what is counted.

Test and live are separate sets of books. A test payment can never appear in your live totals, and a test key cannot read live data.