Повторяющиеся платежи, или recurring billing — это процесс автоматического списания денег с клиента каждый месяц или каждый год, за которым стоит куда более сложная техническая инфраструктура, чем принято думать. Стоит пользователю один раз активировать подписку, как все последующие списания должны происходить без его участия, а это порождает целый набор задач: безопасное хранение карты, точный расчёт в нужное время и обработку неудачных попыток списания. В этой статье мы рассматриваем не бизнес-модель подписок, а лежащую под ней механику биллинга — что именно и как нужно построить разработчику. Цель — дать вам знания, необходимые для построения надёжной системы повторяющихся платежей в вашем сайте или приложении.
Хранится не карта, а токен
Первое и самое важное правило повторяющихся платежей заключается в том, что вы никогда не храните номер карты клиента на своём сервере. Вместо этого платёжный провайдер (Stripe, Payme, Click или другой шлюз) хранит карту в своей защищённой среде и возвращает вам специальный идентификатор, называемый токеном. Сам по себе этот токен не позволяет списать деньги с карты, но даёт возможность отдать вашему провайдеру команду «спиши столько-то денег с той сохранённой карты». Благодаря такому подходу ваша система освобождается от самой тяжёлой части требований PCI DSS, поскольку реальные данные карты вообще не касаются вашего сервера.
Технически этот процесс обычно проходит в два этапа. При первом платеже клиент вводит данные карты в защищённую форму провайдера, и провайдер через операцию вроде «setup intent» подтверждает и сохраняет карту. Затем в вашей базе данных вместе с записью пользователя сохраняются этот токен и идентификатор клиента на стороне провайдера. Для всех последующих платежей вы просто используете этот токен, отправляя запрос к API провайдера, и он списывает деньги с карты. Важный нюанс — срок действия токена может истечь или карта может быть перевыпущена, поэтому провайдеры часто предлагают сервис «card updater», который автоматически обновляет устаревшие данные карты.
Расписание списаний и биллинговый цикл
Каждая подписка имеет свой биллинговый цикл: месячный, годовой или произвольный интервал. Система должна вести расчёт следующей даты списания для каждой активной подписки и запускать платёж, когда эта дата наступает. Реализовать это можно двумя основными способами. Первый — опереться на собственную биллинговую систему провайдера (например, Stripe Billing), при которой Stripe сам управляет расписанием и в нужный момент уведомляет вас через webhook. Второй — ваш собственный cron-процесс ежедневно запускается, находит подписки с наступившей сегодня датой списания и для каждой отправляет провайдеру запрос на charge.
При управлении собственным расписанием возникает несколько тонких вопросов. Например, на какой день февраля попадёт месячная подписка, начавшаяся 31 января? Большинство систем в таком случае переносят списание на последний день месяца, а затем по возможности возвращаются к исходной дате. Часовые пояса тоже важны — платёж должен запускаться в заданное время на вашем сервере, а не в полночь по времени клиента, иначе переход на летнее время или разные пояса создадут путаницу. Необходим и ключ идемпотентности, поскольку при повторном запуске cron одна и та же подписка не должна быть списана дважды.
Dunning: повторные попытки неудачного списания
В реальной жизни заметная доля повторяющихся платежей завершается неудачей с первой попытки — на карте недостаточно средств, истёк срок действия или банк временно отклоняет транзакцию. Процесс обработки таких ситуаций называется dunning, и он играет решающую роль в сохранении вашего дохода. Из-за плохо построенной логики dunning теряются клиенты, которые на самом деле способны платить, а просто столкнулись с разовой ошибкой, тогда как хорошо выстроенная логика может вернуть более половины неудачных платежей.
Основа стратегии dunning — умное расписание повторных попыток. Немедленная повторная попытка сразу после первой неудачи часто бесполезна, поскольку причина не изменилась. Вместо этого система повторяет списание с интервалом в несколько дней, например на 1-й, 3-й, 5-й и 7-й день, ведь за это время клиент может пополнить карту или получить зарплату. С каждой попыткой клиенту следует отправлять уведомление по email или SMS — «ваш платёж не прошёл, пожалуйста, обновите данные карты». Если все попытки исчерпаны, подписка приостанавливается или переводится в ограниченный режим. Некоторые системы применяют «smart retry», когда машинное обучение выбирает для каждой карты время с наибольшей вероятностью успеха.
Проратация: смена тарифа в середине цикла
Проратация (proration) — это механизм справедливого перерасчёта платежа, когда клиент меняет тариф в середине биллингового цикла. Например, клиент 1-го числа подписался на план за 100 тысяч сумов, но на 15-е число хочет перейти на план за 200 тысяч. В этом случае он должен получить возврат за неиспользованную часть старого плана за оставшиеся полмесяца и доплатить разницу за оставшуюся часть нового плана. Проратация автоматизирует этот расчёт и определяет, сколько именно денег взять с клиента или сколько кредита добавить к его будущему счёту.
Технически проратация рассчитывается на уровне дней: число оставшихся дней делится на общее число дней в биллинговом цикле, и эта пропорция умножается на цену. При переходе на более дорогой тариф (upgrade) разница обычно списывается сразу, а при понижении (downgrade) чаще сохраняется как кредит и учитывается в следующем счёте. Важное решение здесь — выполнять ли проратацию сразу отдельной транзакцией или добавлять её к следующему обычному счёту. Многие провайдеры поддерживают оба варианта, однако с точки зрения структуры налогов и чека этот выбор имеет существенное значение.
Инвойсы, чеки и налоги
Для каждого успешного платежа система должна создавать инвойс или чек, в котором фиксируется, кто, когда, за какую услугу и сколько заплатил. Это нужно не только для прозрачности перед клиентом, но и для бухгалтерии и налоговой отчётности. Инвойс обычно имеет уникальный номер и должен генерироваться в последовательном порядке, поскольку во многих странах налоговое законодательство требует нумерации без пропусков. В Узбекистане же действуют требования к фискальному чеку, и каждый платёж может потребоваться передавать в систему онлайн-касс налоговых органов.
Расчёт налогов создаёт в повторяющемся биллинге отдельную сложность, особенно если услуга продаётся клиентам в разных юрисдикциях. Ставка НДС или налога с продаж меняется в зависимости от местоположения клиента и должна либо учитываться как часть цены, либо добавляться сверху. Хотя сервисы вроде Stripe Tax автоматизируют этот процесс, в локальных условиях, то есть при адаптации к налоговой системе Узбекистана, часто требуется дополнительная ручная интеграция. Лучший подход — отделить налоговую логику от биллинговой и построить её как отдельный модуль, тогда при изменении ставок не придётся переписывать всю систему.
Chargeback и споры
Chargeback — это ситуация, когда клиент обращается в свой банк с требованием вернуть проведённый платёж. В повторяющихся платежах chargeback особенно распространён, поскольку клиенты порой забывают отменить подписку или, увидев неожиданное списание, принимают его за мошенничество. Каждый chargeback приводит к потере не только суммы транзакции, но и дополнительного штрафа, удерживаемого банком, а если доля chargeback'ов высока, платёжный провайдер может вообще заблокировать ваш аккаунт.
Лучший способ снизить число chargeback'ов — прозрачность. Описание платежа в выписке (statement descriptor) должно быть понятным, перед каждым списанием клиенту следует отправлять напоминание, а процесс отмены подписки должен быть простым. Технически ваша система должна слушать webhook'и о chargeback и при их получении автоматически приостанавливать подписку, ограничивать доступ клиента и обновлять внутренние записи. В некоторых случаях в споре по chargeback в качестве доказательства требуются логи использования сервиса пользователем и подтверждения доставки, поэтому хранить эти данные полезно.
Синхронизация состояния через webhook
Одна из самых критичных технических частей системы повторяющегося биллинга — синхронное хранение состояния. Ваша база данных и платёжный провайдер всегда должны быть согласны относительно реального состояния подписки. Инструмент для этого — webhook'и, то есть HTTP-запросы, которые провайдер отправляет на ваш сервер в реальном времени при наступлении важных событий (платёж прошёл успешно, платёж не прошёл, подписка отменена, начат chargeback). Если опираться только на собственную базу, можно остаться в неведении об отменённой на стороне провайдера подписке и продолжать оказывать клиенту услугу.
Правильная обработка webhook'ов требует соблюдения нескольких правил. Во-первых, проверяйте подпись каждого webhook'а, иначе кто-то сможет отправить поддельное событие и обмануть вашу систему. Во-вторых, делайте обработку webhook'ов идемпотентной — одно и то же событие может прийти несколько раз, поэтому перед обработкой проверяйте, не было ли оно обработано ранее. В-третьих, принимайте webhook сразу, а тяжёлую обработку выполняйте в фоне через очередь, поскольку провайдер ждёт быстрого ответа и при задержке отправит запрос повторно. Учитывайте и беспорядочную доставку: события могут прийти не в том порядке, в каком были отправлены, поэтому определяйте актуальное состояние по временной метке каждого события.
Инструменты и реалии Узбекистана
В мировом масштабе Stripe Billing считается де-факто стандартом для повторяющихся платежей, поскольку решает значительную часть задач по проратации, dunning, налогам и webhook'ам прямо из коробки. В качестве альтернативы существуют специализированные биллинговые платформы вроде Chargebee, Recurly и Paddle, которые особенно удобны для бизнесов со сложными тарифными моделями или глобальными налоговыми требованиями. Эти инструменты избавляют вас от необходимости строить биллинговую инфраструктуру с нуля, но работают преимущественно с международными картами и валютами.
В условиях Узбекистана ситуация специфична. Локальные платёжные системы — Payme, Click, Uzum и другие — ориентированы в основном на разовые платежи, и их возможности по повторяющимся списаниям и сохранённой карте (токенизации) развиты не так, как у международных провайдеров. Некоторые системы позволяют сохранить токен карты и провести повторный платёж позже, но функции высокого уровня вроде проратации или автоматического dunning вам зачастую придётся строить самим. Поэтому при построении повторяющегося биллинга в Узбекистане самый практичный подход — написать ядро биллинговой логики (расписание, повторы, проратацию, управление состоянием) самостоятельно, а платёжного провайдера использовать лишь как слой «списания денег». Это даёт независимость от ограничений провайдера и возможность в будущем подключать новые платёжные системы.