<!-- Source: https://docs.infraio.xyz/es/sdks/javascript -->
<!-- Last updated: 2026-10-04 -->

# SDK de JavaScript / Navegador

`@lartech/infraio-checkout-js` es el único SDK que publicamos hoy.
Corre en el navegador y abre nuestro checkout alojado. Todavía no hay
un SDK de backend. Llama directamente a la API; el
[Inicio rápido](https://docs.infraio.xyz/es/get-started/quickstart) incluye un helper de firma
HMAC.
HMAC.

- Versión actual: `0.1.1-beta.17` (pre-1.0; espera rupturas menores)
- Formatos: **ESM** (`index.js`), **CJS** (`index.cjs`), **IIFE** (`index.global.js`)
- Tipos incluidos (`index.d.ts`)
- Cero peers en runtime: sin React, jQuery u otras dependencias

> **Note:**
>
> **No hay entry point del lado servidor.** Los helpers de
> verificación de firma para webhooks no se incluyen: impleméntalos
> tú mismo con `crypto` (la [página de verificación de firma](https://docs.infraio.xyz/es/webhooks/signature-verification)
> tiene código copia-pega en 4 lenguajes).

## Instalación

**npm**

```bash
npm install @lartech/infraio-checkout-js
```

**pnpm**

```bash
pnpm add @lartech/infraio-checkout-js
```

**yarn**

```bash
yarn add @lartech/infraio-checkout-js
```

**bun**

```bash
bun add @lartech/infraio-checkout-js
```

**CDN**

```html
<script src="https://unpkg.com/@lartech/infraio-checkout-js/dist/index.global.js"></script>
<script>
  const sdk = await InfraIo.loadInfraIo("pk_live_yourkeyhere");
  sdk.checkout({ sessionId, checkoutUrl });
</script>
```

## `loadInfraIo(publicKey, options?)`

Devuelve un `Promise<InfraIoInstance>`.

```ts
import { loadInfraIo } from "@lartech/infraio-checkout-js";

const sdk = await loadInfraIo("pk_live_yourkeyhere", {
  // Opcional. Sobreescribe solo cuando apuntes a un entorno no-prod.
  checkoutUrl: "https://checkout-dev.infraio.xyz",
});
```

| Param | Tipo | Requerido | Notas |
| --- | --- | --- | --- |
| `publicKey` | `string` | ✓ | Debe coincidir con `pk_(live\|test)_…` |
| `options.checkoutUrl` | `string` | — | Sobreescribe la URL base del checkout. Por defecto: `https://checkout.infraio.xyz`. La página de checkout se conecta automáticamente a la API correspondiente. |

## `sdk.checkout({ … })`

Abre el checkout alojado. Devuelve `void` (usa callbacks para el
estado).

| Campo | Tipo | Requerido | Notas |
| --- | --- | --- | --- |
| `sessionId` | `string` | ✓ | `session_key` devuelta por `POST /b2b/v1/checkout-sessions/quick` |
| `checkoutUrl` | `string` | — | URL completa devuelta por el mismo endpoint. Si se omite, el SDK la construye a partir del `checkoutUrl` de `loadInfraIo()` (o el por defecto) + `sessionId` |
| `mode` | `"popup" \| "redirect" \| "embed"` | — | Por defecto `"popup"` |
| `container` | `string \| HTMLElement` | solo embed | Selector CSS o elemento DOM donde se monta el iframe |
| `width` | `number` | — | Solo popup. Por defecto `560`. Acotado a `[320, 1280]` |
| `height` | `number` | — | Solo popup. Por defecto `780`. Acotado a `[400, 1000]` |
| `timeoutMs` | `number` | — | Solo popup. Timeout de carga del iframe. Por defecto `30000`. Pasa `0` para deshabilitar |
| `locale` | `string` | — | Tag BCP-47 reenviado como `?locale=` a la página de checkout (`en`, `vi`, `ja`, `ko`, `es`, `pt-BR`, `ru`, `tr`, `zh-CN`, `zh-TW`) |
| `hideSummary` | `boolean` | — | Oculta la columna de resumen de orden. Por defecto `false` |
| `hideHeader` | `boolean` | — | Oculta el header de InfraIO Pay y el botón de conexión de wallet integrado. Por defecto `false`. Combina con `walletAddress` para white-label total |
| `walletAddress` | `string` | — | Pre-conecta una wallet de comprador. Requiere `onSignRequest` |
| `walletChainId` | `number` | — | Chain ID EVM para la wallet pre-conectada |
| `onReady` | `() => void` | — | Se dispara una vez que el iframe es interactivo. Solo popup/embed |
| `onSignRequest` | `(req: { method: string; params: unknown[] }) => Promise<string>` | cuando `walletAddress` está fijado | El SDK reenvía los RPCs de wallet a tu handler; devuelve el hex firmado |
| `onSuccess` | `({ sessionId }) => void` | — | Se dispara en pago exitoso. **No es autoritativo: el webhook lo es** |
| `onCancel` | `() => void` | — | Se dispara cuando el comprador cierra el popup/embed sin pagar |
| `onError` | `(err: InfraIoError) => void` | — | Se dispara en fallo de carga del iframe (modos popup + embed). Los argumentos inválidos se lanzan de forma síncrona, **no** se entregan aquí. El modo redirect no tiene superficie de error en runtime: el fallo se observa en la página redirigida. |

> **Warning:**
>
> `onSuccess` **no** es autoritativo. Puede dispararse incluso cuando
> el webhook luego determina que el pago falló (reorgs de testnet,
> timing del lado comprador). Úsalo solo para UX (mostrar "¡Gracias!",
> redirigir). Confirma siempre vía webhook antes de cumplir.

## `sdk.close()`

Descarta programáticamente un popup o embed abierto. No-op para el
modo redirect.

```ts
sdk.checkout({ sessionId, mode: "popup", /* … */ });
// más tarde, p. ej. cuando el usuario navega fuera
sdk.close();
```

## `sdk.openRefundRequest({ … })`

Abre el **formulario de solicitud de reembolso** alojado para un
token de un solo uso que tu backend emitió vía
`POST /b2b/v1/merchants/{merchant_id}/refund-requests`. El comprador
rellena su dirección de destino del reembolso + razón + metadatos
(opcional) en nuestra página; tu página solo maneja el ciclo de
vida de apertura/cierre. Devuelve una **función `close()`**:
llámala para descartar programáticamente el popup o separar el
iframe embed. En modo redirect la función devuelta es un no-op.

> **Note:**
>
> El formulario vive en `https://checkout.infraio.xyz/refund-request/:token`.
> Este método solo envuelve esa URL en un popup / redirect / embed
> para que el comprador nunca deje tu dominio (en popup / embed) o
> regrese automáticamente (en redirect). El endpoint que tu backend
> pega para emitir el token es
> `POST /b2b/v1/merchants/{merchant_id}/refund-requests`: firmado con
> HMAC con tu clave secreta, misma auth que el resto de la superficie
> B2B. Consulta
> [Conceptos → Reembolsos](https://docs.infraio.xyz/es/concepts/refunds#mint-via-b2b-api).

```ts
import { loadInfraIo } from "@lartech/infraio-checkout-js";

const sdk = await loadInfraIo("pk_live_yourkeyhere");

// Emite el token en el lado servidor, luego entrégaselo al SDK en el navegador.
const { token } = await fetch("/api/mint-refund-token", { method: "POST" }).then(r => r.json());

const close = sdk.openRefundRequest({
  token,
  mode: "popup",
  onSuccess: ({ linkToken, refundId }) => {
    // El comprador envió el formulario.
    // linkToken → página de estado /r/:linkToken (compartir con el comprador).
    // refundId  → usar con la API B2B para aprobar / rechazar.
    window.location.href = `/r/${linkToken}`;
  },
  onCancel: () => { /* el comprador cerró sin enviar */ },
});

// Descartar programáticamente el popup más tarde si es necesario:
// close();
```

| Campo | Tipo | Requerido | Notas |
| --- | --- | --- | --- |
| `token` | `string` | ✓ | El token `rfqt_…` devuelto por `POST /b2b/v1/merchants/{merchant_id}/refund-requests` |
| `mode` | `"popup" \| "redirect" \| "embed"` | — | Por defecto `"popup"`. Misma semántica de superficie que `sdk.checkout()`: consulta [Notas de modo](#mode-notes) |
| `container` | `string \| HTMLElement` | solo embed | Selector CSS o elemento DOM donde se monta el iframe |
| `locale` | `string` | — | Tag BCP-47 reenviado como `?locale=` (`en`, `vi`, `ja`, `ko`, `es`, `pt-BR`, `ru`, `tr`, `zh-CN`, `zh-TW`) |
| `hideHeader` | `boolean` | — | Oculta el header de InfraIO Pay dentro del iframe. En popup/embed el SDK dibuja su propio chrome de modal, así que el header de la página suele ser innecesario. Por defecto `false` |
| `hideSummary` | `boolean` | — | Oculta la columna de Resumen de Orden, mostrando solo el formulario de reembolso. Por defecto `false` |
| `walletAddress` | `string` | — | Pre-rellena el campo de wallet de destino (`?wallet_address=`). Deja que un comerciante que ya conoce la wallet del comprador salte el re-tipeo manual |
| `onSuccess` | `(data: { linkToken: string; refundId: string }) => void` | — | Se dispara tras el envío del formulario por el comprador. `linkToken` → página de estado `/r/:linkToken` para compartir con el comprador. `refundId` → usar con la API B2B para aprobar / rechazar |
| `onCancel` | `() => void` | — | Se dispara cuando el comprador cierra el popup/embed sin enviar |
| `onError` | `(err: InfraIoError) => void` | — | Se dispara en fallo de carga del iframe o args inválidos. La expiración / cancelación del token la maneja la página alojada, no vía `onError` |

**Estados del token expuestos vía `onError`**

Si el comprador abre un token caducado, la propia página maneja la
visualización (renderiza un prompt "Expirado: solicita un enlace
nuevo", etc.), y el SDK **no** dispara `onError` para esos casos:
el comprador está dentro del flujo del formulario y tu código no
necesita reaccionar. `onError` solo se dispara para cosas sobre las
que tu código puede actuar (argumentos incorrectos, fallo de red al
cargar el iframe).

> **Warning:**
>
> `openRefundRequest` registra la intención del reembolso: **no**
> mueve fondos. Tras `onSuccess`, la fila de reembolso es `PENDING`
> (o `APPROVED` si tu config de comerciante auto-aprueba reembolsos
> de cliente). Aún necesitas firmar y difundir la transferencia
> on-chain desde tu wallet de Tesorería, luego publicar el tx hash
> en `POST /b2b/v1/refunds/:id/submit-tx`. Consulta
> [Conceptos → Reembolsos](https://docs.infraio.xyz/es/concepts/refunds) para el ciclo de
> vida completo.

## Clase de error

```ts
import { InfraIoError } from "@lartech/infraio-checkout-js";

sdk.checkout({
  sessionId,
  onError: (err: InfraIoError) => {
    switch (err.code) {
      case "invalid_request_error":   /* sessionId / args malos */ break;
      case "iframe_load_error":       /* el iframe falló al cargar */ break;
      case "iframe_timeout_error":    /* excedió timeoutMs */ break;
      case "already_open_error":      /* otro checkout ya está abierto */ break;
      case "network_error":           /* problema de red transitorio hablando con el origin del checkout */ break;
      case "api_error":               /* el backend devolvió un no-2xx para una llamada emitida por el SDK */ break;
    }
  },
});
```

`sdk.openRefundRequest()` solo lanza `invalid_request_error`
(token / args ausentes o inválidos), de forma síncrona.
`iframe_load_error` (el iframe de solicitud de reembolso falló al
cargar) se entrega de forma **asíncrona vía `onError`**, no se
lanza. **No** emite `iframe_timeout_error` ni `already_open_error`:
el popup de solicitud de reembolso no tiene timeout de carga y
permite múltiples popups concurrentes.

## Notas de modo

### Popup
- Overlay centrado con un fondo oscuro semi-transparente
- `z-index: 2147483647` (max int32): se sienta encima de todo lo demás
- El scroll del body se bloquea mientras está abierto; se restaura al cerrar
- El botón de cerrar recibe el foco inicial; Tab queda atrapado en el popup
- Se cierra por: botón de cerrar, Escape, clic fuera, `sdk.close()`. Todos ellos llaman a `onCancel`
- La página de checkout puede solicitar redimensión vía `postMessage`: el SDK acota dentro de los límites de `width`/`height`

### Redirect
- Navegación dura vía `window.location.href`
- Añade automáticamente `?return_url=<página-actual>` para que el
  comprador regrese de donde vino. Si tu `success_url` /
  `cancel_url` en la sesión ya cubren esto, el viaje de ida y vuelta
  ignora `return_url`

### Embed
- iframe con `allow="payment; clipboard-write"` (directivas de
  Feature Policy HTML5, no el atributo `sandbox`). El iframe se
  sirve desde el origin del checkout, así que los popups de wallet
  y las escrituras al portapapeles desde el lado del comprador
  funcionan sin opt-in adicional.
- El ancho del contenedor es 100%; la altura se autodimensiona vía
  postMessage `INFRAIO_RESIZE`, acotado a `[200, 2000]px`
- Sin aislamiento CSS más allá del borde del iframe: los estilos
  de tu página padre no se filtran
- Cablea siempre `onReady` para que puedas ocultar tu propio estado
  de carga cuando el checkout se vuelva interactivo

## TypeScript

Todos los tipos están incluidos. Las exportaciones más útiles:

```ts
import type {
  CheckoutOptions,
  RefundRequestOptions,
  LoadOptions,
  InfraIoInstance,
  InfraIoErrorCode,
} from "@lartech/infraio-checkout-js";
import { loadInfraIo, InfraIoError, VERSION } from "@lartech/infraio-checkout-js";
```

`VERSION` es la cadena de versión del propio SDK: útil en reportes
de bug.
