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

Тарифы

Бот поддерживает два способа описания продаж:

  • JSON-каталог тарифов из TARIFFS_CONFIG_PATH (по умолчанию data/tariffs.json);
  • конфигурация через переменные .env, если JSON-файл отсутствует.

JSON-каталог может содержать несколько тарифов разных моделей: подписки на срок, пакеты трафика без срока действия, разные наборы Internal Squads, лимиты устройств и пакеты докупки. Пример формата: data/tariffs.example.json.

Важно для Docker: TARIFFS_CONFIG_PATH обычно указывает на файл внутри /app/data, и этот каталог должен быть доступен migrate, backend и worker. Мигратор тоже читает каталог тарифов, когда привязывает существующие активные подписки без tariff_key к тарифу по умолчанию и заполняет тарифные лимиты. В штатных compose-файлах это уже настроено: корневой compose использует shop-data:/app/data, а production-примеры - ./data:/app/data для всех трех сервисов.

Коротко по моделям:

  • period - подписка на срок с периодическим лимитом трафика, настраиваемым сбросом и опциональной докупкой GB поверх лимита;
  • traffic - покупка пакетов GB без пользовательского срока действия, где повторная покупка добавляет трафик к текущему остатку;
  • premium-сквады - дополнительный набор Internal Squads внутри любого тарифа, с отдельным названием, счётчиком, настраиваемым периодом лимита и отдельными пакетами докупки.

Каталог тарифов можно настраивать из Web App админки: раздел Система → Тарифы. Админка читает и сохраняет файл из TARIFFS_CONFIG_PATH, валидирует данные той же моделью TariffsConfig, что и бот, и атомарно перезаписывает JSON только после успешной проверки.

В интерфейсе доступны:

  • добавление, редактирование и удаление тарифов;
  • включение и выключение тарифа на витрине;
  • выбор тарифа по умолчанию;
  • настройка тарифов на срок (period): базовый лимит и стратегия сброса, периоды, цены в платежной валюте/Stars, реферальные бонусы и пакеты докупки трафика;
  • настройка тарифов по трафику (traffic): пакеты GB, цены в платежной валюте/Stars, курс конвертации;
  • настройка базовых Internal Squads из списка Remnawave;
  • настройка premium-раздела: названия RU/EN, premium Internal Squads, лимит/безлимит и независимая стратегия сброса, пакеты докупки premium-трафика в платежной валюте/Stars;
  • настройка базового HWID-лимита и пакетов докупки устройств;
  • отдельные переключатели, шаги, максимумы и цены для слайдеров устройств, обычного и premium-трафика на этапе оформления подписки.

Порядок продаваемых вариантов управляется в админке перетаскиванием строк: это работает для периодов покупки подписки, traffic-пакетов, обычных докупок трафика, premium-докупок и HWID-пакетов. Такой же порядок сохраняется в JSON и затем используется в Web App и Telegram-боте.

После сохранения изменения применяются к новым запросам Web App сразу, потому что конфиг тарифов загружается из JSON при обращении. Уже созданные подписки сохраняют свой tariff_key; при удалении или отключении тарифа проверьте, что активные подписки с этим ключом не требуют дальнейшего продления или смены.

Подробности по админ-панели, правам доступа, сохранению настроек и списку разделов есть в админ-панели.

Если файл из TARIFFS_CONFIG_PATH существует и проходит валидацию, используется каталог тарифов. В этом режиме TRAFFIC_PACKAGES и цены подписок из .env не формируют витрину продаж, потому что цены и пакеты берутся из JSON.

Если JSON-файл отсутствует, бот использует значения .env:

  • RUB_PRICE_*, STARS_PRICE_* и *_MONTHS_ENABLED для подписок на срок;
  • TRAFFIC_PACKAGES и STARS_TRAFFIC_PACKAGES для продажи пакетов трафика;
  • USER_TRAFFIC_LIMIT_GB, USER_TRAFFIC_STRATEGY, USER_SQUAD_UUIDS, USER_HWID_DEVICE_LIMIT для пользователей Remnawave.

В режиме без JSON-каталога наличие TRAFFIC_PACKAGES или STARS_TRAFFIC_PACKAGES переключает витрину на продажу трафика вместо подписок на срок.

Валюта каталога и ограничения провайдеров

Заголовок раздела «Валюта каталога и ограничения провайдеров»

JSON-каталог поддерживает default_currency. По умолчанию используется rub, поэтому существующие каталоги с prices_rub, rub-пакетами и .env-ценами продолжают работать без изменений. Для другой валюты укажите код в нижнем регистре, например usd, eur или usdt, и задайте цены в generic-полях:

  • prices: { "usd": { "1": 4.99, "3": 12.99 } };
  • traffic_packages, topup_packages, premium_topup_packages, hwid_device_packages: ключ валюты вместо rub, например { "usd": [{ "gb": 50, "price": 2.5 }] };
  • conversion_rate_per_gb: курс конвертации оплаченной стоимости в GB для выбранной валюты.

Legacy-поля остаются алиасами: prices_rub, conversion_rate_rub_per_gb и ключ rub автоматически попадают в generic-модель. Telegram Stars остаются отдельной валютой stars/XTR и не могут быть default_currency.

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

Провайдер Валюты по умолчанию
YooKassa RUB
WATA RUB, USD, EUR
FreeKassa RUB, USD, EUR, UAH, KZT
CryptoPay fiat: USD, EUR, RUB, BYN, UAH, GBP, CNY, KZT, UZS, GEL, TRY, AMD, THB, INR, BRL, IDR, AZN, AED, PLN, ILS; crypto: USDT, TON, BTC, ETH, LTC, BNB, TRX, USDC
Heleket настраиваемый список HELEKET_SUPPORTED_CURRENCIES
Platega настраиваемый список PLATEGA_SUPPORTED_CURRENCIES
SeverPay настраиваемый список SEVERPAY_SUPPORTED_CURRENCIES
LAVA RUB
Pally RUB, USD, EUR
Stripe по умолчанию без локального фильтра; ограничьте через STRIPE_SUPPORTED_CURRENCIES под ваш аккаунт Stripe
Telegram Stars XTR, отдельные Stars-цены

В админке раздел Система → Тарифы показывает текущую платежную валюту и матрицу провайдеров: включен ли метод, настроен ли сервис и будет ли он доступен при выбранной валюте. Для Platega, SeverPay и Heleket список валют нужно держать в соответствии с условиями вашего мерчанта.

