Files
remnawave-minishop/docs/features/tariffs.md
T

371 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Тарифы
Бот поддерживает два способа описания продаж:
- 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).
Коротко по моделям:
- `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-лимита и пакетов докупки устройств.
После сохранения изменения применяются к новым запросам 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`. |
| `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-докупки. |
Для `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` | Периоды, доступные для покупки. |
| `topup_packages` | Пакеты докупки трафика именно для этого тарифа. Если поле не задано или списки пустые, докупка для тарифа не показывается в Web App и Telegram-боте. |
Для `traffic`-тарифа используются:
| Поле | Назначение |
| --- | --- |
| `traffic_packages` | Пакеты трафика в GB по валютам каталога и Telegram 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` защищает от микроплатежей в конце периода;
- при продлении подписки докупленные устройства не продлеваются автоматически: старая докупка действует до прежнего `end_date`, а для нового срока создается отдельная `hwid_devices_renewal`-покупка;
- `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`.
Промокоды с бонусными днями применяются к покупке period-подписки.
Реферальные бонусы за оплату в JSON-каталоге задаются прямо в period-тарифе рядом с ценами периода: `referral_bonus_days_inviter` для пригласившего и `referral_bonus_days_referee` для приглашенного. Ключи этих словарей - месяцы периода (`"1"`, `"3"`, `"6"`, `"12"` или любые другие периоды тарифа, например `"2"`, `"4"`, `"8"`, `"16"`). Для `traffic`-тарифов такие бонусы не применяются.
Если приглашенный покупает один тариф, а пригласивший находится на другом, размер бонуса берется из тарифа и периода, который купил приглашенный. При этом подписка пригласившего только продлевается на бонусные дни: лимиты, 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` берется из последнего успешного платежа или из цены тарифа по умолчанию.
Это позволяет существующим активным подпискам отображаться и управляться в интерфейсах тарифов.