Simulate a payment's outcome
const url = 'https://api.example.com/v1/test_helpers/payment_intents/example/simulate';const options = { method: 'POST', headers: { 'Idempotency-Key': 'order-1042-attempt-1', Authorization: 'Bearer <token>', 'Content-Type': 'application/json' }, body: '{"outcome":"paid"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.example.com/v1/test_helpers/payment_intents/example/simulate \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: order-1042-attempt-1' \ --data '{ "outcome": "paid" }'Gives a test-mode payment the outcome you choose, now, as if your customer had paid it, or
failed to. No customer pays a simulated QR code or a test cash reference, and no bank answers a
simulated card, so this is how a test payment moves on from requires_action. It works with a test key only: a live
key is refused with test_mode_only, and a live intent is never reached. See Testing your
integration.
The intent must have a payment with the customer: confirmed, or, for an intent created without a method, with the method chosen on our checkout page. Outcomes are for the intent’s method:
outcome |
Method | The intent then |
|---|---|---|
paid |
qrph |
succeeded, with amount_received equal to amount. A code already past its time is expired instead. |
expired |
qrph |
requires_payment_method, with last_payment_error payment_intent_payment_attempt_expired. The code can no longer be paid; confirm again for a new one. |
paid |
cash |
succeeded through processing, as when the customer pays the exact amount at a cash collection location. A reference already past its time is expired instead. |
expired |
cash |
requires_payment_method, with last_payment_error payment_intent_payment_attempt_expired. The reference can no longer be paid; confirm again for a new one. |
approved |
card |
succeeded. A card payment of 40400 is then charged back: see Test mode and test values. |
three_d_secure_passed |
card |
succeeded, as after the customer’s bank has authenticated them. |
declined |
card |
requires_payment_method, with last_payment_error card_declined. A decline_reason adds a decline_code to it (test mode only). |
three_d_secure_failed |
card |
requires_payment_method, with last_payment_error payment_intent_authentication_failure. |
abandoned |
card |
requires_payment_method, with last_payment_error payment_intent_payment_attempt_failed: the customer left without paying. |
The intent and its events are those of a real payment: payment_intent.succeeded or
payment_intent.payment_failed is sent, and a failed card payment can be confirmed again. An
intent already settled, whether paid, canceled, or its attempt failed or expired, is answered
as it is, and nothing changes: asking again, for the same outcome or another, is safe. An
outcome that is not one of the intent’s method is refused with validation_failure
whatever the intent’s state.
Idempotency-Key is required, as on every write on an intent.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”The object’s id.
Header Parameters
Section titled “Header Parameters”A value unique to this operation, such as your order number and attempt, of 1 to 255 characters. A retry with the same key and body is done at most once; see Idempotency.
Example
order-1042-attempt-1Request Bodyrequired
Section titled “Request Bodyrequired”object
paid or expired for a QR Ph or cash payment; approved, declined,
three_d_secure_passed, three_d_secure_failed or abandoned for a card payment.
With declined only: the reason the decline names, which comes back as
last_payment_error.decline_code. Leave it out, or send null, for a plain decline.
Examples
{ "outcome": "paid"}{ "outcome": "expired"}{ "outcome": "approved"}{ "outcome": "declined", "decline_reason": "insufficient_funds"}{ "outcome": "three_d_secure_failed"}Responses
Section titled “Responses”The intent as it then is.
object
In the currency’s minor unit.
The amount paid: amount once succeeded, and 0 until then.
A three-letter ISO 4217 code, in lower case.
The chosen method, such as [qrph], once there is one. Until then, the methods that can
take this payment now: confirm with one. In an event for an intent with no method, the
methods you allowed at creation, or null if you allowed any.
Where the payment is taken: ecommerce for every intent your server creates. Payments
taken on terminals will say pos (Pending).
object
object
The QR payload. Render it as a QR code for the customer to scan with their banking app.
When the code stops being payable, as a Unix timestamp.
object
object
The payment reference, unique to this attempt. Give it to the customer to pay at a cash collection location.
The exact amount to pay, in the currency’s minor unit.
A three-letter ISO 4217 code, in lower case.
When the reference stops being payable, as a Unix timestamp. After it the attempt fails as expired.
Words to show the customer. May be reworded, so never branch on it.
object
object
The payment page: a card’s, or our hosted checkout page for an intent created without a method. Send the customer there.
Where the customer comes back to, or null when you gave none: your return_url, or our hosted checkout page when
the customer chose card there, which then sends them on to yours. For the hosted
checkout page itself, the return_url you gave on create.
An intent created with checkout_mode embedded, waiting for the customer in embedded
checkout on your page. See Embedded checkout.
object
object
What your page mounts embedded checkout with, beside your publishable key, from one of your registered origins. The same on every read while it is here. It opens this one payment’s checkout: give it only to the customer paying.
When the checkout session ends, as a Unix timestamp. After it, the client token opens nothing.
object
card_declined: the card was declined. expired_card: the card has expired (test mode
only today). payment_intent_authentication_failure: the customer did not complete the
card’s authentication. payment_intent_payment_attempt_failed: the attempt failed, or the
customer left without paying. payment_intent_payment_attempt_expired: the customer did
not pay in time.
Why the card was declined, when the decline names a reason. Test mode only today: it
appears when you simulate a decline with a decline_reason (see Simulate a payment’s
outcome), and a live decline does not carry it yet, so
never depend on it there.
Words you can show the customer. May be reworded, so never branch on it.
The reason given when it was canceled, if any.
When it was canceled, as a Unix timestamp.
The metadata you gave it. {} if none.
object
When it was created, as a Unix timestamp.
Examples
A QR Ph payment simulated paid
{ "id": "pi_01j9x2m8k4q7r5t3v6w8y0z2a4", "object": "payment_intent", "amount": 10000, "amount_received": 10000, "currency": "php", "payment_method_types": [ "qrph" ], "channel": "ecommerce", "status": "succeeded", "next_action": null, "last_payment_error": null, "cancellation_reason": null, "canceled_at": null, "description": "Order 1042", "metadata": { "order_id": "1042" }, "livemode": false, "created": 1790411400}A card payment simulated declined for insufficient funds
{ "id": "pi_01j9x2p4d6f8h0k2m4n6q8s0t2", "object": "payment_intent", "amount": 10000, "amount_received": 0, "currency": "php", "payment_method_types": [ "card" ], "channel": "ecommerce", "status": "requires_payment_method", "next_action": null, "last_payment_error": { "code": "card_declined", "decline_code": "insufficient_funds", "message": "The card has insufficient funds." }, "cancellation_reason": null, "canceled_at": null, "description": null, "metadata": { "order_id": "1042" }, "livemode": false, "created": 1790411400}Headers
Section titled “Headers”This request’s id. Quote it when you contact support.
validation_failure with invalid_parameter: param is outcome when it is missing,
not an outcome, or not one for the intent’s method; decline_reason when it is not
insufficient_funds, do_not_honor or expired_card, or is sent with an outcome other
than declined; Idempotency-Key when the header is missing.
object
object
A stable name for this refusal. Branch on this, never on message.
For the developer reading a log. May be reworded.
The field or parameter at fault, when there is one.
Example
{ "error": { "type": "validation_failure", "code": "invalid_parameter", "message": "One of paid, expired for a qrph payment.", "param": "outcome", "request_id": "req_4f1c0b2e9a7d" }}authentication_failure: access_token_required when no token was sent, invalid_access_token when it is not valid, has expired, or is a client secret. Sent with a WWW-Authenticate: Bearer header, which adds error="invalid_token" when a token was sent.
object
object
A stable name for this refusal. Branch on this, never on message.
For the developer reading a log. May be reworded.
The field or parameter at fault, when there is one.
Example
{ "error": { "type": "validation_failure" }}authorization_failure with test_mode_only: a live key was used. Nothing is simulated
in live mode, and a live intent is not reachable even with a test key. Also
merchant_mismatch, as on every call.
object
object
A stable name for this refusal. Branch on this, never on message.
For the developer reading a log. May be reworded.
The field or parameter at fault, when there is one.
Example
{ "error": { "type": "authorization_failure", "code": "test_mode_only", "message": "Test helpers take a test key: no outcome is simulated in live mode.", "param": null, "request_id": "req_4f1c0b2e9a7d" }}not_found: resource_missing for an id this key cannot see, route_missing for a path that does not exist.
object
object
A stable name for this refusal. Branch on this, never on message.
For the developer reading a log. May be reworded.
The field or parameter at fault, when there is one.
Example
{ "error": { "type": "validation_failure" }}conflict with intent_state: the intent has no payment with the customer to give an
outcome to. It is not confirmed yet, it is requires_action at our checkout page with no
method chosen, or it is processing. Confirm it, or have the customer choose a method, and
ask again. idempotency_key_reused and idempotency_key_in_use as on every write.
object
object
A stable name for this refusal. Branch on this, never on message.
For the developer reading a log. May be reworded.
The field or parameter at fault, when there is one.
Example
{ "error": { "type": "validation_failure" }}dependency_failure with service_unavailable: something on our side, or a payment network behind it, did not answer, or failed part-way. Whether the call took effect is not known, so do not assume it did not. Retry the same request with the same Idempotency-Key, and never make a new key for the retry: if the first call took effect, you are answered with its result (the payment intent or refund it made, or the intent as it now is) and nothing is done a second time. A retry refused with conflict and intent_state means the intent is no longer in a state that allows the call: the first call may have taken effect, or the customer may have acted since. Read the intent. If the failure came while the answer was being stored, the key is held for five minutes and a retry in that time answers 409 with idempotency_key_in_use: retry shortly. See When a write answers 503.
object
object
A stable name for this refusal. Branch on this, never on message.
For the developer reading a log. May be reworded.
The field or parameter at fault, when there is one.
Example
{ "error": { "type": "validation_failure" }}