Минимальная структура:

{
  "default_tariff": "standard",
  "referral_welcome_bonus_tariff": "standard",
  "default_currency": "rub",
  "tariffs": [
    {
      "key": "standard",
      "names": { "ru": "Стандарт", "en": "Standard" },
      "descriptions": { "ru": "Базовый набор серверов" },
      "premium_names": { "ru": "Premium-серверы", "en": "Premium servers" },
      "squad_uuids": ["uuid-1"],
      "premium_squad_uuids": ["premium-uuid-1"],
      "billing_model": "period",
      "monthly_gb": 500,
      "traffic_limit_strategy": "MONTH",
      "flexible_traffic_limit": {
        "step_gb": 100,
        "max_total_gb": 1000,
        "price_per_step": 79,
        "stars_price_per_step": 40
      },
      "premium_monthly_gb": 50,
      "premium_traffic_limit_strategy": "MONTH",
      "premium_flexible_traffic_limit": {
        "step_gb": 25,
        "max_total_gb": 150,
        "price_per_step": 129,
        "stars_price_per_step": 65
      },
      "prices_rub": { "1": 200, "3": 600 },
      "referral_bonus_days_inviter": { "1": 3, "3": 7 },
      "referral_bonus_days_referee": { "1": 1, "3": 3 },
      "enabled_periods": [1, 3],
      "hwid_device_limit": 5,
      "topup_packages": {
        "rub": [{ "gb": 10, "price": 99 }],
        "stars": [{ "gb": 10, "price": 2500 }]
      },
      "hwid_device_packages": {
        "rub": [
          {
            "count": 1,
            "price": 99,
            "prices": { "1": 99, "3": 249 },
            "min_price": 20,
            "traffic_bonus_gb": 15
          }
        ],
        "stars": [
          {
            "count": 1,
            "price": 50,
            "prices": { "1": 50, "3": 130 },
            "traffic_bonus_gb": 15
          }
        ]
      },
      "checkout_addons": {
        "devices": {
          "enabled": true,
          "max_extra_devices": 5,
          "price_per_device": 79,
          "stars_price_per_device": 40
        },
        "traffic": { "enabled": true },
        "premium_traffic": { "enabled": true }
      },
      "enabled": true
    }
  ]
}

Основные поля:

Поле Назначение
default_tariff Тариф по умолчанию для первичного выбора и привязки активных подписок без tariff_key.
referral_welcome_bonus_tariff Необязательный period-тариф для приветственной подписки приглашённого пользователя. Должен ссылаться на включённый period-тариф; если поле отсутствует, используется default_tariff. Лимиты трафика, Internal Squads, стратегия сброса и лимит устройств наследуются из выбранного тарифа.
default_currency Валюта цен по умолчанию для JSON-каталога. По умолчанию rub; stars запрещен, потому что Stars используют отдельные цены.
tariffs[].key Стабильный ключ тарифа. Используется в платежах, подписках и смене тарифа.
tariffs[].legacy_keys Предыдущие ключи этого тарифа. Позволяют дообработать сохранённые платежи и подписки после переименования ключа; каждый legacy-ключ должен быть уникален во всём каталоге.
tariffs[].names Названия тарифа по языкам.
tariffs[].descriptions Описания тарифа по языкам.
tariffs[].enabled Доступность тарифа на витрине.
tariffs[].squad_uuids Internal Squads Remnawave для пользователей тарифа.
tariffs[].premium_names Название premium-раздела по языкам. Используется в карточке лимита, модалке докупки premium-трафика и предупреждениях. Если поле не задано, используется Premium-серверы / Premium servers.
tariffs[].premium_squad_uuids Internal Squads с отдельным premium-лимитом. Ноды для учета берутся автоматически из accessible nodes этих сквадов через API панели.
tariffs[].premium_monthly_gb Отдельный лимит трафика по premium-сквадам на один premium-период. Имя поля сохранено для совместимости; частоту периода задаёт premium_traffic_limit_strategy. 0 или отсутствие поля означает нулевой базовый лимит: premium-сквады становятся доступны только после докупки или выдачи дополнительного premium-трафика.
tariffs[].premium_unlimited Явно включает безлимитный доступ к premium-сквадам. По умолчанию false; при true значение premium_monthly_gb не применяется.
tariffs[].premium_traffic_limit_strategy Необязательная отдельная стратегия premium-счётчика: NO_RESET, DAY, WEEK, MONTH или MONTH_ROLLING («месяц от начала подписки»). Если поле отсутствует, premium-трафик наследует фактическую стратегию обычного трафика.
tariffs[].premium_topup_packages Пакеты докупки premium-трафика в формате { "rub": [{ "gb": 10, "price": 99 }], "stars": [...] }. Требуют premium_squad_uuids. Порядок строк задает порядок premium-докупок на витрине и меняется drag&drop в админке.
tariffs[].premium_flexible_traffic_limit Параметры гибкого premium-лимита: step_gb, max_total_gb, ежемесячные price_per_step и stars_price_per_step. Лимит сбрасывается вместе с premium-счётчиком и не увеличивает переносимый top-up баланс. Требует premium_squad_uuids.
tariffs[].premium_topup_always_available Показывать premium-докупку независимо от процента расхода premium-лимита. По умолчанию false: предложение появляется после 80% использования premium-лимита (см. когда показывается докупка).
tariffs[].billing_model Модель тарифа: period или traffic.
tariffs[].hwid_device_limit Базовый лимит HWID-устройств. 0 означает безлимит, отсутствие поля использует USER_HWID_DEVICE_LIMIT.
tariffs[].hwid_device_packages Пакеты докупки устройств. traffic_bonus_gb задаёт ежемесячный бонус трафика всего пакета и по умолчанию равен 0; для одной строки он должен совпадать во всех валютах. price — legacy/monthly fallback, prices задаёт полную цену пакета для периодов тарифа ("1", "3", "6", "12"), min_price задаёт минимальную цену prorate-докупки. Порядок строк задает порядок HWID-докупок и меняется drag&drop в админке.
tariffs[].flexible_traffic_limit Параметры гибкого лимита обычного трафика: шаг слайдера step_gb, итоговый максимум max_total_gb, ежемесячная цена шага price_per_step и необязательная цена stars_price_per_step. Это отдельная модель от несгораемых topup_packages.
tariffs[].checkout_addons Настройки докупок прямо в checkout period-тарифа. Для devices используются enabled, максимум дополнительных устройств max_extra_devices и ежемесячные цены одного устройства price_per_device/stars_price_per_device. Для traffic/premium_traffic здесь хранится переключатель enabled, а шаг, максимум и цены задаются соответствующим гибким лимитом.

