Перейти к основному содержимому

Регулярные платежи по СБП

Подписка позволяет автоматически списывать согласованную сумму после подтверждения плательщиком. Мерчант работает только с API RollyPay, планом своей кассы и обычными колбэками платежей.

Выбор плана

Получите GET /api/v1/subscription-plans?terminal_id=.... План определяет условия подписки:

Данные планаРежимПараметры создания
Указан payer_amount_rub, нет cap_amount_rubФиксированный тарифplan_id и payer_id; сумму не передавайте
Указан cap_amount_rubСумма в пределах лимитаplan_id и amount; payer_id и description не передавайте

Используйте только UUID плана из ответа API. Режим и периоды доступны после подключения соответствующих условий к вашей кассе. Пустой items означает, что доступных планов пока нет.

Перед запуском

Подтвердите с RollyPay готовность кассы и тарифов, условия согласия плательщика и обработку платежей. Активная привязка не заменяет подтверждение оплаты.

Как это работает

  1. Получите доступные тарифы вашей кассы и выберите id нужного плана.
  2. Создайте подписку с plan_id, payer_id и ключом идемпотентности.
  3. Сохраните id подписки вместе с вашим идентификатором клиента.
  4. Направьте клиента по pay_url. Он принимает условия регулярных списаний и подтверждает привязку в банковском приложении.
  5. Проверяйте состояние подписки через API. Выдавайте оплаченный доступ только по подтверждённому платежу paid.
  6. Последующие списания выполняются автоматически по условиям подписки. По успешному платежу RollyPay отправляет колбэк на сервер мерчанта.
  7. При отказе клиента от автопродления вызовите остановку и проверьте её результат.

Активная привязка не доказывает оплату. state: active, возврат клиента из банка и успешное открытие платёжной страницы не заменяют подтверждение платежа.

Что настроить заранее

Для каждого тарифа согласуются сумма списания с клиента, комиссия и сторона, которая её оплачивает, период, ограничения по числу циклов и отказов, условия согласия. Настройку выполняет RollyPay; мерчант не создаёт планы через этот API.

ИдентификаторГде используется
terminal_idUUID вашей кассы RollyPay
plan_idUUID из GET /api/v1/subscription-plans; определяет конкретный тариф
payer_idВаш стабильный идентификатор плательщика, например tg_123456789
merchant_subscription_refНеобязательная ссылка на подписку в вашей системе
id подпискиВозвращает RollyPay; нужен для проверки, истории и остановки

Одна касса может иметь несколько тарифов с одинаковым периодом и разными суммами. Выбирайте по id, а не только по interval.

Периоды

Период определяется подключённым планом. Для тарифов с периодичностью в днях используйте согласованное количество дней: 30 дней не равны календарному месяцу, 365 дней не всегда равны календарному году.

Код APIПериод для планов с фиксированным числом дней
day1 день
month30 дней
quarter90 дней
half_year180 дней
year365 дней

Точное время первого списания, повторы после отказа и расписание следующих списаний подтверждаются при подключении сервиса. next_charge_at — расчётная дата в RollyPay, а не подтверждение будущего платежа. Продлевайте доступ по факту оплаченного цикла; не запускайте собственное списание по этой дате.

Аутентификация и повтор запросов

Во всех запросах нужны X-API-Key вашей кассы и новый X-Nonce; см. аутентификацию. Ключ храните на сервере мерчанта.

При создании подписки также обязателен Idempotency-Key длиной до 128 байт. Если ответ потерялся или запрос завершился сетевой ошибкой, повторите то же тело и тот же Idempotency-Key, но с новым X-Nonce. Новым ключом создаётся новая подписка: не меняйте его автоматически после тайм-аута.

merchant_subscription_ref не заменяет ключ идемпотентности. Смена плательщика или тарифа при повторе того же ключа приводит к конфликту 409.

1. Получить тарифы

curl 'https://rollypay.io/api/v1/subscription-plans?terminal_id=TERMINAL_UUID' \
-H 'X-API-Key: YOUR_ROLLYPAY_API_KEY' \
-H "X-Nonce: $(uuidgen)"

Пример ответа 200 (идентификаторы условные):

{
"items": [
{
"id": "8f1c0b6e-2a44-4f0f-9d1b-6d2f9a3c7b10",
"code": "vpn_30_days",
"name": "VPN — 30 дней",
"version": 1,
"merchant_amount_rub": "150.00",
"payer_amount_rub": "150.00",
"interval": "month"
}
]
}

payer_amount_rub — окончательная сумма списания с клиента; merchant_amount_rub — сумма заказа, которая может отличаться при комиссии на клиенте. Не вычисляйте итоговое списание самостоятельно. max_cycles, если присутствует, задаёт предел успешных циклов; отсутствие этого поля означает отсутствие такого предела в плане. Отмена остаётся возможной.

