Учет подписок и успешных платежей строится вокруг трех вещей: четкой модели данных, надежной платформы биллинга для подписок и автоматического списания средств, и системы событий (вебхуки, логи, сверки). Цель — всегда знать, кто на что подписан, когда платёж прошёл, а когда — нет, и что делать дальше.
Что проверить перед запуском учёта подписок

- Сделать: описать жизненный цикл подписки (создание, пробный период, продление, пауза, отмена, возврат) и согласовать статусы с бизнесом и поддержкой.
- Сделать: выбрать основной источник истины по платежам — биллинг, ваша БД или бухгалтерская система, и зафиксировать приоритеты при расхождениях.
- Проверить: есть ли в платёжном провайдере стабильные вебхуки обо всех изменениях статуса платежа и подписки, а также журналы повторной отправки.
- Проверить: схема БД позволяет однозначно связать пользователя, подписку, инвойс и транзакцию, без дублирования и неочевидных правил.
- Не допустить: начала миграции или маркетинговых кампаний до настройки логирования, алертов и минимальных ежедневных сверок по деньгам и количеству подписок.
- Не допустить: ситуации, когда изменение статуса в биллинге не синхронизируется в продукт (доступ остаётся открытым после неуспешного списания).
Структура данных для подписок и транзакций
Этот раздел подходит продуктам с повторяющимся биллингом: подписки, SaaS, сервисы с рекуррентными списаниями. Для простых разовых платежей сложная модель не нужна и только повысит стоимость поддержки.
- Сделать: разделить сущности по уровням
- Пользователь / аккаунт.
- Подписка (product + тариф + период + статус).
- Инвойс (счёт на конкретный период).
- Транзакция (конкретная попытка списания).
Пример связей: user → subscriptions → invoices → payments.
- Сделать: ввести явные статусы подписок
- draft, trial, active, past_due, paused, canceled, expired, refunded.
- Хранить дату начала и дату окончания периода (current_period_start / end).
- Сделать: унифицировать статусы транзакций
- pending, succeeded, failed, canceled, refunded.
- Хранить код ошибки провайдера и человекочитаемое описание.
- Сохранять raw-payload от платёжной системы (JSON текстом).
- Короткий пример поля:
payments.raw_provider_payload JSONB.
- Проверить: уникальные идентификаторы
- В каждой таблице — свой внутренний ID (UUID или bigint).
- Отдельные поля для внешних ID:
provider_subscription_id,provider_payment_id. - Уникальность:
UNIQUE(provider, provider_payment_id).
- Проверить: согласованность дат
- Все даты в UTC, хранить таймзону только для отображения.
- Избегать логики по локальному времени (особенно при месячных периодах).
- Пример проверки:
SELECT * FROM subscriptions WHERE current_period_end < now()и статус = ‘active’.
- Не допустить: смешивание бизнес-логики и провайдера
- Не завязывать статусы UI на «как их называет Stripe/YooKassa».
- Внутри использовать свои статусы, а маппинг держать в одном месте.
- Не хранить критичную бизнес-логику только в настройках внешней системы.
Выбор инструментов: биллинг, база данных, брокер событий
- Сделать: определить роль биллинга
- Нужна ли полноценная платформа биллинга для подписок и автоматического списания средств или достаточно платёжного агрегатора с рекуррентами.
- Оценить наличие API, вебхуков, ретраев, поддержки разных методов оплаты.
- Понять, нужна ли экспортируемая отчётность для бухучёта.
- Сделать: спроектировать базу данных
- Реляционная БД для критичных финансовых данных: PostgreSQL / MySQL.
- Отдельные схемы или БД для биллинга, не смешивать всё в одной таблице users.
- Пример: таблицы
subscriptions,invoices,payments,webhook_events.
- Сделать: добавить брокер событий
- Использовать очередь (RabbitMQ, Kafka, SQS) для обработки вебхуков и внутренних событий.
- Отделить приём события от бизнес-логики (ретраи, идемпотентность, логирование).
- Пример ключа:
billing.subscription.renewed.
- Проверить: возможности выбранной системы
- Если планируете учет подписок и рекуррентных платежей для бизнеса со сложными тарифами — проверьте, что биллинг поддерживает прайс-листы, купоны, прайсы по объёму.
- Сервис для отслеживания подписок и успешных платежей онлайн должен уметь фильтровать по статусам, пользователям, источникам.
- Если планируете программу для учета регулярных платежей и подписок SaaS, проверьте лимиты API и вебхуков.
- Проверить: SLA и поддержку
- Наличие статуса системы, логов инцидентов, регламентов по времени ответа.
- Песочница / тестовый контур для отладки и регресса.
- Наличие SDK для нужных языков.
- Не допустить: преждевременную покупку и интеграцию
- Не покупать систему учета подписок и автоматизации платежей купить «по маркетингу» без тестирования ключевых сценариев.
- Не полагаться только на визуальные отчёты — обязательна выгрузка в сырых данных.
- Не начинать миграцию, не имея плана отката и бэкапов.
Отслеживание успешных и повторных платежей: процессы и вебхуки
Подготовительный чек-лист перед внедрением процессов:
- Сделать: включить все нужные вебхуки на стороне платёжного провайдера (создание подписки, успешный платёж, неуспех, возврат, отмена).
- Сделать: завести таблицу
webhook_eventsс полямиid,provider,type,payload,processed_at,status. - Проверить: входящий IP/подпись запросов от провайдера, хранить заголовки безопасности.
- Не допустить: прямое выполнение бизнес-логики из контроллера вебхука без очереди и идемпотентности.
- Настроить приём вебхуков и базовую безопасность
Создайте отдельный HTTP endpoint для каждого провайдера, логируйте все запросы и ответы. Проверяйте подпись (secret) или IP-диапазоны, возвращайте 2xx только после записи события.
- Пример пути:
POST /webhooks/stripe,/webhooks/yookassa. - Храните весь
payloadв текстовом поле для последующей отладки.
- Пример пути:
- Сделать идемпотентность обработки
Вебхуки могут приходить повторно, поэтому обработка должна быть безопасной при нескольких вызовах. Используйте внешний ID события и статус обработки.
- Поле:
external_event_id+UNIQUE(provider, external_event_id). - Флаг:
processed_at IS NOT NULL— событие уже обработано.
- Поле:
- Связать события с подписками и платежами
На этапе обработки вебхука найдите соответствующую подписку и платёж по внешним ID. Если не нашли — заведите отложенную задачу и лог ошибки.
- Пример SQL:
SELECT * FROM payments WHERE provider_payment_id = :id. - Храните mapping
provider_subscription_id→subscription_idв отдельной таблице.
- Пример SQL:
- Обновлять статусы и давать/забирать доступ
При успешном платеже обновляйте транзакцию на
succeeded, продлевайтеcurrent_period_endподписки и активируйте доступ. При неуспехе — отмечайтеfailed, планируйте ретраи и уведомления.- Пример обновления:
UPDATE subscriptions SET status = 'active', current_period_end = :date WHERE id = :id. - Избегайте изменения доступа до завершения транзакции в БД.
- Пример обновления:
- Настроить повторные попытки списания
Логику ретраев можно строить в биллинге или своей системе. Определите график (например, через 1, 3, 5 дней) и условия окончательной отмены подписки.
- Сценарий: статус
past_dueпри первом неуспехе, отмена только после исчерпания ретраев. - Храните количество попыток и последнюю ошибку.
- Сценарий: статус
- Синхронизировать изменения статуса подписки
Любое событие из биллинга, влияющее на активность подписки (cancel, pause, resume, refund), должно обновлять вашу БД и продукт. Не полагайтесь только на Cron-задачи.
- События:
subscription_canceled,subscription_resumed,invoice.payment_failed. - Пример события во внутренней шине:
billing.subscription.canceled.
- События:
- Построить мониторинг и оповещения
Отслеживайте процент неуспешных платежей, количество необработанных вебхуков и задержки обработки. Это позволяет вовремя замечать проблемы с провайдером или кодом.
- Метрики:
failed_payments_rate,webhook_lag_seconds. - Алерты в Slack/почту при превышении порогов.
- Метрики:
Решение проблем с синхронизацией статусов и возвратами
- Сделать: регулярные сверки со стороны биллинга
- Использовать API-списки подписок и платежей для периодической сверки (ежедневно/еженедельно).
- Выгружать только изменённые за период записи по временной метке обновления.
- Сделать: процедуру ручной ресинхронизации
- Добавить админ-инструмент «обновить из провайдера» по ID подписки/платежа.
- Разрешить операции только администраторам, логировать все изменения.
- Сделать: отдельную логику по возвратам
- Возврат не всегда означает отмену подписки, отделяйте эти события.
- Храните сумму возврата, причину, ссылку на исходный платёж.
- Решите, будет ли доступ немедленно закрываться при полном возврате.
- Проверить: консистентность статусов
- Запросы-контрольные: активные подписки с истёкшим периодом, платежи
succeededбез активной подписки. - Проверять количество активных подписок и сумму платежей против отчётов провайдера.
- Запросы-контрольные: активные подписки с истёкшим периодом, платежи
- Проверить: обработку задержанных событий
- Иногда вебхуки приходят позже, чем пользователь видит статус в интерфейсе.
- Убедитесь, что обрабатываете события по времени создания, а не прихода.
- Разрешите «откат» статуса при получении более старого, но важного события.
- Проверить: работу с несколькими провайдерами
- Если используете несколько платёжных шлюзов, убедитесь, что маппинг статусов сделан для каждого.
- Разведите namespace по провайдерам в ID и типах событий.
- Не допустить: скрытых расхождений
- Не игнорируйте несовпадения «по мелочи» между БД и отчётами — они накапливаются.
- Не исправляйте данные вручную в БД без отражения в провайдере или журналах.
- Не удаляйте «странные» записи без предварительного экспорта и анализа.
- Не допустить: смешения возвратов и чарджбеков
- Чарджбек от банка и добровольный возврат — разные сценарии, требующие разной бизнес-логики.
- Отдельно отслеживайте, какой инициатор и как это влияет на доступ и метрики.
Автоматизация сверок и триггерных уведомлений
- Сделать: ежедневные сверки по деньгам
- Автоматическая выгрузка платежей из провайдера и сравнение с внутренней БД.
- Флажок «расхождение найдено» и уведомление ответственных.
- Фокус на суммах и количестве транзакций по дням.
- Сделать: сценарии уведомлений для клиентов
- Уведомления о предстоящем списании, неуспешном платеже, скорой заморозке доступа.
- Автоматические письма/пуши с ссылкой на обновление платёжных данных.
- Примеры триггеров:
payment_failed_first,subscription_expires_3d.
- Сделать: алерты для команды
- Резкий рост доли неуспешных списаний.
- Инциденты с вебхуками (нет успешных обработок за N минут).
- Ошибки при запросах к API биллинга.
- Проверить: приоритизацию уведомлений
- Не перегружать пользователя письмами — лимитировать количество сообщений на один инцидент.
- Убедиться, что повторные напоминания прекращаются после успешного платежа.
- Хранить историю отправленных уведомлений с типом и временем.
- Проверить: дружелюбность и безопасность текстов
- Не логировать полные реквизиты карт или конфиденциальные данные.
- В письмах давать только безопасные ссылки для обновления оплаты.
- Примеры: ссылки одноразовые, с коротким временем жизни.
- Не допустить: ручных сверок как постоянной практики
- Ручная сверка подходит только как временная мера или для инцидентов.
- Регулярные операции должны быть автоматизированы и воспроизводимы скриптами.
- Избегайте зависания процесса на одном человеке.
- Не допустить: отсутствия единого центра уведомлений
- Соберите все сценарии в одном месте: кто, когда, при каком событии получает сообщение.
- Иначе разные команды будут отправлять конфликтующие письма по одним и тем же событиям.
Метрики и отчёты для контроля удержания и дохода
- Вариант 1: встроенная аналитика биллинга
- Использовать готовые отчёты платёжной системы или стороннего сервиса как «быстрый старт».
- Подходит для небольших проектов и ранних этапов, когда важна скорость.
- Минус — ограниченная гибкость и сложности с кросс-продуктовым анализом.
- Вариант 2: собственное хранилище и BI
- Выгружать данные из биллинга и своей БД в отдельное хранилище (DWH) и строить отчёты в BI-системах.
- Подходит, если учет подписок и рекуррентных платежей для бизнеса критичен и нужно много разрезов.
- Потребует больше усилий, но даёт гибкость.
- Вариант 3: специализированный сервис для подсчёта метрик
- Использовать внешнюю программу для учета регулярных платежей и подписок SaaS, которая считает MRR, churn, LTV.
- Подходит для SaaS и подписочных бизнесов, если нет ресурсов на собственный BI.
- Важно проверить интеграции с вашим биллингом и CRM до подключения.
- Вариант 4: комбинированный подход
- Операционную аналитику (неуспешные платежи, потоки вебхуков) держать у себя, а высокоуровневые метрики — во внешнем сервисе.
- Подходит, если используете сервис для отслеживания подписок и успешных платежей онлайн плюс базовый BI.
- Важно задокументировать, какие числа откуда берутся, чтобы избежать путаницы.
Практические ответы на типовые сценарии по учёту
Как понять, что платёж точно прошёл и подписка продлена?
Смотрите не только на ответ при оплате, а на вебхук об успешном платеже. Подписка считается продлённой, когда транзакция в вашей БД имеет статус succeeded и обновлён current_period_end, а пользователю выдан доступ.
Что делать, если вебхуки иногда не доходят?
Включите ретраи на стороне провайдера и делайте периодическую сверку через API по изменённым за период платежам и подпискам. Все необработанные или ошибочные события логируйте и поднимайте алерт, если накопилось много «подвисших» записей.
Как безопасно обрабатывать повторные списания при ошибках?