Checkout period-тарифа может предложить изменить итоговое число устройств и периодические лимиты обычного/premium-трафика до создания платежа. Это отдельная модель от несгораемых докупок после оформления:

Ресурс Минимум Настройки и условия показа Что получает подписка
Устройства Эффективный базовый HWID-лимит MY_DEVICES_SECTION_ENABLED=True, конечный положительный базовый лимит, checkout_addons.devices.enabled, max_extra_devices и price_per_device; шаг всегда 1 Временные дополнительные устройства на оплаченный срок. device_count в checkout payload — число устройств сверх базы.
Обычный трафик monthly_gb Положительная конечная база, checkout_addons.traffic.enabled и валидный flexible_traffic_limit Выбранный итоговый лимит regular_limit_gb на каждый период сброса.
Premium-трафик premium_monthly_gb, в том числе 0 Premium squads, premium_unlimited=false, checkout_addons.premium_traffic.enabled и валидный premium_flexible_traffic_limit Выбранный итоговый лимит premium_limit_gb на каждый premium-период.

flexible_traffic_limit и premium_flexible_traffic_limit содержат:

  • step_gb — объём одного равномерного шага;
  • max_total_gb — итоговый максимум, который обязан равняться базе плюс целое число шагов;
  • price_per_step — цена одного шага за один оплачиваемый месяц в default_currency;
  • stars_price_per_step — необязательная целочисленная цена шага за месяц в ⭐ Stars.

Например, база 100 GB, шаг 50 GB и максимум 300 GB дают отметки 100/150/200/250/300 GB. Для 250 GB число платных шагов равно (250 - 100) / 50 = 3. Цена за срок M месяцев равна 3 × price_per_step × M. Частота сброса квоты не меняет единицу цены: при DAY, WEEK, MONTH, MONTH_ROLLING и даже NO_RESET доплата всё равно считается за оплачиваемые месяцы подписки. Не задавайте чрезмерно маленький шаг при большом максимуме: каждая отметка становится отдельным вариантом в API и интерфейсе.

Для устройств используется та же линейная формула с шагом 1: дополнительные устройства × price_per_device × месяцы. Если Stars-цена ресурса отсутствует или равна нулю, расширенные отметки этого ресурса не предлагаются при оплате Stars. Нулевая цена в валюте каталога допустима и делает соответствующие шаги бесплатными.

topup_packages, premium_topup_packages и hwid_device_packages не участвуют в этих слайдерах. Они остаются отдельными сценариями после оформления: traffic-пакеты увеличивают переносимый расходуемый баланс, а HWID-пакеты имеют собственные цены, сроки и необязательный traffic_bonus_gb.

Сценарий Расчёт и результат
Первая покупка Базовая цена периода плюс полная цена выбранных ресурсов за весь срок.
Раннее продление того же тарифа Новый срок оплачивается полностью. Положительная разница между выбранным и уже оплаченным активным вариантом дополнительно оплачивается пропорционально оставшейся части текущего окна. Расчёт использует 30-дневную ценовую долю месяца.
Понижение лимита Возврата за текущий остаток нет; более низкое значение начинает действовать с границы нового оплаченного окна. Базовый выбор поэтому может сохраняться в снимке даже с нулевой доплатой.
Несколько ранних продлений Для трафика backend делит оставшийся срок на уже оплаченные окна и считает только положительную разницу в каждом из них. Более высокий пересекающийся лимит не затирается меньшим.
Будущее HWID-окно уже запланировано Новый checkout с дополнительными устройствами отклоняется как конфликт расписания, чтобы не сложить перекрывающиеся права дважды.
Другой активный тариф Обычное продление отклоняется с tariff_switch_required; используется отдельный сценарий смены тарифа и его правила конвертации/доплаты.

Активные гибкие лимиты хранятся временными окнами в flexible_traffic_limits. Тарифный worker применяет максимальный активный лимит соответствующего вида, а после окончания последнего окна возвращает базу текущего тарифа. Автопродление переносит вариант, активный у границы подписки, и его зафиксированную месячную цену в следующий срок. Переносимые top-up-балансы при этом не покупаются повторно и не обнуляются.

Во время движения слайдера Mini App обновляет цены карточек периодов и кнопки оплаты локально, без запроса на каждый pointer event. После завершения взаимодействия POST /api/subscription/quote возвращает авторитетную котировку, а создание платежа полностью пересчитывает корзину ещё раз. В запросах regular_limit_gb и premium_limit_gb — итоговые лимиты, тогда как device_count — только число дополнительных устройств.

Процентная скидка промокода применяется к полному subtotal: базовой подписке, цене ресурсов на новый срок и пропорциональной доплате за немедленное повышение. После скидки рассчитывается внешняя часть платежа с учётом партнёрского баланса. Те же правила действуют для Stars, где положительная итоговая сумма не может стать меньше 1 Star.

В Payment.checkout_bundle_snapshot версии 2 сохраняются тариф, граница активной подписки, период, базовая цена, выбранные итоговые лимиты и ценовые составляющие; checkout_bundle_hash участвует в безопасном переиспользовании pending-платежа. Если ID или дата окончания подписки изменились до активации, снимок считается устаревшим и права не выдаются повторно. Редактирование каталога после создания счёта не меняет уже зафиксированную выдачу.

Провайдер с внешней ценой или собственной рекуррентной подпиской может не поддерживать дополнения в первом платеже. Для такого метода слайдеры становятся недоступными; базовую подписку всё ещё можно купить без выбранных дополнений. Backend повторяет эту проверку при котировке и создании платежа.

При переименовании тарифа перенесите его прежний key в legacy_keys, а не удаляйте его сразу. Платежи и подписки хранят ключ тарифа в момент создания; при последующей активации старый ключ разрешается через alias, после чего сервис использует текущий канонический key. Текущий ключ нельзя дублировать в legacy_keys, а один legacy-ключ нельзя назначить нескольким тарифам.