Если items пуст, тарифы не доступны для создания подписок. Используйте только UUID из ответа API.

2. Создать подписку

Фиксированный тариф

curl -X POST 'https://rollypay.io/api/v1/subscriptions' \
-H 'Content-Type: application/json' \
-H 'X-API-Key: YOUR_ROLLYPAY_API_KEY' \
-H "X-Nonce: $(uuidgen)" \
-H 'Idempotency-Key: customer-123-plan-30d-attempt-1' \
-d '{
"terminal_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"plan_id": "8f1c0b6e-2a44-4f0f-9d1b-6d2f9a3c7b10",
"payer_id": "tg_123456789",
"merchant_subscription_ref": "customer-123-vpn"
}'
ПолеОбязательноОграничения
terminal_idДаКасса должна принадлежать вашему API-ключу
plan_idДаДоступный план именно этой кассы
payer_idДаНепустой идентификатор, до 32 байт; для СБП телефон не обязателен. ASCII-идентификатор вроде tg_123 исключает неоднозначность длины
merchant_subscription_refНетДо 128 байт; сохраните связь с вашим клиентом
descriptionНетОписание подписки

Не передавайте amount, период или комиссию. Сумма и расписание берутся из выбранного плана. Запрос с amount отклоняется с 400: фиксированную цену тарифа изменить этим параметром нельзя.

При первом создании — 201, при повторе ключа — 200. Фрагмент ответа для обычной кассы:

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"plan_id": "8f1c0b6e-2a44-4f0f-9d1b-6d2f9a3c7b10",
"payer_id": "tg_123456789",
"merchant_subscription_ref": "customer-123-vpn",
"state": "new",
"billing_status": "consent_pending",
"payer_amount_rub": "150.00",
"interval": "month",
"successful_cycles": 0,
"pay_url": "https://pay.rollypay.io/pay/recurring/SIGNED_TOKEN"
}

Сохраните id и pay_url из ответа. Ссылку используйте без изменения, не собирайте её самостоятельно. По публичной форме можно работать без API-ключа — не публикуйте ссылку для посторонних.

Касса с прямой интеграцией (H2H)

Если для вашей кассы отдельно включён H2H, создание сразу начинает подключение подписки. В ответе используйте redirect_url; pay_url для такой кассы не выдаётся. До создания получите согласие плательщика в своём интерфейсе с согласованными суммой, периодом и условиями отмены.

Не смешивайте эти два сценария. При отсутствии ожидаемой ссылки перечитайте подписку и обратитесь в поддержку с её id; не создавайте автоматически вторую подписку новым ключом.

Сумма в пределах лимита плана

Если план содержит cap_amount_rub, передавайте amount вместо payer_id. Сумма фиксируется при создании подписки и не меняется в последующих циклах. Она должна быть больше нуля и не превышать лимит выбранного плана.

{
"terminal_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"plan_id": "8f1c0b6e-2a44-4f0f-9d1b-6d2f9a3c7b10",
"amount": "990.00",
"merchant_subscription_ref": "customer-123-vpn"
}

Отправьте это тело в POST /api/v1/subscriptions с теми же заголовками аутентификации и Idempotency-Key. Не добавляйте payer_id или description. Лимит берите из ответа API; не зашивайте его в интеграцию.

Для таких планов day означает сутки, month — календарный месяц, quarter — три календарных месяца, year — календарный год. Расчёт ведётся по московскому времени; если нужного числа нет, используется последний день месяца. Для фиксированных тарифов с количеством дней действуют согласованные периоды из таблицы выше.

Ответ обрабатывается так же: сохраните id, направьте клиента по pay_url (для H2H — redirect_url) и подтверждайте оплаченный доступ только по платежу paid.

3. Проверить подписку и платежи

curl 'https://rollypay.io/api/v1/subscriptions/SUBSCRIPTION_UUID' \
-H 'X-API-Key: YOUR_ROLLYPAY_API_KEY' \
-H "X-Nonce: $(uuidgen)"
billing_statusЧто делать
consent_pendingОтправить клиенту сохранённый pay_url; согласие ещё не получено
pendingПривязка начата; дождаться результата. Оплата этим статусом не подтверждается
enabledПодписка разрешена к списаниям; проверять оплаченные циклы отдельно
reviewТребуется разбор операции. Связаться с поддержкой, не создавать замену автоматически
stop_pendingЗапрошена остановка, результат ещё не подтверждён
stoppedОстановка подтверждена; проверить уже начатые операции отдельно

state имеет значения new, active, stop. Для обработки подписки учитывайте оба поля. В частности, active вместе с review не означает нормальную работу биллинга.

Список подписок: GET /api/v1/subscriptions?terminal_id=TERMINAL_UUID&page=1. Ответ — items и total, по 50 записей на страницу. Фильтр state относится к state: new, active, stop.

