# Test cards, blocklists and disputes

Stripe's test cards and tokens decline, settle, refund slowly or open a chargeback as Stripe's do; blocklists refuse payments; a route opens a dispute on any captured charge.

Stripe's test cards drive every payment outcome you test: which card you pay with decides whether the payment succeeds, declines, settles at once, refunds slowly or is charged back. Charge a card and open a dispute on it, then decline a payment:

```bash
curl -s "$RYSTIC_URL/v1/charges" -u sk_test_123: -d amount=2000 -d currency=usd -d source=tok_visa | jq -c '{id, captured}'
# -> {"id":"ch_000000000000000000000001","captured":true}
curl -s -X POST "$RYSTIC_URL/_dispute" -d charge=ch_000000000000000000000001 -d reason=product_not_received | jq -c '{id, status, reason}'
# -> {"id":"du_000000000000000000000001","status":"needs_response","reason":"product_not_received"}
curl -s "$RYSTIC_URL/v1/refunds" -u sk_test_123: -d charge=ch_000000000000000000000001 | jq -c '.error.code'
# -> "charge_disputed"
curl -s "$RYSTIC_URL/v1/payment_intents" -u sk_test_123: -d amount=2000 -d currency=usd -d 'payment_method_types[]=card' -d payment_method=pm_card_visa_chargeDeclinedInsufficientFunds -d confirm=true | jq -c '.error | {type, code, decline_code}'
# -> {"type":"card_error","code":"card_declined","decline_code":"insufficient_funds"}
```

### The test cards served

A PaymentIntent create or confirm takes a stored PaymentMethod or one of these test cards: `pm_card_visa`, `pm_card_us`, `pm_card_visa_debit`, `pm_card_mastercard`, `pm_card_mastercard_debit`, `pm_card_mastercard_prepaid`, `pm_card_amex`, `pm_card_discover`, `pm_card_diners`, `pm_card_jcb`, `pm_card_unionpay`, `pm_card_chargeCustomerFail`, `pm_card_createDispute`, `pm_card_bypassPending`, `pm_card_pendingRefund` and the decline cards below. `POST /v1/charges` takes their `tok_` forms. Any other id answers 400 `resource_missing` ("No such PaymentMethod: '…'") and writes nothing, a documented Stripe test card such as `pm_card_ca` or `pm_card_authenticationRequired` included; attach and customer create refuse it the same way. Each test card reads one fingerprint on every path, the same for `tok_visa`, `pm_card_visa` and a PaymentMethod made from either.

### Declines

A decline token on a charge create, or a decline card on a PaymentIntent created with `confirm=true` or confirmed, answers 402 `card_error`:

| Decline | PaymentIntent cards |
|---|---|
| generic | `pm_card_chargeDeclined`, `pm_card_visa_chargeDeclined` |
| insufficient funds | `pm_card_visa_chargeDeclinedInsufficientFunds` |
| lost or stolen card | `pm_card_visa_chargeDeclinedLostCard`, `pm_card_visa_chargeDeclinedStolenCard` |
| expired card, incorrect CVC, processing error | `pm_card_chargeDeclinedExpiredCard`, `pm_card_chargeDeclinedIncorrectCvc`, `pm_card_chargeDeclinedProcessingError` |
| velocity limit | `pm_card_visa_chargeDeclinedVelocityLimitExceeded` |
| fraud (`decline_code` `fraudulent`) | `pm_card_chargeDeclinedFraudulent`, `pm_card_visa_chargeDeclinedFraudulent`, the Radar card `pm_card_radarBlock`; `tok_visa_chargeDeclinedFraudulent` on a charge |

A failed PaymentIntent stays retrievable with `requires_payment_method` and `last_payment_error`. A charge create, and a PaymentIntent created with `confirm=true`, store the declined charge their `error.charge` names as `failed`, list it and send `charge.failed`; on a charge create it carries Stripe's `outcome` and the card's `last4` and `funding`. The PaymentMethod an immediate decline names is not stored, so confirming with its id answers 400, where Stripe keeps it. The failed charge of a declined confirm and retry-limit cancellation are not modeled.

