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

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

Регулярные списания по СБП позволяют списывать с плательщика фиксированную сумму по расписанию. Плательщик один раз подтверждает условия на платёжной форме RollyPay и оплачивает первый платёж — дальше списания выполняются автоматически, без его участия, пока подписка не будет остановлена.

Ключевые свойства:

  • Сумма и период фиксируются в момент создания подписки и после подтверждения не меняются.
  • Первое и каждое последующее успешное списание создаёт обычный платёж в вашем списке платежей — со способом оплаты sbp_recurrent и полем subscription_id.
  • По каждому такому платежу приходит обычный вебхук payment.paid на ваш callback_url.
  • Валюта — только рубли (RUB).

Сценарии (subscription plans)

Подписка всегда создаётся на основе сценария — заранее согласованной и подключённой к вашей кассе конфигурации регулярных списаний. Сценарии подключает RollyPay при настройке кассы: через API их создать нельзя.

Сценарий задаёт:

  • период списаний (interval);
  • максимально допустимую сумму одного списания для этого периода (cap_amount_rub);
  • текст согласия плательщика и версию политики, которые показываются на форме;
  • ограничение по числу циклов (max_cycles), если оно предусмотрено.

Что задаёт мерчант при создании подписки: сценарий (plan_id) и сумму списания (amount) в пределах лимита этого сценария.

Список доступных сценариев кассы возвращает GET /api/v1/subscription-plans (см. ниже).

Поддерживаемые периоды

intervalПериодичность списаний
dayраз в сутки
monthраз в месяц
quarterраз в 3 месяца
yearраз в год

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

Лимиты сумм по периодам

Сумма одного списания не может превышать лимит, установленный для периода:

ПериодМаксимальная сумма одного списания
day500,00 ₽
month4 000,00 ₽
quarter9 000,00 ₽
year24 000,00 ₽

Сумма должна быть больше нуля. Принимаются записи вида 1000, 1000.50, 1000,50 — значение нормализуется до двух знаков после запятой. При превышении лимита вернётся 400 с текстом вида сумма не может превышать 4000,00 ₽ для периода «раз в месяц».


Флоу интеграции

  1. Получите список сценариевGET /api/v1/subscription-plans?terminal_id=.... Возьмите id нужного сценария и его лимит cap_amount_rub.
  2. Создайте подпискуPOST /api/v1/subscriptions с terminal_id, plan_id, amount и заголовком Idempotency-Key.
  3. Перенаправьте плательщика по pay_url из ответа. Подписка создана в состоянии provider_state: "new", billing_status: "consent_pending" — списаний ещё нет.
  4. Плательщик подтверждает условия на форме RollyPay: видит название магазина, сумму, период и текст согласия, ставит подтверждение.
  5. Плательщик оплачивает первый платёж по СБП. Подписка переходит в billing_status: "pending" — идёт проверка оплаты.
  6. Подписка становится активной: после подтверждения первой оплаты provider_stateactive, billing_statusenabled, заполняются activated_at и next_charge_at. Создаётся платёж со статусом paid и уходит вебхук payment.paid.
  7. Последующие списания выполняются автоматически при наступлении next_charge_at. Каждое успешное списание — снова платёж paid и вебхук payment.paid, next_charge_at сдвигается на период вперёд.
  8. ОстановкаPOST /api/v1/subscriptions/{subscriptionID}/stop в любой момент.
примечание

Пока плательщик не подтвердил условия и не оплатил первый платёж, подписка не становится активной и списаний по ней не происходит. Никаких действий с вашей стороны между шагами 3 и 6 не требуется.


Эндпоинты

Все запросы — с заголовками X-API-Key и X-Nonce (см. Аутентификация).

Список доступных сценариев

GET /api/v1/subscription-plans?terminal_id={terminalID}

ПараметрОбязательныйОписание
terminal_idдаUUID кассы

Возвращает только активные сценарии, подключённые к этой кассе.

