- Commercy te llama a dos rutas de tu servidor: una para abrir la sesión de cobro y otra para consultar su estado. Cada request va firmado.
- Vos le avisás a Commercy con un webhook mínimo cuando el estado del pago cambia. El webhook sólo avisa: el estado lo volvemos a consultar nosotros.
Cómo se habilita
- Guardá el secreto de webhook de tu app (el
whsec_...que ves en el portal): es con el que firmamos nuestros requests y con el que tenés que firmar el webhook que nos mandás, el mismo de los webhooks. Si lo rotás, las sesiones en curso dejan de poder verificarse hasta que tu app use el secreto nuevo. - Cargá en el portal la URL de tu app (
appUrl): tiene que ser un dominio propio, no una IP nilocalhost. Es la referencia para validar elcheckout_urlde cada sesión. - Pedile a soporte que habilite tu app como medio de pago. Pasale el id de tu app (el de la URL de tu app en el portal) y la URL base de tu API (
apiBaseUrl): tiene que serhttpsy pública, por ejemplohttps://api.tuapp.com. Esa base se concatena con las rutas de esta página. - El comercio instala la app y la prende en Ajustes > Cobros de su tienda. Desde ese momento la ofrecemos en el checkout, siempre que su plan incluya las apps. Desinstalar la app deja de ofrecerla.
Cómo te llamamos
Todas las operaciones son unPOST con cuerpo JSON a <apiBaseUrl><ruta>.
- Usamos
httpshacia una dirección pública. No seguimos redirects. - Leemos como máximo 64 KB de tu respuesta. Si es más larga la tratamos como error, nunca como JSON parcial.
- Tiempos máximos: 8 segundos para abrir la sesión y 5 segundos para consultar el estado.
- Respondé
2xxcon un cuerpo JSON. Cualquier otro código, un timeout o un JSON inválido cuentan como error.
Headers
El cuerpo de todas las operaciones lleva
installation_id y shop_id (la sucursal de la tienda, el mismo id que devuelve GET /shops).
Verificar la firma
Es el mismo esquema que los webhooks:HMAC-SHA256(secreto, "<X-Commercy-Timestamp>.<cuerpo crudo>") en hexadecimal y con el prefijo v1=, con tu secreto completo (incluido whsec_). Verificala siempre, porque cualquiera podría llamar a tus rutas y abrir o consultar cobros:
- Leé el cuerpo crudo, antes de parsear el JSON.
- Comparala con
X-Commercy-Signatureen tiempo constante. - Rechazá timestamps que difieran más de 5 minutos de tu reloj.
- Comprobá que el
installation_iddel cuerpo sea el deX-Commercy-Installation. La firma cubre el cuerpo, no la URL, así que el recurso viaja en el cuerpo.
raw en esas rutas para conservar el cuerpo crudo:
Abrir la sesión de cobro
Cuando el comprador confirma la compra con tu medio de pago, creamos la venta y te pedimos la sesión.idempotency_keyes el id de la venta. Si recibís dos veces la misma, devolvé la misma sesión.amountes el total de la venta en pesos, con envío y descuentos.currencyes siempreARS.back_urles la página de la tienda adonde tenés que devolver al comprador cuando termina de pagar. Esa página consulta el estado de la venta y le muestra el resultado.expires_atlo fija Commercy (60 minutos desde que el comprador confirma) y tu app no tiene que aceptar pagos después de ese momento. Cualquier vencimiento que devuelvas se ignora.
Los campos de más se ignoran. Si la respuesta no cumple las reglas, la compra no se arma: la venta queda cancelada y el comprador ve «No se pudo iniciar el pago». Lo mismo si tu app tarda más de 8 segundos o responde un error.
payment_id identifica la sesión
El payment_id tiene que ser estable durante toda la sesión, incluidos los reintentos del comprador: si su primera tarjeta es rechazada y paga con otra, sigue siendo el mismo payment_id. Tu /status devuelve el estado agregado de la sesión: approved si algún intento se aprobó, y rejected sólo si ningún intento está aprobado ni en curso. Un webhook con un payment_id distinto al de la sesión se descarta.
El checkout_url tiene que ser de tu dominio
Por seguridad del comprador, el checkout_url debe ser https y compartir el dominio registrable de la URL de tu app (appUrl): con appUrl = https://tuapp.com, sirven https://pagos.tuapp.com/... y https://tuapp.com/..., y no sirve https://otro.com/.... Los dominios que comparten un proveedor de hosting (por ejemplo *.vercel.app) no cuentan como un mismo dominio: a.vercel.app y b.vercel.app son distintos. Si tu procesador de pagos te da una URL externa, redirigí desde una ruta de tu propio dominio.
Consultar el estado
Antes de aplicar cualquier estado, Commercy comprueba que lo que respondiste corresponde a la venta:
- Binding:
payment_ides el de la sesión yorder_ides elorder.idde la sesión. Si no coinciden, ignoramos la respuesta y avisamos a nuestro equipo. - Monto y moneda: para aprobar,
amounttiene que ser igual al total de la venta ycurrencytiene que serARS. Si no, no confirmamos la venta. - Si no podemos verificar (timeout, error
5xx, respuesta inválida), no aplicamos nada y no cancelamos la venta: lo que no se pudo verificar no se da por no pagado.
Avisarnos que el estado cambió
Cuando el pago se aprueba, se rechaza o cambia de estado, mandanos un webhook. El webhook no lleva el estado: sólo nos dice qué sesión cambió, y nosotros la consultamos con tu/status.
<installation_id>es el mismo id de instalación que te mandamos en cada request.Content-Type: application/jsones obligatorio.- Va firmado igual que nuestros requests, con tu secreto de webhook: el header
X-Commercy-Signature(v1=...) y elX-Commercy-Timestampde ese mismo momento. La firma se hace sobre el cuerpo exacto que enviás y vale 5 minutos.
Respondemos el webhook después de consultar tu
/status. Tu app tiene que poder atender /status mientras espera la respuesta de su propio webhook (no bloquees las dos cosas en el mismo hilo), o el webhook va a dar timeout. Si recibís un 500, reintentá.
El webhook sirve para que la venta se confirme sin demora, pero no es la única vía: el comprador, al volver a la tienda, también dispara una consulta (como mucho una cada 10 segundos por venta), y al vencer la sesión consultamos de nuevo. Aun así mandá siempre el webhook.
Qué pasa con la venta
- Aprobado: la venta pasa a
confirmed, se emite el eventoorder.paidconpaid_via: "app"y, si el comercio tiene facturación automática, se factura. Tu app no recibe su propioorder.paid. - A los 60 minutos (
expires_at) consultamos tu/statusuna última vez:pendingorejectedverificados: cancelamos la venta.approved: la confirmamos.in_process: no la cancelamos, extendemos la sesión y volvemos a consultar. El tope es de 72 horas desde que se creó la venta: si pasado ese plazo siguein_process, se cancela y avisamos al comercio.- No se pudo verificar: no la cancelamos y reintentamos. Con el mismo tope de 72 horas de creada, se cancela y avisamos al comercio.
- Si tu medio de pago es lento (un cupón de pago en efectivo que vence en 3 a 5 días, por ejemplo), resolvelo antes de las 72 horas: o tu
/statusinformaapprovedorejecteddentro de ese plazo, o hacés vencer el cupón antes. Si el comprador paga después de la cancelación es un cobro tardío (ver abajo).
- Cobro tardío: si tu app informa un cobro de una venta que ya se canceló (incluida una cancelada por el tope de 72 horas), lo registramos y avisamos al comercio, pero no la reabrimos ni devolvemos la plata: la devolución la hacés vos en tu plataforma.
- Si el comercio cancela la venta antes de que se pague, escuchá el webhook
order.cancelledy cerrá la sesión de tu lado. - Si el comercio desinstala tu app o se suspende, no se abren sesiones nuevas, pero las que estaban abiertas se siguen reconciliando con tu secreto hasta cerrarse. Tu
/statusy tus webhooks tienen que seguir funcionando.
Reembolsos
Un reembolso lo hace la app en su plataforma, con su propio flujo. Commercy no tiene un endpoint para informar reembolsos y ignora el estadorefunded de /status.
- Si el comercio cancela o devuelve la venta desde Commercy, la plata no se mueve: tu app recibe el webhook
order.cancelledy tiene que devolverle al comprador de su lado. - Si el comercio intenta reembolsar desde Commercy un cobro de tu app, ve el aviso «Los pagos registrados por una app se reembolsan desde esa app» y no se hace nada.
- No uses Informar un cobro para esto: ese endpoint registra cobros, no devoluciones.
Checklist de salida
- Verificás la firma sobre el cuerpo crudo y que
installation_idcoincida con el header. -
/commercy/payments/sessiondevuelve la misma sesión para la mismaidempotency_keyy uncheckout_urlhttpsde tu dominio. - El
payment_ides el mismo durante toda la sesión, aunque el comprador reintente. -
/statusdevuelve los cinco campos, conorder_idyamountexactos. - No aceptás pagos después de
expires_at. - Mandás el webhook
payment.updatedfirmado, conContent-Type: application/json, y reintentás ante un500. - Atendés
/statusmientras esperás la respuesta de tu webhook.
Ejemplo
payment-provider es una app de pago de ejemplo, lista para copiar y adaptar.