SaaS billing jest problemem synchronizacji stanu
Billing często sprowadza się w prezentacjach do „podłącz Stripe i dodaj plany”. Produkcyjny SaaS ma jednak więcej stanu niż może być właścicielem payment provider: product catalog, subscription lifecycle, invoice/payment state, feature entitlements, usage, credits, tax context, dunning i operational access. Architektura staje się niezawodna, gdy ownership każdego stanu jest jawny, a provider events są wejściem do produktu — nie jego bazą danych.
Najpierw określ właścicieli stanu
| Stan | Primary owner | Dlaczego |
|---|---|---|
| Product / price catalog | Product + billing configuration | Definiuje, co można kupić |
| Subscription | Billing domain zsynchronizowany z providerem | Śledzi lifecycle i contractual period |
| Invoice / payment | Provider + normalized local record | Financial event wymaga reconciliation i historii |
| Entitlement | Application domain | Kontroluje, czego tenant może używać teraz |
| Usage | Product metering | Mierzone blisko konsumowanej funkcji |
| Audit / support history | Application | Wyjaśnia zmianę access lub balance |
Częsty błąd to wyprowadzanie feature access bezpośrednio z ostatniego webhooka lub pojedynczego subscription.status. Entitlements zasługują na własny product model, szczególnie przy trials, grace periods, manual contracts, credits i wielu payment rails.
Stosuj idempotent webhook inbox
Payment webhooks mogą być ponawiane, opóźnione i przychodzić w kolejności innej niż oczekuje UI. Weryfikuj provider signatures, zapisuj event identity, rozpoznawaj duplicates i przetwarzaj przez idempotent handler. Zachowuj raw provider reference do debugowania, ale normalizuj potrzebne business facts do lokalnych records.
Handler nie powinien zakładać, że pojedynczy event opisuje cały stan. Jeśli provider udostępnia authoritative resource, pobierz lub reconcile current state tam, gdzie kolejność eventów ma znaczenie.
Entitlements powinny być jawnym product state
Entitlement odpowiada na pytanie produktowe: czy tenant X może używać capability Y, z jakim limitem, do kiedy i z jakiego powodu? Odpowiedź może wynikać z active subscription, paid add-on, trial, negotiated enterprise contract albo temporary grace rule. Taka abstrakcja uniezależnia feature gates od provider-specific status names.
Server-side entitlement checks powinny chronić sensitive APIs i expensive operations. Frontend feature visibility może odzwierciedlać decyzję dla UX, ale nie może być enforcement layer.
Plan changes są workflow, nie aktualizacją pola
Upgrade, downgrade, cancellation i renewal wymagają jawnych timing semantics. Zdecyduj, czy zmiana jest natychmiastowa czy od kolejnego okresu, jak działa proration, kiedy zmieniają się entitlements i co dzieje się z obecnym usage. Zapisuj requested transition, aby support potrafił wyjaśnić resulting invoice i access state.
W enterprise SaaS scheduled downgrade może też wymagać validation: tenant może przekraczać limity niższego planu dla users, storage czy facilities. Billing workflow powinien rozwiązać ten warunek, a nie po cichu usuwać dane.
Reconciliation zamyka lukę między providerami a internal state
Nawet poprawna obsługa webhooków potrzebuje reconciliation. Okresowo porównuj provider subscriptions, invoices i payments z local billing records oraz twórz jawne discrepancies. Pozwala to wykryć missing events, manual dashboard changes, deployment outages i historyczne bugs.
Reconciliation powinno być repair-oriented. Zapisuj klasę rozbieżności i bezpieczną corrective action zamiast jedynie logować, że dwie wartości się różnią.
Normalizuj wiele payment rails za jednym billing domain
Cards, bank payments, CoinGate lub direct stablecoin settlement mają inne mechanizmy, ale produkt powinien normalizować je do wspólnych pojęć: payment intent/obligation, received amount, currency/asset, finality state, allocation, refund/credit i reconciliation evidence.
Tu łączą się nasze kompetencje crypto payments i CoinGate integration z SaaS billing. Blockchain transaction lub gateway callback nie powinien bezpośrednio włączać feature. Powinien rozliczyć billing obligation, po czym entitlement model zmienia się według tych samych domain rules co dla innych rails.
Zachowaj audytowalną ścieżkę payment → access
Gdy support pyta „dlaczego klient stracił dostęp?”, system powinien odtworzyć subscription period, invoice/payment facts, provider events, reconciliation, entitlement transition i manual override. Manual interventions potrzebują actor, reason i expiry, aby temporary support fix nie stał się niewidzialnym permanent state.
First-party product pattern: vertical SaaS
Vertical systems dobrze pokazują złożoność billingu, bo access często mapuje się na operational units zamiast pojedynczego seat count. W Rentya product logic obejmuje operators, facilities, inventory i booking workflows. Taki produkt korzysta z explicit entitlements i tenant-aware billing zamiast UI-only plan flags.
Ten wzorzec jest częścią naszego szerszego podejścia do SaaS development: billing, permissions i tenant lifecycle są jednym product architecture, a nie trzema niezależnymi integracjami.
Failure modes do przetestowania
- Ten sam paid event jest dostarczany wielokrotnie.
- Cancellation event przychodzi przed starszym update.
- Payment kończy się sukcesem podczas awarii aplikacji.
- Provider state zmienia się ręcznie poza aplikacją.
- Downgrade koliduje z current usage limits.
- Crypto/gateway payment rozlicza inną kwotę niż oczekiwana.
- Manual entitlement override nigdy nie wygasa.
- Provider i local records rozjeżdżają się bez alertu.