Разрешение и проверка алиасов реализованы в TariffsConfig, а activate_subscription канонизирует сохранённый ключ перед обработкой платежа. Регрессионные сценарии находятся в test_tariffs_config.py.

Для period-тарифа также используются:

Поле Назначение
monthly_gb Базовый лимит трафика на один период traffic_limit_strategy. Имя сохранено для совместимости; 0 означает безлимит.
traffic_limit_strategy Стратегия сброса трафика Remnawave: NO_RESET, DAY, WEEK, MONTH или MONTH_ROLLING («месяц от начала подписки»). Если поле отсутствует, используется legacy-настройка USER_TRAFFIC_STRATEGY.
prices Generic-цены периодов по валютам, например { "usd": { "1": 4.99 } }.
prices_rub Legacy-цены периодов в рублях, ключ - количество месяцев. Эквивалент prices.rub.
prices_stars Цены периодов в Telegram Stars.
referral_bonus_days_inviter Бонус пригласившему в днях для каждого периода. Ключ - количество месяцев, как в enabled_periods.
referral_bonus_days_referee Бонус приглашенному в днях для каждого периода. Ключ - количество месяцев, как в enabled_periods.
enabled_periods Периоды, доступные для покупки. Порядок элементов в массиве задаёт порядок периодов на витрине (в Telegram-боте и Web App) — отсортируйте их так, как нужно показывать. В веб-админке этот порядок меняется перетаскиванием строк периодов.
tribute Необязательная конфигурация Creator fallback: link, subscription_id и period_ids для подписки, а также traffic_products/premium_traffic_products для фиксированных Digital Products. Основной режим Shop API использует локальную сумму и этих полей не требует. Подробнее в настройке Tribute.
topup_packages Пакеты докупки трафика именно для этого тарифа. Если поле не задано или списки пустые, докупка для тарифа не показывается в Web App и Telegram-боте. Порядок строк задает порядок докупок на витрине и меняется drag&drop в админке.
flexible_traffic_limit Шаг, итоговый максимум и ежемесячная цена шага для слайдера оформления/продления. Выбранный лимит сбрасывается по traffic_limit_strategy.
topup_always_available Показывать докупку трафика независимо от процента расхода. По умолчанию false: предложение докупки появляется, когда израсходовано не менее 80% лимита трафика (см. когда показывается докупка).

Для traffic-тарифа используются:

Поле Назначение
traffic_packages Пакеты трафика в GB по валютам каталога и Telegram Stars. Порядок пакетов в списке задаёт порядок на витрине (в Telegram-боте и Web App): сначала идут пакеты валюты каталога, затем пакеты, доступные только за Stars. В веб-админке порядок меняется перетаскиванием строк.
conversion_rate_per_gb Курс для конвертации оставшихся дней period-тарифа в GB при смене на traffic-тариф в валюте каталога.
conversion_rate_rub_per_gb Legacy-алиас для рублевых каталогов.
tribute Необязательный Creator fallback для пакетов трафика: traffic_products сопоставляет объём пакета с фиксированными product_id и HTTPS-ссылкой Digital Product. При включённом Shop API обычная покупка использует динамический заказ на локальную сумму.

Если у traffic-тарифа нет пакетов в default_currency, conversion_rate_per_gb обязателен.

period продает доступ на срок с месячным лимитом трафика.

При покупке или продлении:

  • дата начала берется от текущей активной подписки, если она еще действует, иначе от текущего времени;
  • срок считается календарными месяцами через add_months;
  • промокод может добавить бонусные дни к рассчитанному сроку;
  • tier_baseline_bytes получает значение monthly_gb;
  • topup_balance_bytes сохраняется из текущей активной подписки;
  • traffic_limit_bytes становится tier_baseline_bytes + topup_balance_bytes;
  • в Remnawave отправляется trafficLimitStrategy из тарифа с fallback на USER_TRAFFIC_STRATEGY;
  • в Remnawave отправляются Internal Squads из тарифа;
  • в Remnawave отправляется эффективный HWID-лимит тарифа.

traffic_limit_strategy задаётся отдельно для каждого period-тарифа. Выбранное значение применяется уже в CREATE нового пользователя Remnawave, а также при активации, продлении и смене тарифа. Если поле отсутствует в старом tariffs.json, сохраняется прежнее поведение через USER_TRAFFIC_STRATEGY; редактор показывает режим наследования, пока администратор явно не выберет стратегию. При NO_RESET панель не выполняет календарный сброс; DAY, WEEK, MONTH и MONTH_ROLLING задают соответствующий период. Для traffic-тарифов поле запрещено, а обычный пакет всегда использует NO_RESET.

В редакторе MONTH_ROLLING называется «Месяц от начала подписки». В отличие от MONTH, это не календарный месяц с первого числа: месячный цикл привязан к дню начала подписки. Сводка стратегий:

Стратегия Граница периода Кто ведёт обычный счётчик
NO_RESET сброса нет Remnawave
DAY начало каждого дня Remnawave
WEEK начало каждой недели Remnawave
MONTH первое число календарного месяца Remnawave
MONTH_ROLLING каждый календарный месяц от даты начала подписки Remnawave; фактическая текущая граница приходит в lastTrafficResetAt

Core передаёт Remnawave только значение trafficLimitStrategy: физическим сбросом обычного счётчика управляет панель. Поэтому для уже существующего пользователя источником правды остаётся возвращаемый панелью lastTrafficResetAt; простая смена стратегии не стирает уже учтённый трафик. Локальный period_start_at — кеш текущего периода для предупреждений, а не отдельный счётчик или настраиваемый якорь.

Карточка пользователя показывает фактическую стратегию из Remnawave, поэтому ручной per-user override виден сразу. Фоновые синки не заменяют его значением тарифа; следующее изменение entitlement (активация, продление или смена тарифа) снова применяет стратегию текущего тарифа.

Докупка трафика для period-тарифа увеличивает topup_balance_bytes и общий traffic_limit_bytes. Этот баланс сохраняется в подписке и учитывается при продлении period-тарифа. В панель отправляется актуальный лимит, а доступ переводится в ACTIVE.

