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:

  1. Crea un registro de pago interno con estado processing
  2. Extrae los datos necesarios: monto, moneda, referencia de la orden
  3. 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:

  1. Recibe la solicitud en el modelo de dominio interno
  2. Traduce al formato que el proveedor espera
  3. Invoca la API del proveedor
  4. 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:

  1. El proveedor devuelve un estado pending o processing
  2. El microservicio de pagos guarda este estado intermedio
  3. El proveedor procesa el pago (puede tardar segundos o minutos)
  4. 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:

  1. Recibe la petición HTTP del proveedor
  2. Verifica la firma del webhook (para asegurar que viene del proveedor real)
  3. Pasa el evento al ACL para traducción
  4. 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 proveedorError interno
card_declinedPaymentDeclined
insufficient_fundsPaymentDeclined
rate_limitProviderTemporarilyUnavailable
invalid_requestPaymentConfigurationError

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.