AZZałuczkowski realizacja 01 z 04
Wszystkie realizacje

Centralny hub subskrypcji dla wielu SaaS-ów

realizacja 01 z 04, 2026

Kilka niezależnych aplikacji potrzebowało płatności cyklicznych. Zamiast integrować każdą z osobna z operatorem, zbudowałem hub: jedno miejsce, które zna plany, subskrypcje i uprawnienia, a aplikacje pytają go prostym API.

Problem

Każdy kolejny produkt oznaczał tę samą pracę od nowa: konto u operatora płatności, własne plany, własna obsługa webhooków, własna tabela subskrypcji i własny sposób sprawdzania, czy użytkownik ma opłacony dostęp. Cztery aplikacje to cztery implementacje tego samego, z czterema różnymi błędami.

Co zbudowałem

Hub subskrypcji: jedna organizacja u operatora płatności, jedna baza i jedno API, z którego korzystają wszystkie produkty. Każdy SaaS dostaje swój slug, klucz API i sekret do podpisywania, i rozmawia z hubem czterema wywołaniami:

  • POST /checkout — start subskrypcji dla własnego identyfikatora użytkownika; w odpowiedzi adres, na który przekierowuje swojego klienta;
  • GET /entitlement — pytanie „czy ten użytkownik ma aktywny dostęp”, zwracane natychmiast z lokalnej bazy huba;
  • POST /:id/cancel i /change-plan — zarządzanie subskrypcją z poziomu własnego interfejsu;
  • push na adres zwrotny — przy każdej zmianie stanu, podpisany HMAC-em.
SaaS Awłasny klucz API SaaS Bwłasny pushUrl SaaS Cwłasny sekret HMAC HUB SUBSKRYPCJI plany · subskrypcje uprawnienia · audyt entitlement w < 50 ms Operator płatnościcheckout + webhooki checkout / entitlement API webhook → push do SaaS-a
Rys. — schemat przepływu

Decyzje, które okazały się ważne

Pull i push jednocześnie. Push jest szybki, ale zawodny: aplikacja może akurat wstawać po deployu. Pull jest niezawodny, ale wymaga pytania. Hub robi oba — przy webhooku od operatora zapisuje stan i od razu, w tym samym żądaniu, wysyła push; niezależnie od tego zadanie cykliczne co pięć minut ponawia pushe, których odbiorca nie potwierdził. Dla integratora znaczy to, że może zaufać pushowi i nie musi.

Rozróżnienie „anuluj” od „odbierz dostęp”. Klient, który anuluje subskrypcję w połowie okresu, zapłacił za cały okres. Hub trzyma więc osobno status subskrypcji i moment wygaśnięcia uprawnienia. Pomylenie tych dwóch rzeczy to klasyczny sposób na odebranie komuś dostępu, za który zapłacił, i na reklamację, której nie da się obronić.

Klucz API nigdy nie wychodzi do przeglądarki. Checkout startuje po stronie serwera integratora; przeglądarka widzi wyłącznie gotowy adres przekierowania. Push weryfikuje się podpisem po surowym ciele żądania, nie po sparsowanym JSON-ie — parsowanie i ponowna serializacja potrafią zmienić bajt i zepsuć podpis.

Do diagnozy całego huba bez wchodzenia w panel służy osobny klucz tylko do odczytu: zwraca stan subskrypcji, historię webhooków i dostarczeń pushy, a nie zwraca sekretów integratorów. Agent albo dyżurny widzi wszystko, czego potrzebuje, i nie może niczego zmienić.

Czego się nauczyłem

Najwięcej czasu zajęła nie integracja z operatorem, tylko semantyka stanów: co dokładnie znaczy „aktywna”, co znaczy „w okresie wypowiedzenia”, co się dzieje przy zmianie planu w środku okresu rozliczeniowego i który z tych przypadków ma wysłać push. Gdy to zostało spisane jako tabela przejść, kod zrobił się krótki.

Realizacje Napisz