---
title: "Come rilasciamo funzioni che toccano i soldi senza cambiare per sbaglio il prezzo a nessuno"
description: "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."
url: "https://bookatu.com/it-it/blog/shipping-money-features-safely"
language: "it"
section: "Blog"
category: "Tecnica"
author: "Il team tecnico di Bookatu"
published: "2026-07-01"
updated: "2026-07-01"
tags: ["feature flag", "codice sicuro sui pagamenti", "progettazione motore prezzi", "validazione lato server", "confine di fiducia"]
---

# 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.

> **In breve**
>
> - Tutto quello che può muovere un prezzo viene rilasciato dietro un flag spento di default. Il codice è in produzione ma non fa niente finché non lo accendiamo.
> - Il prezzo finale viene ricalcolato sul server al momento del pagamento. Un numero arrivato dal browser non è mai considerato attendibile.
> - La matematica dello sconto sta in una sola piccola funzione pura con i suoi test, così la si può ragionare da sola.
> - Prima che una funzione sui prezzi vada in produzione, una serie di controlli ostili prova a farla addebitare troppo, addebitare troppo poco o scendere sotto zero.

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.

*Diagram: The price pipeline: base price, then staff price, then category sale, then the best coupon, then the amount charged*

Diagram labels: REBUILT ON THE SERVER, EVERY BOOKING · Base price · Staff price · Category sale · Best coupon · Charged

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

*Diagram: The flag lifecycle: written behind a flag that is off by default, merged and deployed while still off, then after the audit passes the flag is flipped and the feature goes live*

Diagram labels: 1 · Behind a flag · off by default · 2 · Merge & deploy · still off · 3 · Audit passes · 0 money bugs · 4 · Flip the flag · the go-live · Live

*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.

*Diagram: The browser only displays a price; the server independently loads the service, resolves the staff and sale price, applies the best coupon, and charges that amount*

Diagram labels: BROWSER · Shows a price so the client · knows what they will pay. · A display only. Never trusted. · request · SERVER · SOURCE OF TRUTH · 1 · Load the real service · 2 · Resolve staff + sale price · 3 · Apply the best coupon · 4 · 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.

*Diagram: One new pricing feature is checked by four adversarial skeptics — does it overcharge, lose money, leak when the flag is off, or drift on rounding — and only a clean sweep is a go*

Diagram labels: New pricing feature · Overcharges? · 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.
