Verhalen
Techniek3 min lezen

Zo brengen we functies uit die geld raken zonder per ongeluk iemands prijs te veranderen

Een prijsbug is het soort dat je niet meer kunt terugnemen zodra er een kaart is belast. Dit is de discipline waarmee we kortingen, medewerkerprijzen en acties aan de boekingsflow toevoegen zonder dat er één prijs beweegt voordat we dat willen.

HTHet technische team van Bookatu

De meeste bugs kosten je een excuus. Een prijsbug kost je een terugbetaling, een chargeback, en een klant die het getal op het scherm niet meer vertrouwt. De belangen zijn scheef verdeeld, dus is het proces rond alles wat geld raakt bewust trager dan de rest van ons werk. Zo ziet het eruit, met een recente functie als voorbeeld: een actie op een hele categorie, waarbij een salon elke gezichtsbehandeling in één keer in de aanbieding kan zetten.

REBUILT ON THE SERVER, EVERY BOOKINGBase priceStaff priceCategory saleBest couponCharged
De prijs van elke boeking wordt in dezelfde vaste volgorde opgebouwd, en de hele som wordt op de server overgedaan.

Standaard uit, in productie, en doet niets

De eerste regel van de functie is een flag. Hij leest een omgevingsvariabele en staat standaard uit. Dat betekent dat we de code kunnen mergen en uitrollen lang voordat hij iets doet, en elke prijs blijft byte voor byte gelijk aan die van de dag ervoor. Er is geen grote lancering om zenuwachtig van te worden, want de lancering is een aparte, saaie stap die we zetten zodra we er vertrouwen in hebben.

ts
export const FEATURE_LIVE =  process.env.FEATURE_LIVE === "true"; // Read at the one place it matters. When the flag is off, the branch// is skipped entirely: no extra query, no price change, nothing.if (FEATURE_LIVE) {  const pct = await activeCategorySalePercent(orgId, "service", service.category);  if (pct > 0) fullPriceCents = applyCategorySaleCents(fullPriceCents, pct);}
1Behind a flagoff by default2Merge & deploystill off3Audit passes0 money bugs4Flip the flagthe go-liveLive
De lancering is een aparte, saaie stap. De code gaat live lang voordat hij iets doet.

Eén functie, één taak

Het rekenwerk van de korting zit in één pure functie. Hij krijgt centen en een percentage binnen, en hij geeft centen terug. Hij heeft geen database, geen request en geen klok, wat betekent dat je elke rand ervan losstaand kunt testen: een actie van nul, een normale actie, een begrenzing aan de bovenkant, een negatief getal dat nooit zou mogen voorkomen. Als het rekenwerk één kleine functie is die je in je hoofd kunt houden, hoef je er niet meer naar te gissen.

ts
export function applyCategorySaleCents(  baseCents: number,  salePercent: number | null | undefined,): number {  const base = cents(baseCents);  const pct = Math.max(0, Math.min(100, Math.round(salePercent ?? 0)));  if (pct === 0) return base;  return cents((base * (100 - pct)) / 100);}

Altijd opnieuw berekenen op de server

De browser toont een prijs zodat de klant weet wat hij gaat betalen, maar dat getal is nooit meer dan een weergave. Komt er een boeking binnen, dan laadt de server de echte dienst, bepaalt de prijs van de medewerker, past een lopende categorie-actie toe, en rekent dan de korting uit, allemaal op basis van data die hij zelf heeft opgehaald. Heeft iemand onderweg het verzoek aangepast, dan verandert dat niets, want de prijs die de kaart belast wordt was nooit de prijs die de browser stuurde. Dit is de allerbelangrijkste gewoonte in code die geld raakt: de vertrouwensgrens ligt bij de server, en de server maakt de som.

BROWSERShows a price so the clientknows what they will pay.A display only. Never trusted.requestSERVER · SOURCE OF TRUTH1 · Load the real service2 · Resolve staff + sale price3 · Apply the best coupon4 · Charge exactly that
Het getal dat de browser toont is alleen een weergave. De server haalt zijn eigen data op en maakt de som opnieuw.

Probeer het kapot te maken voordat een klant het doet

Geslaagde tests vertellen je dat de gevallen waaraan je dacht werken. Ze vertellen je niets over het geval dat je miste. Dus voordat een prijs-flag aangaat, doen we een vijandige ronde die als enige taak heeft een manier te vinden om het geld verkeerd te laten uitpakken. Aparte controles nemen elk een andere invalshoek: één jaagt op elk pad dat de klant te veel rekent, één op een pad dat het bedrijf geld kost, één controleert of de functie echt inert is als de flag uit staat, en één gaat achter afrondingen en randgevallen aan. Een actie bovenop een kortingscode bovenop een aanbetaling is precies waar een halve cent zich verstopt, dus daar kijken we het scherpst.

New pricing featureOvercharges?Loses money?Leaks when off?Rounding drift?Clean sweep → GO
Vier sceptici, elk een andere manier om ernaast te zitten. Alleen een schone ronde is een GO.

Niets hiervan is slim bedacht. Het is een flag, een kleine functie, een server die zijn eigen rekenwerk doet, en de gewoonte om je eigen werk kapot te proberen te maken voordat het live gaat. Bij elkaar is dat het verschil tussen een functie toevoegen die geld raakt en er wakker van liggen.

feature flagsveilige code rond geldontwerp prijsberekeningvalidatie op de serververtrouwensgrens
Klaar om hiermee aan de slag te gaan?

Bookatu geeft je een boekingspagina in je eigen huisstijl, met aanbetalingen, lidmaatschappen, cadeaubonnen en herinneringen, en 0% commissie op je boekingen.

Gratis beginnen