Предложение докупки не показывается пользователю сразу после покупки подписки. По умолчанию оно разблокируется, когда израсходовано не менее 80% соответствующего лимита. Правило действует одинаково в Web App и в Telegram-боте: кнопка докупки в меню бота скрыта, пока предложение заблокировано, а нажатие на устаревшую кнопку показывает алерт с остатком трафика и обновляет меню.

  • Обычный и premium-трафик разблокируются независимо: каждый считает свой процент от своего лимита.
  • Тумблеры topup_always_available (обычная докупка) и premium_topup_always_available (premium-докупка) — в редакторе тарифа это переключатель «Докупка доступна всегда» на вкладках «Трафик» и «Премиум трафик» — убирают порог для своего типа трафика.
  • Для traffic-тарифов порог не применяется: покупка пакетов и есть продукт, докупка доступна всегда.
  • Докупка не предлагается при безлимитном лимите (0) или включенном безлимитном оверрайде у подписки.

Тариф может включать дополнительный набор Internal Squads с отдельным лимитом трафика. Это удобно для сценария “обычные серверы без изменений, premium-серверы ограничены отдельно”.

{
  "billing_model": "period",
  "monthly_gb": 0,
  "traffic_limit_strategy": "NO_RESET",
  "squad_uuids": ["standard-squad-uuid"],
  "premium_names": { "ru": "Premium-серверы", "en": "Premium servers" },
  "premium_squad_uuids": ["premium-squad-uuid"],
  "premium_monthly_gb": 32,
  "premium_traffic_limit_strategy": "MONTH",
  "premium_topup_packages": {
    "rub": [{ "gb": 10, "price": 99 }],
    "stars": [{ "gb": 10, "price": 2500 }]
  }
}

Правила:

  • обычный лимит тарифа продолжает работать через trafficLimitBytes Remnawave;
  • premium-трафик считается отдельно по нодам, доступным из premium_squad_uuids. На Remnawave 3.x воркер получает все ноды одним POST /api/bandwidth-stats/nodes/usage; на 2.8.1 используется совместимый агрегат POST /api/bandwidth-stats/nodes/users. По одной ноде статистика запрашивается только как fallback и для локализации продолжающегося трафика после ограничения;
  • список UUID нод не хранится в тарифе: бот запрашивает accessible nodes каждого premium-сквада у Remnawave и кеширует результат;
  • снимок агрегированной статистики переиспользуется до 115 секунд: это короче примерно двухминутного интервала агрегации Remnawave, поэтому частый premium-тик не повторяет заведомо одинаковые тяжёлые запросы;
  • пока premium-лимит не исчерпан, пользователь получает squad_uuids + premium_squad_uuids;
  • при исчерпании premium-лимита бот убирает только premium-сквады, обычный доступ остается. Изменения с одинаковым непустым итоговым набором сквадов объединяются в POST /api/users/bulk/update-squads (до 500 пользователей за запрос); селектор автоматически меняется с uuids в 2.8.1 на userIds в 3.x. Пустой итоговый набор применяется через per-user PATCH /api/users, потому что Remnawave 3.0.0 отвечает A088/500 при попытке удалить последний сквад bulk-маршрутом. Тот же PATCH остаётся подтверждённым fallback для панели без bulk-маршрута;
  • проверка premium-лимита не ждет полного тика воркера: подписки, у которых израсходовано не меньше TARIFF_PREMIUM_FAST_WATCH_PERCENT процентов premium-лимита (и все временно ограниченные), перепроверяются каждые TARIFF_PREMIUM_FAST_TICK_SECONDS секунд, поэтому перерасход ограничен минутами, а не значением TARIFF_WORKER_TICK_SECONDS;
  • если Remnawave не отдал статистику по одной из premium-нод, использованный premium-трафик текущего периода не уменьшается: неполные данные не должны возвращать доступ пользователю сверх лимита;
  • снятие сквадов останавливает только новые подключения, поэтому сразу после ограничения бот просит панель разорвать уже установленные сессии на premium-нодах. Пользователи с одинаковым набором нод объединяются в один запрос: POST /api/ip-control/drop-connections с userUuids на 2.8.1 или POST /api/connections/drop с userIds на 3.x. Обычный доступ и чужие сессии не затрагиваются. Отключается настройкой TARIFF_PREMIUM_DROP_CONNECTIONS, повтор ограничен TARIFF_PREMIUM_DROP_CONNECTIONS_COOLDOWN_SECONDS. Для этого ноды Remnawave должны быть запущены с CAP_NET_ADMIN — см. раздел ниже;
  • если у уже ограниченной подписки premium-трафик продолжает расти, бот пишет warning с перечнем нод и показывает предупреждение в разделе диагностики админки;
  • после докупки premium-трафика бот возвращает premium-сквады, если новый лимит снова больше использованного premium-трафика.
  • докупленный premium-трафик не сгорает: сначала расходуется premium_monthly_gb, а докупленный остаток уменьшается только на трафик сверх базового premium-лимита;
  • если premium_traffic_limit_strategy отсутствует или null, premium-учёт наследует обычную стратегию. У period-тарифа это фактический trafficLimitStrategy пользователя из Remnawave с fallback сначала на traffic_limit_strategy тарифа, затем на USER_TRAFFIC_STRATEGY; у traffic-тарифа наследуется его обязательный NO_RESET. Поэтому старые period-тарифы продолжают работать как раньше, а старые traffic-тарифы теперь последовательно наследуют отсутствие сброса обычного пакета;
  • если premium_traffic_limit_strategy задан явно, premium-период полностью независим от обычного счётчика и per-user override в Remnawave. Например, сочетание monthly_gb: 0, traffic_limit_strategy: "NO_RESET", premium_monthly_gb: 32 и premium_traffic_limit_strategy: "MONTH" даёт безлимитный обычный трафик без сброса и отдельные 32 GB premium-трафика каждый календарный месяц;
  • при NO_RESET premium-период идет от start_date подписки и не сбрасывает premium_topup_used_bytes; при DAY, WEEK, MONTH и MONTH_ROLLING счетчик premium-трафика и premium_topup_used_bytes сбрасываются на границе соответствующего периода, а premium_topup_balance_bytes переносится дальше. Явные DAY, WEEK и MONTH сразу выравниваются по календарной границе даже после смены стратегии. Явный MONTH_ROLLING каждый раз пересчитывается от неизменяемого start_date подписки и не использует даты сброса Remnawave или старый premium_period_start_at как новый якорь;
  • premium_period_start_at хранит начало уже вычисленного текущего premium-периода. Это кеш окна, к которому относятся premium_used_bytes и premium_topup_used_bytes, а не дата подключения premium и не отдельная настройка тарифа;
  • при переходе существующей подписки на явный premium MONTH_ROLLING следующий premium-тик переустанавливает окно от start_date. Для legacy-записи без корректного start_date безопасным fallback остаётся сохранённый premium_period_start_at; если нет и его, используется начало текущего календарного месяца;
  • месяцы считаются через add_months: если нужного числа в следующем месяце нет, используется последний день этого месяца. Статистика premium-нод запрашивается по датам YYYY-MM-DD, поэтому время суток в timestamp границы не сужает окно статистики внутри дня;
  • trial-подписки не используют тарифную premium-стратегию: для них, как и прежде, действует TRIAL_TRAFFIC_STRATEGY.

