Magazín
Technika3 min čtení

Událostmi řízené záměrně: jak se z jedné platby stane věrnost, udržení klienta a účtenka

Rezervační platforma stojí a padá s konzistencí. Takhle se jediná platba přes Stripe rozvětví do věrnostních bodů, udržení klienta, účtenky a zápisu do knih, proč je každý dopad podmíněný skutečnou změnou stavu a kam architekturu posuneme dál.

TTTechnický tým Bookatu

Když klient zaplatí za rezervaci, zálohu, balíček nebo členství, měla by ta jediná událost pohnout celým systémem. Má mu narůst věrnostní zůstatek. Má se zase počítat jako aktivní, ne spící. Má dostat účtenku. A peníze mají přistát v knihách kvůli párování. Když jednu z těch věcí zvoráte, produkt potichu ztrácí důvěru: člen je strhnutý, ale kredity se nikdy neobjeví, nebo opakovaný pokus od Stripe započítá prodej dvakrát.

Nějakou dobu byl každý z těch kousků vlastním ostrovem. Webhook označil věci jako zaplacené a tím skončil. Udělali jsme proto z platby událost a nechali zbytek systému, ať na ni reaguje.

Vzorec: rozvětvit, ale jen při skutečné změně stavu

Diagram: platba přes Stripe dorazí do idempotentního webhooku, který volá applyPaidRipple a rozvětví se do věrnosti, udržení klienta, účtenky a účetní knihy.
Jedna platba, čtyři dopady. Webhook je šev; vlna je best-effort a nikdy neblokuje cestu peněz.

Stripe posílá checkout.session.completed do jednoho webhooku. Handler nejdřív provede zápis kritický pro peníze (označit zálohu jako zaplacenou, překlopit fakturu na zaplaceno, přidělit kredit z balíčku) a pak zavolá jedinou sdílenou funkci applyPaidRipple pro stranu zapojení klienta. Vlna je best-effort: každý krok je zabalený tak, aby se selhání zalogovalo a spolklo, protože rozhozený poskytovatel e-mailů nám nikdy nesmí zabránit zaznamenat skutečnou platbu.

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.

Idempotence je celý ten vtip

Stripe doručí stejnou událost víckrát. To je funkce, ne chyba, a znamená to, že každý handler musí být bezpečné spustit dvakrát. Chrání nás dvě vrstvy. Zaprvé se id události zapisuje do tabulky zpracovaných událostí s unikátním omezením, takže opakované doručení úplně stejné události se zkratuje. Zadruhé, a to je důležitější, každá cesta podmiňuje svou vlnu vlastní skutečnou změnou stavu: booleanem, který je pravdivý jen napoprvé, kdy se řádek doopravdy překlopí.

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.

Klíč na odstranění duplicit není příjemný bonus. Je to rozdíl mezi tím, že členovi jednou poděkujete, a tím, že mu dvakrát strhnete peníze.

Kam to směřuje

Dnes je vlna rozvětvením uvnitř procesu, což je právě tolik mechaniky, kolik odpovídá tomu, kde jsme: je to jednoduché, transakční a snadno se o tom přemýšlí. Ale webhook je záměrně šev a roadmapa z toho švu udělá odolnou sběrnici událostí. Tvar zůstane stejný; doručování zesílí.

  • Fronta před každým dopadem (věrnost, udržení klienta, zprávy), takže pomalý konzument nikdy nezdrží webhook a opakované pokusy běží automaticky s odstupem.
  • Tabulka outbox zapsaná ve stejné transakci jako peníze a pak přeposlaná na sběrnici, takže se dopad nikdy neztratí mezi stavem zaplaceno a publikováno.
  • Rozdělení nejtěžších konzumentů (notifikace, skórování udržení klientů) do vlastních nasaditelných služeb, jak poroste zátěž, zatímco hlavní zápis rezervace zůstane jedním silně konzistentním celkem.
  • Spravovaná vrstva událostí a front v našem cloudu, takže škálování bude změna konfigurace, ne migrace.

Poučení, které platí napříč tím vším: držte cestu peněz silně konzistentní a synchronní a všechno, co na ní visí, dělejte řízené událostmi, idempotentní a best-effort. Platba je fakt. Všechno dobré, co z ní plyne, je reakce, a reakce musí být bezpečné přehrát znovu.

vývojudálostmi řízenéarchitekturastripeidempotenceroadmapa
Chcete to uvést do praxe?

Bookatu vám dá rezervační stránku ve vašem stylu, zálohy, členství, dárkové poukazy a připomínky, s 0% provizí z vašich rezervací.

Začít zdarma