4 · Desembolso en GTQ
Fase 4 del flujo. Los GTQ ya están en la cuenta de Felix en Nexa; ahora Tesser instruye a Nexa que los desembolse.
El desembolso es sincrónico. Nexa lo ejecuta y devuelve el resultado en
la respuesta de esa misma llamada. No hay ningún estado pendiente que
consultar ni ningún webhook de avance que esperar. Llega un webhook en
exactamente un caso: una falla que aparece en la red de pagos después de una
respuesta COMPLETED.
Alcance de esta integración: un solo beneficiario fijo
En esta fase Nexa siempre paga al mismo beneficiario, configurado una sola vez fuera de banda y que nunca aparece en el cuerpo de una llamada de API. Ese beneficiario es el socio de desembolso, que ejecuta el desembolso de última milla hacia los receptores de remesas individuales.
Nexa no participa en la última milla. Todos los desembolsos van al mismo lugar, así que en este contrato no hay datos de beneficiario por destinatario, ni objetos de destino, ni campos de cumplimiento del beneficiario. Que Nexa asuma los desembolsos de remesas de última milla es una etapa posterior del programa y queda fuera de alcance aquí. Véase alcance del MVP.
Paso 5 · Crear el pago [N]
Operación: POST /v1/payments: Crear pago (op 5)
· lo aloja Nexa, lo llama Tesser.
La solicitud lleva tesser_quote_id y gtq_amount. El monto es
redundante a propósito: Nexa valida que sea igual al monto acreditado en
su propio evento quote.credit.confirmed y, si no coincide, devuelve
422 AMOUNT_MISMATCH. Un monto explícito dentro de una instrucción que mueve
dinero es un seguro barato contra errores por ID de cotización equivocado.
- La respuesta es el resultado. Un
201significa que el desembolso se ejecutó — normalmentepayment_status: "COMPLETED"yquote_status: "PAYMENT_COMPLETED", concompleted_aty elnexa_payment_idpara la conciliación. Tesser no consulta en bucle ni espera ningún evento de confirmación. - Condición previa: la cotización debe estar en
CREDIT_CONFIRMED. Antes de eso se obtiene409 QUOTE_NOT_CREDITED; una cotización rechazada da409 QUOTE_REJECTED. - Un solo pago por cotización en el MVP. Un reintento devuelve
201con el objeto de pago actual; enviar ungtq_amountdistinto cuando ya existe un pago da409 PAYMENT_CONFLICT. - Un desembolso rechazado también vuelve en esta misma llamada. Como el
dinero se mueve dentro de la solicitud, Nexa responde
201conpayment_status: "FAILED"yquote_status: "PAYMENT_FAILED"cuando rechaza el desembolso o no puede ejecutarlo durante la llamada. Una falla solo es un webhook cuando aparece en la red de pagos después de una respuestaCOMPLETED. - Una llamada que expira se reintenta, y el reintento se une a la ejecución
que ya está en curso. Como el desembolso ocurre dentro de la llamada, un
tiempo de espera agotado en la red deja a Tesser sin resultado mientras
Nexa quizá sigue ejecutando. Por eso un reintento que lleva el mismo
tesser_quote_idse une a esa ejecución en curso y devuelve su resultado final: nunca inicia un segundo desembolso y nunca falla solo porque ya haya uno en ejecución. No existe una lectura del pago durante la ejecución, porque la lectura del pago [N] usa como llavenexa_payment_id, que solo existe una vez que llega la respuesta. La lectura de la cotización [N] sí es alcanzable a mitad de la ejecución, pero no es informativa: ningún estado intermedio es representable, así que sigue reportandoCREDIT_CONFIRMED. Si el reintento también expira, la recuperación es otro reintento o la lectura de la cotización una vez que exista un resultado. Si Nexa puede implementar esta unión es la pregunta abierta 5.
Qué puede salir mal: crear el pago
error_code | HTTP | Cuándo |
|---|---|---|
VALIDATION_ERROR | 400 | Monto mal formado o UUID inválido. |
UNAUTHENTICATED | 401 | Falta el certificado de cliente o la llave de API del cliente, o son inválidos. |
QUOTE_NOT_FOUND | 404 | No existe ninguna cotización con este tesser_quote_id. |
QUOTE_NOT_CREDITED | 409 | La acreditación todavía no llega a CREDIT_CONFIRMED. |
QUOTE_REJECTED | 409 | La cotización está en REJECTED. |
PAYMENT_CONFLICT | 409 | La cotización ya tiene otro pago distinto. |
AMOUNT_MISMATCH | 422 | El gtq_amount no es igual al monto acreditado. |
INTERNAL_ERROR | 500 | Falla inesperada del lado de Nexa. |
Paso 6 · Falla después de una respuesta COMPLETED [T]
Operación: POST /v1/webhooks/nexa/{organizationId}: Recibir webhook de Nexa (op 4)
· lo aloja Tesser, lo llama Nexa.
Este es el único webhook del tramo de desembolso, y Nexa lo envía solo
cuando algo sale mal después de una respuesta exitosa: el desembolso se
ejecutó y se reportó como COMPLETED, y luego falló o fue devuelto en la red
de pagos. Un rechazo durante la llamada misma nunca se convierte en webhook;
vuelve de forma sincrónica en la creación del pago.
event_type | Significado | data |
|---|---|---|
payment.status.changed | El desembolso falló o fue devuelto después del hecho. | tesser_quote_id, nexa_payment_id, payment_status (FAILED o RETURNED), quote_status (PAYMENT_FAILED o PAYMENT_RETURNED), status_metadata (objeto de forma libre: referencias de la red de pagos, motivos de falla) |
No existe un webhook de éxito. Una cotización que llega a
PAYMENT_COMPLETED y se queda ahí simplemente terminó; el silencio significa
éxito.
status_metadata es el único lugar del contrato donde se espera contenido de
forma libre, porque las referencias de las redes de pago y los motivos de
falla sí varían de verdad.
La recuperación desde PAYMENT_FAILED y PAYMENT_RETURNED es manual y
fuera de banda en el POC. No existen semánticas de reintento en la API
(pregunta abierta 7).
Qué red o redes de pago guatemaltecas se usan, si RETURNED existe realmente
en esa red, cuánto tiempo después de una respuesta COMPLETED puede aparecer
todavía una falla y el contenido de status_metadata son la
pregunta abierta 5.
Qué puede salir mal: notificación de falla
| Situación | Comportamiento |
|---|---|
VALIDATION_ERROR 400 | Envoltorio o data mal formados para ese tipo de evento. |
UNAUTHENTICATED 401 | Falta la identidad mTLS, no se reconoce, o no coincide con {organizationId}. |
INTERNAL_ERROR 500 | Falla del lado de Tesser. Nexa reintenta. |
| Entrega duplicada | Se deduplica por event_id; igual se confirma con 2xx. |
Un evento de falla para una cotización que ya está en PAYMENT_FAILED | Se deduplica o se trata como una operación sin efecto; el estado no se mueve dos veces. |
Recuperar el estado
Si se pierde el webhook de falla, el opcional
GET /v1/payments/{nexa_payment_id}
[N] devuelve el payment_status y el status_metadata actuales, y
GET /v1/quotes/{tesser_quote_id}
[N] devuelve la cotización consolidada, incluido su subobjeto payment.
Siguiente: 5 · Tesorería.