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

# 3D Secure Element

> Run 3DS authentication challenges and handle success, failure, and retry flows.

3D Secure Element renders issuer authentication challenges for transactions that require cardholder verification.

You create the element once, mount it with a `tds_session_id`, and react to success, failure, or technical errors through callbacks.

## Create a 3DS Element

Create the element once with callbacks, then call `mount(sessionId)` whenever you need to authenticate.

```typescript theme={null}
const threeDSecure = await rinne.elements.threeDSecure({
  target: '#three-ds-container',
  size: { width: '100%', height: '450px' },
  colorScheme: 'dark',
  locale: 'pt',
  onReady: () => console.log('Challenge is ready'),
  onSuccess: () => console.log('Authentication succeeded'),
  onFailure: (error) => console.log('Authentication failed', error),
  onError: (error) => console.log('3DS technical error', error)
})

await threeDSecure.mount('tds_session_123')
```

## Options

```typescript theme={null}
interface ThreeDSecureElementOptions {
  target?: string | HTMLElement   // default: modal overlay
  size?: { width: string; height: string }
  colorScheme?: 'light' | 'dark'  // iframe root color-scheme; match your page theme
  theme?: ThemeOption
  locale?: 'pt' | 'en' | 'es'     // default: 'pt'
  onReady?: () => void
  onSuccess?: () => void
  onFailure?: (error?: { code?: string; message?: string }) => void
  onError?: (error: { code: string; message: string }) => void
}
```

## Callback Semantics

| Callback    | Meaning                                     | Typical action                           |
| ----------- | ------------------------------------------- | ---------------------------------------- |
| `onReady`   | Challenge UI is rendered and ready          | Hide loading placeholders                |
| `onSuccess` | Authentication completed successfully       | Call backend to confirm/finalize payment |
| `onFailure` | Authentication was attempted but failed     | Show retry CTA and create a new session  |
| `onError`   | Technical error (component/network/runtime) | Show fallback path and log diagnostics   |

```typescript theme={null}
const threeDSecure = await rinne.elements.threeDSecure({
  onReady: () => console.log('Challenge is ready'),
  onSuccess: () => confirmOnBackend(transactionId),
  onFailure: (error) => showError(error?.message ?? 'Authentication failed'),
  onError: (error) => showError(`Technical error: ${error.message}`)
})
```

## colorScheme

Use `colorScheme` to align the 3DS iframe root with your page theme.

```typescript theme={null}
const colorScheme =
  document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'

const threeDSecure = await rinne.elements.threeDSecure({
  target: '#three-ds-container',
  colorScheme,
  onSuccess: () => confirmPayment()
})
```

## Theme

`theme` supports preset strings (`clean`, `minimal`, `material`) and function themes.

```typescript theme={null}
const threeDSecure = await rinne.elements.threeDSecure({
  theme: 'material'
})
```

<Info>
  If you pass `theme` as an object, the 3DS component currently uses the `preset` value.
</Info>

### Full Theme Example

```typescript theme={null}
const isDark = document.documentElement.classList.contains('dark')

const threeDSTheme = (utils) => ({
  styles: {
    ':root': {
      colorScheme: isDark ? 'dark' : 'light',
      backgroundColor: isDark ? '#020617' : '#ffffff'
    },
    body: {
      margin: 0,
      fontFamily: 'Inter, ui-sans-serif, system-ui, sans-serif'
    },
    ...utils.media('(max-width: 640px)', {
      body: {
        fontSize: 14
      }
    })
  }
})

const threeDSecure = await rinne.elements.threeDSecure({
  target: '#three-ds-container',
  size: { width: '100%', height: '450px' },
  colorScheme: isDark ? 'dark' : 'light',
  theme: threeDSTheme
})
```

<Info>
  3DS challenge copy and issuer-controlled challenge UI cannot be fully restyled. Use `theme`, `size`, and `colorScheme` to style the container experience around it.
</Info>

## Mount Behavior