Если premium_squad_uuids заданы, но premium_monthly_gb пустой или 0, premium-сквады не выдаются до появления положительного premium-баланса. Такой тариф можно использовать как top-up-only: при наличии premium_topup_packages докупка доступна сразу, без ожидания порога расходования. Для безлимитного premium-доступа задайте premium_unlimited: true. Если заданы premium_topup_packages или положительный premium_monthly_gb, premium_squad_uuids обязательны.

Состояние хранится в подписке:

  • premium_baseline_bytes - базовый premium-лимит тарифа;
  • premium_topup_balance_bytes - оставшийся докупленный premium-трафик;
  • premium_topup_used_bytes - часть докупленного premium-трафика, уже потраченная в текущем premium-периоде;
  • premium_used_bytes - использованный premium-трафик за текущий premium-период;
  • premium_period_start_at - начало premium-периода, к которому относится premium_used_bytes;
  • premium_is_limited - признак, что premium-сквад временно снят.

В пользовательском Web App premium-лимит показывается отдельной карточкой: использовано, лимит, остаток, докупленный переносимый остаток и список серверов/сквадов, на которые действует отдельное ограничение. В Telegram-разделе “Моя подписка” выводится тот же блок.

Обычная докупка и premium-докупка показываются отдельно. Обычная докупка использует topup_packages у period-тарифа или traffic_packages у traffic-тарифа. Premium-докупка использует только premium_topup_packages, получает sale_mode=premium_topup и в заголовке показывает premium_names, а не название обычной докупки.

Предупреждения по premium-лимиту отправляются отдельно от обычного трафика на тех же процентах TARIFF_TRAFFIC_WARNING_LEVELS. Сообщение использует название из premium_names, перечисляет серверы/сквады и ведет пользователя в докупку premium-трафика.

В Web App админке premium-сквады и стратегия их счётчика настраиваются на вкладке Премиум трафик в редакторе тарифа. Режим «Наследовать стратегию обычного трафика» выбран по умолчанию; явное значение делает premium-сброс независимым. Список сквадов берется из API Remnawave (/api/admin/panel/internal-squads), поэтому UUID обычно не нужно копировать вручную.

Когда premium-лимит исчерпан, бот убирает premium-сквады у пользователя в панели. Ноды после этого перестают принимать новые подключения, но уже установленную сессию xray сам по себе не разрывает. Разрывом занимается нода Remnawave, и только если её контейнер запущен с CAP_NET_ADMIN: без этой capability нода не может получить список онлайн-IP пользователя и закрыть его сокеты, поэтому клиент продолжает качать через открытое соединение уже после ограничения.

Проверить ноду можно по её логу при старте:

docker logs remnawave-node 2>&1 | grep CAP_NET_ADMIN

Строка [OK] CAP_NET_ADMIN is available означает, что всё в порядке. Строка CAP_NET_ADMIN is not available. означает, что разрыв сессий работать не будет. В этом случае добавьте capability в docker-compose.yml ноды и перезапустите её:

services:
  remnawave-node:
    image: remnawave/node:latest
    cap_add:
      - NET_ADMIN

Если у ограниченной подписки premium-трафик продолжает расти, бот пишет предупреждение в лог и показывает баннер в разделе диагностики админки со списком проблемных нод. Кроме отсутствия CAP_NET_ADMIN причиной может быть нода, потерявшая связь с панелью (снятие до неё не доехало и применится при переподключении), пустой обычный сквад без inbound’ов или общий inbound между обычным и premium-сквадом — в последнем случае трафик на premium-ноде остается легальным, и разносить сквады по разным inbound’ам нужно на стороне панели.

traffic продает объем трафика без пользовательского срока действия.

При покупке:

  • end_date ставится в дальнюю дату 2099-01-01 UTC, если у активной подписки нет более поздней даты;
  • duration_months = 0;
  • period_start_at = NULL;
  • tier_baseline_bytes = 0;
  • topup_balance_bytes хранит доступный остаток трафика;
  • в Remnawave отправляется trafficLimitStrategy = NO_RESET;
  • автопродление и уведомления о скором окончании срока отключаются для такой подписки.

Очередная покупка добавляет GB к фактическому остатку:

remaining = max(0, current_limit - current_used)
balance_after = remaining + purchased
limit_after = current_used + balance_after

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

Если докупка трафика вызывается для traffic-тарифа, она обрабатывается как покупка очередного пакета этого же traffic-тарифа.

Тариф может задавать базовый лимит устройств и пакеты докупки:

{
  "hwid_device_limit": 5,
  "hwid_device_packages": {
    "rub": [
      {
        "count": 1,
        "price": 99,
        "prices": { "1": 99, "3": 249, "6": 449, "12": 799 },
        "min_price": 20,
        "traffic_bonus_gb": 15
      }
    ],
    "stars": [
      {
        "count": 1,
        "price": 50,
        "prices": { "1": 50, "3": 130 },
        "traffic_bonus_gb": 15
      }
    ]
  }
}

