ROKIConnect
Conectores Empezar API SDK Migrar MCP Portal EN

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:

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á por code. 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:

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.