curl "https://rollypay.io/api/v1/subscription-plans?terminal_id=d290f1ee-6c54-4b01-90e6-d701748f0851" \
-H "X-API-Key: rpk_live_ВАШ_КЛЮЧ" \
-H "X-Nonce: $(uuidgen)"

Ответ 200 OK (фрагмент):

{
"items": [
{
"id": "8f1c0b6e-2a44-4f0f-9d1b-6d2f9a3c7b10",
"code": "sbp_recurring_month",
"name": "Регулярные списания СБП · month",
"version": 1,
"interval": "month",
"cap_amount_rub": "4000.00"
}
]
}
ПолеОписание
idИдентификатор сценария, передаётся в plan_id при создании подписки
codeТехнический код сценария
nameНазвание сценария
versionВерсия сценария (условия версии неизменяемы)
intervalПериод списаний
cap_amount_rubМаксимально допустимая сумма одного списания
max_cyclesОграничение по числу списаний, если задано. Отсутствует — значит списания идут бессрочно, пока подписку не остановят

Ошибки

КодСообщение
400terminal_id is required
403terminal access denied
503recurring plans not configured

Создание подписки

POST /api/v1/subscriptions

Обязательный заголовок: Idempotency-Key — ваш уникальный ключ запроса (до 128 символов).

curl -X POST https://rollypay.io/api/v1/subscriptions \
-H "Content-Type: application/json" \
-H "X-API-Key: rpk_live_ВАШ_КЛЮЧ" \
-H "X-Nonce: $(uuidgen)" \
-H "Idempotency-Key: sub-req-2026-000123" \
-d '{
"terminal_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"plan_id": "8f1c0b6e-2a44-4f0f-9d1b-6d2f9a3c7b10",
"amount": "990.00",
"merchant_subscription_ref": "sub_12345"
}'
ПолеТипОбязательноеОписание
terminal_idstringдаUUID кассы. Должен совпадать с кассой API-ключа
plan_idstringдаИдентификатор сценария из GET /api/v1/subscription-plans
amountstringдаСумма одного списания в рублях, в пределах лимита периода
merchant_subscription_refstringнетВаш идентификатор подписки (до 128 символов)

Тело запроса не должно содержать неизвестных полей — иначе 400 invalid json body. Поля payer_id и description для регулярных списаний по СБП не принимаются.

Ответ 201 Created (фрагмент):

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"terminal_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"plan_id": "8f1c0b6e-2a44-4f0f-9d1b-6d2f9a3c7b10",
"merchant_subscription_ref": "sub_12345",
"provider_state": "new",
"billing_status": "consent_pending",
"plan_code": "sbp_recurring_month",
"plan_version": 1,
"merchant_amount_rub": "990.00",
"payer_amount_rub": "990.00",
"interval": "month",
"successful_cycles": 0,
"consecutive_failures": 0,
"pay_url": "https://pay.rollypay.io/pay/recurring/a1b2c3d4-...",
"created_at": "2026-08-06T12:00:00Z"
}

Плательщика нужно перенаправить по pay_url.

Идемпотентность. Повтор запроса с тем же Idempotency-Key и тем же телом вернёт 200 OK с уже созданной подпиской (новая не создаётся). Тот же ключ с другим телом — 409.

Ошибки

КодСообщение / причина
400terminal_id is required
400invalid json body — некорректный JSON или неизвестное поле в теле
400укажите кассу, сценарий, сумму и ключ идемпотентности — не передан Idempotency-Key, plan_id или amount
400ключ идемпотентности и ваш идентификатор подписки не должны превышать 128 символов
400выбранный сценарий недоступен для этой кассы
400сценарий регулярных списаний временно недоступен
400сумма указана неверно: введите рубли и не более двух знаков после запятой
400сумма списания должна быть больше нуля
400сумма не может превышать 4000,00 ₽ для периода «раз в месяц» (лимит и период — по выбранному сценарию)
400terminal is unavailable
400Переданы payer_id или description — для регулярных списаний по СБП эти поля не принимаются
403subscription write access required — API-ключ выдан для другой кассы
403terminal access denied / account access denied
409idempotency key conflicts with another request
503subscriptions not configured / provider not available

