---
id: docs.test-cards
title: "Test cards"
canonical: https://docs.secureprocessing.app/test-cards
---

# Test cards

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

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.

| Button | What happens |
|---|---|
| The payment works | Succeeds |
| It works, and the money is ready at once | Succeeds, and skips the wait before it can be paid out |
| Declined: not enough money | Fails, with `decline_code` `insufficient_funds` |
| Declined: the customer’s bank says no | Fails, with `decline_code` `generic_decline` |
| The customer leaves without paying | Expires, with no `decline_code` |
| The customer’s bank asks them to confirm | Waits at `requires_action`, then they confirm, fail or give up |
| It works, then the customer disputes it | Succeeds, 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 |

| Number | Expiry | CVC | What happens |
|---|---|---|---|
| `5555555555555599` | 12/34 | 123 | Succeeds |
| `4111111111111111` | 12/26 | 123 | Succeeds |
| `5168494895055780` | 12/26 | 123 | Declined |

> **Note:** 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](https://docs.secureprocessing.app/risk.md) page says exactly what is counted.

> **Note:** 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.