Правила:

  • пользовательский раздел и все новые HWID-счета доступны только при MY_DEVICES_SECTION_ENABLED=True;
  • у пользователя должна быть активная подписка с каноническим tariff_key, который существует в текущем каталоге, включён и относится к модели period;
  • тариф должен иметь конечный базовый лимит и хотя бы один пакет с положительной ценой в платежной валюте каталога или Telegram Stars;
  • Web App и Telegram используют одну проверку доступности и показывают точную причину блокировки; перед созданием счёта сервис повторно проверяет флаг раздела, активный тариф и пакет, поэтому устаревшая кнопка или callback не обходят ограничения;
  • hwid_device_limit хранит базовый лимит тарифа;
  • extra_hwid_devices хранит только текущую активную сумму докупленных устройств;
  • срок действия каждой докупки хранится в hwid_device_purchases.valid_from / valid_until;
  • эффективный лимит равен hwid_device_limit + active extra_hwid_devices;
  • traffic_bonus_gb — ежемесячная добавка к основному лимиту на весь пакет, а не на каждое устройство; 0 отключает бонус;
  • бонус фиксируется в платеже и записи докупки, поэтому последующее редактирование тарифа не меняет уже купленное право;
  • бонусы активных докупок складываются, начинают действовать в valid_from и исчезают после valid_until;
  • базовый лимит 0 означает безлимит, в Remnawave отправляется hwidDeviceLimit = 0;
  • при безлимитном базовом лимите докупка устройств не применяется;
  • полная цена HWID-пакета берется из prices[duration_months]; если периода нет, используется fallback price * duration_months;
  • фактическая цена докупки считается пропорционально оплачиваемому окну valid_from -> valid_until относительно периода подписки и фиксируется в платежe;
  • для Telegram Stars цена округляется вверх до целого Stars, для платежной валюты — вверх до копеек; min_price защищает от микроплатежей в конце периода;
  • кнопка докупки устройств всегда покупает устройства только для текущей активной подписки и только до текущего срока ее действия;
  • при продлении подписки пользователь видит отдельный чекбокс продления действующих докупленных устройств; чекбокс включен по умолчанию, цена считается по текущему тарифу и добавляется в тот же платеж подписки;
  • если пользователь продлил подписку без продления устройств, старая докупка продолжает действовать до своего valid_until, а Web App показывает предупреждение о возможном временном возврате к базовому лимиту;
  • админские продления, промокоды и реферальные бонусы добавляют фиксированное количество дней отдельно к подписке и к действующим докупкам устройств, не склеивая даты окончания;
  • traffic-тарифы не показывают и не принимают докупку HWID-устройств, потому что у них нет срока подписки;
  • при смене тарифа базовый лимит берется из целевого тарифа, а неиспользованная стоимость HWID-докупок в платежной валюте конвертируется в дни нового period-тарифа или GB traffic-тарифа; XTR/Stars-докупки не конвертируются без явного курса и продолжают жить по своему valid_until;
  • история докупок пишется в hwid_device_purchases;
  • платеж хранит количество устройств в payments.purchased_hwid_devices и снимок бонуса в payments.hwid_traffic_bonus_bytes.

Докупка устройств доступна в Web App через /api/devices/topup-options и /api/payments, а также в Telegram-боте из раздела устройств. Свободные места внутри базового лимита (например, занято 1 из 5) не влияют на видимость покупки: кнопка означает увеличение максимального лимита, а не разблокировку текущих пяти мест.

Смена тарифа доступна для активных подписок с tariff_key и записывается в таблицу tariff_changes.

Варианты расчета:

Переход Поведение
period -> period Остаток оплаченных дней оценивается по legacy-полю effective_monthly_price_rub, где хранится месячная цена в платежной валюте каталога, затем пересчитывается в дни целевого тарифа через месячную цену целевого тарифа. Неиспользованная стоимость HWID-докупок в платежной валюте добавляется к этому расчету как дополнительные дни. Количество дней округляется вниз.
period -> period с доплатой Если целевой тариф дороже, может быть создан платеж tariff_upgrade; неиспользованная стоимость HWID-докупок в платежной валюте уменьшает сумму доплаты. После оплаты применяется целевой тариф, а конвертированные HWID-окна закрываются.
period -> traffic Остаток оплаченных дней и неиспользованная стоимость HWID-докупок в платежной валюте конвертируются в GB по conversion_rate_per_gb или минимальной цене GB из пакетов целевого тарифа.
traffic -> period Пользователь выбирает и оплачивает период целевого тарифа; остаток GB сохраняется как topup_balance_bytes поверх лимита period-тарифа.

При смене тарифа бот меняет:

  • tariff_key;
  • Internal Squads в Remnawave;
  • trafficLimitBytes;
  • trafficLimitStrategy;
  • базовый HWID-лимит;
  • effective_monthly_price_rub для period-тарифов;
  • auto_renew_enabled и уведомления для traffic-тарифов.

В платежах используются поля:

Поле Назначение
sale_mode Тип продажи: subscription, traffic_package, topup, premium_topup, tariff_upgrade, hwid_devices.
tariff_key Ключ тарифа, к которому относится платеж.
purchased_gb Купленный объем GB для traffic-пакетов и докупки трафика.
purchased_hwid_devices Количество устройств при докупке HWID.
hwid_valid_from, hwid_valid_until Зафиксированное окно действия HWID-докупки на момент создания платежа.
hwid_pricing_period_months, hwid_proration_ratio, hwid_full_price Метаданные расчета цены HWID-докупки: период тарифа, коэффициент prorate и полная цена пакета для периода.
subscription_duration_months Количество месяцев для подписки на срок; также используется платежными обработчиками как числовое поле покупки.
checkout_bundle_snapshot, checkout_bundle_hash Неизменяемый состав докупок при оформлении подписки и его SHA-256 для сопоставления pending-платежей.

В callback и metadata платежных провайдеров sale_mode может передаваться с суффиксом тарифа, например subscription@standard или topup@standard. При активации платежа тариф сохраняется отдельно в tariff_key.

Remnawave ограничивает доступ при достижении trafficLimitBytes, переводя пользователя в статус LIMITED. Бот не удаляет пользователя из Internal Squads при 100% использования трафика.

TariffTrafficWorker запускается, когда активен JSON-каталог тарифов. Раз в 300 секунд он:

  • синхронизирует из панели status, trafficLimitBytes, usedTrafficBytes и trafficLimitStrategy;
  • для period-тарифов использует trafficLimitStrategy из Remnawave, а стратегию тарифа и затем USER_TRAFFIC_STRATEGY применяет как fallback, если панель не вернула стратегию;
  • отправляет предупреждения на уровнях из TARIFF_TRAFFIC_WARNING_LEVELS (по умолчанию 85,90,95);
  • дедуплицирует предупреждения через traffic_warnings.

Для period-тарифов дедупликация предупреждений привязана к началу текущего периода по стратегии из Remnawave с fallback на стратегию тарифа и USER_TRAFFIC_STRATEGY; уведомления о сбросе отправляются только когда эффективная стратегия сброса не NO_RESET. Для traffic-тарифов дедупликация учитывает текущий trafficLimitBytes, чтобы после покупки очередного пакета пользователь мог получить следующий набор предупреждений.

