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.
ChytaPay.checkoutCómo funciona
El recorrido de un pago con el widget, entre el navegador del pagador, tu backend y Chytapay.
- Tu backend crea el cobro
checkoutconPOST /payment-requesty elid_tokendel comercio. - Chytapay responde con el
paymentRequestId. - Tu backend se lo pasa a tu página, que no crea cobros: solo muestra uno que ya existe.
- 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. - El pagador transfiere al CVU.
- Chytapay avisa a tu backend con
payment_request.payment_received.
Qué ve el pagador
- Ingresa su documento (CUIL, CUIT o DNI), que es por lo que se concilia.
- Ve el nombre que ARCA tiene para ese documento: tiene que transferir desde una cuenta suya. Es informativo: si ARCA no responde, sigue.
- Recibe el CVU, asignado recién ahora, con botón para copiar y cuenta regresiva.
- Transfiere desde su cuenta.
- Ve el resultado: con el total,
onSuccess. Con menos, el cobro queda enpartial_paymenty 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_ / pk_ |
En el navegador. Es pública | Leer un cobro, consultar el titular de un documento y asociar el pagador. Header X- |
Una pk sirve para todas las cuentas de comercio de la integración.
- Entrá al Portal y elegí el ambiente: Sandbox para
pk_test, Producción parapk_live. - Abrí Credenciales, sección Widget de pago.
- Tocá Generar clave si no tenés una. La Clave publicable queda visible para copiar.
- 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, lapkfunciona 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. |
expiresIn |
No | Entero de 3 a 60, 15 por defecto. Reemplaza a due. |
webhook |
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
CVUnibankAccountInfo: se asignan cuando el pagador se identifica. expiresInMinutesse vuelve un vencimiento con hora endueDates. Al cumplirse, el cobro pasa aoverduey llegapayment_request.expired.
Como en standard, un referenceId repetido da 409 ReferenceIdAlreadyExists.
3Instalá el SDK
Por CDN, el SDK queda en window.ChytaPay:
<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:
npm i @chytapay/integration-widgetimport { 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:
// 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- |
https:// |
Cargar el SDK por CDN. Con npm, el SDK va en tu bundle y no hace falta. |
connect- |
https:// |
Consultar el cobro. |
frame- |
https:// |
Mostrar el iframe del checkout. |
frame- |
mercadopago: e intent: |
Que el botón Abrir en Mercado Pago abra la app. |
img- |
https:// 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
- Generá la
pk_testen Credenciales, con el ambiente en Sandbox. - Creá el cobro en
https://integration-api.test.chytapay.com.arcon elid_tokende un comercio de prueba. - Inicializá el SDK apuntando a Sandbox:
Node
document.addEventListener('DOMContentLoaded', () => { ChytaPay.init({ pk: PK_TEST_DE_TU_INTEGRACION, }); }); - 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ó | ¿on? |
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. |
api |
No | Según la pk: https:// con pk_; https:// con pk_. |
iframe |
No | Según la pk: https:// con pk_; https:// con pk_. |
request |
No | Timeout por llamada, en ms. Por defecto, 10000. |
ChytaPay.checkout(options)
| Opción | ¿Obligatoria? | Qué es |
|---|---|---|
paymentId |
Sí | El payment. |
on |
No | Una vez, al llegar a total_. Recibe el paymentId. |
on |
No | Ante overdue o un error sin arreglo. Recibe un Error. |
on |
No | Si el comprador cierra o el cobro pasa a canceled. |
on |
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- por defecto. Los textos siguen en español. |
theme |
No | Acepta --, --, -- (alias --) y --; ignora las demás y valida contraste AA. |
poll |
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.
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.