Наборы сквадов на уровне тарифа
Версия тарифа несёт две коллекции сквадов:remnawave_squad_ids— назначаются каждой подписке на этом тарифе всегда;plan_version_squads— выбираемый набор, где каждая строка несёт русскую и английскую подпись и порядок сортировки.
squad_selection решает, что клиенту позволено:
Пустое значение ведёт себя как
automatic, а любое значение вне этих трёх отклоняется сразу, а не додумывается.
Итоговый набор сквадов для права на услугу — это включённые сквады плюс принятый выбор, дедуплицированные и отсортированные. Сортировка важна: два клиента, выбравшие одни и те же сквады в разном порядке, обязаны дать побайтово идентичное состояние права на услугу, иначе обнаружение расхождений сообщило бы о несовпадении, которого нет.
Выбор проверяется на сервере по тарифу. Данные коллбэка, называющие сквад, которого тариф не предлагает, отклоняются с squad_not_offered, поэтому подделанный коллбэк не может прикрепить сквад, за который клиент не платил.
Конфигуратор появляется только тогда, когда тариф действительно предлагает выбор. Тариф с выбором automatic идёт с экрана тарифа прямо к оплате, ровно как и раньше.
Полный список отказов
Все пять оборачивают одну доменную ошибку и проявляются как HTTP 422 с причиной в поле
type по RFC 9457, которое обе клиентские поверхности переводят в собственные формулировки.
База данных подкрепляет границы: min_selectable_squads не меньше 0, max_selectable_squads больше 0, если задан, именованная межколоночная проверка требует, чтобы максимум был не меньше минимума, а подписи — от 1 до 80 символов на каждом языке.
Выбор сквадов при оформлении заказа
Веб. На/account/checkout группа полей Серверы появляется только тогда, когда тариф сообщает, что выбор настраиваемый. Каждый чекбокс отправляет PATCH /v1/account/checkout со всем массивом squadIds, а сервер пересчитывает цену и возвращает оформление заказа целиком. Границы, которые показывает страница, — только подсказка: настоящая проверка выполняется при создании заказа, потому что вторая реализация того же правила подсчёта на React — это ровно то, что рано или поздно пропустило бы набор, который заказ затем отклонит.
Telegram. Выбор способа оплаты сначала открывает конфигуратор сквадов, когда тариф настраиваемый, и каждый коллбэк squad: переключает один сквад.
GET /v1/account/plans/{planVersionID} возвращает всю картину в поле squads с selection, minimum, maximum, configurable и списком предложенных сквадов. Подтверждённый набор хранится в заказе в selected_squad_ids; если он пуст, при расчёте используется собственный набор версии тарифа.
Дополнения
Дополнение добавляет подписке ёмкость. Купить его можно двумя способами:- Вместе с тарифом, при оформлении заказа или в сохранённой корзине. Оно списывается тем же заказом, поэтому одно подтверждение — это один платёж, и покрывает весь первый период по каталожной цене.
- В середине периода, против живого права на услугу. Оно списывается отдельным заказом с
operation = 'addon'и оценивается по правилу пропорционального расчёта своей версии относительно оставшегося времени периода.
Дополнение предлагается только для версий тарифа, которые перечисляют его в
plan_version_addons. Покупка дополнения для тарифа, который его не предлагает, отклоняется с addon_unavailable. Эта связь — не только курирование: идентификатор дополнения приходит в данных коллбэка или в пути URL, которые контролирует клиент, поэтому именно она останавливает попытку прикрепить дополнение к тарифу, для которого оно никогда не оценивалось. Идентификатор, который даже не является UUID, обрабатывается так же, а не как ошибка разбора.
Версионированные цены
Дополнения версионируются точно так же, как тарифы. Строка дополнения несёт представление и изменяема; версия несёт то, что получает клиент, и то, сколько это стоит, и неизменяема.addon_versions неизменяема, как только на неё ссылается заказ, а order_addon_lines хранит снимок ёмкости, цены и правила пропорционального расчёта версии на момент покупки. Изменение цены дополнения создаёт новую версию; оно никогда не переписывает исторический заказ.
Создание дополнения
1
Откройте каталог
Перейдите в
/admin/catalog и переключитесь на вкладку Дополнения. В
списке показаны код, метка типа, текущая версия в виде v{n} и метка
«Видно» или «Скрыто». Список только для чтения — в строке нет элементов
редактирования или вывода из обращения.2
Опишите его
Нажмите Новое дополнение. Заполните Код, Тип, Название (EN), Название
(RU) и зависящее от типа содержимое: Трафик в ГБ, Слоты устройств или список
ID сквадов через запятую. Трафик переводится в байты в браузере до отправки.
3
Назначьте цену и границы
Задайте «Максимум в заказе», правило цены в середине периода и Цены в том же
виде
RUB=19900, USD=299, который использует редактор тарифов. Включите
Видимость и введите причину.4
Сохраните
Отправка вызывает
PUT /v1/panel/catalog/addons (catalog.write) и
возвращает ID дополнения. Аудит panel.addon.saved.- Отсутствующий код или код, не соответствующий
^[a-z][a-z0-9_-]{1,63}$; - Правило пропорционального расчёта вне трёх известных;
- Максимум в заказе меньше 1;
- Полное отсутствие цен;
- Дополнение
trafficбез трафика, дополнениеdevicesбез слотов, дополнениеsquadsбез сквадов или любой другой тип; - Отсутствие названия на одном из языков. Отсутствующий перевод отклоняется, а не сохраняется пустым, потому что дополнение без названия на одном языке — это пустая строка на экране клиента.
POST /v1/panel/catalog/addon-versions/{addonVersionID}/retire (catalog.write, аудит panel.addon_version.retired). Вывод уже выведенной версии не находит ни одной строки и отвечает 404. Элемента управления для вывода из обращения в панели тоже нет.
Набор предлагаемых дополнений на версии тарифа заполняется только массивом
addonIds в POST /v1/panel/catalog/plans/{planID}/versions, и публикация
заменяет этот набор, а не дополняет его. Редактор версий в панели это поле
не отправляет, поэтому каждая версия тарифа, опубликованная из
/admin/catalog, не предлагает никаких дополнений вовсе. Заполнить набор
можно только прямым вызовом эндпоинта версий.Покупка дополнений вместе с тарифом
Веб./account/checkout рисует раздел Дополнения, когда версия тарифа предлагает хотя бы одно. Каждая запись — кнопка-переключатель; нажатие вызывает POST /v1/account/checkout/addons/{addonVersionID}, который переключает строку с количеством один и возвращает всё оформление заказа с пересчитанной ценой. Разбивка расчёта после этого сообщает addonMinor отдельно от subtotalMinor и discountMinor, потому что клиент, которому назвали только итог к оплате, не может отличить акцию от дополнения, а последствия ошибки в каждом из них совершенно разные.
Telegram. На экране оформления заказа появляется кнопка дополнений, когда тариф их предлагает; в списке одно нажатие переключает одну запись.
Переключение дополнения, которого тариф не предлагает, отвечает 404 plan_unavailable в вебе и «это дополнение больше недоступно» в боте. Архивированное дополнение, выведенная из обращения версия или версия без цены в валюте заказа отклоняются с addon_unavailable.
Промокод даёт скидку только на цену тарифа. Промежуточная сумма — это цена
тарифа плюс сумма дополнений, но скидка считается только от цены тарифа,
поэтому процент скидки никогда не съедает ёмкость дополнений.
Покупка дополнений в середине периода
Telegram. Подписки → выберите одну → Дополнения → нажмите на дополнение. Покупка становится собственным заказом с количеством один; кошелёк оплачивает его, когда баланса хватает, а иначе появляется обычный экран оплаты. Веб. Недоступно. Под/v1/account нет маршрута для покупки дополнения к живой подписке. Клиентская веб-панель может выбирать дополнения внутри оформления заказа и больше нигде.
Покупка в середине периода отклоняется с:
Режим обслуживания блокирует покупку целиком, как и любой другой заказ. Повторное использование ключа идемпотентности с другими параметрами дополнения отклоняется, а не разрешается молча в прежний заказ. Одно и то же дополнение дважды в одном заказе отклоняется сразу.
Правила пропорционального расчёта
Правило пропорционального расчёта — это свойство версии дополнения, и оно всегда задано явно. Omniflow никогда не выводит частичную цену из оставшегося времени, если дополнение этого не говорит. Правило применяется только к покупке в середине периода; дополнение, купленное вместе с тарифом, покрывает полный период и списывается по каталожной цене, что бы ни говорило его правило.
Три свойства выполняются для любого правила, и у каждого есть собственный модульный тест:
- Пропорциональное списание никогда не превышает каталожную цену.
- Округление всегда вверх, поэтому частичный период никогда не округляется до нуля.
- Покупка, сделанная до начала периода или против периода, в котором не осталось времени, откатывается к полной цене. Списывать ноль за ёмкость, которая всё равно будет предоставлена, значило бы отдать её даром.
Применение дополнения
При расчёте купленная ёмкость сворачивается в само право на услугу до того, как вычисляется желаемое состояние выдачи доступа, поэтому Remnawave получает один совокупный результат, а не тариф с последующей поправкой:max_quantity у дополнения со сквадами должно быть равно 1.
Каждая оценённая строка снимается в order_addon_lines с кодом, типом, версией, трафиком, слотами, сквадами, правилом пропорционального расчёта и концом периода, чтобы заказ объяснял себя сам спустя годы, без обращения к каталогу.
Идемпотентное исполнение
entitlement_addons уникальна по (order_id, addon_version_id). Повторный расчёт находит строку уже на месте, пропускает её и оставляет право на услугу нетронутым. В итоги входят только те строки, которые были фактически вставлены, поэтому частичный повтор не может посчитать дважды.
Заказ на дополнение в середине периода ставит в очередь собственную операцию исполнения с ключом order:<order id>:addon, несущую операцию set_limits для совокупного результата, и заказ переходит в fulfilled. Дополнению, купленному вместе с тарифом, отдельная операция не нужна: оно уже часть права на услугу, которое выдаёт собственная операция create тарифа. Что происходит с этой операцией дальше, описано в разделе Исполнение заказов.
Когда прикреплять не к чему
Если к моменту расчёта у целевой подписки нет живого права на услугу, деньги не теряются и ничто не откатывается. Заказ остаётся вpaid, пишется событие аудита addon.no_active_entitlement в категории financial с результатом «сбой», и в очередь ставится операторское уведомление типа fulfillment_failure, дедуплицированное по заказу. Откат записанного платежа был бы хуже, чем оставить оператору понятную задачу, — ничто здесь не вправе откатывать уже принятые деньги.
Справочник API по этой области
Маршруты сохранения и вывода дополнения из обращения, а также маршрут версий тарифа реализованы, но отсутствуют в
api/openapi.yaml, поэтому их нет ни в сгенерированном клиенте, ни в опубликованном справочнике API. Как их всё-таки вызывать, объясняет страница Интеграция с API.
Смежные страницы
Каталог и акции
Тарифы, неизменяемые версии, цены, пробные периоды и правила акций, которым подчиняется скидка.
Одновременные подписки
Право на услугу, к которому прикрепляется дополнение, и что происходит, когда его период заканчивается.
Исполнение заказов
Как совокупное желаемое состояние доходит до Remnawave и как выглядит неудавшаяся операция.
Пополнение кошелька и сохранённые корзины
Дополнения внутри сохранённой корзины и почему корзина всегда переоценивается перед списанием.