Files
remnawave-minishop/docs/tariffs.md
T
2026-05-09 22:32:44 +03:00

243 lines
18 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` (по умолчанию `config/tariffs.json`);
- конфигурация через переменные `.env`, если JSON-файл отсутствует.
JSON-каталог может содержать несколько тарифов разных моделей: подписки на срок, пакеты трафика без срока действия, разные наборы Internal Squads, лимиты устройств и пакеты докупки. Пример формата: [config/tariffs.example.json](../config/tariffs.example.json).
## Управление через админку
Каталог тарифов можно настраивать из Web App админки: раздел **Система → Тарифы**. Админка читает и сохраняет файл из `TARIFFS_CONFIG_PATH`, валидирует данные той же моделью `TariffsConfig`, что и бот, и атомарно перезаписывает JSON только после успешной проверки.
В интерфейсе доступны:
- добавление, редактирование и удаление тарифов;
- включение и выключение тарифа на витрине;
- выбор тарифа по умолчанию;
- настройка `period`-тарифов: месячный лимит, периоды, RUB/Stars цены, пакеты докупки трафика;
- настройка `traffic`-тарифов: пакеты GB, RUB/Stars цены, курс конвертации;
- настройка Internal Squads, базового HWID-лимита и пакетов докупки устройств.
После сохранения изменения применяются к новым запросам 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-каталога
Минимальная структура:
```json
{
"default_tariff": "standard",
"topup_packages_default": {
"rub": [{ "gb": 10, "price": 99 }],
"stars": [{ "gb": 10, "price": 2500 }]
},
"tariffs": [
{
"key": "standard",
"names": { "ru": "Стандарт", "en": "Standard" },
"descriptions": { "ru": "Базовый набор серверов" },
"squad_uuids": ["uuid-1"],
"billing_model": "period",
"monthly_gb": 500,
"prices_rub": { "1": 150, "3": 400 },
"enabled_periods": [1, 3],
"enabled": true
}
]
}
```
Основные поля:
| Поле | Назначение |
| --- | --- |
| `default_tariff` | Тариф по умолчанию для первичного выбора и привязки активных подписок без `tariff_key`. |
| `topup_packages_default` | Пакеты докупки трафика для period-тарифов, у которых не задан `topup_packages`. |
| `tariffs[].key` | Стабильный ключ тарифа. Используется в платежах, подписках и смене тарифа. |
| `tariffs[].names` | Названия тарифа по языкам. |
| `tariffs[].descriptions` | Описания тарифа по языкам. |
| `tariffs[].enabled` | Доступность тарифа на витрине. |
| `tariffs[].squad_uuids` | Internal Squads Remnawave для пользователей тарифа. |
| `tariffs[].billing_model` | Модель тарифа: `period` или `traffic`. |
| `tariffs[].hwid_device_limit` | Базовый лимит HWID-устройств. `0` означает безлимит, отсутствие поля использует `USER_HWID_DEVICE_LIMIT`. |
| `tariffs[].hwid_device_packages` | Пакеты докупки устройств: `{ "count": 1, "price": 99 }`. |
Для `period`-тарифа также используются:
| Поле | Назначение |
| --- | --- |
| `monthly_gb` | Базовый месячный лимит трафика тарифа. `0` означает безлимит. |
| `prices_rub` | Цены периодов в рублях, ключ - количество месяцев. |
| `prices_stars` | Цены периодов в Telegram Stars. |
| `enabled_periods` | Периоды, доступные для покупки. |
| `topup_packages` | Пакеты докупки трафика именно для этого тарифа. |
Для `traffic`-тарифа используются:
| Поле | Назначение |
| --- | --- |
| `traffic_packages` | Пакеты трафика в GB для рублей и Telegram Stars. |
| `conversion_rate_rub_per_gb` | Курс для конвертации оставшихся дней period-тарифа в GB при смене на traffic-тариф. |
Если у traffic-тарифа нет RUB-пакетов, `conversion_rate_rub_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`.
## 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 }],
"stars": [{ "count": 1, "price": 2500 }]
}
}
```
Правила:
- `hwid_device_limit` хранит базовый лимит тарифа;
- `extra_hwid_devices` хранит количество докупленных устройств;
- эффективный лимит равен `hwid_device_limit + extra_hwid_devices`;
- базовый лимит `0` означает безлимит, в Remnawave отправляется `hwidDeviceLimit = 0`;
- при безлимитном базовом лимите докупка устройств не применяется;
- при смене тарифа базовый лимит берется из целевого тарифа, а докупленные устройства сохраняются;
- история докупок пишется в `hwid_device_purchases`;
- платеж хранит количество устройств в `payments.purchased_hwid_devices`.
Докупка устройств доступна в Web App через `/api/devices/topup-options` и `/api/payments`, а также в Telegram-боте из раздела устройств.
## Смена тарифа
Смена тарифа доступна для активных подписок с `tariff_key` и записывается в таблицу `tariff_changes`.
Варианты расчета:
| Переход | Поведение |
| --- | --- |
| `period -> period` | Остаток оплаченных дней оценивается по `effective_monthly_price_rub`, затем пересчитывается в дни целевого тарифа через месячную цену целевого тарифа. Количество дней округляется вниз. |
| `period -> period` с доплатой | Если целевой тариф дороже, может быть создан платеж `tariff_upgrade`; после оплаты применяется целевой тариф. |
| `period -> traffic` | Остаток оплаченных дней конвертируется в GB по `conversion_rate_rub_per_gb` или минимальной RUB-цене 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`, `tariff_upgrade`, `hwid_devices`. |
| `tariff_key` | Ключ тарифа, к которому относится платеж. |
| `purchased_gb` | Купленный объем GB для traffic-пакетов и докупки трафика. |
| `purchased_hwid_devices` | Количество устройств при докупке HWID. |
| `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`. Он не выбирает тариф из JSON-каталога.
Промокоды с бонусными днями применяются к покупке period-подписки. Реферальные бонусы по периодам также относятся к подпискам на срок; в режиме продажи трафика без 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` берется из последнего успешного платежа или из цены тарифа по умолчанию.
Это позволяет существующим активным подпискам отображаться и управляться в интерфейсах тарифов.