Diario
Tecnica3 min di lettura

Come rilasciamo funzioni che toccano i soldi senza cambiare per sbaglio il prezzo a nessuno

Un bug sui prezzi è di quelli che non puoi più ritirare una volta che una carta è stata addebitata. Ecco la disciplina che usiamo per aggiungere sconti, prezzi per operatore e promozioni al flusso di prenotazione senza che un solo prezzo si muova prima che lo vogliamo noi.

ITIl team tecnico di Bookatu

Quasi tutti i bug ti costano delle scuse. Un bug sui prezzi ti costa un rimborso, uno storno e un cliente che non si fida più del numero sullo schermo. Il rischio è sbilanciato, quindi il processo intorno a tutto quello che tocca i soldi è volutamente più lento del resto del nostro lavoro. Ecco com'è fatto, prendendo come esempio una funzione recente: l'offerta su un'intera categoria, dove un salone può mettere in promozione tutti i trattamenti viso in una volta sola.

REBUILT ON THE SERVER, EVERY BOOKINGBase priceStaff priceCategory saleBest couponCharged
Il prezzo di ogni prenotazione viene costruito sempre nello stesso ordine fisso, e tutta la somma viene rifatta sul server.

Spento di default, in produzione, senza fare niente

La prima riga della funzione è un flag. Legge una variabile d'ambiente e per impostazione predefinita è spento. Vuol dire che possiamo fare merge e deploy del codice molto prima che faccia qualcosa, e ogni prezzo resta identico byte per byte a quello del giorno prima. Non c'è nessun lancio in grande stile per cui essere nervosi, perché il lancio è un passaggio separato e noioso che facciamo quando siamo sicuri.

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
Il lancio è un passaggio separato e noioso. Il codice viene rilasciato molto prima di fare qualcosa.

Una funzione, un compito

L'aritmetica dello sconto sta in una singola funzione pura. Prende centesimi e una percentuale, e restituisce centesimi. Non ha database, non ha richieste e non ha orologio, il che vuol dire che è banale testarne ogni caso limite in isolamento: un'offerta a zero, un'offerta normale, un tetto massimo, un negativo che non dovrebbe mai capitare. Quando la matematica è una piccola funzione che ti sta in testa, smetti di andare a intuito.

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

Ricalcola sul server, sempre

Il browser mostra un prezzo così il cliente sa quanto pagherà, ma quel numero è solo una visualizzazione. Quando arriva una prenotazione, il server carica il servizio vero, risolve il prezzo dell'operatore, applica l'eventuale offerta di categoria attiva e poi calcola lo sconto, tutto a partire da dati che ha recuperato da solo. Se qualcuno ha modificato la richiesta per strada, non cambia niente, perché il prezzo addebitato sulla carta non è mai stato il prezzo mandato dal browser. Questa è l'abitudine più importante in assoluto nel codice che tocca i soldi: il confine di fiducia è il server, ed è il server che fa la somma.

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
Il numero che mostra il browser è solo una visualizzazione. Il server recupera i propri dati e rifà la somma.

Prova a romperlo prima che lo faccia un cliente

I test che passano ti dicono che i casi a cui hai pensato funzionano. Non ti dicono niente del caso che ti sei perso. Quindi prima di accendere un flag sui prezzi facciamo un giro ostile il cui unico compito è trovare un modo di far sbagliare i conti. Controlli separati prendono ciascuno un'angolazione diversa: uno va a caccia di percorsi che addebitano troppo al cliente, uno cerca un percorso che fa perdere soldi all'attività, uno verifica che la funzione sia davvero inerte quando il flag è spento, e uno se la prende con gli arrotondamenti e gli scarti ai valori limite. Un'offerta sommata a un coupon sommato a un acconto è esattamente il punto dove si nasconde una frazione di centesimo, quindi è lì che guardiamo con più attenzione.

New pricing featureOvercharges?Loses money?Leaks when off?Rounding drift?Clean sweep → GO
Quattro scettici, ognuno un modo diverso di sbagliare. Solo un risultato pulito su tutta la linea è un via libera.

Niente di tutto questo è geniale. È un flag, una piccola funzione, un server che fa i conti per conto suo, e l'abitudine di provare a rompere il proprio lavoro prima di rilasciarlo. Messi insieme, sono la differenza tra aggiungere una funzione che tocca i soldi e perderci il sonno.

feature flagcodice sicuro sui pagamentiprogettazione motore prezzivalidazione lato serverconfine di fiducia
Vuoi metterlo in pratica?

Bookatu ti dà una pagina di prenotazione con il tuo brand, acconti, abbonamenti, carte regalo e promemoria, con lo 0% di commissioni sulle tue prenotazioni.

Inizia gratis