
Меня зовут Лидия, я основатель LBX биллинга для SaaS-продуктов. Мы провели десятки внедрений: от команд, которые только запускают монетизацию, до сервисов с несколькими тысячами активных подписчиков. Видели, как команды наступают на одни и те же грабли. Некоторые с хрустом. Вот четыре ситуации, которые встречаются чаще всего.
Грабли №1. «У нас простая логика, напишем сами»
ЮKassa и CloudPayments поддерживают рекуррентные платежи. Настраивается периодичность, дата списания в месяце, сумма, количество повторений. На первый взгляд есть всё необходимое.
Но рекуррент — это только механизм списания. Он не знает, что клиент перешёл на другой тариф в середине месяца и сумма должна измениться. Не знает, что один пользователь подключился первого числа, другой семнадцатого, и периоды у них разные. Не знает, что делать, если платёж не прошёл: ждать три дня, заблокировать доступ или дать льготный период. Не знает, что клиент попросил паузу на месяц.
Всю эту логику нужно строить поверх платёжной системы самостоятельно.
Льготный период уже отдельная история. Заблокировать юзера немедленно при первой просрочке, значит рисковать его потерять. Карта могла устареть, платёж завис на стороне банка, бухгалтер был в отпуске.
Грабли №2. «Пока клиентов немного — справляемся вручную»
На старте большинство команд ведут учет подписок и оплат вручную. Счёт сформировали в банке или 1С, отправили клиенту, дождались оплаты, отметили в таблице. Двадцать клиентов занимает пару часов в неделю. Раздражает, но еще терпимо.
При пятидесяти клиентах час превращается в несколько дней. Счёт нужно не только сформировать, но и отследить — оплатили или нет. Платежи разносить вручную: открыть банк, найти входящий перевод, сопоставить с клиентом по ИНН, обновить статус. Если платёж пришёл с некорректным назначением или частично, то разбирать отдельно.
Параллельно копятся исключения. Один клиент договорился о скидке, бухгалтер помнит, что ему выставлять меньше. Другой платит не первого, а двадцатого, так договорились при подключении. Третьему по какой-то причине счёт уходит на квартал вперёд. Каждое такое исключение живёт в голове конкретного человека или в заметке в Notion. Когда этот человек уходит в отпуск или увольняется, исключения теряются. Клиент получает счёт на неправильную сумму. Или не получает вовсе.
Грабли №3. «Автоматизировали — и думали, что готово»
Команда дорастает до автоматизации: счета генерируются и отправляются сами, входящие платежи разносятся по ИНН автоматически. Ощущение, что проект биллинга наконец закрыт.
Но к этому моменту исключения переехали из голов людей в код. Для одного клиента в базе захардкожена спеццена. Для другого — отдельное условие в логике расчёта периода. Для третьего написан отдельный обработчик, про который знает только тот разработчик, который его писал.
Формально, это уже не ручная работа. Но это хрупкая конструкция: изменить условия для пользователя, значит идти в код. Добавить новый тип исключения, ещё один if где-то в обработчике. Разобраться, почему конкретному клиенту выставляется именно такая сумма уже становится задачей разработчика, а не бухгалтера.
Грабли №4. «Добавим позже»
Хотим запустить новый тариф — нужно писать код.
Хотим добавить опцию — нужно писать код.
Хотим скорректировать цену — нужно писать код, тестировать, выкатывать.
Разработчики заняты продуктом. Новый тариф откладывается на следующий спринт. Потом ещё на один. «Добавим позже» в большинстве случаев означает «никогда».
То же самое с архитектурными решениями по биллингу. Переделать модель данных на работающей системе с живыми пользователями это один из самых болезненных технических проектов. Не потому что сложно технически, а потому что цена ошибки высока. Каждый раз, когда команда говорит «сделаем нормально позже», она повышает стоимость этого «позже».
Собственно туториал
Ни одни из этих граблей не уникальны - это стандартная эволюция биллинга в большинстве SaaS команд. Рекуррент в платёжной системе, таблица с оплатами, исключения в коде проходят почти все. Отличается только момент, когда это начинает мешать.
Хорошая новость: грабли стандартные, и о них можно знать заранее.
Разделить тарифный план и условия конкретного клиента. Публичный тариф и то, на что фактически подписан пользователь, разные сущности в модели данных. Скидка, индивидуальная цена, нестандартный расчётный период хранятся в клиентском договоре, а не как исключение в логике тарифа. Это делает все отклонения от стандарта явными и видимыми без чтения кода.
Хранить историю событий, а не только текущее состояние. Не «клиент на тарифе PRO», а «клиент перешёл на тариф PRO 15 марта в 14:32». Без событийной истории перерасчёт за прошлый период невозможен, разбор спорной ситуации невозможен, аудит невозможен. Добавить это задним числом — отдельное приключение.
Не хранить расчётный период как фиксированный объект. Он должен вычисляться на основе истории: дата подключения, смены тарифа, паузы, апгрейды в середине цикла. Если есть полная история, то период всегда можно пересчитать корректно. Если нет, то при любом нестандартном кейсе система начинает выдавать неверные суммы, которые сложно объяснить клиенту и ещё сложнее исправить задним числом.
Предусмотреть состояния подписки. Минимальный набор: активна, ожидает оплаты, заблокирована, отменена. Каждое состояние имеет свою логику доступа, начислений и автоматических действий системы. Состояние «активен / неактивен» это не покрывает.
Обеспечить идемпотентность денежных операций. Одно и то же списание не должно выполняться дважды при сетевом сбое или перезапуске. Каждая транзакция должна иметь уникальный идентификатор, повторный вызов возвращать тот же результат без повторного списания. Это базовое требование к надёжности, которое на старте кажется излишним, но его лучше заложить сразу.
Управление тарифами без изменений кода. Добавление тарифа, изменение цены, запуск новой опции, всё это должно настраиваться через интерфейс, а не через задачу разработчику. Это снимает зависимость коммерческих решений от бэклога и позволяет тестировать ценообразование в спокойном режиме.
Разделить выставление счёта и факт оплаты. Счёт формируется в начале расчётного периода. Оплата фиксируется, когда деньги фактически поступили. Между ними есть состояние ожидания с собственной логикой и сроками. Без этого разделения корректный учёт задолженностей и работа с просрочками существенно усложняются.
Итог
Биллинг редко попадает в список приоритетных задач. Есть продукт, есть метрики, есть задачи, которые видны пользователям. Биллинг не в этом списке, пока не начинает мешать. Именно поэтому архитектурные решения по нему так часто откладываются, и именно поэтому потом возвращаются в виде технического долга с высоким приоритетом.
Я ни разу не встречала команду, которая пожалела, что нормально заложила биллинг с самого начала. Зато команд, которые пожалели об обратном, хватает.
А как у вас? Писали ли биллинг сами? интересно услышать, что оказалось неожиданно сложным и на каком этапе стало понятно, что задача переросла первоначальную оценку в пару спринтов. Или, наоборот, написали и всё до сих пор работает.


















