Білінг
Як команда платить і що саме білінг повідомляє решті продукту. П'ять слайсів у групі billing/, і між ними та всім іншим — одне-єдине поле: Team.planId.
Ця вузькість і є задум. Ніщо поза billing/ не питає, хто провайдер, який вигляд має підписка на дроті й чи було захоплено платіж, — воно читає тариф у команди й живе далі.

Те, що показує кабінет сьогодні, вужче за те, що вміє api. На екрані вище не пропонується нічого купити: він називає команду, пише Free (early access) і повідомляє, що підписки, ліміти й оплата за споживанням з'являться ближче до v0.9. Усе описане нижче зібрано й доступне через api — просто шлях купівлі ще не виведено в інтерфейс.
П'ять слайсів
| слайс | що тримає |
|---|---|
billing/product | каталог: одна продавана річ — ім'я, ціна, planId, який отримує передплатник |
billing/subscription | наш запис про підписку команди й запис у Team.planId |
billing/paymentProvider | провайдер за шлюзом — сьогодні PayPal |
billing/webhook | приймання подій провайдера |
billing/usage | що команда справді спожила і проти якої квоти |
Провайдер захований за шлюзом
IPaymentProviderGateway живе в domain/, PayPal — у data/. Споживачі ніколи не бачать ані SDK, ані формату дроту, ані словника провайдера — та сама форма, що в infra/storage та infra/vector, і з тієї самої причини.
У 1.x було навпаки: слайс звався paypal, його називав на ім'я кожен споживач, а написання провайдера (APPROVAL_PENDING, BILLING.SUBSCRIPTION.ACTIVATED) дотягувалися до сервісів, яким не було потреби їх знати.
Хід підписки
POST /billing/subscriptions— спершу пишеться наш рядок, бо його id і є те посилання, яке провайдер носить із собою й повертає в кожній події. Потім у провайдера запитується підписка, а платникові видається посилання на підтвердження.- Платник підтверджує в провайдера. До цього не списується нічого.
- Провайдер доставляє
ACTIVATED. Слайс вебхуків перевіряє подію, застовплює її в журналі повторів і застосовує — підписка стає активною, а вTeam.planIdз'являється тариф. POST /billing/subscriptions/:id/cancelзупиняє майбутні списання; приходитьCANCELLED, і тариф знімається.
Одна відкрита підписка на команду. pending, active і suspended вважаються відкритими, тож платник, який перезавантажив недооформлену оплату, отримує те саме посилання на підтвердження, а не другу підписку, за яку треба платити.
Два правила, що існують через те, що 1.x їх порушувала
Тут ніщо не обчислює дату. currentPeriodEnd читається в провайдера. У 1.x на кожній події захоплення додавався місяць — через що повторно доставлена подія коштувала безкоштовного місяця.
Кожен запис — присвоєння, ніколи не інкремент, тож обробити подію двічі нешкідливо — як друга лінія оборони за журналом повторів, а не замість нього.
Чому журнал повторів вартий окремої таблиці
Спокуслива заперечність: застосувати «активовано» двічі нічого не змінює. Правда — і грошей коштує не цей випадок. Грошей коштує порушений порядок: активовано, потім скасовано, потім повторна доставка «активовано». Кожен запис окремо ідемпотентний, а результат — команда на платному тарифі, яка перестала платити.
Порядок і є задум:
перевірити → застовпити → обробити → позначити обробленим- Спершу перевірити, потім розбирати — неперевірене тіло є введенням зловмисника.
- Застовпити до обробки — застовплене після лишає вікно, у якому друга доставка приходить посеред обробки, не знаходить заявки й обробляє подію ще раз.
- Звільнити при збої — обробник, що кинув виняток, події не застосував, тож заявку треба зняти, інакше повтор провайдера — те саме, що все б полагодило, — буде відкинуто як дублікат.
Заявка — це INSERT за унікальним ключем, а не читання з подальшим записом: дві одночасні доставки обидві нічого не прочитають і обидві підуть далі, а вирішить база, один раз.
Споживання
GET /usage/summary що витрачено, з розбивкою
GET /usage/total одне число
GET /usage/quota скільки лишилося за тарифом
GET /usage/agents те саме за агентамиВідомі діри, названі, а не сховані
Покинуту оплату можуть підтвердити пізніше. Провайдер не має операції скасування для непідтвердженої підписки — заміряно наживо, /cancel відповідає 404 RESOURCE_NOT_FOUND, — тож скасування підписки у стані pending закриває наш рядок і лишає посилання спливати за розкладом провайдера. Той, хто покинув оплату, підписався на інше, а потім повернувся й підтвердив перше посилання до його спливання, опиниться з двома живими підписками.
Справжньої стелі витрат не існує. Споживання вимірюється й показується; ніщо не зупиняє команду на межі.
Де код
api/src/slices/billing/ — по каталогу на кожен слайс вище. Див. також Розбір слайсів та Ресурси.