> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moduluslabs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 3D Secure

> In-page 3DS challenge rendering, lifecycle status, and window sizing

## Overview

3D Secure is handled automatically inside `confirmPayment`, with no separate call. If the customer's bank requires a challenge, the SDK shows it in-page and resumes when they finish. Frictionless authentications need no UI at all.

When a challenge is required, the customer sees their bank's authentication screen (OTP, biometric, etc.) inside a secure iframe. You choose where it appears.

| Option                 | How                                                                                                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Default modal**      | Do nothing extra. The SDK pops a centered modal overlay on top of your page. Fastest to integrate.                                                            |
| **Your own container** | Pass `threeDS: { container: '#threeds-container' }`, a CSS selector or DOM node, and the challenge renders inside that element, fitting your checkout layout. |

## Rendering in your own container

Put a container in your checkout (only needed if you do not want the default modal), then pass it to `threeDS.container` and drive your UI from `onStatusChange`. That is why the callback matters: it is how you show a spinner during authentication and hide it when the challenge appears.

```html theme={null}
<div id="threeds-container"></div> <!-- the bank's challenge renders in here -->
```

```javascript theme={null}
const result = await modulus.confirmPayment({
  elements,
  billingDetails,
  threeDS: { container: '#threeds-container' }, // omit this to use the default modal
  onStatusChange: (status) => {
    if (status === 'submitting')      spinner.show('Processing...');
    if (status === 'requires_action') spinner.hide();  // let the customer do the challenge
    if (status === 'authenticating')  spinner.show('Authenticating...');
  },
});
```

## Lifecycle status

`onStatusChange` reports the payment's progress:

| Status                                                         | Meaning                                                                                                                |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `submitting`                                                   | `confirmPayment` called. Billing validated, card and intent sent to the server.                                        |
| `requires_action`                                              | The bank requires a 3D Secure challenge, and the SDK is now showing it (modal or your container). Challenge path only. |
| `authenticating`                                               | Challenge finished (or frictionless). Authorizing the payment with the issuer.                                         |
| `succeeded` · `declined` · `failed` · `processing` · `expired` | Terminal outcomes, mirrored in the resolved `result`.                                                                  |

There are two paths through the lifecycle:

* **Frictionless:** `submitting` -> `authenticating` -> terminal. No `requires_action`, the customer sees nothing.
* **Challenge:** `submitting` -> `requires_action` -> `authenticating` -> terminal. The customer completes the OTP in between.

## Challenge window size

The EMVCo challenge window dimensions, passed to `confirmPayment` as `acsWindowSize`. This only applies to the challenge path and is ignored when the authentication is frictionless.

| `acsWindowSize` | Dimensions (W x H)  |
| --------------- | ------------------- |
| `"01"`          | 250 x 400           |
| `"02"`          | 390 x 400           |
| `"03"`          | 500 x 600           |
| `"04"`          | 600 x 400           |
| `"05"`          | Full page (default) |

<Tip>
  Test both paths with the sandbox [test cards](/docs/testing): a challenge card presents the OTP step (enter `1234`), and a frictionless card authenticates with no customer interaction.
</Tip>
