--- title: "Привязка через окно X-Hub" url: "https://docs.x-hub.online/subscriptions/consent-window/" description: "Подписки с согласием плательщика в окне X-Hub: ссылка привязки, первое списание, окно следующего списания, повторы, отмена, возвраты и требования к мерчанту." relatedDocuments: - "https://docs.x-hub.online/subscriptions/" - "https://docs.x-hub.online/subscriptions/api/" - "https://docs.x-hub.online/subscriptions/manage/" - "https://docs.x-hub.online/payments/refunds/" --- # Привязка через окно X-Hub { #consent-window } На части аккаунтов подписка привязывается не в стороннем окне, а в **нашем платёжном окне привязки**. Контракт API тот же: `POST /subscriptions`, `GET`, `POST /:id/charges`, `PATCH /:id`, `POST /:id/cancel`, те же вебхуки. Меняется то, что видит плательщик, и правила вокруг списаний. Какая схема у твоего аккаунта, скажет команда X-Hub при подключении. !!! warning "Старые привязки не переносятся" Банк требует нового согласия плательщика. Для каждого клиента создай **новую** подписку и отправь ему новый `binding_url`. Старые подписки на прежней схеме отмени обычным `POST /subscriptions/:id/cancel`. ## Ссылка привязки { #binding-link } `POST /subscriptions` сразу отдаёт `binding_url` на окно X-Hub вида `https://…/p/s/`, поле `binding_url_pending` всегда `false`. В момент создания подписки деньги не двигаются и банк не вызывается: всё начинается, когда плательщик откроет ссылку. Ссылку можно отправить в мессенджер и открыть через несколько часов. Что видит плательщик: 1. **Экран привязки и оплаты**: название магазина, описание подписки, сумма и периодичность, дата следующего списания и как отменить подписку. СБП QR на компьютере или кнопка «Привязать счёт» на телефоне. Согласие плательщик ставит явной галочкой — кнопка «Перейти к оплате» активируется только после неё; открытие окна само по себе ничего не фиксирует. 2. **«Подписка оформлена»** после оплаты либо **«Не удалось»** со ссылкой «Попробовать ещё раз», которая возвращает к экрану оплаты. После подтверждения в приложении банка плательщик возвращается в это же окно; если при создании подписки передан `success_url`, над статусом появляется кнопка «В магазин» с автовозвратом через 5 секунд. В окне привязки плательщик видит конкретные даты: следующее списание — «не раньше {дата}», конец оплаченного периода — минус день от него. Дата считается от **даты привязки** и по московскому времени. Если после создания подписки ты поменял сумму через `PATCH` до привязки, плательщик увидит на экране уже новые условия; после привязки изменение условий — тема следующего пункта. ### Периодичность: допустимые значения { #presets } `interval` + `interval_count` задаются при создании и потом не меняются: | `interval` | `interval_count` | Пресеты | |---|---|---| | `day` | 1…365 | `day×1` — ежедневно, `day×3` — раз в 3 дня | | `week` | 1…52 | `week×1` — еженедельно, `week×2` — раз в 2 недели | | `month` | 1…12 | `month×1` — ежемесячно | Границы — жёсткие: за пределами вернётся `400 VALIDATION_ERROR` с указанием поля. Следующее списание считается как «дата привязки + `interval_count` × `interval`» с клэмпом конца месяца (привязка 31 января при `month×1` → следующее списание 28 февраля). Ровно эти же даты и числа бюджета повторов (ниже) напечатаны в окне привязки — плательщик и мерчант видят одни и те же условия. ## Первое списание — это сама привязка { #first-charge } Оплата в окне списывает первый период на сумму подписки `amount_rub`. Дальше всё как раньше: - `payment.status_changed` со `status: "paid"`, `payment_method: "recurring"`, `subscription_id` и `period_ref`; - `subscription.status_changed`: `pending_binding` → `active`. Порядок этих двух событий не гарантирован. Первый период уже оплачен, поэтому своё `POST /:id/charges` планируй на **следующий** период (см. [предупреждение о двойном списании](./index.md#kak-eto-rabotaet)). ## Окно следующего списания: «не раньше» { #charge-window } Твои списания (`POST /subscriptions/:id/charges`) разрешены, начиная с даты **следующего списания из согласия** — то есть «якорь» (дата последнего успешного платежа) плюс период подписки. Раньше — `409 CHARGE_BEFORE_WINDOW`, а в теле ошибки поле `details.next_allowed_at` (ISO, московское время): момент, когда списание пройдёт. Планируй свой крон на него, а не на собственный пересчёт периода. - Пауза не нужна и после неуспешной попытки: пока не исчерпан бюджет повторов, повторять можно в любой момент разрешённого периода (см. ниже). - Дата привязки сдвигается только деньгами: каждая **успешная** оплата (в том числе твоё списание) становится новым «якорем», и окно следующего списания отсчитывается от неё. Неуспешные попытки якорь не двигают. ## Списания и повторы после отказа { #retries } Следующие списания — `POST /subscriptions/:id/charges`, как раньше. Итог приходит вебхуком `payment.status_changed`, обычно за секунды. Если списание не прошло (`failed`), в том же периоде его можно повторить — ровно в том бюджете, который плательщик видел в окне привязки («магазин сможет повторить его до N раз в течение H часов»): | Периодичность подписки | Попыток за период | Окно от 1-й попытки | |---|---|---| | `day×1` | 2 | 3 часов | | `day×3` | 3 | 6 часов | | `week×1` | 4 | 12 часов | | `week×2` | 4 | 24 часов | | остальные (окно от недели и больше) | 4 | 20 часов | Правила бюджета: - вышло **окно** с первой попытки или исчерпано число попыток — новые списания в этом периоде отклоняются `409 RECURRING_CHARGE_EXHAUSTED` с `details.next_allowed_at` = начало следующего периода. Об этом же приходит вебхук `subscription.charge_exhausted` (один на период); - попытка, которая ещё **обрабатывается** (платёж `pending`), блокирует новые: `POST /charges` вернёт `200` и **ту же** последнюю попытку, новой не создаст. Банк в этот момент не вызывается; - `period_ref` у всех попыток периода одинаковый — по нему (и по `GET /:id/charges`) видно, сколько попыток период уже израсходовал. !!! danger "`rebind_required: true` — повторять бесполезно" Если банк перестал принимать токен плательщика (он отключил подписку в приложении банка, сменил банк или отозвал согласие), подписка переходит в `paused` с `rebind_required: true` и приходит `subscription.status_changed`. Списания по ней не пройдут (`409 CHARGE_MANDATE_INACTIVE`). Создай клиенту **новую** подписку и отправь новую ссылку, старую отмени. !!! tip "Уведомляй плательщика о повторах" Повтор списания виден плательщику как отдельный дебет. Если клиент не ждал его, он диспутит. Перед каждой повторной попыткой (и при исчерпании бюджета — как сигнале «списание не случится до `next_allowed_at`») отправляй плательщику своё уведомление: письмо, пуш или сообщение в канале, где он оформлял подписку. Магазин, который держит плательщика в курсе, почти не получает диспутов «я не соглашался». ## Изменение условий после привязки { #cap-after-binding } Плательщик согласился на конкретные условия, и мандат фиксирует сумму. `PATCH /subscriptions/:id` работает, только пока подписка `active` или `pending_binding`; у `paused`, `cancelled` и `expired` он отклоняется `409 INVALID_STATE` — мёртвый мандат не «готовят» к новой сумме. Итоговая плановая сумма не может быть выше `cap_amount_rub` (`400 INVALID_AMOUNT`). Условия выше согласованных (сумма, периодичность) — это **новая** подписка и новое согласие плательщика, не `PATCH`. ## Отмена { #cancel } `POST /subscriptions/:id/cancel` работает как раньше и отвечает `cancelled`. Кроме закрытия подписки у нас мы **отключаем списания и на стороне банка**. Если банк в эту секунду недоступен, подписка всё равно отменена, и новые списания по ней невозможны (`409 CHARGE_MANDATE_INACTIVE`). Отключение в банке мы повторим сами, делать ничего не нужно. Повторный `POST /cancel` по отменённой подписке, как и раньше, вернёт `409 INVALID_STATE`. ## Возвраты { #refunds } Любое списание по подписке, включая первое, возвращается обычным [API возвратов](../payments/refunds.md). Правила те же, что у разовых платежей. Комиссия за успешный платёж при возврате не возвращается. ## Требования к мерчанту { #merchant-requirements } Диспуты «я не соглашался» ложатся на тебя. Чтобы их не было: - **Одна подписка на клиента.** Переиспользуй `Idempotency-Key` и не создавай подписку на каждое открытие формы оплаты. - **Явное согласие у себя до выдачи ссылки.** Покажи клиенту сумму, периодичность и как отменить. Рекомендованный текст: «Оформляя подписку, я соглашаюсь на регулярные списания {сумма} ₽ раз в {период}. Отменить подписку можно в любой момент в {где}». - **Не прячь подписку** за кнопками «тестовая», «пробная» или «проверить оплату» без суммы и периодичности. - **Уведомляй о списаниях и повторах** — до списания и при каждой неуспешной попытке, см. [выше](#retries). - **Не перерисовывай ссылку в свой QR** и не открывай оплату в обход окна: плательщик обязан открыть окно X-Hub и нажать «Привязать счёт» — открытие окна фиксирует условия подписки, а подтверждённое диспутами нарушение — основание отключить рекуррентные платежи на аккаунте. В выписке банка клиент видит название магазина, зарегистрированное у банка.