> Chytapay es una plataforma de cobros por transferencia en Argentina. Esta página es parte de la doc de la Integration API: https://chytapay.com.ar/api/
> Conciliación recomendada: por CUIL. Ambientes: test (https://integration-api.test.chytapay.com.ar) y producción (https://integration-api.chytapay.com.ar).
> Página: "Widget de checkout" (https://chytapay.com.ar/api/widget/), actualizada el 2026-10-22.
> Índice completo para asistentes de IA: https://chytapay.com.ar/llms.txt

# Widget de checkout

Cómo cobrar en tu checkout con el widget embebible de Chytapay y sus claves de Sandbox y Producción.

&#xA;Al terminar, tu comprador paga por transferencia sin salir de tu sitio. Necesitás el `id_token` de una [cuenta de comercio conectada](https://chytapay.com.ar/api/conectar-comercios/) y acceso al Portal como admin de la integración.

Para conocer el widget antes de integrarlo, visitá la [página del widget](https://chytapay.com.ar/widget/).

1. [Generá la clave publicable](https://chytapay.com.ar/api/widget/#claves): Portal
2. [Creá el cobro de checkout](https://chytapay.com.ar/api/widget/#cobro-checkout): POST /payment-request
3. [Instalá el SDK](https://chytapay.com.ar/api/widget/#sdk): CDN o npm
4. [Abrí el checkout](https://chytapay.com.ar/api/widget/#checkout): `ChytaPay.checkout`

## Cómo funciona

El recorrido de un pago con el widget, entre el navegador del pagador, tu backend y Chytapay.

1. Tu backend crea el cobro `checkout` con `POST /payment-request` y el `id_token` del comercio.
2. Chytapay responde con el `paymentRequestId`.
3. Tu backend se lo pasa a tu página, que no crea cobros: solo muestra uno que ya existe.
4. El SDK, con tu clave publicable (`pk`), lee el cobro y alimenta al iframe de Chytapay. El pagador ingresa su documento y recibe el CVU. El iframe no ve claves; puede enviar telemetría sin datos del pagador.
5. El pagador transfiere al CVU.
6. Chytapay avisa a tu backend con `payment_request.payment_received`.

## Qué ve el pagador

1. **Ingresa su documento** (CUIL, CUIT o DNI), que es por lo que se concilia.
2. **Ve el nombre que ARCA tiene para ese documento**: tiene que transferir desde una cuenta suya. Es informativo: si ARCA no responde, sigue.
3. **Recibe el CVU**, asignado recién ahora, con botón para copiar y cuenta regresiva.
4. **Transfiere** desde su cuenta.
5. **Ve el resultado**: con el total, `onSuccess`. Con menos, el cobro queda en `partial_payment` y el widget espera el resto.

> **Atención:** Si transfiere otra persona, el pago no se imputa: el cobro sigue en `pending` y, si activaste ese evento, llega un `payment.unmatched` con `no_cuil_match`. Ver [el pagador tercero](https://chytapay.com.ar/api/conciliacion/#pagador-tercero).

## 1. Generá la clave publicable

Las claves son de la integración, no de la cuenta de comercio:

| Clave             | Formato                       | Dónde va                    | Qué puede hacer                                                                                            |
| ----------------- | ----------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Publicable (`pk`) | `pk_test_...` / `pk_live_...` | En el navegador. Es pública | Leer un cobro, consultar el titular de un documento y asociar el pagador. Header `X-Chyta-Publishable-Key` |

Una `pk` sirve para todas las cuentas de comercio de la integración.

1. Entrá al [Portal](https://chytapay.com.ar/portal/login) y elegí el ambiente: Sandbox para `pk_test`, Producción para `pk_live`.
2. Abrí [**Credenciales**](https://chytapay.com.ar/portal/credentials), sección **Widget de pago**.
3. Tocá **Generar clave** si no tenés una. La **Clave publicable** queda visible para copiar.
4. Opcional: en **Dominios autorizados**, tocá **Configurar** y cargá los orígenes exactos de tu sitio (esquema, host y puerto si no es el default, sin ruta ni barra final), por ejemplo `https://tienda.example.com`. Con al menos uno, otro origen recibe 403. Sin ninguno, la `pk` funciona desde cualquier sitio.

> **Producción:** Una `pk_test` no funciona en producción: generá la `pk_live` en [**Credenciales**](https://chytapay.com.ar/portal/credentials), con el ambiente en Producción. `livemode` es `true` solo en eventos reales de producción.

## 2. Creá el cobro de checkout

&#xA;Sirve cuando no conocés al pagador de antemano, como en un ecommerce. En tu backend, creá el cobro con `type: "checkout"`:

**cURL**

```bash
curl -X POST https://integration-api.test.chytapay.com.ar/payment-request \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "checkout",
    "referenceId": "pedido-1001",
    "amount": 15000,
    "description": "Compra en la tienda",
    "expiresInMinutes": 15
  }'
```

**Respuesta**

```json
{
  "paymentRequestId": "00000000-0000-0000-0000-000000000000",
  "type": "checkout",
  "referenceId": "pedido-1001",
  "amount": 15000,
  "description": "Compra en la tienda",
  "dueDates": ["2026-10-01T15:15:00Z"],
  "expiresInMinutes": 15,
  "state": "pending",
  "sendWhatsappNotification": false,
  "sendEmailNotification": false
}
```

Responde 201 con el cobro en `pending`. El `paymentRequestId` es el `paymentId` que le pasás al widget.

| Campo              | ¿Obligatorio? | Qué es                                                                                      |
| ------------------ | ------------- | ------------------------------------------------------------------------------------------- |
| `type`             | Sí            | `"checkout"`. Sin él, el cobro es `standard`.                                               |
| `referenceId`      | Sí            | 8 a 100 caracteres: letras, números, `-` y `_`. Único por integración y cuenta de comercio. |
| `amount`           | Sí            | Pesos, mayor a 0, mandá hasta 2 decimales, hasta 200.000.000. Sin borrador.                 |
| `description`      | Sí            | Lo que ve el pagador, de 3 a 500 caracteres.                                                |
| `expiresInMinutes` | No            | Entero de 3 a 60, 15 por defecto. Reemplaza a `dueDates`.                                   |
| `webhookUrl`       | No            | URL HTTPS de webhook solo para este cobro.                                                  |
| `metadata`         | No            | Pares clave y valor que vuelven en cada evento.                                             |

Mandar `customer`, `dueDates`, `surcharge`, `conciliationType` o un campo de aviso (`sendWhatsappNotification`, `sendEmailNotification`, `sendWhatsappPaymentConfirmation`, `reminderNotificationDate`, `deferredInitialNotificationDate`) da 400 con el nombre del campo.

- Concilia siempre por CUIL.
- La respuesta no trae `CVU` ni `bankAccountInfo`: se asignan cuando el pagador se identifica.
- `expiresInMinutes` se vuelve un vencimiento con hora en `dueDates`. Al cumplirse, el cobro pasa a `overdue` y llega `payment_request.expired`.

Como en `standard`, un `referenceId` repetido da 409 `ReferenceIdAlreadyExists`.

## 3. Instalá el SDK

Por CDN, el SDK queda en `window.ChytaPay`:

```html
<script src="https://unpkg.com/@chytapay/integration-widget@1" defer></script>
```

Con `defer`, el navegador baja el SDK sin frenar el dibujado de tu página y lo ejecuta al terminar de leer el HTML, antes de `DOMContentLoaded`. Un script en línea que corra antes no encuentra `ChytaPay`: llamá a `ChytaPay.init` desde un manejador de `DOMContentLoaded`, como en el paso siguiente.

Por npm, con un bundler:

```bash
npm i @chytapay/integration-widget
```

```js
import { ChytaPay } from '@chytapay/integration-widget';
```

Con `@1` recibís las versiones nuevas de la serie 1; para controlar cada una, fijá la exacta (`@1.5.0`). Sin versión, unpkg sirve la última. Ver el [changelog del SDK](https://unpkg.com/@chytapay/integration-widget@1/CHANGELOG.md), también incluido en el paquete.

## 4. Abrí el checkout

En tu página, inicializá el SDK con tu `pk` y abrí el checkout con el `paymentRequestId`:

```js
// 1. Una sola vez, cuando el SDK ya cargó. El prefijo de la pk elige las URLs: pk_live para Producción, pk_test para Sandbox.
document.addEventListener('DOMContentLoaded', () => {
  ChytaPay.init({ pk: PK_DE_TU_INTEGRACION });
});

// 2. Llamala cuando el comprador confirma, con el cobro que creó tu backend.
function abrirCheckout(paymentRequestId) {
  return ChytaPay.checkout({
    paymentId: paymentRequestId,
    onSuccess: (paymentId) => {
      // Entró el monto total: confirmá la compra en tu UI.
    },
    onError: (error) => {
      // Venció o falló sin arreglo: ofrecé generar un cobro nuevo.
    },
    onCancel: () => {
      // El comprador cerró el checkout sin pagar, o el cobro se canceló.
    },
  });
}

// 3. Opcional: el handle que devuelve cierra el checkout desde tu código. No dispara onCancel.
//    const handle = abrirCheckout(paymentRequestId);
//    handle.close();
```

Se abre un modal que se cierra con Escape, clic en el fondo o la X. Para embeberlo, pasá `container`.

`onSuccess` es para tu interfaz. La confirmación del pago es el evento `payment_request.payment_received` en tu backend, con su [firma verificada](https://chytapay.com.ar/api/webhooks/#firma).

> **Atención:** Tratá el `paymentRequestId` como sensible: con la `pk`, que es pública, alcanza para leer el cobro y asociarle un pagador. No lo pongas en URLs ni en logs.

## Si tu tienda usa CSP

Si tu página manda una Content Security Policy, permití estos orígenes. Sin ellos, el navegador bloquea el SDK, el checkout o el botón de Mercado Pago.

| Directiva     | Valor                                      | Para qué                                                                |
| ------------- | ------------------------------------------ | ----------------------------------------------------------------------- |
| `script-src`  | `https://unpkg.com`                        | Cargar el SDK por CDN. Con npm, el SDK va en tu bundle y no hace falta. |
| `connect-src` | `https://integration-api.chytapay.com.ar`  | Consultar el cobro.                                                     |
| `frame-src`   | `https://widget.chytapay.com.ar`           | Mostrar el iframe del checkout.                                         |
| `frame-src`   | `mercadopago:` e `intent:`                 | Que el botón **Abrir en Mercado Pago** abra la app.                     |
| `img-src`     | `https://widget.chytapay.com.ar` y `data:` | Logo y animación de la pantalla de carga.                               |

Si tu CSP no define `frame-src`, el navegador usa `child-src` y, si tampoco está, `default-src`. Si cambiás `apiBaseUrl` o `iframeUrl` en `init`, permití esos orígenes en lugar de los de producción.

En Sandbox permití `https://integration-api.test.chytapay.com.ar` en `connect-src` y `https://widget-test.chytapay.com.ar` en `frame-src` e `img-src` (más `data:`), aunque no cambies las URLs en `init`.

El botón abre la app desde un frame oculto de tu página. Por eso `mercadopago:` e `intent:` van en `frame-src`: sin ellos, el pagador ve el aviso de que la app no abrió y el CVU para copiar.

## Probalo en Sandbox

1. Generá la `pk_test` en [**Credenciales**](https://chytapay.com.ar/portal/credentials), con el ambiente en Sandbox.
2. Creá el cobro en `https://integration-api.test.chytapay.com.ar` con el `id_token` de un [comercio de prueba](https://chytapay.com.ar/api/getting-started/#testing).
3. Inicializá el SDK apuntando a Sandbox:
   ```js
   document.addEventListener('DOMContentLoaded', () => {
     ChytaPay.init({
       pk: PK_TEST_DE_TU_INTEGRACION,
     });
   });
   ```
4. Abrí el checkout. El widget muestra el monto y pide el documento.

`pk_test_` elige `https://integration-api.test.chytapay.com.ar` y `https://widget-test.chytapay.com.ar/checkout/`; `pk_live_`, los hosts de Producción.

## Cuando algo falla

| Qué ve el pagador                                                                  | Qué pasó                                                    | ¿`onError`? | Qué hacés                      |
| ---------------------------------------------------------------------------------- | ----------------------------------------------------------- | ----------- | ------------------------------ |
| "El documento ingresado no es válido. Revisalo e intentá de nuevo."                | No es un CUIL, CUIT ni DNI.                                 | No          | Nada: lo corrige ahí.          |
| "No pudimos asignarte una cuenta para transferir. Probá de nuevo en unos minutos." | Cambió de documento y no hay CVU libre. Transitorio.        | No          | No crees otro cobro.           |
| "Este cobro ya tiene un pagador asociado y no se puede cambiar."                   | Cambió de documento con plata acreditada o en estado final. | Sí          | Un cobro nuevo, si hace falta. |
| "Este cobro ya no admite pagos."                                                   | Pagado, vencido o cancelado.                                | Sí          | Un cobro nuevo.                |

También dispara `onError` si el cobro vence (`overdue`, se cumplió `expiresInMinutes`) o si al abrir dice "No se pudo cargar el pago.": `paymentId` inexistente o de otra integración (404), `pk` inexistente (401) u origen fuera de los dominios autorizados (403). `partial_overdue` no lo dispara: el widget sigue esperando.

## Referencia rápida

### `ChytaPay.init(config)`

Una vez, antes de `checkout`. Sin `pk`, tira un error.

| Opción             | ¿Obligatoria? | Qué es                                                                                                                                    |
| ------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `pk`               | Sí            | Tu clave publicable.                                                                                                                      |
| `apiBaseUrl`       | No            | Según la `pk`: `https://integration-api.chytapay.com.ar` con `pk_live_`; `https://integration-api.test.chytapay.com.ar` con `pk_test_`.   |
| `iframeUrl`        | No            | Según la `pk`: `https://widget.chytapay.com.ar/checkout/` con `pk_live_`; `https://widget-test.chytapay.com.ar/checkout/` con `pk_test_`. |
| `requestTimeoutMs` | No            | Timeout por llamada, en ms. Por defecto, 10000.                                                                                           |

### `ChytaPay.checkout(options)`

| Opción         | ¿Obligatoria? | Qué es                                                                                                                                                                                            |
| -------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `paymentId`    | Sí            | El `paymentRequestId`.                                                                                                                                                                            |
| `onSuccess`    | No            | Una vez, al llegar a `total_payment`. Recibe el `paymentId`.                                                                                                                                      |
| `onError`      | No            | Ante `overdue` o un error sin arreglo. Recibe un `Error`.                                                                                                                                         |
| `onCancel`     | No            | Si el comprador cierra o el cobro pasa a `canceled`.                                                                                                                                              |
| `onWalletOpen` | No            | Después de cada toque en **Abrir en Mercado Pago**. Recibe un [evento](https://chytapay.com.ar/api/widget/#referencia-wallet). Desde el SDK 1.2.0.                                                |
| `container`    | No            | Elemento para embeberlo. Sin él, modal. Con él, el cierre lo manejás vos.                                                                                                                         |
| `locale`       | No            | Formato de montos y fechas, `es-AR` por defecto. Los textos siguen en español.                                                                                                                    |
| `theme`        | No            | Acepta `--chytapay-primary`, `--chytapay-accent`, `--chytapay-header-bg` (alias `--chytapay-header-gradient`) y `--chytapay-radius`; ignora las demás y valida contraste AA.                      |
| `pollSchedule` | No            | ms fijos o una función del tiempo. Al volver al widget o tocar un botón: 1 s hasta los 20 s, 2 s hasta los 40 s y 10 s después. Mientras transfiere: 5 s, y 15 s pasados 5 min (no configurable). |
| `demo`         | No            | `true` abre una demo con datos ficticios, sin API. `paymentId` pasa a ser opcional.                                                                                                               |

`checkout` devuelve `close()`, que desmonta el widget sin disparar `onCancel`. Las consultas paran en un estado final, o dos minutos después del vencimiento como máximo. Estados del SDK: `pending`, `partial_payment`, `total_payment`, `partial_overdue`, `overdue` y `canceled`; los finales son `total_payment`, `overdue` y `canceled`.

### `onWalletOpen(event)`

Se dispara después de cada toque en **Abrir en Mercado Pago**, con el resultado de ese toque. Sirve para saber en qué navegadores abre la app. Una excepción en tu función no afecta al checkout.

```js
ChytaPay.checkout({
  paymentId: paymentRequestId,
  onWalletOpen: ({ wallet, opened, os, browser, via }) => {
    // { wallet: 'mercadopago', opened: false, os: 'android', browser: 'firefox', via: 'sdk' }
  },
});
```

| Campo     | Valores                                                                                                                                    |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `wallet`  | `"mercadopago"`                                                                                                                            |
| `opened`  | `true` si la página pasó a segundo plano después del toque. En Chrome de Android también es `true` si la app no está y se abre Play Store. |
| `os`      | `"ios"` o `"android"`                                                                                                                      |
| `browser` | `"safari"`, `"chrome"`, `"firefox"`, `"samsung"`, `"edge"`, `"opera"`, `"instagram"`, `"facebook"`, `"tiktok"`, `"webview"` u `"other"`    |
| `via`     | `"sdk"` si el SDK abrió el link desde tu página; `"iframe"` si lo abrió el link del propio widget.                                         |

El evento no trae el user agent ni nada que identifique al pagador. Si la app no abre en 2,5 segundos, el widget se lo avisa al pagador y le deja el CVU para copiar.

## Seguí por acá

- [Crear cobros](https://chytapay.com.ar/api/crear-cobros/): Campos, estados y duplicados.
- [Conciliación](https://chytapay.com.ar/api/conciliacion/): CUIL y el pagador tercero.
- [Webhooks](https://chytapay.com.ar/api/webhooks/): Eventos, firma y reintentos.
- [Operación y errores](https://chytapay.com.ar/api/operacion/): Límites y paso a producción.
