> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Подписки и права на услугу

> Что такое подписка и право на услугу по отдельности, как ограничивается число одновременных подписок и как период продлевается, изменяется, истекает и восстанавливается

Эту область несут две записи, и держать их порознь — это и есть весь замысел.

**Право на услугу** — это собственное утверждение Omniflow о том, что купил один оплаченный заказ: версия тарифа, период, лимит трафика, лимит устройств и набор сквадов. **Подписка** — это стабильная, видимая клиенту ручка ровно к одному пользователю Remnawave: слот, название и идентификатор удалённого пользователя. Одна подписка накапливает права на услугу со временем, по мере того как клиент продлевает; каждое право на услугу принадлежит ровно одному заказу.

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

## Права на услугу

Строка права на услугу несёт то, что Omniflow должен, плюс последнее, что он наблюдал на той стороне: `observed_state`, `remnawave_user_id` и `reconciled_at`. Разделение этих двух вещей и позволяет Omniflow отличить то, чего он хочет, от того, что он видел в последний раз.

Право на услугу существует как отдельная запись, потому что состояние платежа, состояние права и состояние Remnawave отказывают независимо. Успешный платёж обязан пережить недоступность Remnawave: деньги записаны, право на услугу записано, а выдача доступа повторяется позже. Остальное обеспечивает схема — `UNIQUE (order_id)` означает, что один заказ никогда не создаст два права на услугу, а проверка, требующая, чтобы конец был позже начала, делает нулевой или отрицательный период непредставимым.

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

| Статус | Значение |
| - | - |
| `pending` | Рассчитано, но ещё не отправлено в Remnawave |
| `active` | Remnawave сообщает, что пользователь активен |
| `limited` | Remnawave сообщает, что пользователь ограничен, обычно из-за исчерпанного трафика |
| `disabled` | Remnawave сообщает, что пользователь отключён |
| `expired` | Remnawave сообщает, что срок истёк |
| `superseded` | Более позднее право на услугу на той же подписке заменило это |
| `failed` | Remnawave вернул статус, который эта сборка не распознаёт |

Всё, кроме `pending` и `superseded`, пишется из наблюдаемого статуса Remnawave. `superseded` пишется локально, после того как замена действительно выдана.

> [!NOTE]
>
> Сохранённый статус права на услугу никогда не становится `expired` по таймеру.
> Истечение вычисляется для отображения из `ends_at`, а сохранённый статус
> меняется на `expired` только тогда, когда проход сверки вычитает это из
> Remnawave. Экран клиента, показывающий «истекло», и строка в базе данных,
> всё ещё читающаяся как `active`, — это один и тот же факт.

Ничто никогда не удаляет право на услугу. Хранение удаляет разрешённые строки расхождений и ничего больше в этой области.

## Модель подписки

| Колонка | Значение |
| - | - |
| `slot` | Позиция среди подписок клиента, начиная с 1 |
| `label` | Выбранное клиентом название, до 40 символов, показывается на каждом экране и в каждом уведомлении |
| `remnawave_user_id` | Пользователь Remnawave, которому эта подписка выдаёт доступ |
| `status` | `active` или `closed` |

Один клиент может владеть несколькими пользователями Remnawave, и подписка — это стабильная ручка ровно к одному из них. Без промежуточной ручки Omniflow пришлось бы угадывать, о каком удалённом пользователе речь в оповещении, продлении или удалении устройства.

Права на услугу, заказы, доставки уведомлений и настройки автопродления — все несут `subscription_id`. Баланс кошелька, право на участие в акциях, атрибуция рефералов и заявки на пробный период намеренно его не несут: это свойства **клиента**, поэтому открытие второй подписки не даёт ни второго пробного периода, ни второго реферального вознаграждения.

### Что относится к подписке, а что к клиенту

| На подписку | На клиента |
| - | - |
| Тариф, период, сквады, лимит устройств, трафик, срок окончания | Баланс кошелька и книга проводок |
| Пользователь Remnawave, ссылка доступа, устройства | Право на акции и промокоды |
| Уведомления об истечении и о трафике | Атрибуция рефералов и вознаграждения |
| Продление, повышение, понижение тарифа | Право на пробный период и защита от злоупотреблений |
| Намерение автопродления | Обращения в поддержку, новости, согласия, локаль |