* `mount(sessionId)` requires a non-empty session ID.
* If you mount again with a new session ID, the SDK cleans up the previous instance automatically.
* Callbacks from options are automatically wired for each mount.

<Tip>
  3DS sessions expire 1 hour after creation. When a session expires, request a new session ID from your backend and call `mount(newSessionId)`. The SDK automatically attempts to unmount the previous instance, but you should unmount manually for safety.
</Tip>

## Backend Contract

Use the frontend 3DS element as an authentication step between your backend session creation and transaction confirmation.

<Steps>
  <Step title="Create a 3DS session on your backend">
    Return both session IDs from your backend:

    * `tds_session_id` for `threeDSecure.mount(...)` in the frontend
    * `id` for `three_d_secure_session_id` in backend transaction create/authenticate calls

    Use one of the supported create-session endpoints based on your context:

    * `POST /v1/3ds-sessions` (self)
    * `POST /v1/merchants/{merchantId}/3ds-sessions` (organization on behalf of merchant)
  </Step>

  <Step title="Mount the 3DS element">
    In practice, sessions come back as `ACTION_REQUIRED` or `FAILED` at creation — even frictionless authentication completes through the SDK element. The API contract allows `AUTHENTICATED` as a creation-time response for forward compatibility, so always branch on all three statuses.

    * **`FAILED`**: the card was rejected before the flow could start — surface the error and let the user retry.
    * **`AUTHENTICATED`**: authentication already completed — skip mounting and proceed to payment confirmation.
    * **`ACTION_REQUIRED`**: mount with `tds_session_id`. The SDK runs the full 3DS flow internally.

    When mounted, the issuer decides the path:

    * **Frictionless**: the issuer authenticates the transaction silently in the background. No challenge UI is shown. `onSuccess` fires automatically.
    * **Challenge**: the issuer presents a verification step inside the element. `onSuccess` fires after the user completes it.

    Your code handles both identically.

    ```typescript theme={null}
    if (session.auth_status === 'FAILED') {
      showError(session.failure_reason ?? 'Authentication failed')
      return
    }

    if (session.auth_status === 'AUTHENTICATED') {
      confirmPayment()
      return
    }

    await threeDSecure.mount(session.tds_session_id)
    // onSuccess/onFailure/onError callbacks drive the next step
    ```
  </Step>

  <Step title="Handle success/failure callbacks">
    On `onSuccess`, create/authenticate the transaction on your backend using the internal session `id`. On `onFailure`, create a new session before retrying.
  </Step>
</Steps>

## Modal vs Embedded

<Tabs>
  <Tab title="Modal (Default)">
    ```typescript theme={null}
    const threeDSecure = await rinne.elements.threeDSecure({
      onSuccess: () => confirmPayment()
    })

    await threeDSecure.mount(sessionId)
    ```
  </Tab>

  <Tab title="Embedded">
    ```typescript theme={null}
    const threeDSecure = await rinne.elements.threeDSecure({
      target: '#three-ds-container',
      size: { width: '100%', height: '450px' },
      onSuccess: () => confirmPayment()
    })

    await threeDSecure.mount(sessionId)
    ```
  </Tab>
</Tabs>

## Mount Errors

```typescript theme={null}
try {
  const threeDS = await rinne.elements.threeDSecure()
  await threeDS.mount('') // invalid session
} catch (error) {
  console.error('3DS mount failed', error)
}
```

## Unmount

```typescript theme={null}
const mounted = await threeDSecure.mount(sessionId)
mounted.unmount()
```

## Common Failure Handling

```typescript theme={null}
const threeDSecure = await rinne.elements.threeDSecure({
  onFailure: (error) => {
    showError(error?.message ?? 'Authentication failed')
  },
  onError: (error) => {
    showError(`Technical error: ${error.message}`)
  }
})
```

<Info>
  `locale` applies to SDK/component messages. Issuer-hosted challenge copy is controlled by the card issuer.
</Info>