Список подписок

GET /api/v1/subscriptions

ПараметрОписание
terminal_idФильтр по UUID кассы
stateФильтр по состоянию: new, active, stop
pageНомер страницы (по 50 записей)

Ответ: объект с полями items (массив подписок) и total.

curl "https://rollypay.io/api/v1/subscriptions?terminal_id=d290f1ee-6c54-4b01-90e6-d701748f0851&state=active" \
-H "X-API-Key: rpk_live_ВАШ_КЛЮЧ" \
-H "X-Nonce: $(uuidgen)"

Получение подписки

GET /api/v1/subscriptions/{subscriptionID}

Ответ 200 OK (фрагмент активной подписки):

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"terminal_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"merchant_subscription_ref": "sub_12345",
"provider_state": "active",
"billing_status": "enabled",
"plan_code": "sbp_recurring_month",
"plan_version": 1,
"merchant_amount_rub": "990.00",
"payer_amount_rub": "990.00",
"interval": "month",
"successful_cycles": 3,
"consecutive_failures": 0,
"next_charge_at": "2026-11-06T09:00:00Z",
"created_at": "2026-08-06T12:00:00Z",
"activated_at": "2026-08-06T12:03:11Z"
}
ПолеОписание
provider_stateСостояние привязки: new / active / stop
billing_statusСостояние биллинга (см. таблицу ниже)
merchant_amount_rubСумма списания, зафиксированная при создании
payer_amount_rubСумма, которую платит плательщик
intervalПериод списаний
max_cyclesЛимит числа списаний, если задан
successful_cyclesКоличество успешных списаний (включая первую оплату)
next_charge_atДата и время следующего списания (UTC)
activated_atМомент активации подписки
stopped_atМомент остановки

Поле pay_url возвращается только в ответе на создание подписки — сохраните его, если планируете отдать ссылку плательщику позже.

Ошибки: 404 subscription not found, 503 subscriptions not configured.


История списаний по подписке

GET /api/v1/subscriptions/{subscriptionID}/charges

Возвращает массив (до 100 записей, новые первыми).

[
{
"id": "c0ffee00-1111-2222-3333-444455556666",
"payment_id": "b7d1a2c3-4e5f-6789-abcd-ef0123456789",
"status": "payed",
"validation_status": "accepted",
"cycle_number": 1,
"amount_rub": "990.00",
"expected_amount_rub": "990.00",
"scheduled_at": "2026-09-06T09:00:00Z",
"actual_at": "2026-09-06T09:00:04Z",
"created_at": "2026-09-06T09:00:04Z"
}
]
ПолеОписание
payment_idИдентификатор платежа, созданного этим списанием
statuspayed — успешное списание; fail — отклонено; stop — списание пришлось на уже остановленную подписку
cycle_numberНомер цикла: 0 — первая оплата на форме, далее 1, 2, …
amount_rubФактическая сумма списания
expected_amount_rubОжидаемая сумма по зафиксированным условиям
messageПричина отказа, если она есть

Для регулярных списаний по СБП запись создаётся по подтверждённому успешному списанию. Если списание не удалось или его результат не удалось однозначно подтвердить, подписка переводится в billing_status: "review", и дальнейшие списания приостанавливаются.


Остановка подписки

POST /api/v1/subscriptions/{subscriptionID}/stop

curl -X POST https://rollypay.io/api/v1/subscriptions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/stop \
-H "X-API-Key: rpk_live_ВАШ_КЛЮЧ" \
-H "X-Nonce: $(uuidgen)"

Ответ 200 OK:

{ "ok": true }

После успешной остановки: provider_statestop, billing_statusstopped, next_charge_at очищается, заполняется stopped_at. Новых списаний не будет. Повторный вызов для уже остановленной подписки безопасен и вернёт 200.

Если остановку не удалось довести до конца, запрос вернёт ошибку, а подписка перейдёт в billing_status: "review" — новых автосписаний по ней всё равно не будет. Повторите вызов позже или обратитесь в поддержку.

Ошибки

