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

# Secure Fields

> Collect card data in PCI-compliant fields you place and style individually, then pay or enroll with start().

Secure fields are PCI-compliant card inputs (card number, expiry, CVV) that the SDK renders inside isolated iframes. Unlike the SDK's built-in form, you **create and place each field independently** in your own layout, with your own labels and styling. Raw card data never touches your page.

<Note>
  This is the **Web-only** granular, per-field API. If you'd rather let the SDK render its own pre-built card form instead of laying out each input yourself, use the [Payment Methods List](/docs/yuno-sdk/features/payment-methods-list): the SDK composes and renders the form into your `elementSelector`, with the same PCI guarantees and less layout work.
</Note>

Secure fields work for **both** payment (`checkoutSession`) and enrollment (`customerSession`). The fields inherit everything (session, country, language, callbacks) from the `Transaction` that creates them. There is no second configuration surface.

## Quick start

```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
const sdkPayments = await SdkPayments.initialize('your-public-api-key')

const transaction = sdkPayments.transaction({
    checkoutSession: 'session-from-backend',
    countryCode: 'CO',
    onStatus: (result) => showResult(result),
})

const secureFields = await transaction.secureFields()

secureFields.create({ name: 'pan', options: { label: 'Card number' } }).mount('#pan')
secureFields.create({ name: 'expiration', options: { label: 'Expiry' } }).mount('#expiration')
secureFields.create({ name: 'cvv', options: { label: 'CVV' } }).mount('#cvv')

payButton.addEventListener('click', () => {
    transaction.start({
        paymentMethod: { type: 'CARD', data: { card: { holderName: nameInput.value } } },
    })
})
```

`start()` tokenizes the mounted fields, creates the payment, handles 3DS, and reports the result through [`onStatus`](/docs/yuno-sdk/controllers/on-status). The fields supply the card number, expiry, and CVV. Those never leave the iframes. Non-PCI details (`holderName`, and optionally `installment`, `customer`, or `vaultOnSuccess`) travel on `start()` via `paymentMethod.data`.

<Note>
  Passing `number`, `expirationMonth`, `expirationYear`, or `securityCode` in `paymentMethod.data.card` while secure fields are mounted throws: the iframes own the PCI data.
</Note>

## Managed vs manual

By default the SDK creates the payment. Provide [`createPayment`](/docs/yuno-sdk/controllers/create-payment) on the config to POST the token from your own backend instead:

```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
const transaction = sdkPayments.transaction({
    checkoutSession: 'session-from-backend',
    countryCode: 'CO',
    createPayment: async (token) => {
        await myBackend.pay({
            oneTimeToken: token.token,
            checkoutSession: token.checkout_session,
        })
    },
    onStatus: (result) => showResult(result),
})
```

<Warning>
  Always read `token.checkout_session` from the token instead of reusing the session you created the transaction with. After a declined payment the SDK rotates the session automatically. The token always carries the one it actually tokenized against.
</Warning>

Either way the SDK handles 3DS and reports through `onStatus`.

## Declined payments and retry

When a card payment is **DECLINED** or fails with **ERROR** and the response is retryable, the SDK prepares the retry for you:

1. The checkout session is cloned server-side and rotated into the mounted fields automatically.
2. Field-level errors (invalid card number, invalid CVV, insufficient funds) are painted inside the matching iframes.
3. In manual mode (`createPayment` on the config), `onStatus` receives the `DECLINED` / `ERROR` result so your page can re-enable its Pay button. In managed mode the SDK does not call `onStatus` for that attempt. The shopper sees the field errors and can submit again.

A decline that is not retryable reaches `onStatus` in both modes and closes the checkout session.

The fields stay mounted with what the user typed. To retry, just call `start()` again: it is **repeatable** while secure fields are mounted, and the new attempt tokenizes against the rotated session. You do no session bookkeeping.

Once the payment is final (approved, or declined without a retry), the checkout session is closed and `start()` throws. To take another payment, create a new checkout session on your backend and call `transaction.updateCheckoutSession(newSession)`. The mounted fields pick it up and `start()` works again.

Re-enable your Pay button when the `start()` promise settles. That works in both modes:

```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
const transaction = sdkPayments.transaction({
    checkoutSession: 'session-from-backend',
    countryCode: 'CO',
    onStatus: (result) => showResult(result),
})

payButton.addEventListener('click', async () => {
    payButton.disabled = true
    try {
        await transaction.start({
            paymentMethod: { type: 'CARD', data: { card: { holderName: nameInput.value } } },
        })
    } finally {
        payButton.disabled = false
    }
})
```

<Note>
  Disable your Pay button while a `start()` is in flight. Calling `start()` again before the previous attempt finishes throws: two tokenizations would mean two payments.
</Note>

## Enrollment

With a `customerSession`, secure fields vault a card instead of charging it. The flow is identical: `create`, `mount`, `start()`.