Храните количество попыток и последнюю ошибку, а саму логику ретраев выносите в очередь задач. Важно, чтобы каждая попытка имела свой ID транзакции и не приводила к двойному продлению подписки при повторной обработке.
Нужно ли хранить у себя полные данные карты клиента?
Нет, это рискованно и требует сертификаций. Используйте токены платёжного провайдера и не сохраняйте полные реквизиты карт в своей БД. В логах и событиях обрезайте чувствительную часть информации.
Как вести учёт при смене тарифного плана?
Фиксируйте смену тарифа как новое состояние текущей подписки или создавайте новый инвойс с перерасчётом. Важно хранить историю изменений: старый план, новый план, дата перехода, метод расчёта (pro-rate или с нового периода).
Что делать с подписками, если пользователь запрашивает полный возврат?

Зависит от вашей политики, но технически лучше: оформить возврат, зафиксировать причину, перевести подписку в статус, отражающий закрытие доступа (например, refunded_canceled). Убедитесь, что статус синхронизирован и в биллинге, и в продукте.
Как избежать хаоса при использовании нескольких платёжных систем?
Вводите единый внутренний слой: свои статусы, свои события и маппинг для каждого провайдера. Все интеграции должны приводить данные к этой модели, а отчёты и метрики опираться именно на неё, а не на «нативные» статусы шлюза.

