---
title: "Jak nasazujeme funkce, které se dotýkají peněz, aniž bychom někomu omylem změnili cenu"
description: "Chyba v cenách je ta, kterou už nevezmete zpět, jakmile se strhne platba z karty. Tady je kázeň, se kterou přidáváme do rezervačního procesu slevy, ceny podle personálu a akce, aniž by se jediná cena pohnula dřív, než chceme."
url: "https://bookatu.com/cs-cz/blog/shipping-money-features-safely"
language: "cs"
section: "Blog"
category: "Technika"
author: "Technický tým Bookatu"
published: "2026-07-01"
updated: "2026-07-01"
tags: ["feature flags", "bezpečná práce s penězi v kódu", "návrh cenového enginu", "validace na serveru", "hranice důvěry"]
---

# Jak nasazujeme funkce, které se dotýkají peněz, aniž bychom někomu omylem změnili cenu

Chyba v cenách je ta, kterou už nevezmete zpět, jakmile se strhne platba z karty. Tady je kázeň, se kterou přidáváme do rezervačního procesu slevy, ceny podle personálu a akce, aniž by se jediná cena pohnula dřív, než chceme.

> **Ve zkratce**
>
> - Cokoli, co může pohnout cenou, nasazujeme za přepínačem, který je ve výchozím stavu vypnutý. Kód je v produkci, ale nedělá nic, dokud ho nezapneme.
> - Konečná cena se u pokladny znovu spočítá na serveru. Číslu, které přišlo z prohlížeče, se nikdy nevěří.
> - Výpočet slevy žije v jedné malé čisté funkci s vlastními testy, takže se dá promyslet sám o sobě.
> - Než cenová funkce půjde naostro, sada nepřátelských kontrol se ji snaží donutit účtovat příliš, příliš málo, nebo spadnout pod nulu.

Většina chyb vás stojí omluvu. Chyba v cenách vás stojí vratku, chargeback a klienta, který už nevěří číslu na obrazovce. Sázky jsou nevyrovnané, takže postup okolo všeho, co se dotýká peněz, je záměrně pomalejší než zbytek naší práce. Takhle vypadá, a jako příklad použijeme nedávnou funkci: slevu na celou kategorii, kdy salon může dát do slevy všechna ošetření pleti naráz.

*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

*Cena každé rezervace se skládá vždy ve stejném pevném pořadí a celý součet se znovu udělá na serveru.*

## Vypnuto, v produkci, nedělá nic

První řádek funkce je přepínač. Čte proměnnou prostředí a výchozí stav je vypnuto. To znamená, že můžeme kód sloučit a nasadit dlouho předtím, než začne cokoli dělat, a každá cena zůstane bajt po bajtu stejná jako den předtím. Není tu žádné velké spuštění, ze kterého by šel strach, protože spuštění je samostatný nudný krok, který uděláme, až si budeme jistí.

```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

*Spuštění je samostatný nudný krok. Kód se nasadí dlouho předtím, než začne cokoli dělat.*

## Jedna funkce, jeden úkol

Aritmetika slevy žije v jediné čisté funkci. Bere haléře a procenta a vrací haléře. Nemá databázi, nemá požadavek a nemá hodiny, což znamená, že je triviální otestovat každý její okraj izolovaně: nulová sleva, běžná sleva, omezení nahoře, záporná hodnota, která by nikdy neměla nastat. Když je výpočet jedna malá funkce, kterou udržíte v hlavě, přestanete o ní hádat.

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

## Přepočítat na serveru, vždycky

Prohlížeč ukazuje cenu, aby klient věděl, kolik zaplatí, ale to číslo je vždy jen zobrazení. Když přijde rezervace, server načte skutečnou službu, vyhodnotí cenu podle personálu, uplatní případnou aktivní slevu na kategorii a pak spočítá slevu, a to všechno z dat, která si sám stáhl. Když někdo požadavek cestou upravil, nezmění to nic, protože cena stržená z karty nikdy nebyla cenou, kterou poslal prohlížeč. Tohle je nejdůležitější návyk v kódu okolo peněz: hranice důvěry je server a součet dělá server.

*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

*Číslo, které ukazuje prohlížeč, je jen zobrazení. Server si natáhne vlastní data a součet udělá znovu.*

## Zkuste to rozbít dřív než zákazník

Procházející testy vám řeknou, že případy, na které jste mysleli, fungují. Neřeknou vám nic o případu, který vám unikl. Než tedy cenový přepínač zapneme, projedeme nepřátelské kolo, jehož jediným úkolem je najít způsob, jak peníze pokazit. Jednotlivé kontroly berou věc z různých stran: jedna hledá jakoukoli cestu, která klientovi naúčtuje příliš, druhá cestu, kterou podnik přichází o peníze, třetí ověřuje, že je funkce s vypnutým přepínačem opravdu netečná, a čtvrtá jde po zaokrouhlování a driftu na hranicích. Sleva navršená na kupon a na zálohu je přesně to místo, kde se schová zlomek haléře, takže právě tam se díváme nejpozorněji.

*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

*Čtyři skeptici, každý jiná cesta k chybě. Zelenou dává jen čistý průchod.*

Nic z toho není chytré. Je to přepínač, malá funkce, server, který si počítá sám, a zvyk zkoušet si rozbít vlastní práci dřív, než se nasadí. Dohromady je to rozdíl mezi tím přidat funkci, která se dotýká peněz, a nespat kvůli ní.
