Dirigido por eventos desde el diseño: cómo un pago se convierte en fidelidad, retención y un recibo
Una plataforma de reservas vive o muere por su coherencia. Aquí tienes cómo un único pago de Stripe se abre en puntos de fidelidad, retención, un recibo y un apunte contable, por qué cada efecto depende de un cambio de estado real, y hacia dónde llevamos la arquitectura.
Cuando un cliente paga una reserva, un depósito, un pack o una membresía, ese único evento debería mover el sistema entero. Su saldo de fidelidad debería crecer. Debería volver a contar como activo, no como perdido. Debería recibir un recibo. Y el dinero debería aterrizar en la contabilidad para poder cuadrarlo. Si una de esas cosas falla, el producto pierde confianza en silencio: a un miembro se le cobra pero sus créditos no aparecen nunca, o un reintento de Stripe cuenta una venta por duplicado.
Durante un tiempo cada una de esas piezas era una isla. El webhook marcaba las cosas como pagadas y ahí se paraba. Así que convertimos el pago en un evento y dejamos que el resto del sistema reaccionara a él.
El patrón: repartir, pero solo ante un cambio de estado real

Stripe envía checkout.session.completed a un único webhook. El handler hace primero la escritura crítica para el dinero (marcar el depósito como pagado, pasar la factura a pagada, conceder el crédito del pack) y luego llama a una sola función compartida, applyPaidRipple, para la parte de la relación con el cliente. La propagación es de mejor esfuerzo: cada paso va envuelto para que un fallo se registre y se trague, porque un proveedor de correo inestable nunca puede impedirnos registrar un pago real.
// In the webhook, after the money-critical write succeeds:if (fresh) { await applyPaidRipple({ orgId, customerId, amountCents, loyaltyReason: "Package purchase", receiptTo: email, receiptDedupeKey: `payment_receipt:package:${session.id}`, });}// applyPaidRipple: loyalty -> lastVisitAt bump -> transactional receipt,// each in its own try/catch. The ledger write runs unconditionally below.La idempotencia lo es todo
Stripe va a entregar el mismo evento más de una vez. Eso es una característica, no un fallo, y significa que cada handler tiene que ser seguro de ejecutar dos veces. Nos protegen dos capas. Primero, el id del evento se guarda en una tabla de eventos procesados con una restricción de unicidad, así que una reentrega del mismo evento exacto se corta en seco. Segundo, y más importante, cada vía condiciona su propagación a su propio cambio de estado real: un booleano que solo es verdadero la primera vez que la fila cambia de verdad.
// markInvoicePaid flips 'open' -> 'paid' and returns true ONLY on that// real transition. A redelivery sees 'paid' already and returns false.const flipped = await markInvoicePaid(invoiceId, paymentIntentId, amountTotal);if (flipped) { await applyPaidRipple({ /* ...loyalty, retention, receipt */ });}// So even if the event-id dedupe is bypassed, the ripple still fires once.La clave de deduplicación no es un extra bonito. Es la diferencia entre darle las gracias a un miembro una vez y cobrarle dos.
Hacia dónde va esto
Hoy la propagación es un reparto dentro del proceso, que es la cantidad justa de maquinaria para el punto en el que estamos: es simple, transaccional y fácil de razonar. Pero el webhook es a propósito una costura, y la hoja de ruta convierte esa costura en un bus de eventos duradero. La forma se mantiene; la entrega se refuerza.
- Una cola delante de cada efecto (fidelidad, retención, mensajería), para que un consumidor lento nunca frene el webhook y los reintentos sean automáticos con espera creciente.
- Una tabla outbox escrita en la misma transacción que el dinero y retransmitida después al bus, para que un efecto no se pierda nunca entre 'pagado' y 'publicado'.
- Separar los consumidores más pesados (notificaciones, puntuación de retención) en sus propios servicios desplegables a medida que crece la carga, mientras la escritura central de la reserva sigue siendo una única unidad fuertemente consistente.
- Una capa gestionada de eventos y colas en nuestra nube, para que escalar sea un cambio de configuración y no una migración.
La lección que se sostiene en todo esto: mantén el camino del dinero fuertemente consistente y síncrono, y haz que todo lo que cuelga de él sea dirigido por eventos, idempotente y de mejor esfuerzo. Un pago es un hecho. Todo lo bueno que se deriva de él es una reacción, y las reacciones tienen que poder repetirse sin peligro.