Когда рекуррентный платеж проходит без обновления статуса тарифа, клиент видит списание денег, но доступ не продлевается. Это неприятная ошибка на стыке платежной системы, webhook-обработчика, очередей и бизнес-логики подписки.

Нужно проследить один платеж от события в платежной системе до записи в базе: пришел ли webhook, обработался ли он один раз, нашлась ли подписка и обновился ли период доступа.

Что проверить в первую очередь

  • Найдите id платежа, id подписки, время списания и статус в платежной системе.
  • Проверьте, пришел ли webhook и какой HTTP-ответ получил провайдер.
  • Посмотрите очередь задач, если обновление тарифа выполняется асинхронно.
  • Проверьте, не попал ли платеж в ручную обработку или статус pending.

Основные причины

Такая проблема редко появляется сама по себе. Обычно ломается связка из нескольких настроек: данные уходят не туда, событие приходит не в том порядке, старая логика остается в кеше или права проверяются не на том уровне. Поэтому сначала нужно отделить симптом от причины.

  • Webhook пришел позже или не дошел из-за ошибки endpoint.
  • Обработчик не идемпотентен и пропустил повторное событие как дубль неправильно.
  • Подписка ищется по старому id, который изменился после смены тарифа.
  • Период доступа обновляется в одной таблице, а интерфейс читает другую.

Пошаговая диагностика

  • Сравните события invoice, payment_succeeded, subscription_updated и их порядок.
  • Проверьте подпись webhook и тело события до разбора JSON.
  • Найдите запись платежа в локальной базе и связь с пользователем.
  • Проверьте транзакцию: не откатилось ли обновление тарифа после успешной записи платежа.
  • Посмотрите, не блокирует ли cron или очередь задачу продления.

Как исправить

Исправление лучше делать небольшими шагами. Сначала зафиксируйте текущее поведение, затем внесите одну правку, проверьте контрольный сценарий и только после этого переходите к следующему месту. Так проще понять, какая именно правка решила проблему.

  • Сделайте обработку webhook идемпотентной по event_id и payment_id.
  • Обновляйте тариф и платеж в одной транзакции или с понятной компенсацией.
  • Храните внешний id подписки, платежа и клиента в отдельных полях.
  • Добавьте повторную обработку неуспешных webhook-событий.
  • Показывайте пользователю понятный статус, если платеж в обработке.

Безопасный план решения

  • Сделайте резервную копию файлов, базы или конфигурации, если правка затрагивает рабочий проект.
  • Повторите ошибку на тестовом пользователе, заказе, заявке или окружении, чтобы не работать вслепую.
  • Внесите минимальное изменение и сохраните возможность быстрого отката.
  • Проверьте основной сценарий, крайние случаи и права доступа для разных ролей.
  • После выкладки посмотрите логи и реальные события за первые часы работы.

Как проверить результат

  • Тестовый рекуррентный платеж продлевает тариф и период доступа.
  • Повтор webhook не создает двойное продление.
  • Задержанный webhook корректно обновляет подписку после получения.
  • В админке видно событие платежа, пользователя, тариф и результат обработки.

Чего не стоит делать

  • Не отключайте проверки, права, платежные статусы или защиту только ради быстрого исчезновения ошибки.
  • Не правьте рабочую базу массовым запросом без выборки, бэкапа и понимания последствий.
  • Не ориентируйтесь только на один успешный тест: проверьте повторный запуск, отмену, ошибку и нестандартные данные.
  • Не оставляйте временные ключи, токены, debug-режим и лишний вывод в публичном доступе.

Как не допустить повторения

  • Логируйте полный путь платежа: провайдер, webhook, очередь, подписка.
  • Добавьте мониторинг платежей без обновленного доступа.
  • Проверяйте сценарии смены тарифа, отмены, неуспешного списания и возврата.
  • Не полагайтесь только на redirect пользователя после оплаты.

Что подготовить перед исправлением

  • Ссылку на проблемную страницу, кабинет, заказ, интеграцию или API-метод.
  • Точное время ошибки и пример пользователя, товара, платежа или запроса.
  • Скриншот, текст ошибки, лог веб-сервера, приложения или webhook-события.
  • Краткое описание ожидаемого поведения: что должно было произойти вместо ошибки.

Частые вопросы

Почему нельзя обновлять тариф только после редиректа с оплаты?

Пользователь может не вернуться на сайт. Надежным источником должен быть webhook платежной системы.

Что делать с уже списанными платежами?

Нужно сверить платежи провайдера с локальной базой и аккуратно восстановить доступ без двойных начислений.

Когда стоит обратиться за помощью

Для исправления нужны доступы к платежной системе, webhook-логам, базе и тестовому тарифу. Важно не потерять историю списаний.

Итог

Рекуррентная оплата должна быть проверяемой цепочкой, а не набором случайных статусов. Я могу настроить webhook, идемпотентность, сверку платежей и корректное продление тарифа.