Diseña un sistema de procesamiento de pagos: recorre la arquitectura y dónde más importan la idempotencia y la consistencia
El dinero nunca puede cobrarse dos veces ni perderse en silencio, así que cada salto entre tu servicio y el banco necesita una clave de idempotencia y un job de reconciliación como red de seguridad.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
Diseña un sistema de procesamiento de pagos: recorre la arquitectura y dónde más importan la idempotencia y la consistencia
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#La única regla a la que todo lo demás sirve
La propiedad de corrección primaria de un sistema de pagos no es "rápido" ni "escalable": es esta: nunca cobrar dos veces por una misma intención de pago, y nunca perder el rastro de dinero que se movió. Cada decisión de arquitectura de abajo — las claves de idempotencia, la máquina de estados, el patrón outbox, la reconciliación nocturna — existe para servir a esa única regla en condiciones donde las redes no son confiables y un "timeout" de una pasarela externa es, por naturaleza, ambiguo: ¿el cobro ocurrió o no?
#Arquitectura de alto nivel
Las piezas centrales: un Servicio de Pagos que orquesta el flujo y es dueño del ciclo de vida del pago; un Ledger (libro mayor) — el registro durable, de solo-append, de doble entrada de cada movimiento de dinero, y la verdadera fuente de la verdad (el "estado" que cachea el servicio de pagos es una proyección del ledger, no al revés); una pasarela de pagos externa (Stripe, Adyen, un procesador de red de tarjetas) que no controlas y debes tratar como no confiable; un receptor de webhooks para las confirmaciones asíncronas de la pasarela; un bus de eventos / outbox para publicar eventos internos (pedido confirmado, reembolso emitido) sin una condición de carrera de doble escritura; y un job de reconciliación que periódicamente compara tu ledger contra los reportes de liquidación de la pasarela y marca cualquier cosa que no coincida.
#La máquina de estados del pago
Un pago no es un booleano. Modélalo explícitamente:
| Estado | Significado |
|---|---|
CREATED | Intención de pago registrada, nada enviado aún a la pasarela |
PENDING | Petición en vuelo hacia la pasarela |
AMBIGUOUS | La llamada a la pasarela hizo timeout, resultado desconocido, hay que resolverlo antes de reintentar |
AUTHORIZED | La pasarela aprobó el cobro; fondos reservados, aún no capturados |
CAPTURED | Fondos realmente capturados/liquidados |
FAILED | La pasarela rechazó, o una ambigüedad se resolvió como fallo |
REFUNDED | Revertido después de la captura |
DISPUTED | El titular de la tarjeta inició un contracargo |
Cada transición es una fila que se agrega al ledger, no una actualización in-place — se quiere el historial completo, no solo el estado actual, porque las disputas y las auditorías preguntan "qué pasó, en qué orden, y por qué".
#El flujo insignia: idempotencia, manejo de timeouts, webhooks, reconciliación
#Dónde tiene que vivir realmente la idempotencia
La idempotencia no es un solo mecanismo — tiene que existir en cada salto que se pueda reintentar:
- Cliente → Servicio de Pagos: el cliente genera la clave de idempotencia una sola vez (típicamente por intento de checkout) y reenvía exactamente la misma clave en cada reintento. Una restricción
UNIQUEsobreidempotency_keyen la base de datos es lo que realmente lo hace cumplir bajo concurrencia — dos peticiones simultáneas con la misma clave harán que una tenga éxito en el INSERT y la otra falle, y la que falla simplemente consulta y devuelve el resultado de la ganadora en vez de dar un error. - Servicio de Pagos → Pasarela: los reintentos propios ante una llamada a la pasarela que hizo timeout deben reutilizar la misma clave de idempotencia, y la pasarela está contractualmente obligada a deduplicar por ella (Stripe, por ejemplo, guarda las claves de idempotencia durante 24 horas). Sin esto, un timeout de red de tu lado — donde el cobro sí tuvo éxito pero la respuesta nunca llegó — se convierte en un doble cobro en el momento en que reintentas ingenuamente.
- Pasarela → Servicio de Pagos (webhooks): la entrega de webhooks es al-menos-una-vez, nunca exactamente-una-vez. El mismo evento
charge.capturedpuede llegar, y llegará, dos veces. Manejarlo bien significa comprobar el estado actual antes de aplicar la transición (¿ya está CAPTURED? → 200 OK, no-op) en vez de asumir que cada entrega de webhook es nueva.
#El patrón outbox: por qué no basta con "escribir en la BD y luego publicar un evento"
Si el servicio de pagos actualiza su propia base de datos y por separado publica un evento a una cola, hay una ventana donde el commit de la BD tiene éxito y el proceso se cae antes de publicar — el evento se pierde, y nada río abajo (confirmación de pedido, liberación de inventario, notificaciones) se dispara jamás, aunque el dinero sí se haya movido. El patrón outbox soluciona esto escribiendo el evento en una tabla outbox dentro de la misma transacción de base de datos que el cambio de estado, y un proceso relay separado sigue esa tabla y publica al bus de eventos, marcando las filas como enviadas. La publicación se puede reintentar libremente porque ahora está desacoplada de la transacción original — se cambió una condición de carrera de doble escritura por un relay de al-menos-una-vez, un problema que los consumidores de eventos pueden manejar siendo ellos mismos idempotentes.
#La reconciliación es la red de seguridad, no el plan
Incluso con claves de idempotencia en todas partes, trata un job de reconciliación nocturno (u horario, para volúmenes altos) como obligatorio, no opcional. Este obtiene el reporte de liquidación de la pasarela y lo compara línea por línea contra el ledger interno: un cobro que la pasarela muestra como capturado pero que tu ledger tiene atascado en PENDING significa que se perdió un webhook o que hay un bug en la lógica de reintentos; un cobro en tu ledger sin liquidación correspondiente en la pasarela significa lo contrario. La reconciliación no previene la inconsistencia — de eso se encarga la maquinaria de idempotencia en tiempo real —, es el mecanismo que garantiza que la inconsistencia, cuando inevitablemente se cuela por algún caso límite, se detecte en horas en vez de aparecer como una queja de cliente semanas después.