Anulación, reembolso y recibos
13.0 No hay autorizar y capturar por separado, y de dónde viene ese límite
Connect cobra en un solo paso. Un pago autoriza y captura junto —una venta— y no hay ningún endpoint que deje una retención ahora y tome el dinero después.
Si despachás mercadería, ese es el límite con el que hay que diseñar: no podés reservar los fondos en el checkout y capturarlos cuando la caja sale de la bodega. Lo que sí tenés es la reversión después del hecho: anular mientras la transacción no ha liquidado, reembolsar cuando ya liquidó. Las dos están en esta sección.
De quién es el límite, contestado por el equipo del API el 25/08/2026, porque cambia cómo se planifica:
| ¿El procesador lo soporta? | Sí. PowerTranz tiene autorizar y capturar como llamadas separadas |
| ¿Lo bloquea el banco adquirente? | No |
| ¿Entonces por qué falta? | Connect no lo expone. Es un hueco del API, no una imposibilidad técnica |
Vale la pena saberlo en vez de suponerlo. Un límite que viene del procesador es permanente y uno construye alrededor de él para siempre; un límite que es "todavía no expuesto" es algo que se puede pedir cuando aparezca un comercio que lo necesite.
13.0b 3-D Secure: versión, y quién carga con el fraude
Connect autentica con 3DS2 (EMV 3-D Secure 2.x); el procesador informa la subversión exacta, por
ejemplo 2.1.0.
Cuando la autenticación pasa, la responsabilidad por fraude puede trasladarse del comercio al emisor de la tarjeta. No hay una llamada del API para eso ni un interruptor que encender: se desprende automáticamente de las reglas de la propia red de tarjetas, cuando el valor de ECI, el estado de autenticación y la prueba de autenticación (el CAVV) coinciden.
| Red | Autenticada por completo | Intentada | Fallida: no hay traslado |
|---|---|---|---|
| Visa / Amex | ECI 05, estado Y, con CAVV | ECI 06, estado A, con CAVV | ECI 07, estado N / U / R |
| Mastercard | ECI 02, estado Y, con CAVV | ECI 01, estado A, con CAVV | ECI 00, estado N / U / R |
Dos salvedades, las dos dichas por el equipo del API:
- El traslado solo está disponible para ciertos códigos de categoría de comercio. Cuáles califican no está publicado; tu banco adquirente sabe el código de tu cuenta y si aplica.
- Un comercio inscrito en el programa de monitoreo de fraude de Visa puede seguir cargando con la pérdida incluso después de una autenticación exitosa.
Nada de esto se ve en el objeto del pago de Connect. El procesador devuelve el ECI, el CAVV, el
estado de autenticación y la versión del protocolo, pero Connect no expone ninguno, y no hay una
bandera liability_shift. Así que no podés ramificar por eso ni mostrarle a un comercio si un pago
determinado trasladó la responsabilidad. Tratá la autenticación como algo que ocurrió, no como algo
que se pueda leer después.
Estas operaciones actúan sobre una transacción, identificada por el UUID transaction_id de la
respuesta del pago, nunca por el id numérico del pago.
Esa distinción importa porque un enlace reutilizable puede llevar varias transacciones: anular o reembolsar la transacción de un cliente deja las demás intactas.
| Operación | Ruta |
|---|---|
| Anulación | POST /payments/{transaction_id}/void |
| Reembolso | POST /payments/{transaction_id}/refund |
| Metadatos del recibo | GET /payments/{transaction_id}/receipt |
| PDF del recibo | GET /payments/{transaction_id}/receipt/download |
13.1 Pasar el identificador equivocado
Estas rutas quieren el UUID de la transacción. Si les pasás el id numérico del pago, el endpoint
contesta 404 con code: "transaction_id_expected" y nombra el identificador que esperaba:
POST /payments/9e2b6a34-6f1d-4e2a-8c9b-2f7a1d4e5c6b/void -> la ruta coincidió
POST /payments/873/void -> 404 {"code":"transaction_id_expected",
"message":"Este endpoint requiere
transaction_id (el UUID de la
respuesta del pago o cobro), no el
id numérico del pago."}
Eso es un endpoint vivo rechazando el identificador, no una ruta que falta. La distinción importa
porque los arreglos son opuestos: not_found significa cambiá la URL, transaction_id_expected
significa cambiá el identificador.
Cambió el 2026-08-21. Este caso devolvía el 404 de ruteo (
"The route ... could not be found."), y las versiones anteriores de esta guía - y los clientes de ejemplo que trae - te decían que lo detectaras así. Si tu integración sigue ramificando por"could not be found", ahora lee este error como "el pago no existe". Ramificá porcode. El mensaje de acá se traduce; la prosa del 404 de ruteo no.
13.2 ¿Anulación o reembolso?
| Anulación | Reembolso | |
|---|---|---|
| Cuándo | El mismo día, antes de la liquidación | Después de la liquidación |
| Monto | Solo total | Total o parcial |
| Velocidad | Inmediata | 3-5 días hábiles de vuelta a la tarjeta |
| El cliente ve | Que en realidad nunca se le cobró | Dinero devuelto |
| Webhook | payment.voided |
payment.refunded / payment.partially_refunded |
| Caso típico | Error, cancelación inmediata | Devolución, disputa, cobro de más |
La elegibilidad la gobierna la ventana de tiempo configurada de la transacción, no solo el estado de
liquidación. Intentar un reembolso demasiado pronto puede devolver 422 con un mensaje del
procesador como "Invalid transaction" - ese texto viene del procesador y no es una respuesta que
ROKI garantice.
Implementá la reversión así: probá primero la anulación; si se rechaza porque no es anulable o porque ya está liquidada, reembolsá en su lugar.
13.3 Anulación
POST /api/connect/v1/payments/{transaction_id}/void
{ "reason": "Customer cancelled" } # optional, max 2000 chars
Devuelve el pago con status: "voided". Los rechazos llegan como 422 bajo errors.void: ya
anulado, ya reembolsado (usá reembolso), no está en un estado anulable, o ya liquidado.
Una anulación también llena los campos de reembolso. Verificado en dos transacciones reales el 2026-08-14: después de anular, el pago lleva
{ "status": "voided", "refunded_amount": 25, "refund_status": "full" }
aunque no se reembolsó nada. Así que refund_status === 'full' no significa "esto fue
reembolsado" - es igual de cierto para una anulación. Ramificá según status (voided vs
refunded vs partially_refunded) y tratá los campos de reembolso como el monto devuelto al
tarjetahabiente por cualquier vía, no como evidencia de qué operación se usó. Un reporte que cuente
reembolsos por refund_status va a contar anulaciones como reembolsos sin avisar, y las dos son
comercialmente distintas: una anulación nunca llega al estado de cuenta del cliente, un reembolso sí.
El webhook payment.voided llegó como un segundo después en ambas transacciones, con los mismos campos.
13.4 Reembolso
POST /api/connect/v1/payments/{transaction_id}/refund
{ "amount": 500.00, "reason": "Partial return" }
amount es obligatorio, mínimo 0.01, hasta el monto reembolsable restante. Un reembolso parcial deja
partially_refunded; uno total pone refunded. 422 bajo errors.amount (debajo del mínimo, arriba
del restante) o errors.refund (transacción anulada, no reembolsable).
El reembolso funciona igual para una transacción creada por cualquier modo, incluido un checkout
embebido o un cobro con tarjeta guardada. No hay un endpoint de reembolso aparte para cobros con
token: usá acá como transaction_id el id que devolvió el cobro.
13.5 Recibos
GET /payments/{transaction_id}/receipt devuelve payment_id, transaction_id y un receipt_url
público. Agregá /download para el PDF. Los recibos sobreviven a una anulación o un reembolso
mientras el cobro original siga teniendo una transacción.
13.6 Una nota sobre los identificadores
Dos referencias distintas, y mezclarlas es el error más común:
- El
idde pago numérico es paraGET /payments/{id}. - El UUID
transaction_ides para anulación, reembolso y recibos.
Vale la pena guardar los dos cuando se cobra un pago. El listado (9.2) puede recuperar un id perdido, pero solo si sabés qué estás buscando.
