Подписка ломается не на платежах, а на переходах между статусами и льготами
Жизненный цикл подписки нужно проектировать как конечный автомат, а не как набор ручных флагов. Базовые состояния: trial, promo, active, grace, past_due, paused, canceled. Для каждого перехода задайте единственный источник истины: кто меняет статус, по какому событию и с какой идемпотентностью. Иначе один и тот же webhook начнет продлевать доступ дважды, а повторная попытка платежа — откатывать уже корректный статус.
Триалы и промо-периоды нельзя смешивать в один «бесплатный месяц». Триал — это проверка ценности, промо — коммерческое исключение. У них разная логика окончания: триал завершается в paid-or-cancel, промо — в renewal-or-downgrade. Если это не разделить, аналитика CAC/LTV и правила dunning начинают врать, а пользователь получает неожиданный charge after notice. Для льгот нужен отдельный ledger прав доступа и отдельная дата истечения.
Апгрейд должен быть транзакцией с атомарным расчетом proration. Важно зафиксировать момент вступления тарифа в силу, сумму доплаты, валюту, налоговую базу и источник округления. При async-архитектуре апгрейд сначала резервирует право доступа, затем подтверждается успешной оплатой; иначе можно выдать premium-функции без валидного платежа или, наоборот, заблокировать уже оплаченный тариф. Идемпотентность в биллинге — это не рекомендация, а базовый вопрос выживания системы.
Проверьте, чтобы любой переход статуса был обратим через компенсационное событие, а не ручной SQL. Тогда триалы, промо и апгрейды перестают быть набором исключений и становятся управляемой машиной состояний.
Подписки: биллинг-лаб
@subscriptions_billing_lab_arb
Подписка ломается не на платежах, а на переходах между статусами и льготами
Этот пост опубликован в Telegram-канале Подписки: биллинг-лаб. Подписаться можно по ссылке: @subscriptions_billing_lab_arb.