Jak wdrażamy funkcje dotykające pieniędzy, nie zmieniając nikomu ceny przez przypadek
Błąd w cenie to taki błąd, którego nie da się cofnąć, gdy karta już została obciążona. Oto dyscyplina, dzięki której dodajemy do rezerwacji rabaty, ceny per osoba i promocje, a żadna cena nie drgnie, dopóki sami tego nie zechcemy.
Większość błędów kosztuje Cię przeprosiny. Błąd w cenie kosztuje zwrot, chargeback i klienta, który już nie wierzy liczbie na ekranie. Stawki są bardzo nierówne, więc proces wokół wszystkiego, co dotyka pieniędzy, jest u nas świadomie wolniejszy niż reszta pracy. Tak to wygląda, na przykładzie świeżej funkcji: promocji na całą kategorię, dzięki której salon przecenia wszystkie zabiegi na twarz naraz.
Domyślnie wyłączone, na produkcji, nic nie robi
Pierwsza linia funkcji to flaga. Czyta zmienną środowiskową i domyślnie jest wyłączona. To znaczy, że możemy scalić i wdrożyć kod długo przed tym, zanim cokolwiek zrobi, a każda cena zostaje co do bajtu taka sama jak dzień wcześniej. Nie ma huczneg premiery, o którą trzeba się denerwować, bo start to osobny, nudny krok, który robimy, gdy jesteśmy pewni.
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);}Jedna funkcja, jedno zadanie
Arytmetyka rabatu mieszka w jednej czystej funkcji. Bierze grosze i procent, zwraca grosze. Nie ma bazy danych, nie ma żądania, nie ma zegara, więc każdy jej skrajny przypadek testuje się trywialnie w izolacji: promocja zerowa, promocja normalna, ograniczenie u góry, wartość ujemna, która nigdy nie powinna wystąpić. Kiedy matematyka to jedna mała funkcja, którą mieścisz w głowie, przestajesz przy niej zgadywać.
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);}Zawsze przeliczaj na serwerze
Przeglądarka pokazuje cenę, żeby klient wiedział, ile zapłaci, ale ta liczba jest wyłącznie wyświetlaczem. Kiedy przychodzi rezerwacja, serwer wczytuje prawdziwą usługę, ustala cenę danej osoby z zespołu, nakłada aktywną promocję na kategorię i dopiero wtedy liczy rabat, wszystko z danych, które sam pobrał. Jeśli ktoś po drodze podmienił żądanie, nic to nie zmienia, bo cena, którą obciążamy kartę, nigdy nie była ceną wysłaną przez przeglądarkę. To najważniejszy nawyk w kodzie od pieniędzy: granicą zaufania jest serwer i to serwer liczy sumę.
Spróbuj to złamać, zanim zrobi to klient
Zielone testy mówią Ci, że przypadki, o których pomyślałeś, działają. Nie mówią nic o tym, który przegapiłeś. Dlatego zanim flaga cenowa zostanie włączona, robimy wrogie przejście, którego jedynym zadaniem jest znaleźć sposób na pomylenie pieniędzy. Osobne testy biorą to z różnych stron: jeden szuka jakiejkolwiek ścieżki, która zawyża rachunek klienta, drugi takiej, która okrada firmę, trzeci sprawdza, czy przy wyłączonej fladze funkcja naprawdę jest martwa, a czwarty czepia się zaokrągleń i dryfu na granicach. Promocja nałożona na kupon przy zaliczce to dokładnie miejsce, w którym chowa się ułamek grosza, więc tam patrzymy najuważniej.
Nic z tego nie jest sprytne. To flaga, mała funkcja, serwer, który sam liczy, i nawyk psucia własnej pracy, zanim trafi do ludzi. Razem wzięte, robią różnicę między dodaniem funkcji dotykającej pieniędzy a nieprzespanymi nocami z jej powodu.