# Test cards and sandbox

> SATIM test cards, what the sandbox does and does not do, and how to rehearse failures.

Source: https://rais.app/docs/testing

The sandbox lets you build and test a complete integration without moving any money. It is the same API as live: only the key changes.

## What the sandbox does

- A **sandbox key** (`rais_sk_test_...`) creates test payments with `livemode: false`. The API, the objects, the errors, the events and the webhooks are the same as in live.
- Test payments are paid on the real hosted checkout with **SATIM's test cards**, typed as a real card is (see [Test cards](#test-cards)). The checkout of a sandbox payment shows a "Test mode" banner. It does not list the cards, and it lets an expired card or a wrong check digit through to SATIM, so that every case below can be rehearsed.
- **No money moves.** A sandbox payment never credits a balance and never writes to the ledger; a sandbox refund never debits one.
- **Fees are simulated.** A paid sandbox payment and a succeeded sandbox refund show the fees they would cost in live, in `fees` (and `net` on the payment): nothing is charged. See [Pricing and fees](https://rais.app/docs/pricing.md).
- Sandbox events go to **sandbox webhook endpoints** only, and sandbox receipts are marked **TEST MODE — NOT A REAL PAYMENT**.
- Sandbox and live data never mix: a sandbox key cannot see a live payment, and the other way round.

A **live** payment never shows a test banner or any test wording, in any state.

## What it does not do

- It does not let you choose the outcome through the API: the card you use decides it.
- It does not speed up time. To rehearse expiry, create a payment with `expires_in: 300` and wait.
- It still goes through SATIM's test system, so an outage over there can answer `gateway_unavailable`, as in live.

## Test cards

Use any of these cards on the checkout of a **sandbox** payment. Type the number, the expiry (month and two digits of the year, as in the table) and the CVV2 in the card form (the name can be any name in Latin letters), then enter the 3-D Secure password of the card when the bank step asks for it. The checkout does not list the cards.

| Card number | Expiry (MM/YY) | CVV2 | 3DS password | Result |
| --- | --- | --- | --- | --- |
| `6280 5811 1000 7215` | 01/27 | 373 | 123456 | Valid card: the payment succeeds |
| `6280 5806 1006 1011` | 01/27 | 992 | 123456 | Valid credit card: the payment succeeds |
| `6280 5811 1000 6712` | 01/27 | 897 | 123456 | Declined: card temporarily blocked |
| `6280 5811 1000 6316` | 01/27 | 657 | 123456 | Declined: lost card |
| `6280 5811 1000 6415` | 01/27 | 958 | 123456 | Declined: stolen card |
| `6280 5811 1000 6613` | 01/26 | 411 | 123456 | Declined: incorrect expiration date |
| `6280 5811 1000 3927` | 01/25 | 834 | 123456 | Refused by the form ("PAN is invalid"): the payment stays pending |
| `6280 5806 1006 1219` | 01/27 | 049 | 123456 | Declined: card limit reached |
| `6280 5806 1006 1110` | 01/27 | 260 | 123456 | Declined: insufficient funds |
| `6280 5811 1000 6514` | 01/27 | 205 | 123456 | Declined: incorrect CVV2 |
| `6280 5806 1006 1318` | 01/27 | 930 | 666666 | Declined: card blocked for e-payments, before the password is asked |
| `6280 5811 1000 7017` | 01/27 | 632 | 123456 | Declined: card blocked for e-payments, before the password is asked |
| `6280 5811 1000 7116` | 01/27 | 040 | 123456 | Declined: inactive card |
| `6280 5811 1000 7314` | 01/27 | 821 | 123456 | Declined: card limit reached |
| `6280 5806 1005 6615` | 12/22 | 428 | 123456 | Approved: SATIM's test platform does not refuse this card, so the payment succeeds |

The first two cards pay successfully. Most of the others reproduce the failures a real payer meets: a blocked, lost or stolen card, a wrong expiry date or CVV2, a card without funds or over its limit. A declined card ends the payment with the status `failed`, a `failure` object that carries `payment_declined` and SATIM's message, and a `payment.failed` event. Three of the cards do not behave as their name suggests, as observed on SATIM's test platform:

- **Card no longer exists at the issuer** is refused by the payment form itself ("PAN is invalid"). No attempt reaches the bank, so the payment stays `pending` and the payer can try another card.
- **Password attempts exceeded** and **Card not authorised for online payment** are both refused as "Card blocked for e-payments" before the 3-D Secure password is asked.
- **Expired card** is **approved**: SATIM's test platform does not refuse it, so the payment succeeds. Do not use it to test a decline.

The wording of `failure.message` is SATIM's and can change: branch on `failure.code`.


> [!NOTE]
> **The wrong expiry card**
>
> The card "Incorrect expiration date" fails because the checkout is filled with an expiry that does not match the card, which is how SATIM's certification scenario works. Enter `01/26`, the expiry in the table: the checkout of a sandbox payment lets it through.


These are the cards SATIM publishes for its certification environment. They work only there.

## Rehearse your integration

Run through these cases before you go live:

1. **Success.** Pay with the first card. Check that your webhook arrives, that you mark the order paid once, and that the return page confirms through the API.
2. **Decline.** Pay with a card that is declined, such as the blocked card, and show the payer a useful message. Check the `payment.failed` event. A declined card closes the payment: the payer cannot pay on the same link.
3. **Abandon.** Open the checkout and press **Back to** your store, then close the page. Check what your cancel page shows: the address carries `status=cancelled` although the payment is still `pending` and can be paid until it expires.
4. **Expire.** Create a payment with `expires_in: 300`, wait five minutes, and retrieve it: its status is `expired`.
5. **Cancel.** Create a payment and cancel it with the API before it is paid.
6. **Refund.** Refund part of a paid payment, then the rest. Watch the status go to `partially_refunded` and then `refunded`, and read the `fees` of each refund (simulated in the sandbox).
7. **Webhook failures.** Make your endpoint answer `500` once, and check that the retry arrives with `Rais-Attempt: 2` and the same event id. Send the same event twice and check that you process it once.
8. **Retries.** Send the same `POST /payments` twice with one `Idempotency-Key`. You should get the same payment, with `Idempotent-Replayed: true` the second time.

When all of it works, follow [Going live](https://rais.app/docs/going-live.md).
