Skip to main content

Stripe's requires_action
fails silently. Test yours
before your users find it.

When a bank requires 3DS, Stripe sets the PaymentIntent to requires_action. If your code doesn't read next_action and redirect the customer, the payment stalls. No error. No log entry. Just a broken checkout.

6

PI statuses

5

webhook events

0

accounts needed

Stripe moved from the Charges API to PaymentIntents in 2019. The main reason was 3D Secure. European banks started requiring it under SCA (Strong Customer Authentication), and the old Charges API had no way to handle it. Every PaymentIntent goes through up to six statuses. Your code needs to handle all of them — not just succeeded.

The most common mistake is ignoring requires_action. When a bank needs 3DS, Stripe sets the PaymentIntent to that status and puts the bank's authentication URL inside next_action.redirect_to_url.url. You have to redirect the customer there. If you don't, the payment stalls. No error appears. No log entry. The customer sees a broken checkout and you have no idea why.

This mock lets you trigger every PaymentIntent status on demand. Pick the outcome on the hosted checkout page — requires_action, succeeded, declined, canceled — and your webhook endpoint gets a real event. No Stripe account, no test card numbers needed.

Why requires_action breaks production for European customers

When you build a Stripe integration in the US and test with US cards, everything works. You create a PaymentIntent, confirm it, get succeeded. Done.

Then a customer in Germany pays. The bank requires 3D Secure. Stripe moves the PaymentIntent to requires_action and sets next_action.redirect_to_url.url to the bank's 3DS page. Your code never reads it. The payment stalls.

You see no error in your logs. The customer sees the checkout spinning or timing out. The PaymentIntent stays at requires_action until it expires. This happens silently in production for every European card that requires SCA.

After confirming a PaymentIntent, always check the status. If it's requires_action, redirect the customer immediately:

// After stripe.confirmCardPayment()

if (pi.status === 'requires_action') {

window.location.href =

pi.next_action.redirect_to_url.url

}

// Then wait for payment_intent.succeeded webhook

// Never fulfill on the return_url callback alone

This mock triggers requires_action on demand so you can validate this redirect path before a real German customer hits it.

The 6 PaymentIntent statuses

Every PaymentIntent lives in exactly one of these states. Your integration needs to handle all of them — including the ones you never see when testing with US cards.

requires_payment_method

No card attached yet

requires_confirmation

Card attached, not charged

requires_action

3DS challenge needed

processing

Bank is processing it

succeeded

Funds captured — done

canceled

Dead — create a new PI

Important: A PaymentIntent can only move forward through this state machine, not backward. Once it reaches succeeded or canceled, it is terminal — you create a new PaymentIntent for any retry. requires_action is the only status where the customer must do something on an external page before the payment can proceed.

Which events your server needs to handle

Stripe sends these events to your webhook endpoint. The first three are required for a correct integration. The last two are optional but useful for logging and edge case handling.

Which events your server needs to handle
EventWhen it firesWhat to doRequired
payment_intent.succeededFunds capturedFulfill the order. This is the only safe fulfillment trigger. required
payment_intent.payment_failedCard declinedAsk the customer to retry with a different card. required
payment_intent.requires_action3DS challenge triggeredRedirect to next_action.redirect_to_url.url. required
payment_intent.processingSubmitted to bankShow a loading state. Wait for succeeded or failed.
payment_intent.canceledPI cancelled via APILog and create a new PI if customer retries.

API request and response examples

curl -X POST https://mockgateway.dev/api/base/stripe/v1/payment_intents \
  -H "Authorization: Bearer YOUR_MOCK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4999,
    "currency": "usd",
    "description": "Acme Pro — monthly",
    "metadata": { "order_id": "ORD-001" },
    "automatic_payment_methods": { "enabled": true }
  }'

New to testing webhooks locally? Read how webhook delivery and retries work →

Everything this mock covers

Trigger any of these scenarios on demand from the hosted checkout page. No test card numbers needed — just pick the outcome you want to test.

PaymentIntent statuses

  • succeeded — funds captured, fulfill the order
  • requires_action — 3DS triggered, redirect required
  • payment_failed — card declined by bank
  • canceled — PI cancelled, create a new one
  • processing — submitted, waiting for result

Webhook events delivered

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • payment_intent.requires_action
  • payment_intent.processing
  • payment_intent.canceled

3DS / SCA flows

  • Full redirect to 3DS challenge page
  • next_action.redirect_to_url correctly populated
  • Return URL redirect after 3DS complete
  • 3DS authentication failure scenario

Decline reasons

  • insufficient_funds — customer needs another card
  • card_declined — generic bank decline
  • expired_card — card has expired
  • incorrect_cvc — wrong security code

Request parameters

Sent to POST /api/base/stripe/v1/payment_intents

Request parameters
ParameterTypeRequiredDescription
amountintegerrequired

Amount in smallest currency unit (e.g., 1000 = $10.00 for USD)

e.g. 1000
currencystringrequired

Three-letter ISO currency code in lowercase

e.g. usd
descriptionstringoptional

An arbitrary string for your reference

e.g. Payment for Order #12345
customerstringoptional

Customer identifier for your reference

e.g. cus_12345
metadataobjectoptional

Key-value pairs for storing additional information

e.g. {"order_id": "12345"}
receipt_emailstringoptional

Email address for the receipt

e.g. customer@example.com

Response fields

Response fields
FieldTypeDescription
idstringUnique identifier for the PaymentIntent
statusenumStatus of the PaymentIntent
client_secretstringSecret for client-side confirmation
createdintegerUnix timestamp of creation time
latest_chargestringID of the latest charge (generated on success)

Questions about Stripe PaymentIntents

Other gateway templates

Looking for a different provider?