# Stripe 3D Secure end to end

3D Secure (3DS) adds a bank authentication step to card payments. For an agent-driven integration, the failure mode is treating the payment as done too early. The payment completes only when the webhook says so.

## The flow

1. Create and confirm the PaymentIntent as usual. Do not pass `payment_method_types`; on current API versions it is removed (separate skill).
2. If the PaymentIntent status comes back as `requires_action` and `next_action.type` is `redirect_to_url`, the customer must authenticate. Redirect them to `next_action.redirect_to_url.url`.
3. Provide a `return_url` when confirming. After authentication, Stripe redirects the customer back there.
4. On return, retrieve the PaymentIntent and check its status. `succeeded` means done. `requires_payment_method` means authentication failed or the card was declined; collect a new payment method.
5. Independently, listen for the `payment_intent.succeeded` webhook and fulfill the order on the webhook, not on the return URL. Customers close tabs; webhooks do not.

## Rules

- The return URL is a UX convenience, not a completion signal. Fulfillment logic keys off the webhook.
- Handle `requires_action` on every confirmation path, including saved-card and off-session payments. Assuming "it worked last time" is how 3DS failures ship.
- In test mode, use Stripe's 3DS test cards to force the `requires_action` path. If your test suite never hits `requires_action`, it does not cover 3DS at all.
- For regulatory contexts (for example SCA in Europe), 3DS is not optional decoration; design the flow assuming authentication will be required.