Подписки, которые были ограничены логикой предыдущих запусков бота (is_throttled=True), восстанавливаются воркером только когда лимит снова больше использованного трафика.

Автопродление через YooKassa применяется к подпискам на срок. Для режима продажи трафика без JSON-каталога автопродление пропускается. Для traffic-тарифов JSON-каталога покупка является пакетом трафика, а не периодической подпиской.

Пробный период использует настройки TRIAL_DURATION_DAYS, TRIAL_TRAFFIC_LIMIT_GB, TRIAL_HWID_DEVICE_LIMIT, TRIAL_TRAFFIC_STRATEGY и TRIAL_SQUAD_UUIDS. Он не выбирает тариф из JSON-каталога, но его можно настроить на странице Система → Тарифы рядом с каталогом продаж. TRIAL_HWID_DEVICE_LIMIT задаёт лимит устройств: пустое значение сохраняет лимит панели или значение по умолчанию, 0 означает безлимит. Докупка устройств во время trial недоступна; после исчерпания конечного лимита Web App предлагает перейти на тариф, только если хотя бы в одном доступном тарифе настроены пакеты устройств. Если TRIAL_SQUAD_UUIDS пустой, для trial применяются squads из USER_SQUAD_UUIDS; premium-сквады из тарифного каталога в обычном списке trial не выдаются как обычный доступ. Premium-доступ для trial включается отдельно через TRIAL_PREMIUM_SQUAD_UUIDS; если список пустой, premium-сквады не выдаются. Отдельный лимит задаёт TRIAL_PREMIUM_TRAFFIC_LIMIT_GB, где 0 означает доступ без отдельного premium-ограничения, а его период следует TRIAL_TRAFFIC_STRATEGY. Переключатель TRIAL_WITHOUT_TELEGRAM_ENABLED управляет активацией trial для аккаунтов без Telegram, а домены из DISPOSABLE_EMAIL_DOMAINS требуют привязки Telegram независимо от этого переключателя.

Промокоды поддерживают несколько режимов: мгновенные бонусные дни, бонусные дни после оплаты, скидку на checkout, множитель срока подписки и множитель выдаваемого трафика. Бонусные дни и множитель срока работают с period-подписками; множитель трафика работает с traffic-пакетами и докупками; скидка может применяться и к подпискам, и к traffic/HWID-покупкам. Подробности: промокоды.

Реферальные бонусы за оплату в JSON-каталоге задаются прямо в period-тарифе рядом с ценами периода: referral_bonus_days_inviter для пригласившего и referral_bonus_days_referee для приглашенного. Ключи этих словарей - месяцы периода ("1", "3", "6", "12" или любые другие периоды тарифа, например "2", "4", "8", "16"). Для traffic-тарифов такие бонусы не применяются.

Вверху отдельного блока Реферальная программа на странице тарифов находится глобальный переключатель REFERRAL_PROGRAM_ENABLED. При выключении он прекращает обычную реферальную атрибуцию и начисление приветственных и платёжных бонусов, скрывает реферальные элементы в Telegram и Web App, а в разделе «Бонусы» оставляет только ввод промокода. Сохранённая конфигурация не удаляется, партнёрская программа работает независимо.

Приветственный бонус приглашённому (REFERRAL_WELCOME_BONUS_DAYS) настраивается в том же блоке. Поле каталога referral_welcome_bonus_tariff выбирает включённый period-тариф для новой бонусной подписки; если оно отсутствует, используется default_tariff. Из тарифа наследуются обычный и premium-лимиты трафика, Internal Squads, стратегия сброса и HWID-лимит. Там же флаги REFERRAL_WEBAPP_LINK_ENABLED и REFERRAL_TELEGRAM_LINK_ENABLED определяют, какие реферальные ссылки видит пользователь в разделе бонусов Web App; хотя бы один из них всегда должен оставаться включённым. REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED разрешает или запрещает выдачу приветственного бонуса аккаунтам без Telegram; disposable email домены из DISPOSABLE_EMAIL_DOMAINS всегда требуют Telegram перед начислением.

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

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

В Web App и Telegram-меню подробные строки по периодам показываются только для legacy-режима или когда активен один period-тариф. Если включено несколько period-тарифов, Web App показывает сообщение, что бонус зависит от тарифа и периода оплаты друга, затем список тарифов с диапазонами “от N до N дней” и раскрытием подробностей по иконке вопроса. Telegram-меню в этом случае показывает только диапазоны по каждому тарифу.

В legacy-режиме без JSON-каталога используются переменные REFERRAL_BONUS_DAYS_* и REFEREE_BONUS_DAYS_*; при продаже трафика без JSON-каталога Web App не показывает детализацию бонусов по месяцам. Переключатель LEGACY_REFS принимает старые ссылки вида /start ref_<telegram_id>, где payload содержит Telegram/user ID пригласившего; в админке он находится в блоке Совместимость с legacy-тарифами.

При запуске с активным JSON-каталогом бот выполняет безопасную сверку активных платных подписок. Он не назначает default_tariff вслепую, а применяет только детерминированные соответствия:

  • legacy-ключ, указанный в legacy_keys, приводится к каноническому ключу;
  • используется тариф последнего успешного платежа подписки или смены тарифа;
  • подписка без ключа получает тариф автоматически только если включён ровно один period-тариф.

Если подходящих тарифов несколько, ключ отсутствует в каталоге или у пользователя одновременно несколько активных подписок, запись остаётся без изменений и попадает в список ручной проверки. Результат, источник и время привязки сохраняются в tariff_binding_source, tariff_bound_at и tariff_binding_note.

В админке Система → Тарифы → Привязки подписок к тарифам доступен dry-run с количеством исправных, безопасно сопоставимых и неоднозначных записей. Кнопка Применить безопасные совпадения изменяет только детерминированные записи; неоднозначные случаи исправляются через карточку пользователя, где перед сменой показывается preview трафика, сквадов и HWID-лимита. Сохранение каталога также запускает эту сверку и явно возвращает ошибку, если она не завершилась.

Новые платные активации при включённом каталоге обязаны содержать канонический tariff_key. Это не позволяет снова создавать подписки, для которых недоступны тарифные функции и HWID-докупка.