Simulators
Test cards, blocklists and disputes
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:
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 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).
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.