← VOLVER

Estudio de caso

Orquestador de pagos idempotente

Arquitectura backend • Sistemas transaccionales

Imágenes

Galería no disponible.

Resumen

Diseñé e implementé un proceso de pagos seguro para reintentos con estrictas garantías de idempotencia bajo envíos concurrentes. Claves de idempotencia en el request; outbox para llamadas al proveedor; reconciliación por webhook con event_id. Garantía: sin doble cobro ante reintentos del cliente, webhooks duplicados o falla de red entre commit en DB y llamada al proveedor.

Client
   ↓
API (Django)
   ↓
PostgreSQL (transactions + idempotency)
   ↓
Outbox Table
   ↓
Worker (Celery)
   ↓
Payment Provider

Arquitectura y diseño

Transactional Flow Overview

Internal System
Internal System

System Invariants

  • ·A payment intent cannot transition from failed to succeeded.
  • ·Reservation "paid" is set only after a verified webhook; frontend and redirect cannot set it.
  • ·Webhook events are processed idempotently by provider event_id.
  • ·Availability for a slot is updated under pessimistic lock (SELECT FOR UPDATE); no optimistic commit.
  • ·Idempotency keys are scoped per client and stored; duplicate key returns original response.
  • ·Payment and reservation state changes for a webhook occur in a single database transaction.

Architecture Decision Records

  • ADR-01Clave de idempotencia requerida para todas las solicitudes de inicio de pago
  • ADR-02Outbox para llamadas a proveedores; sin efectos secundarios en el ciclo del request
  • ADR-03Procesamiento de webhooks idempotente por event_id del proveedor
  • ADR-04Límites de transacción: una transacción DB por transición de estado
  • ADR-05Reconciliación y manejo de modos de falla

Escala y restricciones

Volumen de requests
Reintentos del cliente y del proveedor; requests y webhooks pueden llegar duplicados o fuera de orden.
Concurrencia
Único escritor por clave de idempotencia; outbox para llamadas al proveedor. Sin doble cobro bajo reintentos.
Dependencias externas
API del proveedor de pago; webhooks. Posibles fallos de red entre el commit en DB y la llamada al proveedor.
Modos de fallo
Timeout o proveedor inalcanzable tras el commit → reintento por outbox. Webhook duplicado → idempotente por event_id. Reintento del cliente → la misma clave devuelve el resultado almacenado.
Consistencia de datos
Estado de pago y outbox en la misma DB; commit antes de la llamada al proveedor o al outbox. Sin doble cobro; la clave de idempotencia es la única fuente de resultado para el request.

Qué se rechazó explícitamente

  • Procesamiento simple por request sin claves de idempotencia

    Los reintentos y envíos duplicados causarían doble cobro; la clave a nivel de negocio es obligatoria.

  • Manejar reintentos solo en la capa HTTP

    El estado de la aplicación puede igualmente doble-aplicarse; la idempotencia debe aplicarse en la capa de orquestación con una clave estable.

  • Depender enteramente de las garantías del proveedor

    La semántica de los proveedores varía y puede no garantizar exactly-once; nosotros somos dueños de la garantía de no doble cobro.

  • Procesar efectos secundarios dentro del ciclo del request

    Si el proceso muere tras el commit en DB pero antes de la llamada al proveedor, el estado es inconsistente; el outbox desacopla y permite reintento sin re-ejecutar el request.