ПРАКТИЧЕСКИЙ РАЗБОР · 7 минут

Как настроить повторные платежи через ЮKassa для подписки

Сохранение способа оплаты, повторное списание через API ЮKassa, сроки подписки и обработка неуспешных платежей

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

Через месяц возникает следующий вопрос. Нужно ли снова отправлять пользователя на страницу оплаты или приложение может продлить подписку автоматически? Для второго варианта используют рекуррентные платежи, которые также называют автоплатежами. Разберём, как устроить их через ЮKassa и какие задачи остаются на стороне вашего сервера.

Чем автоплатёж отличается от обычного

При обычной покупке пользователь подтверждает конкретный платёж. Сервер создаёт заказ, ЮKassa проводит оплату, а приложение проверяет результат и выдаёт доступ. При автоплатеже приложение может провести следующее списание с использованием ранее сохранённого способа оплаты. Пользователь не вводит платёжные данные заново при каждом продлении.

Но ЮKassa не управляет расписанием подписки вашего сервиса. Она предоставляет механизм повторного списания. Решение, когда выставлять очередной платёж, какую сумму использовать и когда прекратить продление, принимает ваше приложение. Предположим, AI-чат продаёт доступ на 30 дней. В базе данных нужно хранить не только факт первого платежа, но и состояние подписки, дату окончания оплаченного периода и сведения, необходимые для следующего списания.

Сначала сохраняем способ оплаты

Чтобы проводить автоплатежи, магазин должен иметь доступ к соответствующей функции ЮKassa. Её подключение и доступные способы оплаты проверяют в настройках магазина и документации сервиса. При создании первого платежа можно передать параметр save_payment_method со значением true. В зависимости от сценария ЮKassa показывает пользователю условия сохранения платёжного средства и получает необходимое подтверждение.

После успешной оплаты сервер запрашивает сведения о платеже. Для дальнейших списаний ему понадобится объект payment_method. Прежде чем сохранять идентификатор, нужно проверить, что payment_method.saved имеет значение true.

Далее приложение связывает payment_method.id с пользователем и подпиской. Номер карты и другие платёжные реквизиты самостоятельно хранить не требуется. Не каждый способ оплаты поддерживает повторные списания. Поэтому при первой покупке нельзя заранее считать, что автопродление доступно любому пользователю.

Создаём повторный платёж через API

Предположим, первый платёж успешно завершён, способ оплаты сохранён, а в базе существует подписка, которую разрешено продлевать автоматически. Когда подходит дата продления, сервер Django создаёт новый платёж через ЮKassa. В запросе указывается идентификатор сохранённого способа оплаты.

import os
import requests

def charge_subscription(
    payment_method_id: str,
    subscription_id: str,
    request_key: str,
):
    response = requests.post(
        "https://api.yookassa.ru/v3/payments",
        auth=(
            os.environ["YOOKASSA_SHOP_ID"],
            os.environ["YOOKASSA_SECRET_KEY"],
        ),
        headers={
            "Idempotence-Key": request_key,
        },
        json={
            "amount": {
                "value": "490.00",
                "currency": "RUB",
            },
            "capture": True,
            "payment_method_id": payment_method_id,
            "description": "Продление подписки",
            "metadata": {
                "subscription_id": subscription_id,
            },
        },
        timeout=20,
    )

    response.raise_for_status()
    return response.json()

Это серверная функция создания повторного платежа, а не готовый обработчик подписок. Сумма 490.00 приведена для примера. В работающем сервисе её определяют по тарифу, зафиксированному для конкретной подписки. payment_method_id указывает на сохранённый способ оплаты. subscription_id позволяет связать платёж с записью приложения. Параметр capture=True задаёт одностадийный сценарий.

Для каждого нового периода оплаты сервер создаёт отдельную запись начисления и ключ идемпотентности. Если запрос пришлось повторить после сетевого сбоя, используется тот же ключ для того же начисления. Для следующего месяца нужен уже другой ключ. Так приложение не должно превращать повторную попытку отправить запрос в ещё одно списание за тот же период.

Когда продлевать доступ

Ответ API после создания платежа ещё не означает, что подписка оплачена. Серверу нужно получить подтверждённый результат. Для этого можно использовать уведомление payment.succeeded. После его получения Django запрашивает информацию о платеже у ЮKassa, проверяет сумму, идентификатор начисления, статус и связь с подпиской. Затем отмечает оплаченный период и обновляет срок доступа. Проверки должны учитывать повторную доставку уведомлений. Если сервер уже обработал этот платёж, второе уведомление не должно продлить подписку ещё раз.

Здесь пригодится отдельная таблица начислений. В ней можно хранить идентификатор подписки, оплачиваемый период, сумму, идентификатор платежа ЮKassa и результат обработки. Ограничение уникальности для пары «подписка + период» помогает не создавать два начисления за один месяц. Разбор проверки платёжных уведомлений есть в моей статье на Habr о вебхуках ЮKassa.

Что делать, если повторное списание не прошло

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

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

Как пользователь отключает автопродление

Подписка должна иметь понятный способ отказа от следующих списаний. Например, в личном кабинете можно разместить переключатель «Автопродление включено» и кнопку отключения. Когда пользователь отменяет автопродление, сервер отмечает подписку как не подлежащую следующему списанию. Сохранённый идентификатор платёжного средства больше не используется для новых автоплатежей этой подписки. Это не обязательно означает немедленное отключение уже оплаченных функций. Пользователь может сохранить доступ до конца текущего периода.

ЮKassa не предоставляет вашему приложению готовое расписание таких продлений и отмен. Их учёт, отображение в личном кабинете и выполнение по времени остаются на стороне вашего backend.

Как связать платежи с AI-сервисом

Первый платёж и повторное списание решают разные задачи. Первый позволяет пользователю приобрести доступ. Автопродление поддерживает его в следующих периодах, пока подписка активна и платежи проходят успешно.

В статье «Как принимать оплату на своём сайте» разобрано создание первой оплаты. Здесь мы добавили сохранённый способ оплаты, расписание продлений и учёт каждого нового начисления.

В демонстрационном AI-Chat можно посмотреть пользовательскую часть приложения, в котором AI-функции связаны с сервером Django.

Курс «LLM-Chat + Mermaid + ЮKassa» посвящён развитию такого приложения до сервиса с оплатой, подписками, лимитами доступа и обработкой платёжных событий.

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