```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
const transaction = sdkPayments.transaction({
    customerSession: 'session-from-backend',
    countryCode: 'CO',
    onStatus: (result) => showResult(result),
})

const secureFields = await transaction.secureFields()

secureFields.create({ name: 'pan', options: { label: 'Card number' } }).mount('#pan')
secureFields.create({ name: 'expiration', options: { label: 'Expiry' } }).mount('#expiration')
secureFields.create({ name: 'cvv', options: { label: 'CVV' } }).mount('#cvv')

saveButton.addEventListener('click', () => {
    transaction.start({
        paymentMethod: { type: 'CARD', data: { card: { holderName: nameInput.value } } },
    })
})
```

## Installments

Enable installments when creating the fields:

```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
const secureFields = await transaction.secureFields({ installmentEnable: true })
```

Pass the option on the first `secureFields()` call. The transaction keeps one secure-fields instance. Later calls return it, and a call with a different `installmentEnable` throws. To change the option, call `transaction.unmount()` and create the fields again.

Available plans arrive on the `pan` field's `onChange` as the user types: `data.installments`, `data.fullInstallmentsInfo`, and `data.isInstallmentLoading`. Render your own selector from that data and pass the chosen plan on `start()`:

```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
transaction.start({
    paymentMethod: {
        type: 'CARD',
        data: {
            card: { holderName: nameInput.value },
            installment: { id: selected.id, value: selected.value },
        },
    },
})
```

## Fields

Create each field by name, then mount it into any element in your layout.

| Name | What |
| - | - |
| `pan` | Card number. Emits brand, IIN, and installment data through `onChange`. |
| `expiration` | Expiration date. |
| `cvv` | Security code. |
| `cardLoadPreview` | Optional animated card preview. |
| `cardPin` | PIN entry, for markets that require it. |

## Field options

Pass `options` to `create` to label, style, and observe a field.

| Option | Type | Purpose |
| - | - | - |
| `label` | string | Accessible label. |
| `placeholder` | string | Placeholder text. |
| `styles` | string | CSS injected inside the field's iframe, where your page CSS cannot reach. |
| `designType` | string | Label layout: `float-label`, `float-label-static`, or `label-placeholder`. |
| `showError` | boolean | Render validation errors under the field. |
| `errorMessage` | string | Message shown when the value is invalid. |
| `requiredErrorMessage` | string | Message shown when the field is required and empty. |
| `validationType` | string | When to validate (e.g. `on_blur_full`). |
| `inputMode` | string | Virtual keyboard hint for the input, as in the `inputmode` attribute. |
| `onRenderedSecureField` | function | Fires once the field's iframe has rendered. |
| `onChange` | function | `({ error, errorMessage, data, isDirty, isFieldComplete })`. For `pan`, `data` carries brand, IIN, and installments. |
| `onBlur` / `onFocus` | function | Focus lifecycle (e.g. to flip a card preview). |

## Field methods

Each field returned by `create` (or fetched later with `getElement({ name })`) exposes:

| Method | Purpose |
| - | - |
| `mount('#sel')` | Mount the field's iframe into an element. |
| `focus()` | Focus the field. |
| `validate()` | Validate on demand; resolves to a boolean. |
| `clearValue()` | Clear the input. |
| `setError(message)` / `clearError()` | Show or clear a custom error under the field. |
| `hide()` / `show()` | Toggle visibility without unmounting. |
| `updateProps(props)` | Update label, placeholder, or styles in place. |
| `setCardType(type)` | Force credit or debit on dual-brand cards. |
| `setCardHolderName(name)` | Set the holder name on the card preview. |
| `toggleFrontCardVisibility(visible)` | Flip the card preview. |
| `unmountSync()` | Remove the field's iframe. |

## Instance members

| Member | Purpose |
| - | - |
| `create({ name, options })` | Create a field. |
| `getElement({ name })` | Fetch a previously created field. |
| `unmountSync()` | Remove all field iframes. `transaction.unmount()` calls this for you. |

<Note>
  To point the transaction at a new checkout session, call `transaction.updateCheckoutSession()`: the mounted fields pick it up automatically.
</Note>

## Notes

* **One completion path.** Tokenization, payment creation, 3DS, and status all live inside `start()`: there is no `generateToken` or `continuePayment()` to sequence yourself. To create the payment from your own backend, use manual mode ([`createPayment`](/docs/yuno-sdk/controllers/create-payment)). The SDK still owns 3DS and the result.
* **PCI scope is unchanged.** Raw card data stays inside the SDK's iframes; your code never reads it.
* **One card surface per page.** Create secure fields on one transaction at a time. Wallet buttons and the payment methods list from [`getPaymentMethodsViews`](/docs/yuno-sdk/features/payment-methods-list) can share the page and the transaction: a `CARD` `start()` routes to your fields, wallet buttons drive themselves.

## Related

* [Migration: Secure Fields](/docs/yuno-sdk/migration/web/secure-fields). Coming from v1 `yuno.secureFields`.
* [Payment Methods List](/docs/yuno-sdk/features/payment-methods-list). Prebuilt widgets: wallets and the methods list.
* [`createPayment`](/docs/yuno-sdk/controllers/create-payment). Take over payment creation (manual mode).
* [`onStatus`](/docs/yuno-sdk/controllers/on-status). Receive the final result.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.