Половина проектов, которые хотят перейти на подписную модель, застревает на одном месте: технари говорят «сложно», а владелец не понимает, что именно. В итоге рекуррентные платежи откладываются, деньги теряются на ручных счетах. Вспоминают об автоматизации в таких случаях, когда база подписчиков перестает помещаться в таблицу.
Разберём, как это работает на практике.
Рекуррентный платеж — это сохраненные данные карты и автоматическое списание по расписанию. Без звонков клиенту, без «скиньте ссылку на оплату», без просроченных подписок из-за человеческого фактора. Технически требуется несколько API-запросов, токенизация карты и логика повторных попыток при неуспешном списании.
На бумаге звучит громоздко. На деле для большинства сервисов это два-три дня работы разработчика, если документация нормальная и есть песочница для тестов. Разберем, как устроена токенизация, что происходит при отказе банка, как уведомлять клиента и где чаще всего ломается логика повторных попыток.
Начнём с того, что обычно понимают неправильно еще до первой строки кода.
Рекуррентные платежи подключают двумя способами. Технически они отличаются. Первый — фиксированное автосписание. Одна сумма снимается в один день каждого периода. Подходит для подписок с единой ценой: SaaS-тариф, доступ к курсу, ежемесячный сервисный сбор.
Второй — гибкое автосписание. Сумма каждый раз разная, карта клиента уже сохранена и не требует повторного ввода. Нужно интернет-магазинам с переменными корзинами, маркетплейсам и сервисам с потреблением по факту.
Смешивать их в одной интеграции не нужно. Определитесь с конкретным вариантом заранее. Логика работы с токеном карты в обоих случаях одинакова, но сценарий инициации платежа разный.
При первой оплате клиент вводит данные карты в платежной форме. Провайдер возвращает не номер карты, а токен (строку вида card_tok_xxxxxx). Этот токен сохраняете в базе и используете для всех последующих списаний.
Процесс выглядит так. Клиент нажимает «Оплатить», завершает первый платеж. В вебхуке payment.succeeded вы получаете объект с полем payment_method.id или аналогичным (зависит от провайдера). Это и есть токен. Храните его в зашифрованном виде (требование PCI DSS, игнорировать которое нельзя).
Никогда не передавайте и не фиксируйте полные данные карты. Токенизация решает именно эту проблему: вы инициируете списание, не касаясь персональных данных.
Провайдер не управляет вашим расписанием. Он принимает платежный запрос тогда, когда вы его отправляете. Планирование в данном случае является вашей задачей.
Надежный инструмент для этого cron-задача или очередь задач (Celery, Bull, Sidekiq, зависит от стека). Логика процесса простая. В нужную дату достаете из базы все активные подписки, у которых next_billing_date ≤ сегодня, и отправляете запрос на списание по сохраненному токену.
Предусмотрите состояния: pending, charged, failed, retry. Без них не получится корректно обработать неудачное списание. Типичная причина отказа в недостатке средств. В первые 24 часа после неудачного списания около 30% повторных попыток проходят успешно. Логика повторной попытки напрямую влияет на поступление выручки.
Отказ по карте не какое-то там исключение, а вполне штатная ситуация. Карта заблокирована, лимит исчерпан, банк требует подтверждения. Такое происходит у каждого сервиса с подписками. Без обработки этого сценария вы просто теряете платёж.
Минимальная рабочая схема повторной попытки: первый раз через 24 часа, второй через 72 часа, третий через 7 дней. После трёх отказов клиент должен получить уведомление с просьбой обновить карту или оплатить вручную. Здесь пригодятся вебхуки: провайдер отправляет событие payment.failed, вы ловите его, обновляете статус в базе и отправляете письмо или push-уведомление.
Не блокируйте доступ к сервису после первого отказа. Дайте клиенту grace period в 3–7 дней. Резкое отключение увеличивает отток сильнее, чем неоплаченный период.
Работать без вебхуков технически можно, но это плохая практика. Polling — запросы к API каждые N секунд нагружают и вашу инфраструктуру, и сторону провайдера. Вебхук сам приходит в нужный момент.
Базовая настройка такая: укажите endpoint в личном кабинете провайдера, убедитесь, что он доступен по HTTPS, реализуйте проверку подписи запроса. Подпись передается в заголовке X-Signature или X-Webhook-Token (алгоритм верификации описан в документации). Без проверки подписи любой может имитировать успешный платеж, отправив POST-запрос на ваш адрес.
Отвечайте на вебхук HTTP 200 в течение 5 секунд. Тяжелую логику (начисление баллов, отправку письма, обновление доступа) выносите в очередь, иначе получите таймаут и повторную доставку того же события.
Несколько обязательных пунктов, которые часто пропускают. Клиент должен явно согласиться на автосписания (чекбокс с текстом «Я разрешаю сохранить карту и списывать средства автоматически» на форме оплаты). Это требование платёжных систем, а не просто рекомендация.
Пользователь должен иметь возможность отменить подписку самостоятельно, без звонков в поддержку. Отсутствие этой функции нарушает правила Visa и Mastercard и часто приводит к чарджбэкам.
Токены карт храните только в зашифрованном виде. Большинство платежных провайдеров хранят токены у себя и отдают вам только идентификатор (самый безопасный вариант).
Тестирование в sandbox должно охватить четыре сценария: успешное первое списание и получение токена, успешное повторное списание по токену, отказ по карте и корректная отработка retry-логики, отмена подписки и прекращение дальнейших попыток списания.
Выкатить интеграцию в продакшн без этих четырёх проверок, значит гарантированно получить инциденты в первый расчетный день. Провайдеры с нормальной документацией (URLPAY, например) прямо указывает на необходимость тестирования сценария failed payment (предоставляют sandbox, который полностью повторяет поведение боевого API).
После тестирования проверьте логи: все ли события вебхуков пришли, правильно ли обновились статусы, получил ли тестовый пользователь уведомление об ошибке. Только после этого переключайтесь на боевые ключи.
После запуска нужны три цифры: процент успешных списаний (benchmark для SaaS 85–92%), процент восстановленных платежей после повторной попытки и показатель involuntary churn (отток из-за технических отказов по карте, не по желанию клиента).
Если успешных списаний меньше 80%, то смотрите на структуру отказов. Чаще всего это устаревшие карты или проблемы с конкретным банком-эмитентом. Последнее решается на уровне провайдера. Некоторые автоматически переключают маршрутизацию при повышенном числе отказов с карт определённого банка.
URLPAY передает продавцам данные по статусам транзакций в реальном времени через те же вебхуки. Это позволяет строить аналитику на своей стороне без дополнительных запросов к API.
Рекуррентные платежи пугают только до первой рабочей интеграции. Когда токенизация настроена, вебхуки отдают статусы, а списания уходят по расписанию без участия пользователя, то становится понятно, что большая часть сложности была в голове, а не в коде.
Если не хочется собирать это из трёх разных провайдеров, посмотрите на URLPAY. Один API закрывает карты, СБП, QR и платёжные ссылки, подключение продавца занимает от одного дня, документация с примерами запросов на urlpay.io/docs/api. Работает с ИП и самозанятыми без долгого онбординга.
Посмотреть, как все устроено изнутри, можно тут urlpay.io. Вопросы по интеграции направляйте в бот поддержки @url_pay_help_bot.