# Тарифы Бот поддерживает два способа описания продаж: - JSON-каталог тарифов из `TARIFFS_CONFIG_PATH` (по умолчанию `data/tariffs.json`); - конфигурация через переменные `.env`, если JSON-файл отсутствует. JSON-каталог может содержать несколько тарифов разных моделей: подписки на срок, пакеты трафика без срока действия, разные наборы Internal Squads, лимиты устройств и пакеты докупки. Пример формата: [data/tariffs.example.json](https://github.com/3252a8/remnawave-minishop/blob/main/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-лимит и пакеты докупки premium-трафика в платежной валюте/Stars; - настройка базового HWID-лимита и пакетов докупки устройств. Порядок продаваемых вариантов управляется в админке перетаскиванием строк: это работает для периодов покупки подписки, traffic-пакетов, обычных докупок трафика, premium-докупок и HWID-пакетов. Такой же порядок сохраняется в JSON и затем используется в Web App и Telegram-боте. После сохранения изменения применяются к новым запросам Web App сразу, потому что конфиг тарифов загружается из JSON при обращении. Уже созданные подписки сохраняют свой `tariff_key`; при удалении или отключении тарифа проверьте, что активные подписки с этим ключом не требуют дальнейшего продления или смены. Подробности по админ-панели, правам доступа, сохранению настроек и списку разделов есть в [админ-панели](admin-panel.md). ## Как выбирается режим Если файл из `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` | | Telegram Stars | `XTR`, отдельные Stars-цены | В админке раздел **Система → Тарифы** показывает текущую платежную валюту и матрицу провайдеров: включен ли метод, настроен ли сервис и будет ли он доступен при выбранной валюте. Для Platega, SeverPay и Heleket список валют нужно держать в соответствии с условиями вашего мерчанта. ## Структура JSON-каталога Минимальная структура: ```json { "default_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"], "billing_model": "period", "monthly_gb": 500, "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], "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 } ], "stars": [{ "count": 1, "price": 50, "prices": { "1": 50, "3": 130 } }] }, "enabled": true } ] } ``` Основные поля: | Поле | Назначение | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `default_tariff` | Тариф по умолчанию для первичного выбора и привязки активных подписок без `tariff_key`. | | `default_currency` | Валюта цен по умолчанию для JSON-каталога. По умолчанию `rub`; `stars` запрещен, потому что Stars используют отдельные цены. | | `tariffs[].key` | Стабильный ключ тарифа. Используется в платежах, подписках и смене тарифа. | | `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-сквадам. `0` или отсутствие поля отключает отдельное ограничение. | | `tariffs[].premium_topup_packages` | Пакеты докупки premium-трафика в формате `{ "rub": [{ "gb": 10, "price": 99 }], "stars": [...] }`. Требуют `premium_squad_uuids`. Порядок строк задает порядок premium-докупок на витрине и меняется drag&drop в админке. | | `tariffs[].billing_model` | Модель тарифа: `period` или `traffic`. | | `tariffs[].hwid_device_limit` | Базовый лимит HWID-устройств. `0` означает безлимит, отсутствие поля использует `USER_HWID_DEVICE_LIMIT`. | | `tariffs[].hwid_device_packages` | Пакеты докупки устройств. `price` — legacy/monthly fallback, `prices` задаёт полную цену пакета для периодов тарифа (`"1"`, `"3"`, `"6"`, `"12"`), `min_price` задаёт минимальную цену prorate-докупки. Порядок строк задает порядок HWID-докупок и меняется drag&drop в админке. | Для `period`-тарифа также используются: | Поле | Назначение | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `monthly_gb` | Базовый месячный лимит трафика тарифа. `0` означает безлимит. | | `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) — отсортируйте их так, как нужно показывать. В веб-админке этот порядок меняется перетаскиванием строк периодов. | | `topup_packages` | Пакеты докупки трафика именно для этого тарифа. Если поле не задано или списки пустые, докупка для тарифа не показывается в Web App и Telegram-боте. Порядок строк задает порядок докупок на витрине и меняется drag&drop в админке. | Для `traffic`-тарифа используются: | Поле | Назначение | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `traffic_packages` | Пакеты трафика в GB по валютам каталога и Telegram Stars. Порядок пакетов в списке задаёт порядок на витрине (в Telegram-боте и Web App): сначала идут пакеты валюты каталога, затем пакеты, доступные только за Stars. В веб-админке порядок меняется перетаскиванием строк. | | `conversion_rate_per_gb` | Курс для конвертации оставшихся дней period-тарифа в GB при смене на traffic-тариф в валюте каталога. | | `conversion_rate_rub_per_gb` | Legacy-алиас для рублевых каталогов. | Если у traffic-тарифа нет пакетов в `default_currency`, `conversion_rate_per_gb` обязателен. ## Тарифы на срок (`period`) `period` продает доступ на срок с месячным лимитом трафика. При покупке или продлении: - дата начала берется от текущей активной подписки, если она еще действует, иначе от текущего времени; - срок считается календарными месяцами через `add_months`; - промокод может добавить бонусные дни к рассчитанному сроку; - `tier_baseline_bytes` получает значение `monthly_gb`; - `topup_balance_bytes` сохраняется из текущей активной подписки; - `traffic_limit_bytes` становится `tier_baseline_bytes + topup_balance_bytes`; - в Remnawave отправляется `trafficLimitStrategy = MONTH`; - в Remnawave отправляются Internal Squads из тарифа; - в Remnawave отправляется эффективный HWID-лимит тарифа. `MONTH` означает, что сброс использованного трафика выполняет Remnawave. Бот не рассчитывает дату сброса самостоятельно и не хранит отдельный период сброса для period-тарифов. Докупка трафика для period-тарифа увеличивает `topup_balance_bytes` и общий `traffic_limit_bytes`. Этот баланс сохраняется в подписке и учитывается при продлении period-тарифа. В панель отправляется актуальный лимит, а доступ переводится в `ACTIVE`. ## Premium-сквады и отдельный лимит Тариф может включать дополнительный набор Internal Squads с отдельным лимитом трафика. Это удобно для сценария “обычные серверы без изменений, premium-серверы ограничены отдельно”. ```json { "squad_uuids": ["standard-squad-uuid"], "premium_names": { "ru": "Premium-серверы", "en": "Premium servers" }, "premium_squad_uuids": ["premium-squad-uuid"], "premium_monthly_gb": 50, "premium_topup_packages": { "rub": [{ "gb": 10, "price": 99 }], "stars": [{ "gb": 10, "price": 2500 }] } } ``` Правила: - обычный лимит тарифа продолжает работать через `trafficLimitBytes` Remnawave; - premium-трафик считается отдельно по нодам, доступным из `premium_squad_uuids`; - список UUID нод не хранится в тарифе: бот запрашивает accessible nodes каждого premium-сквада у Remnawave и кеширует результат; - пока premium-лимит не исчерпан, пользователь получает `squad_uuids + premium_squad_uuids`; - при исчерпании premium-лимита бот убирает только premium-сквады, обычный доступ остается; - после докупки premium-трафика бот возвращает premium-сквады, если новый лимит снова больше использованного premium-трафика. - докупленный premium-трафик не сгорает в конце месяца: каждый месяц сначала расходуется `premium_monthly_gb`, а докупленный остаток уменьшается только на трафик сверх месячного лимита; - при новом календарном месяце счетчик premium-трафика и `premium_topup_used_bytes` сбрасываются, но `premium_topup_balance_bytes` переносится дальше. Если `premium_squad_uuids` заданы, но `premium_monthly_gb` пустой или `0` и нет `premium_topup_packages`, premium-сквады работают как дополнительный доступ без отдельного ограничения. Если заданы `premium_topup_packages` или положительный `premium_monthly_gb`, `premium_squad_uuids` обязательны. Состояние хранится в подписке: - `premium_baseline_bytes` - базовый premium-лимит тарифа; - `premium_topup_balance_bytes` - оставшийся докупленный premium-трафик; - `premium_topup_used_bytes` - часть докупленного premium-трафика, уже потраченная в текущем месяце; - `premium_used_bytes` - использованный premium-трафик за текущий календарный месяц; - `premium_period_start_at` - месяц, к которому относится `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 обычно не нужно копировать вручную. ## Тарифы по трафику (`traffic`) `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 к фактическому остатку: ```text remaining = max(0, current_limit - current_used) balance_after = remaining + purchased limit_after = current_used + balance_after ``` Так пользователь не теряет уже оплаченный остаток, а Remnawave продолжает считать общий лимит от текущего использованного трафика. Если докупка трафика вызывается для traffic-тарифа, она обрабатывается как покупка очередного пакета этого же traffic-тарифа. ## HWID-устройства Тариф может задавать базовый лимит устройств и пакеты докупки: ```json { "hwid_device_limit": 5, "hwid_device_packages": { "rub": [ { "count": 1, "price": 99, "prices": { "1": 99, "3": 249, "6": 449, "12": 799 }, "min_price": 20 } ], "stars": [{ "count": 1, "price": 50, "prices": { "1": 50, "3": 130 } }] } } ``` Правила: - `hwid_device_limit` хранит базовый лимит тарифа; - `extra_hwid_devices` хранит только текущую активную сумму докупленных устройств; - срок действия каждой докупки хранится в `hwid_device_purchases.valid_from` / `valid_until`; - эффективный лимит равен `hwid_device_limit + active extra_hwid_devices`; - базовый лимит `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`. Докупка устройств доступна в Web App через `/api/devices/topup-options` и `/api/payments`, а также в Telegram-боте из раздела устройств. ## Смена тарифа Смена тарифа доступна для активных подписок с `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` | Количество месяцев для подписки на срок; также используется платежными обработчиками как числовое поле покупки. | В 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 = MONTH`, если панель еще показывает другую стратегию; - отправляет предупреждения на уровнях из `TARIFF_TRAFFIC_WARNING_LEVELS` (по умолчанию `85,90,95`); - не отправляет `status=ACTIVE` при простой синхронизации стратегии, чтобы не снять статус `LIMITED`, выставленный Remnawave; - дедуплицирует предупреждения через `traffic_warnings`. Для period-тарифов дедупликация предупреждений привязана к началу текущего месяца. Для traffic-тарифов она учитывает текущий `trafficLimitBytes`, чтобы после покупки очередного пакета пользователь мог получить следующий набор предупреждений. Подписки, которые были ограничены логикой предыдущих запусков бота (`is_throttled=True`), восстанавливаются воркером только когда лимит снова больше использованного трафика. ## Автопродление, пробный период и бонусы Автопродление через YooKassa применяется к подпискам на срок. Для режима продажи трафика без JSON-каталога автопродление пропускается. Для traffic-тарифов JSON-каталога покупка является пакетом трафика, а не периодической подпиской. Пробный период использует настройки `TRIAL_DURATION_DAYS`, `TRIAL_TRAFFIC_LIMIT_GB`, `TRIAL_TRAFFIC_STRATEGY` и `TRIAL_SQUAD_UUIDS`. Он не выбирает тариф из JSON-каталога, но его можно настроить на странице **Система → Тарифы** рядом с каталогом продаж. Если `TRIAL_SQUAD_UUIDS` пустой, для trial применяются squads из `USER_SQUAD_UUIDS`. Переключатель `TRIAL_WITHOUT_TELEGRAM_ENABLED` управляет активацией trial для аккаунтов без Telegram, а домены из `DISPOSABLE_EMAIL_DOMAINS` требуют привязки Telegram независимо от этого переключателя. Промокоды с бонусными днями применяются к покупке period-подписки. Реферальные бонусы за оплату в JSON-каталоге задаются прямо в period-тарифе рядом с ценами периода: `referral_bonus_days_inviter` для пригласившего и `referral_bonus_days_referee` для приглашенного. Ключи этих словарей - месяцы периода (`"1"`, `"3"`, `"6"`, `"12"` или любые другие периоды тарифа, например `"2"`, `"4"`, `"8"`, `"16"`). Для `traffic`-тарифов такие бонусы не применяются. Приветственный бонус приглашённому (`REFERRAL_WELCOME_BONUS_DAYS`) настраивается в отдельном блоке **Реферальная программа** на странице тарифов. `REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED` разрешает или запрещает выдачу этого бонуса аккаунтам без Telegram; disposable email домены из `DISPOSABLE_EMAIL_DOMAINS` всегда требуют Telegram перед начислением. Если приглашенный покупает один тариф, а пригласивший находится на другом, размер бонуса берется из тарифа и периода, который купил приглашенный. При этом подписка пригласившего только продлевается на бонусные дни: лимиты, Internal Squads и другие параметры его текущего тарифа не пересчитываются под тариф приглашенного. В Web App и Telegram-меню подробные строки по периодам показываются только для legacy-режима или когда активен один period-тариф. Если включено несколько period-тарифов, Web App показывает сообщение, что бонус зависит от тарифа и периода оплаты друга, затем список тарифов с диапазонами "от N до N дней" и раскрытием подробностей по иконке вопроса. Telegram-меню в этом случае показывает только диапазоны по каждому тарифу. В legacy-режиме без JSON-каталога используются переменные `REFERRAL_BONUS_DAYS_*` и `REFEREE_BONUS_DAYS_*`; при продаже трафика без JSON-каталога Web App не показывает детализацию бонусов по месяцам. ## Привязка существующих подписок При запуске с активным JSON-каталогом бот заполняет активные подписки без `tariff_key`: - `tariff_key` получает `default_tariff`; - `tier_baseline_bytes` берется из текущего лимита подписки или из `monthly_gb` тарифа по умолчанию; - `topup_balance_bytes` становится `0`, если значение отсутствовало; - `period_start_at` очищается; - `effective_monthly_price_rub` берется из последнего успешного платежа или из цены тарифа по умолчанию. Это позволяет существующим активным подпискам отображаться и управляться в интерфейсах тарифов.