Если после неудачного списания подписка остается активной, пользователь продолжает получать услугу без подтвержденной оплаты либо доступ отключается слишком поздно. Иногда это предусмотренный grace period, но часто локальный статус просто не обновился после payment failed.

Не отключайте доступ непосредственно по первому временному сетевому сбою и не оставляйте подписку active навсегда. Нужна явная модель состояний, политика повторных попыток и надежная обработка событий платежной системы.

Сверьте три независимых состояния

Статус подписки, счета и фактического доступа не всегда меняются одновременно.

  • Платеж отклонен, а локальная подписка остается active.
  • Invoice имеет past_due, но дата доступа не меняется.
  • Webhook получен, однако worker завершился с ошибкой.
  • После успешного retry доступ не восстанавливается корректно.

Почему возникает проблема

Платежный lifecycle асинхронный и требует обработки повторных и запоздавших событий.

  • Обрабатывается только событие успешной оплаты.
  • Webhook payment failed не проходит проверку или теряется в очереди.
  • Локальный статус вычисляется только при входе пользователя.
  • Grace period не имеет даты окончания и отдельного состояния.
  • События приходят не по порядку и перезаписывают новый статус старым.

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

Используйте ID подписки, invoice и события без данных карты.

  1. Сверьте timeline событий у провайдера и в локальном журнале.
  2. Проверьте подпись webhook, HTTP-ответ и retries.
  3. Найдите transition локального статуса и задачу отключения.
  4. Сравните period end, grace end и фактический доступ.
  5. Проверьте порядок обработки старых и новых событий.

Используйте явную модель состояний

Active, past_due, grace, suspended и canceled должны иметь разные правила.

  • Первый отказ переводит подписку в past_due или grace по политике.
  • Успешный retry восстанавливает active идемпотентно.
  • Окончание grace запускает контролируемое ограничение доступа.
  • Ручная отмена не конфликтует с автоматическими событиями.

Как исправить проблему

Восстановите обработку событий и пересчитайте затронутые подписки по источнику истины.

  1. Добавьте обработчики отказа, retry, отмены и успешного восстановления.
  2. Храните event ID и игнорируйте повторную обработку.
  3. Сравнивайте версию или время события перед изменением статуса.
  4. Настройте отдельную задачу окончания grace period.
  5. Добавьте периодическую сверку с платежной системой.

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

  • Неудачное списание переводит подписку в ожидаемое состояние.
  • Доступ отключается только по утвержденной политике.
  • Успешный повторный платеж восстанавливает услугу.
  • Повторный webhook не меняет результат второй раз.

Типичные ошибки

  • Отключать доступ по одному timeout.
  • Считать subscription active доказательством оплаты invoice.
  • Не хранить ID обработанных событий.
  • Полагаться только на cron без webhook и сверки.

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

  • Документируйте billing state machine.
  • Мониторьте очередь webhook и число past_due.
  • Тестируйте отказ, retry и запоздалое событие.
  • Запускайте регулярную reconciliation.

Когда нужна помощь

Если подписка остается активной после отказа платежа, я проверю события, очередь, state machine и даты доступа, исправлю синхронизацию и безопасно сверю существующие записи.