Защита от злоупотреблений пробным периодом считает клиентов, а не подписки. `trial_claims` ключуется по клиенту, поэтому вторая подписка клиента, который уже использовал пробный период, не может получить ещё один.

### Названия

Название обрезается по краям, ограничивается 40 рунами и отклоняется сразу, если оно пустое или содержит управляющие символы, — но никогда не усекается молча, потому что оно показывается клиенту дословно.

Клиенты переименовывают подписку через `PATCH /v1/account/subscriptions/{subscriptionID}`; маршрут переименования в боте делает то же самое. Оператор переименовывает через `PATCH /v1/panel/customers/{customerID}/subscriptions/{subscriptionID}` (`subscriptions.write`). Оба принимают название и ничего больше.

## Одновременные подписки

```bash theme={null}
APP_MULTI_SUBSCRIPTION_ENABLED=true
APP_MAX_SUBSCRIPTIONS_PER_CUSTOMER=3
```

`APP_MAX_SUBSCRIPTIONS_PER_CUSTOMER` по умолчанию равен `3` и ограничивается снизу единицей. Пока `APP_MULTI_SUBSCRIPTION_ENABLED` равен false, действующий потолок равен одной подписке, что бы ни говорил максимум.

Тариф может задать собственный, более жёсткий потолок через `plans.max_concurrent_per_customer`, который сужает лимит установки и никогда не может его расширить. Оба проверяются в момент покупки, поэтому тариф, настроенный выше предела установки, ограничивается им, а не переопределяет его. Где редактируется этот лимит, описано на странице [Каталог и акции](https://omniflow-vpn.mintlify.site/ru/commerce/catalog-promotions.md).

Клиент, упёршийся в любой из потолков, получает стабильную причину отказа, проверяемую в таком порядке:

| Причина | Когда возникает |
| - | - |
| `multi_subscription_disabled` | Многоподписочность выключена, и у клиента уже есть одна подписка |
| `subscription_limit_reached` | Многоподписочность включена, и потолок установки исчерпан |
| `plan_limit_reached` | Исчерпан собственный лимит тарифа |

Лимит применяется там, где деньги, а не в интерфейсе. Подсчёт выполняется под рекомендательной блокировкой на клиента в пределах транзакции, поэтому две одновременные покупки не могут обе увидеть устаревший счёт и обе пройти.

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

Строка подписки записывается при создании заказа на неё, до того как двинутся деньги, чтобы платёж мог на неё указывать, а потолок — её учитывать, пока чекаут открыт. Если этот заказ затем закрывается неоплаченным — отменён клиентом или оператором либо истёк по чистке, — строка удаляется, при условии что ею больше ничто никогда не пользовалось: ни право на услугу, ни другой живой заказ, ни пользователь Remnawave, ни настройка автопродления, уведомление, корзина, сессия чекаута или попытка дозвона. Закрытый заказ сначала от неё отвязывается. Отмена оператором делает это в той же транзакции; чистка истёкших заказов — для каждого клиента, чей заказ она закрыла; отмена клиентом на любой поверхности вызывает ту же очистку после коммита. Подписка, которая когда-либо была выдана, этим никогда не удаляется — для неё есть операторская процедура в разделе [Как оставить клиенту одну подписку](#как-оставить-клиенту-одну-подписку).

**Деньги по отвязанному заказу всё ещё могут прийти.** Ручной перевод часто подтверждают после окна оплаты, а чекаут, оставленный открытым в другой вкладке, можно оплатить после отмены. Тогда проведение открывает свежую подписку вместо удалённой — под той же рекомендательной блокировкой и тем же потолком параллельности, что и покупка, — записывает её в заказ и создаёт право на услугу там же, внутри проводящей транзакции. Оно никогда не откатывается к другой подписке клиента: вторая подписка на тридцать дней, подтверждённая поздно, раньше попадала на основную и затирала месяцы оставшегося времени. Если потолок не допускает ещё одну подписку, платёж классифицируется как `subscription_unavailable` и не проводится; решает оператор. По той же причине воркер исполнения никогда не переносит право на услугу на подписку, отличную от его собственной и от подписки его заказа: право, чью подписку нельзя установить, окончательно отказывает с `subscription_unresolved`, а не выдаётся на основную подписку.

> [!WARNING]
>
> В `/admin` есть экран одновременных подписок с эндпоинтом
> `PUT /v1/panel/settings/commerce/subscriptions` (`settings.write`), и он **не**
> управляет применением правил. Он пишет `commerce_settings`, тогда как
> политика, реально проверяемая при покупке, берётся из
> `APP_MULTI_SUBSCRIPTION_ENABLED` и `APP_MAX_SUBSCRIPTIONS_PER_CUSTOMER`,
> прочитанных один раз при старте процесса.
>
> Сохранённая строка читается ровно в одном месте: она решает, показывать ли на
> клиентском дашборде переключатель подписок. Чтобы изменить лимит, нужно
> изменить окружение и перезапустить API, бота и воркер.

## Что меняется для клиента

При включённой многоподписочности:

* главное меню открывает переключатель подписок вместо экрана единственной подписки;
* каждая покупка, продление, повышение, понижение тарифа и покупка дополнения явно называют целевую подписку;
* управление устройствами, ротация ссылки и инструкции по подключению действуют на пользователя Remnawave выбранной подписки;
* уведомления об истечении, трафике, продлении, льготном периоде, восстановлении и исполнении называют подписку, о которой идёт речь.

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

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

В боте экран подписки предлагает переименование, подключение, устройства, продление, ротацию ссылки и дополнения. В браузере `/account` перечисляет карточки, `/account/subscriptions/{id}` показывает одну подписку с индикатором трафика, формой переименования и элементом ротации, а `/account/devices` показывает устройства с переключателем, когда подписок больше одной. Обе поверхности полностью описаны на страницах [Клиентская веб-панель](https://omniflow-vpn.mintlify.site/ru/customer/web-panel.md) и [Telegram-бот](https://omniflow-vpn.mintlify.site/ru/customer/telegram-bot.md).

## Изоляция уведомлений

Дедупликация уведомлений ключуется по `(user_id, kind, subscription_id, dedupe_key)` с nulls not distinct. Без подписки в ключе две подписки, истекающие в один день, столкнулись бы на одном ключе дедупликации, и клиенту сообщили бы только об одной из них. Активная подписка никогда не сможет заглушить уведомление тихой.

## Исполнение заказов

Каждая подписка выдаёт доступ собственному пользователю Remnawave, имя которого детерминированно выводится из идентификатора подписки:

```text theme={null}
omniflow_<first 16 hex characters of the subscription id>
```

Установка, обновлённая с v0.4, сохраняет для слота 1 имя, выведенное из клиента. Воркер сначала ищет имя, выведенное из подписки, и лишь затем — устаревшее имя, выведенное из клиента, поэтому существующий пользователь Remnawave перенимается, а не дублируется. Слот выше 1 без сопоставления никогда не откатывается к устаревшему имени, поэтому вторую подписку невозможно спутать с удалённым пользователем первой.

Сопоставление `remnawave_users` на уровне клиента продолжает отслеживать **основную** подписку (слот 1). Именно это оставляет докоммерческие экраны самообслуживания работающими без изменений.

Обнаружение расхождений, сверка и замещение — всё ограничено одной подпиской. Покупка второй подписки никогда не выводит из обращения право на услугу первой. На странице [Исполнение заказов](https://omniflow-vpn.mintlify.site/ru/commerce/fulfillment.md) описаны конвейер операций, политика повторов и то, как выглядит сбой.

## Изменение подписки: арифметика

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

| Операция | Политика | Начало | Конец | Выдача доступа |
| - | - | - | - | - |
| `purchase` | — | сейчас | сейчас + длительность | сейчас |
| `extension` / `renewal` | — | сейчас | база + длительность | сейчас |
| `upgrade` | `replace` | сейчас | сейчас + длительность | сейчас |
| `upgrade` | `extend` | сейчас | база + длительность | сейчас |
| `upgrade` | `forbid` | отклонено | | |
| `downgrade` | `immediate` | сейчас | сейчас + длительность | сейчас |
| `downgrade` | `at_expiry` | база | база + длительность | база |
| `downgrade` | `forbid` | отклонено | | |

**Нигде в этой таблице нет пропорционального пересчёта.** Повышение тарифа списывает полную каталожную цену нового тарифа; `replace` отбрасывает оставшееся время, а `extend` его сохраняет. Omniflow никогда не угадывает, сколько стоит смена тарифа посреди периода, — именно поэтому выбор является политикой на уровне версии, которую задаёт оператор, а не формулой.

Понижение тарифа с `at_expiry` — это то, ради чего существует отложенный момент выдачи доступа: задание исполнения засыпает до этого момента, а не занимает воркер, и предыдущее право на услугу замещается только после того, как замена действительно выдана. До этого момента обе поверхности показывают действующий тариф: каждое клиентское чтение предпочитает право на услугу, чей старт уже наступил, тому, что запланировано позже, поэтому отложенное понижение не выглядит как «настраивается» весь остаток периода, который ещё не начался.

`extension` и `renewal` выполняют одинаковую арифметику и остаются разными операциями, потому что различается *причина* — одну попросил клиент, другую породило автоматическое списание, — и поддержка должна уметь их различать.

Запрещённая операция недостижима, а не просто не предлагается. Правило жизненного цикла живёт в `internal/commerce` и применяется внутри создания заказа к подписке, на которую заказ разрешился, поэтому любой вызывающий — веб-чекаут, бот, API с bearer-токеном, сохранённая корзина — получает один и тот же отказ, HTTP 422 `operation_forbidden`:

* `purchase` отклоняется на подписке, чьё право на услугу ещё действует (приостановленное считается действующим), — покупка начиналась бы с текущего момента и выбрасывала бы оставшееся время; если подписка не названа, покупка, которая по умолчанию легла бы на действующую основную, отклоняется, а не угадывается;
* `extension` должно быть того тарифа, который держит подписка; продление истёкшей подписки покупает новый период от текущего момента и допускается;
* `upgrade` должен вести к другому тарифу с ценой не ниже текущего, `downgrade` — к тарифу дешевле, и обоим нужен действующий тариф, с которого переходить;
* политика версии тарифа по-прежнему отклоняет `forbid`.

`renewal` — собственная операция воркера продления, открываемая по постоянному согласию клиента для подписки, которую она называет, и не переклассифицируется. Длительность меньше или равная нулю отклоняется сразу.

### Что предлагается клиенту

`GET /v1/account/plans` возвращает массив `operations` по каждому тарифу, вычисленный на сервере. Клиенту без подписок предлагается `purchase`. Клиенту с одной подпиской предлагается `purchase`, если потолок одновременных подписок это ещё позволяет, плюс `extension`, плюс `upgrade` и `downgrade` везде, где политика не `forbid`. Панель рисует кнопки; она не решает политику — панель, которая сама решала бы, какие кнопки рисовать, была бы второй реализацией правил, и рано или поздно они разошлись бы в пользу клиента или против него, а и то и другое — ошибка. Массив — это `commerce.OfferedOperations`, то же правило, которое применяет создание заказа, поэтому всё предложенное принимается.

### Отмена

`cancellation_policy` хранится на версии тарифа, снимается в строку заказа и показывается — и **на неё ничто не реагирует**. В модели заказа нет операции отмены, нет маршрута отмены и нет элемента отмены ни на одной поверхности. Подписка заканчивается тем, что доходит до своей даты окончания без продления.

Оператор, которому нужно остановить доступ раньше, отключает подписку через конвейер исполнения — `POST /v1/panel/customers/{customerID}/subscriptions/{subscriptionID}/operations` с `disable`, причиной и правом `subscriptions.write`, — а это изменение состояния в Remnawave, а не отмена, и оно ничего не возвращает. Деньги возвращаются через путь возврата, описанный на странице [Заказы и платежи](https://omniflow-vpn.mintlify.site/ru/commerce/orders-payments.md).

## Фазы: что сообщается клиенту

Одна функция сводит право на услугу вместе с наблюдаемым трафиком Remnawave к единственной фазе, чтобы бот и браузер не могли описывать одну и ту же подписку по-разному:

| Фаза | Условие |
| - | - |
| `none` | Права на услугу нет или оно было замещено |
| `provisioning` | Статус `pending` — рассчитано, ждёт первой отправки в Remnawave |
| `failed` | Статус `failed` |
| `disabled` | Статус `disabled` |
| `expired` | Remnawave сообщает, что срок пользователя истёк, что бы часы ни говорили о льготном окне |
| `paused` | Статус `paused` — приостановлена оператором с сохранением оставшихся дней |
| `grace` | Конец пройден, льготный период настроен и ещё не истёк, а Remnawave всё ещё сообщает, что пользователь активен или ограничен |
| `expired` | Конец пройден, льготного периода не осталось |
| `limited` | Статус `limited` либо использованные байты достигли положительного лимита |
| `expiring_soon` | Осталось не более 7 дней до конца |
| `active` | Во всех остальных случаях |

Окно в 7 дней совпадает с самым ранним напоминанием о продлении, поэтому бот никогда не показывает «активна», уже прося клиента продлить.

Оставшиеся дни вычисляются на сервере и **округляются вверх**, потому что подписка, у которой осталось восемь часов, с точки зрения клиента имеет ещё один день. Процент трафика ограничивается диапазоном 0–100, чтобы перерасход рисовался полной полосой, а «безлимит» передаётся явно, а не выводится из нулевого лимита: индикатор без потолка — это не индикатор на нуле процентов.

### Льготные периоды

`plan_versions.grace_period_seconds` по умолчанию равен `0`, и ноль означает немедленное истечение. Никакого неявного льготного периода нигде нет. Он задаётся на версию тарифа в редакторе версий, вводится в целых днях и существует для того, чтобы текст об истечении не был догадкой: он сохраняет доступ после конца периода, чтобы клиенту можно было сказать точно, сколько времени у него осталось на продление.

Льготный период доводится до клиента как `gracePeriodSeconds` в данных тарифа, и лестница фаз сообщает `grace` всё время, пока он длится.

Он доводится и до Remnawave — именно это делает обещание настоящим. `ends_at` права на услугу остаётся оплаченным концом — его читают арифметика продления, напоминания и отчётность, — но окончание, которое воркер выдачи доступа проталкивает в Remnawave, это `ends_at` плюс льготный период версии тарифа, для каждой операции: первого `create`, продления, возобновления после паузы и прохода сверки. Расхождение измеряется относительно той же суммы, поэтому удалённое окончание с учётом льготного периода — это согласие, а не расхождение `expiry`. Поскольку Remnawave затем держит пользователя живым всё окно, сообщённый статус `EXPIRED` означает, что окно действительно закончилось, и лестница фаз доверяет ему больше, чем часам: право на услугу, чей удалённый пользователь истёк, — это `expired`, никогда не `grace`.

## Пауза

Отмена и возврат заканчивают подписку. Ни то ни другое не покрывает случай, о
котором на самом деле просят: «меня не будет шесть недель, остановите счётчик».
Обходной путь, к которому иначе тянется оператор, — отключить пользователя в
Remnawave, а это останавливает доступ и **не** останавливает счётчик. Клиент
возвращается к подписке, которая истекла, пока он не мог ею пользоваться.

Поэтому пауза — это два факта, а не один.

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

**Счётчик останавливается.** `entitlements.paused_at` фиксирует момент, и все
цифры оставшегося времени на обеих клиентских поверхностях считаются от него, а
не от «сейчас». Подписка на паузе, отсчитывающая себя до нуля на собственном
экране клиента, была бы функцией, противоречащей себе ровно там, куда клиент
смотрит.

Возобновление сдвигает `ends_at` вперёд ровно на длительность паузы и
записывает ту же величину в `paused_seconds` — поэтому подписка, срок которой
оказался позже оплаченного заказом, объясняется собственными колонками, а не
выглядит ошибкой.

### На чём это держится

| Свойство | Где живёт |
| - | - |
| Поставить на паузу можно только активную или ограниченную подписку | В предикате `UPDATE`, поэтому два оператора, нажавшие кнопку, дают одну паузу и один отказ |
| Возобновить — только поставленную на паузу | Там же, поэтому дважды нажатая кнопка не выдаст прошедшее время дважды |
| `status = 'paused'` ровно тогда, когда задан `paused_at` | Ограничение таблицы |
| В Remnawave уходит тот срок, который держит строка | Считывается из того же обновления в той же транзакции |

Возобновление — **одна** операция исполнения, а не две, и порядок объясняет
почему: новый срок отправляется до включения пользователя, поэтому пользователь
никогда не оказывается ненадолго включённым со сроком, который уже прошёл.

### Покупка изменения во время паузы

Приостановленное право на услугу остаётся действующим правом подписки, и
изменение, купленное во время паузы — продление, продлевающий подарок или код
доступа, повышение или понижение тарифа, — строится на нём и списывает его ровно
так же, как строилось бы на работающем. Строится оно на времени, которое пауза
сохраняла: база нового права — это сейчас плюс `ends_at − paused_at`, так что
клиент получает обратно ровно те дни, которые дало бы возобновление, а купленный
период добавляется сверху. Выдача доступа по новому праву суперседит
приостановленное и закрывает его паузу той же записью — `paused_at` очищается, а
прошедшая пауза добавляется в `paused_seconds`, — поэтому позднейшее
возобновление старой строки отклоняется, и дни не могут быть выданы дважды.
Оплаченное изменение возобновляет подписку: новое право включается в Remnawave,
как любое другое оплаченное изменение.

Понижение `at_expiry` — единственное изменение, которое не вступает в силу сразу:
оно ждёт окончания текущего периода. Поставленное за приостановленным правом, оно
начиналось бы через замороженный остаток от текущего момента, пока подписка
остаётся отключённой, и сохранённые дни истекли бы, пока у клиента нет доступа.
Поэтому его проведение **сначала возобновляет приостановленное право** — в той же
транзакции и ровно так, как это сделало бы возобновление оператором: `ends_at`
сдвигается вперёд на прошедшую паузу, а операция `resume` с ключом
`order:{orderID}:resume` снова включает пользователя, — и понижение начинается,
когда заканчивается возобновлённый период.

Автопродление во время паузы никогда не срабатывает: оно выбирает только
активные и ограниченные права. Дополнение во время паузы отклоняется с
`no_active_subscription`, потому что оно оценивается относительно оставшегося
времени текущего периода, а у остановленных часов оставшегося периода нет.

### Сверка знает об этом

Подписка на паузе и отключённая подписка со стороны Remnawave — одно и то же, и
разницу знает только Omniflow. Поэтому два случая считаются согласием, а не
расхождением:

* Удалённый пользователь **отключён** — так пауза выглядит сразу после
  применения.
* Удалённый пользователь **истёк** — так она выглядит через неделю, когда
  реальное время ушло за замороженный срок. Remnawave ничего не знает о паузе.
  Считать это истечением подписки значило бы забрать дни, ради сохранения
  которых пауза и существует, — через неделю после того, как клиенту сказали,
  что они в безопасности.

Подписка на паузе, чей удалённый пользователь **активен**, — это настоящее
расхождение: кто-то включил его в Remnawave, и клиент подключается на время,
которое никто не считает. Об этом сообщается.

### Паузы со стороны клиента нет

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

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

Автопродление пропускает подписку на паузе без всякого дополнительного правила:
запрос продления и так выбирает только `active` и `limited`.

## Напоминания, уведомления о льготном периоде и восстановление

Нотификатор бота обходит каждую подписку клиента и отправляет, по каждой подписке:

| Уведомление | Триггер |
| - | - |
| Истечение | До истечения по данным Remnawave осталось 7, 3, 1 или 0 дней |
| Трафик | Расход достиг 80% или 100% положительного лимита |
| Продление | Целых дней до конца права на услугу осталось 7, 3, 1 или 0 |
| Льготный период | Конец пройден, и клиент внутри окна льготного периода |
| Восстановление | Окно восстановления после льготного периода началось и ещё не закрылось |
| Исполнение | Последняя операция завершилась успехом или сбоем |

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

`APP_RECOVERY_WINDOW_HOURS` задаёт, как долго это предложение остаётся открытым; по умолчанию это 14 дней, которые `.env.example` поставляет как `336`. Маркетинговые ограничения объёма к ним не применяются — уведомления жизненного цикла проходят через собственные настройки уведомлений клиента и тихие часы. См. [Уведомления](https://omniflow-vpn.mintlify.site/ru/customer/notifications.md).

Восстановление работает только в Telegram. В клиентской веб-панели эквивалентного напоминания нет.

Клиент, импортированный до появления подписок и имеющий только сопоставление Remnawave на уровне клиента, всё равно получает оповещения: нотификатор откатывается к сводке, несущей лишь удалённый идентификатор. Названия подписок добавляются в оповещения только тогда, когда у клиента их больше одной.

## Автоматическое продление

Автопродление — это согласие на списание за следующий период в отсутствие клиента, и оно выключено, пока клиент его не включит. Настраивается оно только в Telegram-боте — ни в клиентской веб-панели, ни в `/admin` нет ни эндпоинта, ни экрана автопродления.

Страница [Регулярные платежи](https://omniflow-vpn.mintlify.site/ru/commerce/recurring-payments.md) описывает запись о согласии, источники средств, заблаговременность списания, график повторных попыток и текущее состояние пути записи.

## Включение и выключение многоподписочности

### Включение

Мигрировать ничего не нужно. Миграция, которая ввела подписки, уже создала по одной на каждого существующего клиента, переняв имевшегося у него пользователя Remnawave. Переключение `APP_MULTI_SUBSCRIPTION_ENABLED` в `true` меняет только то, что клиенты могут добавлять с этого момента.

### Выключение

Возврат в `false` безопасен и неразрушителен:

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

Ничто не осиротеет ни в ту, ни в другую сторону: переключатель управляет созданием, а не существующим состоянием.

### Как оставить клиенту одну подписку

> [!WARNING]
>
> Закрыть подписку невозможно. Колонка `status` допускает `closed`, и запрос,
> устанавливающий это значение, существует, но его никто не вызывает: нет ни
> маршрута, ни элемента управления в панели, ни действия в боте. Открытая
> подписка остаётся в записи клиента.

Что оператор может сделать вместо этого из `/admin/customers/{customerId}`, имея `subscriptions.write` и причину:

**1. Дайте ей истечь или отключите её**

Дайте лишней подписке дойти до даты окончания без продления либо поставьте
против неё операцию `disable`, чтобы её пользователь Remnawave перестал
работать немедленно.

**2. Убедитесь, что ничего не висит**

Проверьте, что право на услугу подписки находится в `expired` или
`superseded` и что ни одна операция исполнения не остаётся в `pending` или
`retrying` в `/admin` → System → Jobs.

**3. Оставьте её на месте**

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

Отключение подписки не удаляет её пользователя Remnawave, и ничто другое в Omniflow тоже не удаляет. Это осознанное действие в панели Remnawave, поэтому ничто здесь не уничтожает доступ, за который клиент заплатил.

> [!NOTE]
>
> Приостановка **клиента** до Remnawave тоже не доходит. Приостановка записывает
> статус, событие жизненного цикла и строку аудита; ни одна часть конвейера
> исполнения не читает статус клиента. Приостановленный клиент сохраняет
> работающий доступ до конца своего периода — единственное, что приостановка
> действительно останавливает, это автоматическое списание за продление. Если
> доступ должен прекратиться сейчас, отключите ещё и подписку.

## Смежные страницы

> **[Каталог и акции](https://omniflow-vpn.mintlify.site/ru/commerce/catalog-promotions.md)**
>
> Где задаются длительность, льготный период, сквады и политики повышения и понижения тарифа.

> **[Исполнение заказов](https://omniflow-vpn.mintlify.site/ru/commerce/fulfillment.md)**
>
> Конвейер операций, сверка, расхождения и изменения по запросу оператора.

> **[Дополнения и конфигуратор](https://omniflow-vpn.mintlify.site/ru/commerce/addons-configurator.md)**
>
> Дополнительный трафик, слоты устройств и сквады поверх живого права на услугу.

> **[Регулярные платежи](https://omniflow-vpn.mintlify.site/ru/commerce/recurring-payments.md)**
>
> Согласие на автопродление, источники средств, повторы и разбор неудавшихся списаний.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.