Pago con proveedor externo
Flujo de pago con un proveedor externo: desde el microservicio de órdenes hasta el proveedor de pagos, pasando por el ACL y el manejo de callbacks.
Por qué el pago externo es un flujo especial
Integrar un proveedor de pagos externo (Stripe, PayPal, MercadoPago) introduce desafíos que no existen en la comunicación entre servicios internos:
- El proveedor tiene su propio modelo de datos y API
- Los tiempos de respuesta son impredecibles
- La comunicación incluye callbacks asíncronos (webhooks)
- El dominio interno no debe contaminarse con el modelo del proveedor
Este flujo muestra cómo el Anti-Corruption Layer (ACL) protege el dominio y cómo se manejan los callbacks de forma segura.
El flujo completo
sequenceDiagram
participant ORD as ms-orders
participant EB as Event Bus
participant PAY as ms-payments
participant ACL as ACL (Adapter)
participant EXT as Proveedor Externo
participant WH as Webhook Handler
ORD->>EB: OrderPlaced
EB->>PAY: OrderPlaced
PAY->>PAY: Crear intención de pago
PAY->>ACL: Solicitar cobro
ACL->>ACL: Traducir al modelo del proveedor
ACL->>EXT: POST /charges (API del proveedor)
EXT-->>ACL: charge_id, status: pending
ACL-->>PAY: paymentId, status: processing
Note over EXT: El proveedor procesa el pago
EXT->>WH: Webhook: charge.completed
WH->>WH: Verificar firma del webhook
WH->>ACL: Traducir evento externo
ACL->>PAY: Pago completado
PAY->>PAY: Actualizar estado
PAY->>EB: PaymentCompleted
EB->>ORD: PaymentCompleted
ORD->>ORD: Actualizar estado de la orden
Fase 1: Iniciar el pago
Microservicio de pagos (ms-payments)
Cuando recibe el evento OrderPlaced:
- Crea un registro de pago interno con estado
processing - Extrae los datos necesarios: monto, moneda, referencia de la orden
- Invoca al ACL para solicitar el cobro al proveedor externo
Anti-Corruption Layer (ACL)
El ACL es el adaptador entre el dominio interno y el proveedor externo:
- Recibe la solicitud en el modelo de dominio interno
- Traduce al formato que el proveedor espera
- Invoca la API del proveedor
- Traduce la respuesta del proveedor al modelo interno
Ejemplo de traducción:
Modelo interno:
{
"orderId": "order-456",
"amount": 150.00,
"currency": "USD",
"customerId": "cust-789"
}
Modelo del proveedor (Stripe):
{
"amount": 15000,
"currency": "usd",
"metadata": { "order_id": "order-456" },
"customer": "cus_stripe_789"
}
El ACL maneja las diferencias: el proveedor usa centavos en lugar de decimales, minúsculas para la moneda, y tiene su propio sistema de IDs de cliente.
Fase 2: Esperar el resultado
Muchos proveedores de pago no confirman el cobro de forma inmediata. El flujo se vuelve asíncrono:
- El proveedor devuelve un estado
pendingoprocessing - El microservicio de pagos guarda este estado intermedio
- El proveedor procesa el pago (puede tardar segundos o minutos)
- Cuando termina, el proveedor envía un webhook al sistema
Fase 3: Recibir el webhook
Webhook Handler
El webhook handler es un endpoint público que recibe notificaciones del proveedor:
- Recibe la petición HTTP del proveedor
- Verifica la firma del webhook (para asegurar que viene del proveedor real)
- Pasa el evento al ACL para traducción
- El ACL convierte el evento externo al modelo interno
Verificación de firma
Los proveedores firman sus webhooks para prevenir falsificaciones:
// El proveedor envía un header con la firma
X-Signature: sha256=abc123...
// El handler verifica la firma usando el secret compartido
const expectedSignature = hmac(secret, requestBody);
if (signature !== expectedSignature) {
return 401; // Rechazar
}
Traducción del evento
El ACL traduce el evento del proveedor al modelo interno:
Evento del proveedor:
{
"type": "charge.succeeded",
"data": {
"id": "ch_stripe_123",
"amount": 15000,
"currency": "usd",
"status": "succeeded",
"metadata": { "order_id": "order-456" }
}
}
Evento interno:
{
"type": "PaymentCompleted",
"payload": {
"paymentId": "pay-internal-789",
"orderId": "order-456",
"amount": 150.00,
"currency": "USD",
"providerReference": "ch_stripe_123"
}
}
El rol del ACL en detalle
El ACL no es solo un traductor de formatos. También encapsula:
Manejo de errores del proveedor
Cada proveedor tiene sus propios códigos de error. El ACL los traduce a errores de dominio:
| Error del proveedor | Error interno |
|---|---|
card_declined | PaymentDeclined |
insufficient_funds | PaymentDeclined |
rate_limit | ProviderTemporarilyUnavailable |
invalid_request | PaymentConfigurationError |
Reintentos
Si la API del proveedor falla por un error transitorio (timeout, 503), el ACL puede reintentar con backoff exponencial. El microservicio de pagos no necesita conocer esta lógica.
Cambio de proveedor
Si se decide cambiar de Stripe a otro proveedor, solo se modifica el ACL. El microservicio de pagos y el resto del sistema no se ven afectados.
Escenarios de error
Webhook no llega
Si el proveedor no envía el webhook (o se pierde), el sistema necesita un mecanismo de reconciliación:
- Un job periódico consulta al proveedor por pagos pendientes
- Si el pago ya fue procesado, actualiza el estado interno
- Si pasó demasiado tiempo, marca el pago como
timeout
Webhook duplicado
Los proveedores pueden enviar el mismo webhook más de una vez. El handler debe ser idempotente: si ya procesó ese evento, lo ignora.
Pago rechazado
Si el proveedor rechaza el pago, el microservicio de pagos publica PaymentFailed y el microservicio de órdenes ejecuta las compensaciones correspondientes (liberar inventario, notificar al usuario).
Resumen
El flujo de pago con proveedor externo muestra cómo el ACL protege el dominio de las particularidades de sistemas externos. La comunicación combina llamadas sincrónicas (iniciar el cobro) con callbacks asíncronos (webhooks). La verificación de firmas, la traducción de modelos y el manejo de errores específicos del proveedor quedan encapsulados en el ACL, permitiendo que el resto del sistema trabaje con un modelo de dominio limpio.