---
title: "Så släpper vi funktioner som rör pengar utan att ändra någons pris av misstag"
description: "En prisbugg är den sorten du inte kan ta tillbaka när ett kort väl har debiterats. Här är disciplinen vi använder för att lägga till rabatter, personalprissättning och rea i bokningsflödet utan att ett enda pris rör sig innan vi vill det."
url: "https://bookatu.com/sv-se/blog/shipping-money-features-safely"
language: "sv"
section: "Blogg"
category: "Teknik"
author: "Bookatus teknikteam"
published: "2026-07-01"
updated: "2026-07-01"
tags: ["funktionsflaggor", "pengasäker kod", "design av prismotor", "validering på servern", "förtroendegräns"]
---

# Så släpper vi funktioner som rör pengar utan att ändra någons pris av misstag

En prisbugg är den sorten du inte kan ta tillbaka när ett kort väl har debiterats. Här är disciplinen vi använder för att lägga till rabatter, personalprissättning och rea i bokningsflödet utan att ett enda pris rör sig innan vi vill det.

> **Kort sammanfattning**
>
> - Allt som kan flytta ett pris släpps bakom en flagga som är avstängd som standard. Koden ligger i produktion men gör ingenting förrän vi slår på den.
> - Slutpriset räknas ut igen på servern i kassan. En siffra som kom från webbläsaren litar vi aldrig på.
> - Rabattmatematiken bor i en enda liten ren funktion med egna tester, så att den kan resoneras om för sig.
> - Innan en prisfunktion går live försöker en uppsättning fientliga kontroller få den att ta för mycket, för lite eller hamna under noll.

De flesta buggar kostar dig en ursäkt. En prisbugg kostar dig en återbetalning, en återkrävning och en kund som inte längre litar på siffran på skärmen. Insatserna är sneda, så processen runt allt som rör pengar är medvetet långsammare än resten av vårt arbete. Så här ser den ut, med en färsk funktion som exempel: en rea på en hel kategori, där en salong kan sätta alla ansiktsbehandlingar på rea på en gång.

*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

*Varje bokningspris byggs i samma fasta ordning, och hela summan görs om på servern.*

## Avstängd som standard, i produktion, utan att göra något

Funktionens första rad är en flagga. Den läser en miljövariabel och är avstängd som standard. Det betyder att vi kan merga och driftsätta koden långt innan den gör något, och varje pris förblir byte för byte identiskt med dagen innan. Det finns ingen stor smäll att vara nervös inför, för lanseringen är ett separat, tråkigt steg som vi tar när vi väl känner oss trygga.

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

*Lanseringen är ett separat, tråkigt steg. Koden släpps långt innan den gör något.*

## En funktion, en uppgift

Rabattens aritmetik bor i en enda ren funktion. Den tar ören och en procentsats, och den returnerar ören. Den har ingen databas, ingen förfrågan och ingen klocka, vilket betyder att det är trivialt att testa varje kant av den isolerat: en rea på noll, en vanlig rea, ett tak i överkanten, ett negativt värde som aldrig borde inträffa. När matematiken är en enda liten funktion du kan hålla i huvudet slutar du gissa om den.

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

## Räkna om på servern, alltid

Webbläsaren visar ett pris så att kunden vet vad hen kommer att betala, men den siffran är bara en visning. När en bokning kommer in laddar servern den riktiga tjänsten, tar fram personalpriset, tillämpar eventuell aktiv kategorirea och räknar sedan ut rabatten, allt utifrån data den själv har hämtat. Om någon har ändrat i förfrågan på vägen in ändrar det ingenting, för priset kortet debiteras var aldrig priset webbläsaren skickade. Det här är den enskilt viktigaste vanan i pengakod: förtroendegränsen är servern, och servern gör summan.

*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

*Siffran webbläsaren visar är bara en visning. Servern hämtar sina egna data och gör summan igen.*

## Försök knäcka den innan en kund gör det

Tester som går igenom berättar att fallen du tänkte på fungerar. De berättar inte om fallet du missade. Så innan en prisflagga slås på kör vi en fientlig genomgång vars enda uppgift är att hitta ett sätt att få pengarna fel. Separata kontroller tar var sin vinkel: en jagar varje väg som tar för mycket betalt av kunden, en jagar en väg som får företaget att förlora pengar, en kontrollerar att funktionen verkligen är passiv när flaggan är av, och en går på avrundning och gränsdrift. En rea staplad på en rabattkod på en handpenning är precis där ett brutet öre gömmer sig, så det är där vi tittar hårdast.

*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

*Fyra skeptiker, var och en ett annat sätt att ha fel. Bara en helt ren genomgång är ett GO.*

Inget av det här är smart. Det är en flagga, en liten funktion, en server som gör sin egen matematik och en vana att försöka knäcka sitt eget arbete innan det släpps. Tillsammans är det skillnaden mellan att lägga till en funktion som rör pengar och att ligga vaken över den.
