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.
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.
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.
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);}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.
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.
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.
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.