Quiero recorrer el diseño de un motor de conciliación de pagos con suficiente detalle como para ser realmente útil, porque la mayoría de los artículos sobre este tema se quedan en el nivel de "usá claves de idempotencia" sin explicar qué significa eso en un sistema que procesa miles de webhooks concurrentes de tres proveedores de pago diferentes.

Por Qué la Conciliación Es Difícil

El problema en la superficie es simple: hacer coincidir los eventos de pago entrantes con los pedidos en tu base de datos. El problema real es que los eventos de pago llegan en un entorno hostil.

Los webhooks llegan en desorden. Un gateway de pago puede enviar el evento payment.captured antes del evento payment.authorized. Tu sistema no puede asumir que los eventos reflejan la secuencia real de transiciones de estado.

Los eventos se entregan al menos una vez, no exactamente una vez. Los proveedores de pago reintentan las entregas de webhooks fallidas. Tu manejador recibirá eventos duplicados, a veces con minutos u horas de diferencia. Procesar un evento payment.captured dos veces no debe resultar en doble cumplimiento.

Los errores de red también causan reintentos de tu lado. Si tu manejador de webhooks falla después de actualizar la base de datos pero antes de devolver un 200, el proveedor reintentará. Necesitás manejar el reprocesamiento del mismo evento sin corrupción.

El estado del gateway y tu estado pueden desincronizarse. Si un reembolso se procesa directamente a través del panel del gateway de pago sin pasar por tu sistema, tus registros locales ahora son incorrectos. El motor de conciliación debe ser capaz de detectar y reparar esta desviación.

La Fundación: Claves de Idempotencia

Cada operación en un sistema de pago debe ser idempotente — segura de ejecutar más de una vez con el mismo resultado. Esto no es opcional.

Para los webhooks entrantes, uso el ID de evento proporcionado por el gateway de pago como clave de idempotencia. Antes de procesar cualquier evento, verifico en una tabla processed_webhook_events ese ID. Si existe, devuelvo 200 inmediatamente sin reprocesar. Si no existe, lo inserto y proceso el evento dentro de la misma transacción de base de datos.

Esta es la única forma confiable de manejar la entrega duplicada. Una caché en memoria no es suficiente — si tu aplicación se reinicia entre la escritura en caché y la actualización de la base de datos, perdiste el registro de deduplicación.

La Máquina de Estados

El estado del pago no es un booleano. Lo modelo como una máquina de estados con transiciones explícitas y guardas de transición.

Un pago puede estar en estados: pending, authorized, captured, partially_refunded, refunded, failed, disputed. Las transiciones entre estados siguen reglas estrictas — no podés mover de refunded a captured, y disputed requiere revisión humana antes de cualquier otra transición.

Implemento esto como una máquina de estados en la capa de dominio, separada de la base de datos y la API del gateway de pago. La máquina de estados valida cada intento de transición y lanza una excepción de dominio si se intenta una transición inválida. Esto resuelve el problema de webhooks fuera de orden: si un evento captured llega para un pago que ya está refunded, la máquina de estados lo rechaza y lo marca para revisión en lugar de corromper el registro.

Manejo de Condiciones de Carrera

En un sistema que procesa webhooks concurrentes, dos eventos para el mismo pago pueden llegar simultáneamente y procesarse en diferentes instancias de aplicación. Sin coordinación, se producen condiciones de carrera.

Uso bloqueo pesimista a nivel de base de datos en el registro de pago durante las transiciones de estado. En SQL Server y PostgreSQL esto es SELECT ... FOR UPDATE (o WITH (UPDLOCK, ROWLOCK) en SQL Server). El bloqueo de fila se adquiere al inicio de la transacción y se mantiene hasta el commit. El segundo manejador concurrente bloquea hasta que el primero completa, luego lee el estado actualizado — en ese punto la máquina de estados determina si el segundo evento todavía es aplicable.

El bloqueo optimista (comparación de versión de fila) es una alternativa, pero en escenarios de alta concurrencia de pagos, prefiero la garantía explícita del bloqueo pesimista. El costo de rendimiento es aceptable dado que la sección crítica es la transición de estado en sí misma, que es rápida.

El Manejador de Webhooks Asíncrono

Los manejadores de webhooks deben ser rápidos y simples. Mi patrón:

  1. Validar la firma del webhook (rechazar todo lo que no esté firmado)
  2. Escribir el payload del evento crudo en una tabla webhook_queue
  3. Devolver 200 inmediatamente

Un worker de fondo separado lee de webhook_queue, procesa eventos usando la máquina de estados y maneja errores. Esto desacopla la recepción del webhook del procesamiento — si el procesamiento falla, el evento permanece en la cola para reintento sin que el gateway reciba nunca una respuesta no-200.

El Barrido de Conciliación

Incluso con todo lo anterior, la desviación de estado es posible. El barrido de conciliación es un trabajo programado que se ejecuta cada pocas horas y compara tus registros de pago locales con los registros del gateway de pago a través de su API de reportes.

Para cada discrepancia, el barrido crea un registro reconciliation_exception con el estado local, el estado del gateway y la diferencia. Las excepciones caen en categorías automatizadas (corregibles por el barrido) y categorías manuales (que requieren revisión humana). Las correcciones automatizadas incluyen marcar pagos como capturados cuando el gateway muestra captura pero el registro local muestra autorizado — un resultado común de un fallo en la entrega del webhook.

Este barrido es la red de seguridad. El pipeline orientado a eventos maneja el 99%+ de los casos correctamente en tiempo real. El barrido captura el resto y proporciona una pista de auditoría que satisface los requisitos de cumplimiento.

Para equipos que trabajan en sistemas financieros y también necesitan pensar en la seguridad de la base de datos junto con la arquitectura, mi artículo sobre prácticas de seguridad de base de datos cubre la capa de infraestructura.

Si estás diseñando o auditando un sistema de pago y querés una revisión de la arquitectura, contactáme.