---
title: "Wie wir Funktionen rund ums Geld ausliefern, ohne versehentlich jemandes Preis zu ändern"
description: "Ein Preisfehler ist die Sorte Fehler, die sich nicht zurücknehmen lässt, sobald eine Karte belastet wurde. Hier ist die Disziplin, mit der wir Rabatte, Mitarbeiterpreise und Aktionen in den Buchungsablauf bringen, ohne dass sich ein einziger Preis bewegt, bevor wir es wollen."
url: "https://bookatu.com/de-de/blog/shipping-money-features-safely"
language: "de"
section: "Blog"
category: "Technik"
author: "Das Bookatu-Technikteam"
published: "2026-07-01"
updated: "2026-07-01"
tags: ["feature flags", "sicherer code beim geld", "aufbau preislogik", "prüfung auf dem server", "vertrauensgrenze"]
---

# Wie wir Funktionen rund ums Geld ausliefern, ohne versehentlich jemandes Preis zu ändern

Ein Preisfehler ist die Sorte Fehler, die sich nicht zurücknehmen lässt, sobald eine Karte belastet wurde. Hier ist die Disziplin, mit der wir Rabatte, Mitarbeiterpreise und Aktionen in den Buchungsablauf bringen, ohne dass sich ein einziger Preis bewegt, bevor wir es wollen.

> **Kurz gesagt**
>
> - Alles, was einen Preis verändern kann, geht hinter einem Schalter live, der standardmäßig aus ist. Der Code liegt in der Produktion und tut nichts, bis wir ihn einschalten.
> - Der Endpreis wird beim Bezahlen auf dem Server noch einmal berechnet. Einer Zahl aus dem Browser wird nie vertraut.
> - Die Rabattrechnung steckt in einer einzigen kleinen, reinen Funktion mit eigenen Tests, man kann sie also für sich allein durchdenken.
> - Bevor eine Preisfunktion live geht, versucht eine Reihe feindseliger Prüfungen, sie zu zu viel, zu wenig oder unter null zu bewegen.

Die meisten Fehler kosten dich eine Entschuldigung. Ein Preisfehler kostet dich eine Erstattung, eine Rückbuchung und eine Kundin, die der Zahl auf dem Bildschirm nicht mehr traut. Das Risiko ist einseitig verteilt, deshalb ist der Prozess rund um alles, was Geld berührt, bewusst langsamer als der Rest unserer Arbeit. So sieht er aus, am Beispiel einer neueren Funktion: eine Aktion für eine ganze Kategorie, mit der ein Salon jede Gesichtsbehandlung auf einmal ins Angebot setzen kann.

*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

*Der Preis jeder Buchung entsteht in derselben festen Reihenfolge, und die ganze Summe wird auf dem Server neu gerechnet.*

## Standardmäßig aus, in der Produktion, ohne Wirkung

Die erste Zeile der Funktion ist ein Schalter. Er liest eine Umgebungsvariable und steht standardmäßig auf aus. Das heißt, wir können den Code lange, bevor er irgendetwas tut, zusammenführen und ausliefern, und jeder Preis bleibt Byte für Byte derselbe wie am Tag davor. Es gibt keinen großen Knall beim Start, vor dem man nervös sein müsste, denn der Start ist ein eigener, langweiliger Schritt, den wir gehen, sobald wir sicher sind.

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

*Der Start ist ein eigener, langweiliger Schritt. Der Code geht raus, lange bevor er etwas tut.*

## Eine Funktion, eine Aufgabe

Die Rechnerei hinter dem Rabatt steckt in einer einzigen reinen Funktion. Sie nimmt Cent und ein Prozent und gibt Cent zurück. Sie hat keine Datenbank, keine Anfrage und keine Uhr, damit lässt sich jeder Grenzfall isoliert ganz einfach testen: keine Aktion, eine normale Aktion, eine Deckelung nach oben, ein negativer Wert, den es nie geben dürfte. Wenn die Rechnung eine kleine Funktion ist, die du im Kopf behalten kannst, hörst du auf, darüber zu rätseln.

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

## Immer auf dem Server neu rechnen

Der Browser zeigt einen Preis, damit die Kundin weiß, was sie zahlen wird, aber diese Zahl ist immer nur Anzeige. Kommt eine Buchung herein, lädt der Server die echte Leistung, ermittelt den Mitarbeiterpreis, wendet eine laufende Kategorie-Aktion an und rechnet dann den Rabatt aus, alles aus Daten, die er sich selbst geholt hat. Wenn jemand die Anfrage unterwegs bearbeitet hat, ändert das nichts, denn der Preis, mit dem die Karte belastet wird, war nie der Preis, den der Browser geschickt hat. Das ist die wichtigste Gewohnheit in Code rund ums Geld: Die Vertrauensgrenze ist der Server, und der Server macht die Rechnung.

*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

*Die Zahl im Browser ist nur Anzeige. Der Server holt sich seine eigenen Daten und rechnet noch einmal.*

## Versuch es kaputtzumachen, bevor es ein Kunde tut

Bestandene Tests sagen dir, dass die Fälle funktionieren, an die du gedacht hast. Über den Fall, den du übersehen hast, sagen sie nichts. Bevor ein Preisschalter angeht, machen wir deshalb einen feindseligen Durchgang, dessen einzige Aufgabe es ist, einen Weg zu finden, das Geld falsch zu machen. Getrennte Prüfungen nehmen dabei jeweils eine andere Perspektive ein: eine sucht nach jedem Weg, auf dem die Kundin zu viel zahlt, eine nach einem Weg, auf dem das Geschäft Geld verliert, eine prüft, ob die Funktion bei ausgeschaltetem Schalter wirklich untätig bleibt, und eine geht auf Rundung und Abweichung an den Grenzen los. Eine Aktion auf einem Gutschein auf einer Anzahlung ist genau die Stelle, an der sich ein Bruchteil eines Cents versteckt, dort schauen wir also am genauesten hin.

*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

*Vier Skeptiker, jeder auf eine andere Art falsch zu liegen. Nur ein sauberer Durchgang ist ein GO.*

Nichts davon ist raffiniert. Es ist ein Schalter, eine kleine Funktion, ein Server, der seine Rechnung selbst macht, und die Gewohnheit, die eigene Arbeit kaputtmachen zu wollen, bevor sie rausgeht. Zusammengenommen ist das der Unterschied zwischen einer Funktion rund ums Geld und schlaflosen Nächten deswegen.
