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/canceli/change-plan— zarządzanie subskrypcją z poziomu własnego interfejsu;- push na adres zwrotny — przy każdej zmianie stanu, podpisany HMAC-em.
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.