Journal
Technique3 min de lecture

Pensé par événements : comment un paiement devient fidélité, rétention et reçu

Une plateforme de réservation vit ou meurt sur sa cohérence. Voici comment un seul paiement Stripe se diffuse en points de fidélité, en rétention, en reçu et en écriture comptable, pourquoi chaque effet est conditionné à un vrai changement d'état, et où nous emmenons l'architecture ensuite.

LTL'équipe technique de Bookatu

Quand un client paie une réservation, un acompte, un forfait ou un abonnement, cet unique événement doit faire bouger tout le système. Son solde de fidélité doit augmenter. Il doit redevenir un client actif, plus un client perdu. Il doit recevoir un reçu. Et l'argent doit atterrir dans les livres pour le rapprochement. Ratez l'un de ces points et le produit perd discrètement la confiance : un abonné est débité mais ses crédits n'apparaissent jamais, ou une nouvelle tentative de Stripe compte une vente en double.

Pendant un temps, chacun de ces points était une île. Le webhook marquait les choses comme payées et s'arrêtait là. Nous avons donc fait du paiement un événement, et laissé le reste du système y réagir.

Le principe : diffuser, mais seulement sur un vrai changement d'état

Schéma : un paiement Stripe arrive sur un webhook idempotent, qui appelle applyPaidRipple et diffuse vers la fidélité, la rétention, le reçu et la comptabilité.
Un paiement, quatre effets. Le webhook est la jointure ; la propagation se fait au mieux et ne bloque jamais le chemin de l'argent.

Stripe envoie checkout.session.completed à un seul webhook. Le gestionnaire effectue d'abord l'écriture critique pour l'argent (marquer l'acompte payé, passer la facture en payée, créditer le forfait), puis appelle une unique fonction partagée, applyPaidRipple, pour tout ce qui touche à l'engagement. La propagation se fait au mieux : chaque étape est encapsulée pour qu'un échec soit journalisé puis absorbé, car un fournisseur d'e-mails capricieux ne doit jamais nous empêcher d'enregistrer un paiement réel.

ts
// 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.

Tout se joue sur l'idempotence

Stripe enverra le même événement plus d'une fois. C'est une fonctionnalité, pas un défaut, et cela veut dire que chaque gestionnaire doit pouvoir s'exécuter deux fois sans dommage. Deux couches nous protègent. D'abord, l'identifiant de l'événement est enregistré dans une table d'événements traités avec une contrainte d'unicité, donc un renvoi du même événement s'arrête immédiatement. Ensuite, et c'est le plus important, chaque parcours conditionne sa propagation à son propre changement d'état réel : un booléen qui n'est vrai que la première fois où la ligne bascule vraiment.

ts
// 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 clé de déduplication n'est pas un confort. C'est la différence entre un abonné remercié une fois et un abonné débité deux fois.

Où tout cela nous mène

Aujourd'hui, la propagation est une diffusion dans le processus, ce qui correspond exactement au niveau de machinerie qu'il nous faut : c'est simple, transactionnel et facile à suivre. Mais le webhook est délibérément une jointure, et la feuille de route transforme cette jointure en bus d'événements durable. La forme reste la même ; la livraison devient plus solide.

  • Une file d'attente devant chaque effet (fidélité, rétention, messages), pour qu'un consommateur lent ne retienne jamais le webhook et que les nouvelles tentatives soient automatiques, avec temporisation croissante.
  • Une table de sortie écrite dans la même transaction que l'argent, puis relayée vers le bus, pour qu'un effet ne soit jamais perdu entre « payé » et « publié ».
  • Le découpage des consommateurs les plus lourds (notifications, score de rétention) en services déployables à part à mesure que la charge grandit, pendant que l'écriture centrale de la réservation reste une unité unique et fortement cohérente.
  • Une couche d'événements et de files managée sur notre cloud, pour que la montée en charge soit un changement de configuration et non une migration.

La leçon vaut pour l'ensemble : gardez le chemin de l'argent fortement cohérent et synchrone, et faites en sorte que tout ce qui en dépend soit événementiel, idempotent et au mieux. Un paiement est un fait. Tout ce qui en découle de bon est une réaction, et les réactions doivent pouvoir être rejouées sans danger.

ingénierieévénementielarchitecturestripeidempotencefeuille de route
Envie de mettre tout ça en pratique ?

Bookatu vous donne une page de réservation à votre image, des acomptes, des abonnements, des cartes cadeaux et des rappels, avec 0 % de commission sur vos réservations.

Commencer gratuitement