Как вести учет подписок и успешных платежей в сервисах и онлайн-бизнесе

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

Что проверить перед запуском учёта подписок

Как вести учет подписок и успешных платежей - иллюстрация
  • Сделать: описать жизненный цикл подписки (создание, пробный период, продление, пауза, отмена, возврат) и согласовать статусы с бизнесом и поддержкой.
  • Сделать: выбрать основной источник истины по платежам — биллинг, ваша БД или бухгалтерская система, и зафиксировать приоритеты при расхождениях.
  • Проверить: есть ли в платёжном провайдере стабильные вебхуки обо всех изменениях статуса платежа и подписки, а также журналы повторной отправки.
  • Проверить: схема БД позволяет однозначно связать пользователя, подписку, инвойс и транзакцию, без дублирования и неочевидных правил.
  • Не допустить: начала миграции или маркетинговых кампаний до настройки логирования, алертов и минимальных ежедневных сверок по деньгам и количеству подписок.
  • Не допустить: ситуации, когда изменение статуса в биллинге не синхронизируется в продукт (доступ остаётся открытым после неуспешного списания).

Структура данных для подписок и транзакций

Этот раздел подходит продуктам с повторяющимся биллингом: подписки, 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/подпись запросов от провайдера, хранить заголовки безопасности.
  • Не допустить: прямое выполнение бизнес-логики из контроллера вебхука без очереди и идемпотентности.
  1. Настроить приём вебхуков и базовую безопасность

    Создайте отдельный HTTP endpoint для каждого провайдера, логируйте все запросы и ответы. Проверяйте подпись (secret) или IP-диапазоны, возвращайте 2xx только после записи события.

    • Пример пути: POST /webhooks/stripe, /webhooks/yookassa.
    • Храните весь payload в текстовом поле для последующей отладки.
  2. Сделать идемпотентность обработки

    Вебхуки могут приходить повторно, поэтому обработка должна быть безопасной при нескольких вызовах. Используйте внешний ID события и статус обработки.

    • Поле: external_event_id + UNIQUE(provider, external_event_id).
    • Флаг: processed_at IS NOT NULL — событие уже обработано.
  3. Связать события с подписками и платежами

    На этапе обработки вебхука найдите соответствующую подписку и платёж по внешним ID. Если не нашли — заведите отложенную задачу и лог ошибки.

    • Пример SQL: SELECT * FROM payments WHERE provider_payment_id = :id.
    • Храните mapping provider_subscription_idsubscription_id в отдельной таблице.
  4. Обновлять статусы и давать/забирать доступ

    При успешном платеже обновляйте транзакцию на succeeded, продлевайте current_period_end подписки и активируйте доступ. При неуспехе — отмечайте failed, планируйте ретраи и уведомления.

    • Пример обновления: UPDATE subscriptions SET status = 'active', current_period_end = :date WHERE id = :id.
    • Избегайте изменения доступа до завершения транзакции в БД.
  5. Настроить повторные попытки списания

    Логику ретраев можно строить в биллинге или своей системе. Определите график (например, через 1, 3, 5 дней) и условия окончательной отмены подписки.

    • Сценарий: статус past_due при первом неуспехе, отмена только после исчерпания ретраев.
    • Храните количество попыток и последнюю ошибку.
  6. Синхронизировать изменения статуса подписки

    Любое событие из биллинга, влияющее на активность подписки (cancel, pause, resume, refund), должно обновлять вашу БД и продукт. Не полагайтесь только на Cron-задачи.

    • События: subscription_canceled, subscription_resumed, invoice.payment_failed.
    • Пример события во внутренней шине: billing.subscription.canceled.
  7. Построить мониторинг и оповещения

    Отслеживайте процент неуспешных платежей, количество необработанных вебхуков и задержки обработки. Это позволяет вовремя замечать проблемы с провайдером или кодом.

    • Метрики: 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). Убедитесь, что статус синхронизирован и в биллинге, и в продукте.

Как избежать хаоса при использовании нескольких платёжных систем?

Вводите единый внутренний слой: свои статусы, свои события и маппинг для каждого провайдера. Все интеграции должны приводить данные к этой модели, а отчёты и метрики опираться именно на неё, а не на «нативные» статусы шлюза.