Journal
Teknik3 min. læsning

Sådan sender vi funktioner, der rører ved penge, ud uden ved et uheld at ændre nogens pris

En prisfejl er den slags, du ikke kan tage tilbage, når kortet først er trukket. Her er den disciplin, vi bruger til at lægge rabatter, medarbejderpriser og tilbud ind i bookingflowet, uden at en eneste pris flytter sig, før vi vil det.

BUBookatus udviklingsteam

De fleste fejl koster dig en undskyldning. En prisfejl koster dig en tilbagebetaling, en indsigelse på kortet og en kunde, der ikke længere stoler på tallet på skærmen. Risikoen er skæv, så processen omkring alt, der rører ved penge, er bevidst langsommere end resten af vores arbejde. Sådan ser den ud, med en nylig funktion som eksempel: et kategoritilbud, hvor en salon kan sætte alle ansigtsbehandlinger på tilbud på én gang.

REBUILT ON THE SERVER, EVERY BOOKINGBase priceStaff priceCategory saleBest couponCharged
Prisen på enhver booking bygges op i den samme faste rækkefølge, og hele regnestykket laves om på serveren.

Slået fra som udgangspunkt, i produktion, uden at gøre noget

Den første linje i funktionen er et flag. Det læser en miljøvariabel og står som udgangspunkt på fra. Det betyder, at vi kan merge og deploye koden længe før den gør noget som helst, og at hver eneste pris forbliver byte for byte den samme som dagen før. Der er ingen brag af en lancering at være nervøs over, for lanceringen er et separat, kedeligt skridt, vi tager, når vi er trygge ved det.

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
Lanceringen er et separat, kedeligt skridt. Koden sendes ud længe før den gør noget.

Én funktion, én opgave

Regnestykket bag rabatten ligger i én enkelt ren funktion. Den tager cents og en procentsats ind, og den giver cents ud. Den har ingen database, ingen forespørgsel og intet ur, hvilket gør det ligetil at teste hver eneste yderkant af den isoleret: et tilbud på nul, et almindeligt tilbud, en øvre grænse, et negativt tal der aldrig burde kunne opstå. Når regnestykket er én lille funktion, du kan rumme i hovedet, holder du op med at gætte om det.

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

Regn altid efter på serveren

Browseren viser en pris, så kunden ved, hvad hun kommer til at betale, men det tal er kun og altid en visning. Når en booking kommer ind, henter serveren den rigtige ydelse, finder medarbejderprisen, lægger et eventuelt aktivt kategoritilbud oveni og regner så rabatten ud, alt sammen ud fra data, den selv har hentet. Hvis nogen har rettet i forespørgslen på vejen ind, ændrer det ingenting, for den pris, kortet bliver trukket for, var aldrig den pris, browseren sendte. Det er den allervigtigste vane i kode, der rører ved penge: tillidsgrænsen er serveren, og det er serveren, der laver regnestykket.

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
Tallet, browseren viser, er kun en visning. Serveren henter sine egne data og laver regnestykket om.

Prøv at bryde det, før en kunde gør det

Tests, der består, fortæller dig, at de tilfælde du kom i tanke om virker. De fortæller dig ikke noget om det tilfælde, du overså. Derfor kører vi, før et prisflag bliver tændt, en fjendtlig gennemgang, hvis eneste opgave er at finde en måde at få pengene til at blive forkerte på. Adskilte tjek angriber hver deres vinkel: ét jager enhver vej, der opkræver for meget af kunden, ét jager en vej, der koster forretningen penge, ét kontrollerer, at funktionen virkelig er passiv, når flaget er slået fra, og ét går efter afrunding og skred ved grænserne. Et tilbud oven på en rabatkode oven på et depositum er præcis der, hvor en brøkdel af en cent gemmer sig, så det er der, vi kigger allerhårdest.

New pricing featureOvercharges?Loses money?Leaks when off?Rounding drift?Clean sweep → GO
Fire skeptikere, hver med sin måde at tage fejl på. Kun et rent bord er et GO.

Intet af det her er specielt smart. Det er et flag, en lille funktion, en server der laver sit eget regnestykke, og en vane med at prøve at bryde sit eget arbejde, før det bliver sendt ud. Tilsammen er det forskellen på at tilføje en funktion, der rører ved penge, og at ligge søvnløs over den.

feature flagssikker kode med pengedesign af prismotorvalidering på serverentillidsgrænse
Klar til at bruge det i praksis?

Bookatu giver dig en bookingside i dit eget look, depositum, medlemskaber, gavekort og påmindelser, med 0% kommission på dine bookinger.

Start gratis