История списаний: GET /api/v1/subscriptions/SUBSCRIPTION_UUID/charges. Возвращается массив до 100 последних записей. Для полной истории храните события у себя и используйте список платежей.

Поле списанияЗначение
statuspayed — успешное списание; fail — отказ; stop — сообщение об остановке
validation_statusaccepted — проверка пройдена; review — операция требует разбора
validation_reasonПричина проверки, если есть
cycle_numberНомер цикла: в фиксированном тарифе начинается с 1; в режиме суммы в пределах лимита первая оплата имеет номер 0. Несколько попыток могут относиться к одному циклу
payment_idПлатёж RollyPay, когда операция проведена в учёте
amount_rub, expected_amount_rubФактическая и ожидаемая суммы
scheduled_at, actual_atПлановое и фактическое время списания
messageСообщение об отказе, если есть

payed у списания и paid у платежа — разные значения API. При review или отсутствии payment_id не выдавайте оплаченный доступ только по записи о списании: требуется завершение проверки и учёта.

4. Обработать колбэк и продлить доступ

Успешный проведённый платёж получает payment_method: sbp_recurrent, валюту RUB и subscription_id. На callback_url вашей кассы приходит обычное событие payment.paid.

  1. Проверьте подпись по правилам колбэков.
  2. Найдите платёж через GET /api/v1/payments/{payment_id}. Проверьте кассу, status: paid и subscription_id.
  3. По subscription_id найдите своего клиента и тариф, при необходимости получите подписку через API.
  4. Продлите доступ ровно один раз для данного payment_id. Запись обработанного платежа и изменение доступа выполняйте атомарно в своей базе.
  5. Подтвердите получение колбэка ответом 2xx. Повторная доставка уже обработанного события тоже должна получать 2xx.

Сам колбэк платежа не содержит subscription_id; получайте его из объекта платежа. Не разбирайте автоматически сформированный order_id для определения клиента.

Настройте callback_url вашей кассы на адрес собственного сервера. Здесь принимаются события платежей RollyPay; дополнительные технические колбэки мерчанту настраивать не требуется.

Отдельных внешних событий subscription.active или subscription.stopped сейчас нет. Для состояния привязки и отмены используйте GET подписки. Результаты отказов смотрите в истории списаний.

5. Остановить автопродление

curl -X POST 'https://rollypay.io/api/v1/subscriptions/SUBSCRIPTION_UUID/stop' \
-H 'X-API-Key: YOUR_ROLLYPAY_API_KEY' \
-H "X-Nonce: $(uuidgen)"

При успешной обработке ответ — 200 и {"ok":true}. Перечитайте подписку и убедитесь в state: stop и billing_status: stopped. Повторная остановка уже остановленной подписки допустима.

При ошибке или review не сообщайте клиенту, что отмена завершена. Локальный статус проверки не подтверждает завершение отмены подписки. Повторите запрос с новым X-Nonce и передайте id подписки в поддержку, если результат остаётся неопределённым.

Отмена автопродления не возвращает деньги и не отменяет уже начатую операцию. Сохраните клиенту ранее оплаченный срок доступа. Возврат конкретного платежа рассматривается отдельно.

Для смены тарифа сначала подтвердите остановку старой подписки, затем создайте новую с новым согласием клиента. Цена и период существующей подписки через merchant API не редактируются. Эндпоинт /confirm с SMS-кодом для СБП-привязки не используется: клиент подтверждает её в банке.

Ошибки и восстановление

СитуацияДействие
400 при созданииПроверить обязательный payer_id, длины полей, доступность плана и ключ идемпотентности
401 / 403Проверить API-ключ, новый nonce и принадлежность кассы/подписки
404Проверить сохранённый UUID подписки
409 при созданииПроверить, не изменили ли тело запроса с прежним ключом идемпотентности
Сетевой сбой / 5xx при созданииПовторить то же тело с прежним Idempotency-Key и новым X-Nonce
Клиент закрыл банк или формуПеречитать подписку и платежи; закрытие страницы не подтверждает ни оплату, ни отмену
review, отсутствует платёж после списания, отмена не подтвержденаПередать поддержке id подписки, время, payment_id при наличии; не запускать повторную подписку автоматически

Проверка перед включением для клиентов

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

У каждого тарифа должны совпадать сумма и период на форме, в API и в согласованных условиях. Параметр test=true из разовых платежей не превращает эту подписку в тестовую: не создавайте реальную привязку для проверки без согласованного плательщика.

Нужны ли email и телефон

Для фиксированного тарифа достаточно стабильного payer_id, например tg_123456789. Email и телефон для подключения подписки не обязательны. Ограничение payer_id — 32 байта; используйте короткий идентификатор из латинских букв и цифр.