Saltar al contenido
ESENIr al Portal

Widget de checkout

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

Al terminar, tu comprador paga por transferencia sin salir de tu sitio. Necesitás el id_token de una cuenta de comercio conectada y acceso al Portal como admin de la integración.

Para conocer el widget antes de integrarlo, visitá la página del widget.

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.

1Generá 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 y elegí el ambiente: Sandbox para pk_test, Producción para pk_live.
  2. Abrí Credenciales, 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, con el ambiente en Producción. livemode es true solo en eventos reales de producción.

2Creá el cobro de checkout

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

cURL

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

{
  "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.

3Instalá 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:

cURL
npm i @chytapay/integration-widget
Node
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, también incluido en el paquete.

4Abrí el checkout

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

Node
// 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.

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, 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.
  3. Inicializá el SDK apuntando a Sandbox:
    Node
    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

Ver las opciones del SDK y los endpoints

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. 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.

Node
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á