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

# Entrada segura de tarjeta

> Recopila una tarjeta física y agrégala como método de pago en la cuenta de Fluz de un usuario, sin que el PAN ni el CVV toquen tus servidores ni el JavaScript de tu página.

<Note>
  Esta página asume que ya leíste la [Descripción general de Secure
  Elements](/build-a-platform/secure-elements-overview): ahí se cubre la carga
  del SDK y el flujo compartido de client-token, ambos aplican aquí.
</Note>

## Velo en vivo

<iframe
  src="https://demo.secure.fluz.app/collect/"
  title="Fluz Secure Elements — demo de Entrada segura de tarjeta"
  loading="lazy"
  style={{
width: "100%",
height: "720px",
border: "1px solid #e5e5e5",
borderRadius: "8px",
}}
/>

La demo genera su propio token y monta los campos automáticamente. Ingresa cualquier número de tarjeta que pase una verificación de Luhn, completa el nombre del titular y envía. Usa **Remint token & remount** si el formulario deja de responder. [Ábrela en su propia pestaña →](https://demo.secure.fluz.app/collect/)

## Genera un token de tokenización

Llama a [`POST /v1/client-token`](/build-a-platform/secure-elements-overview#mint-a-client-token) con `"purpose": "tokenization"`:

```json theme={null}
{
  "purpose": "tokenization"
}
```

A diferencia de un token de revelado, este no necesita un `virtualCardId`. Sí requiere un alcance diferente en tu token de acceso — `MANAGE_PAYMENT`, no `CREATE_VIRTUALCARD` — y tiene una vida útil más larga (30 minutos por defecto) ya que un usuario tarda más llenando un formulario de tarjeta que haciendo un clic de revelado.

## Renderiza los campos

```js theme={null}
const inputs = renderFieldsForTokenization({
  clientToken,
  loadToken,
  frameHostOrigin: "https://staging.secure.fluz.app",
});
```

`renderFieldsForTokenization` no tiene la opción `fields`: PAN, vencimiento y CVV siempre se montan juntos como un solo iframe combinado, porque la validación del CVV es sensible a la marca (el CVV de Amex tiene 4 dígitos; el de las demás marcas, 3), lo que solo funciona si el campo conoce el número de tarjeta escrito en el campo contiguo dentro del frame. No puedes montarlos de forma independiente como se puede con los campos de `createCardViewer`.

| Opción                      | Requerido | Detalles                                                                                                                  |
| :-------------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------------ |
| `clientToken` / `loadToken` | Sí        | Del mint de arriba                                                                                                        |
| `frameHostOrigin`           | No        | Mismas reglas de origen en la allowlist que [Card Reveal](/build-a-platform/card-reveal): por omisión apunta a producción |
| `style`                     | No        | Mismo `{ color, fontSize, fontFamily, fontWeight }` que `createCardViewer`; ver la salvedad abajo                         |
| `mountTimeoutMs`            | No        | 10 segundos por defecto                                                                                                   |
| `submitTimeoutMs`           | No        | 15 segundos por defecto — ver [Enviar](#submit)                                                                           |
| `excludedCardBrands`        | No        | p. ej. `["amex"]` — ver [Rastrea el estado de los campos](#track-field-state)                                             |

<Warning>
  Una Google Font pasada en `style.fontFamily` se renderiza en los campos de
  [Card Reveal](/build-a-platform/card-reveal) pero aquí se omite en silencio:
  estos campos se renderizan dentro de un iframe de un proveedor con bóveda, sin
  forma de cargar CSS externo. Solo la allowlist de fuentes del sistema
  (`system-ui`, `Arial`, `Georgia`, `monospace`, etc.) realmente aplica una
  fuente en esta capacidad.
</Warning>

## Móntalo

```js theme={null}
await inputs.mount(document.getElementById("card-fields"));
```

Misma forma que en [Card Reveal](/build-a-platform/card-reveal#mount-it): rechaza con `FluzElementsError` por `INVALID_STYLE`, `MOUNT_TIMEOUT` o `MOUNT_FAILED` (el frame no cargó, o esta instancia ya está montada). `frameHostOrigin` se valida de forma sincrónica cuando llamas a `renderFieldsForTokenization`, igual que en `createCardViewer`: un origen no reconocido lanza `INVALID_FRAME_HOST_ORIGIN` antes de llegar a `mount()`.

## Rastrea el estado de los campos

```js theme={null}
inputs.onChange((field, state) => {
  // field: "pan" | "expiry" | "cvv"
  // state: { isEmpty, isValid, isDirty, brand? }
});
```

Se activa en cada pulsación dentro del frame. `brand` solo aparece en el estado de `pan`, detectada a partir de los dígitos ingresados hasta el momento: `amex`, `visa`, `mastercard`, `discover`, `diners` o `jcb`. Usa `isValid` para controlar tu propio botón de envío y para los mensajes de validación inline: ninguno de estos campos expone el valor subyacente a tu página.

`excludedCardBrands` (p. ej. `["amex"]`) no bloquea la escritura: fuerza que `isValid` de `pan` sea `false` cuando se detecta una marca coincidente, por lo que el usuario aún puede ingresar el número pero `submit()` no tendrá éxito hasta que use una tarjeta distinta.

## Enviar

Recopila el nombre del titular y la dirección de facturación como inputs normales en tu propia página: el SDK no los renderiza dentro de un frame alojado por Fluz porque no son datos de tarjeta. Si manejarlos tú afecta tu propio alcance PCI DSS depende de tu entorno más amplio de datos de titulares de tarjeta; confírmalo con tu QSA.

```js theme={null}
await inputs.submit({
  cardholderName: "Jane Doe",
  billingAddress: {
    line1: "123 Main St",
    line2: "Apt 4", // optional
    city: "Austin",
    state: "TX", // optional
    zipCode: "78701",
    country: "US",
  },
  isBackupPayment: false, // optional
});
```

`cardholderName` se divide en nombre y apellido solo por el primer espacio: `"Mary Ann Smith"` se convierte en nombre `"Mary"`, apellido `"Ann Smith"`; un nombre de una sola palabra se usa como ambos. Para reutilizar una dirección ya existente en la cuenta en lugar de recopilar una nueva, pasa `billingAddress: { userAddressId: "<uuid>" }`.

<Note>
  `submit()` casi nunca rechaza, y nunca por una denegación. Solo lanza
  sincrónicamente por `MOUNT_FAILED` (aún no montado) o `SUBMIT_FAILED` ("ya
  hay una llamada a submit() en curso": ignora una segunda llamada mientras hay
  una en progreso). Cualquier otro resultado — éxito, denegación, fallo de
  validación, timeout — se resuelve normalmente y llega a través de los
  callbacks de abajo.
</Note>

## Maneja los resultados

```js theme={null}
inputs.onSuccess((result) => {
  // result.bankCardId, brand, last4, expirationMonth, expirationYear,
  // cardholderName, billingAddress, createdAt
});

inputs.onDeclined((decline) => {
  // decline.code, decline.message
});

inputs.onError((error) => {
  // error.code, error.message
});
```

`onSuccess` se activa cuando la tarjeta se agrega como fuente de fondos. `onDeclined` se activa para una tarjeta que el procesador rechazó: sigue siendo un resultado normal y esperado, no un error:

| Código de denegación    | Significado                                                     |
| :---------------------- | :-------------------------------------------------------------- |
| `CARD_DECLINED`         | Denegación genérica                                             |
| `INSUFFICIENT_FUNDS`    | Denegada por fondos insuficientes                               |
| `CARD_EXPIRED`          | La tarjeta ha expirado                                          |
| `CARD_INVALID`          | No se pudo verificar la tarjeta                                 |
| `CVV_MISMATCH`          | El código de seguridad no coincide                              |
| `AVS_MISMATCH`          | La dirección de facturación no coincide                         |
| `CONTACT_BANK`          | Denegada: contactar al emisor de la tarjeta                     |
| `DUPLICATE_CARD`        | Esta tarjeta ya está en la cuenta                               |
| `PREPAID_REJECTED`      | No se aceptan tarjetas prepagas                                 |
| `FRAUD_FILTER`          | Bloqueada por un filtro antifraude                              |
| `BIN_BLOCKED`           | El BIN de la tarjeta está bloqueado                             |
| `EXPANDED_BIN_REQUIRED` | La tarjeta requiere datos de BIN ampliado que Fluz aún no tiene |
| `KYB_GATE`              | La cuenta aún no es elegible para agregar una tarjeta           |
| `TRUST_STATUS_FAILED`   | La cuenta no es elegible para agregar una tarjeta               |
| `DEVICE_BLOCKED`        | Este dispositivo no es elegible para agregar una tarjeta        |
| `MAX_CARDS_REACHED`     | La cuenta alcanzó su límite de tarjetas                         |
| `DECLINED_OTHER`        | Cajón de sastre para un motivo de denegación no mapeado         |

`onError` es para todo lo que no es una denegación normal:

| Código de error             | Dónde aparece                                    | Significado                                                                                                                                                                                                                                                                                                                                                      |
| :-------------------------- | :----------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_FRAME_HOST_ORIGIN` | Lanzado por `renderFieldsForTokenization`        | `frameHostOrigin` no es un host de frames de Fluz reconocido                                                                                                                                                                                                                                                                                                     |
| `INVALID_STYLE`             | Rechazado por `mount()`                          | Un valor de `style` falló la validación                                                                                                                                                                                                                                                                                                                          |
| `MOUNT_TIMEOUT`             | Rechazado por `mount()`                          | El frame no completó su handshake dentro de `mountTimeoutMs`                                                                                                                                                                                                                                                                                                     |
| `MOUNT_FAILED`              | Rechazado por `mount()` / lanzado por `submit()` | El frame no cargó, ya estaba montado o se llamó a `submit()` antes de que `mount()` se resolviera                                                                                                                                                                                                                                                                |
| `SUBMIT_FAILED`             | Lanzado por `submit()`, o entregado a `onError`  | Un segundo `submit()` mientras ya hay uno en curso; o, vía `onError`, los valores de los campos fallaron la validación (`VALIDATION_FAILED`), el entorno no está aprovisionado para collect (`COLLECT_UNAVAILABLE`), el backend de Fluz rechazó la solicitud (`FUNDING_SOURCE_UNAUTHORIZED`) o un error inesperado del procesador (`FUNDING_SOURCE_UNAVAILABLE`) |
| `SUBMIT_TIMEOUT`            | Entregado a `onError`                            | No llegó un resultado de envío dentro de `submitTimeoutMs` (15 segundos por defecto)                                                                                                                                                                                                                                                                             |
| `FIELD_ERROR`               | Entregado a `onError`                            | El campo subyacente de la bóveda de tarjetas reportó un error interno                                                                                                                                                                                                                                                                                            |
| `RATE_LIMITED`              | Entregado a `onError`                            | Demasiados intentos de envío para este grant                                                                                                                                                                                                                                                                                                                     |

## Limpieza

```js theme={null}
inputs.destroy();
```

Quita el frame y desuscribe todos los listeners. Llama esto al desmontar, o antes de generar un token nuevo para reintentar.

## Ejemplo completo

```html theme={null}
<div id="card-fields"></div>
<input id="cardholder-name" placeholder="Name on card" />
<button id="submit-button">Add card</button>

<script src="https://secure-cdn.fluz.app/secure-elements/v0.3.0/index.global.js"></script>
<script>
  (async () => {
    const { renderFieldsForTokenization } = FluzSecureElements;

    const res = await fetch("/mint-tokenization-token", { method: "POST" });
    const { clientToken, loadToken } = await res.json();

    const inputs = renderFieldsForTokenization({
      clientToken,
      loadToken,
      frameHostOrigin: "https://staging.secure.fluz.app",
      excludedCardBrands: ["amex"],
      style: { fontFamily: "system-ui", fontSize: "16px", color: "#1a1a1a" },
    });

    inputs.onChange((field, state) => console.log(field, state));
    inputs.onDeclined((decline) => alert(decline.message));
    inputs.onError((error) => console.error(error.code, error.message));
    inputs.onSuccess((result) => console.log("card added", result.bankCardId));

    await inputs.mount(document.getElementById("card-fields"));

    const submitButton = document.getElementById("submit-button");
    submitButton.addEventListener("click", async () => {
      submitButton.disabled = true;
      try {
        await inputs.submit({
          cardholderName: document.getElementById("cardholder-name").value,
          billingAddress: { userAddressId: "<existing-address-uuid>" },
        });
      } catch (error) {
        console.error(error.code, error.message);
      } finally {
        submitButton.disabled = false;
      }
    });
  })();
</script>
```

`/mint-tokenization-token` es tu propia ruta de backend: la que llama a `POST /v1/client-token` con `"purpose": "tokenization"` y tu token de acceso OAuth de Fluz.

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Descripción general de Secure Elements" icon="book-open" href="/build-a-platform/secure-elements-overview">
    Generación de tokens, carga del SDK y CSP.
  </Card>

  {" "}

  <Card title="Card Reveal" icon="eye" href="/build-a-platform/card-reveal">
    La otra capacidad de Secure Elements: mostrar a un usuario los datos de su propia tarjeta.
  </Card>

  {" "}

  <Card title="Demo en vivo" icon="play" href="https://demo.secure.fluz.app/collect/">
    Prueba el formulario para agregar tarjeta ejecutándose contra staging.
  </Card>

  <Card title="Integraciones de ejemplo" icon="github" href="https://github.com/fluz-app/secure-elements-examples">
    Ejemplos ejecutables en HTML plano y React de entrada segura de tarjeta con un servidor que genera tokens.
  </Card>
</CardGroup>