КодСообщение
400Остановку не удалось завершить, подписка переведена в review
403subscription write access required
404subscription not found
503subscriptions not configured / provider not available
примечание

Эндпоинт POST /api/v1/subscriptions/{subscriptionID}/confirm в сценарии регулярных списаний по СБП не используется — подтверждение выполняется плательщиком на форме и в его банковском приложении.


Статусы подписки

У подписки два независимых поля состояния: provider_state — состояние привязки счёта плательщика, billing_status — состояние биллинга (можно ли списывать).

provider_state

ЗначениеЧто означает для мерчанта
newПривязка начата, плательщик ещё не завершил её. Списаний нет
activeПривязка выполнена, подписка списываемая
stopПодписка остановлена. Новых списаний не будет

billing_status

ЗначениеЧто означает для мерчанта
consent_pendingСоздана, ждём подтверждения условий плательщиком на форме. Отправьте его по pay_url
pendingПлательщик подтвердил условия, идёт первая оплата и её проверка
enabledПодписка активна, списания идут по расписанию (next_charge_at)
reviewСписания приостановлены: результат последней операции требует проверки. Новых автосписаний не будет до разбора
stop_pendingОстановка запрошена и выполняется
stoppedПодписка остановлена окончательно

Платежи и вебхуки по списаниям

Каждое успешное списание — включая первую оплату — создаёт обычный платёж в вашей кассе. Отдельного «мира подписок» в отчётности нет.

У такого платежа:

  • payment_method = sbp_recurrent;
  • payment_currency = RUB;
  • subscription_id — идентификатор подписки, по которой прошло списание;
  • status = paid после подтверждения;
  • order_id формируется на стороне RollyPay (вы его не задаёте) — для сопоставления используйте subscription_id и merchant_subscription_ref подписки.

Такие платежи попадают в обычные списки и выгрузки:

  • GET /api/v1/payments — в общем списке; можно отфильтровать ?recurring=true (только списания по подпискам) или ?recurring=false (только разовые платежи);
  • GET /api/v1/payments?format=csv — в CSV-выгрузке есть колонка «Рекуррентный»;
  • GET /api/v1/payments/{paymentID} — вернёт полный объект платежа, включая subscription_id и payment_method.

Вебхуки

По каждому успешному списанию на ваш callback_url приходит обычный вебхук payment.paid — тот же формат и та же подпись, что и для разовых платежей:

{
"event_type": "payment.paid",
"payment_id": "b7d1a2c3-4e5f-6789-abcd-ef0123456789",
"order_id": "...",
"status": "paid",
"amount": "990.00",
"currency": "RUB",
"test": false
}

Отдельных событий об изменении состояния подписки нет. Тело вебхука не содержит subscription_id — чтобы связать платёж с подпиской, запросите GET /api/v1/payments/{payment_id} и возьмите оттуда subscription_id. Проверка подписи и повторы доставки — как обычно, см. Колбэки.


Согласие плательщика и фиксация условий

Перед первой оплатой плательщик видит на форме RollyPay:

  • название вашего магазина,
  • сумму одного списания,
  • периодичность списаний,
  • текст согласия на регулярные списания и версию политики.

Списание не начнётся, пока плательщик явно не подтвердит согласие на форме. Текст согласия задаётся при подключении сценариев к кассе и не редактируется через API.

Условия фиксируются снапшотом. В момент создания подписки в неё копируются сумма, период, код и версия сценария, текст и версия согласия, версия политики. После этого они не меняются — ни при изменении сценария, ни как-либо ещё. Чтобы списывать другую сумму или с другой периодичностью, остановите текущую подписку и создайте новую: плательщику придётся подтвердить новые условия.

Что видит плательщик

Состояние подпискиЧто показывает форма
consent_pendingСумма, период, название магазина, текст согласия и кнопку подтверждения
pending«Первая оплата принята. Проверяем подключение регулярных списаний.»
enabled«Регулярные списания по СБП подключены.»
review«Платёж ожидает проверки. Повторно подтверждать условия не нужно.»
stopped«Регулярный платёж больше недоступен.»

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