`pm_card_chargeCustomerFail` attaches, and every charge to it declines with `card_declined`, `generic_decline`, leaving a `failed` charge (`charge.failed`) and the PaymentIntent `requires_payment_method` (`payment_intent.payment_failed`). A PaymentIntent created with `confirm=true` or confirmed answers the 402; an invoice pay answers it and leaves the invoice open with one attempt (`invoice.payment_failed`); a subscription charge fails as [Subscriptions and invoices](https://www.rystic.ai/docs/simulators/stripe/features/subscriptions.md) says. A declined confirm stores the card without attaching it: it reads back with `customer` null, and the PaymentIntent keeps `payment_method` null and names the card only in `last_payment_error`. `POST /v1/charges` declines `tok_chargeCustomerFail` as it declines the other decline tokens, its 402 naming no PaymentIntent or PaymentMethod, and refuses a `pm_` source with Stripe's 400 "You cannot create a charge with a PaymentMethod. Use the Payment Intents API instead."

### Saving a card

A customer create with `payment_method` attaches that PaymentMethod as the attach route does, names it as `invoice_settings.default_payment_method` when the body sets it there and sends `payment_method.attached`, then `customer.created`. An unknown `payment_method` answers 400 `resource_missing` and creates nothing, an empty one 400 `parameter_invalid_empty` and a hash or array 400 "Invalid string: …". A declining test token answers the attach route's 402 `card_error`, and a detached card or one another customer holds its 400, each creating nothing. A PaymentIntent created with `automatic_payment_methods[enabled]` lists `card` and `link` in `payment_method_types`.

### Blocklists

Every account starts with Stripe's 24 default Radar value lists, which cannot be updated or deleted, and whose aliases a create or update may not take. A payment whose email is an item of `email_blocklist` is refused with 402 `card_error` and `decline_code` `generic_decline` by a charge create, a confirm and a PaymentIntent created with `confirm=true`; the charge it records reads `failed`, with an outcome naming the list. A charge create and a PaymentIntent created with `confirm=true` send `charge.failed` for it; a confirm sends none. The email is the request's `receipt_email`, else the PaymentIntent's own, else its customer's, matched in any letter case; a billing details email, and a saved card's customer email on a charge, are not read. A card whose fingerprint is an item of `card_fingerprint_blocklist` is refused on the same three paths, as a saved PaymentMethod, a test card (`pm_card_jcb`) or a test token on a charge (`tok_jcb`). An item on any other list blocks nothing.

### Settlement cards

`pm_card_bypassPending` and `tok_bypassPending` (card 4000000000000077, also when saved as a PaymentMethod and paid by PaymentIntent or invoice) put a payment's funds straight into `available`, where every other card's go to `pending`; fund payouts with it, as Stripe's test-mode docs advise ([Balance and payouts](https://www.rystic.ai/docs/simulators/stripe/features/balance.md)).

A refund of a captured `pm_card_pendingRefund` or `tok_pendingRefund` charge, on any of the three refund create routes or through a credit note's `refund_amount`, answers `pending` with `pending_reason` `insufficient_funds` and a card destination naming no acquirer reference yet, and writes its `pending` balance transaction. Once the clock reaches 122 seconds after its `created` it reads `succeeded`, and at 249 seconds its `destination_details.card` names its acquirer reference (`reference_status` `available`, `reference_type` `acquirer_reference_number`). Each move sends `refund.updated` and `charge.refund.updated`, naming no request, at the instant it was due; Stripe's own instants vary by a few seconds. A legacy `POST /v1/charges/{charge}/refund` refund of a charge whose balance transaction has not attached yet, which only a seeded charge shows, is `succeeded` at once with none, and on this card its reference stays `pending`.

### Disputes

A captured charge paid with Stripe's dispute test card, `tok_createDispute` on `POST /v1/charges` or `pm_card_createDispute` on a PaymentIntent created with `confirm=true` or confirmed, opens a `needs_response` dispute at once, with reason `fraudulent` and a `du_` id. The charge then reads `disputed: true` and names the dispute, and `charge.dispute.created` goes out. `GET /v1/disputes`, `GET /v1/disputes/{dispute}` and `GET /v1/charges/{charge}/dispute` return it. The charge create itself answers `disputed: false`, as Stripe's does before the chargeback lands. A charge with no dispute answers its dispute read 404 "No dispute for charge: ch_…".

`POST /_dispute` opens the same dispute on any captured charge you made: it takes `charge`, and optionally `reason` and `amount`, as a form or a JSON body. It stores the dispute, marks the charge and sends `charge.dispute.created` to the event log and your endpoints. It answers 400 without `charge`, for an unknown `reason` or an `amount` outside 1 to the amount captured; 404 for an unknown charge; and 409 for a charge that took no funds or is already disputed. A scenario timeline can schedule it as a call. It is a control route: open by default, and once the simulator boots with `RYSTIC_ADMIN_TOKEN` set, a request needs that value in an `X-Rystic-Admin-Token` header.

No API call updates or closes a dispute, and its withdrawal and fee do not move `GET /v1/balance`. Refunding a disputed charge answers Stripe's 400 `charge_disputed`. Stripe tells an inquiry (a `warning_` dispute) apart and still allows a refund there; the simulator opens no inquiry and refuses a refund of every disputed charge.

---

Source: https://www.rystic.ai/docs/simulators/stripe/features/test-cards · Markdown: https://www.rystic.ai/docs/simulators/stripe/features/test-cards.md
