243 lines
18 KiB
Markdown
243 lines
18 KiB
Markdown
# Тарифы
|
||
|
||
Бот поддерживает два способа описания продаж:
|
||
|
||
- 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` берется из последнего успешного платежа или из цены тарифа по умолчанию.
|
||
|
||
Это позволяет существующим активным подпискам отображаться и управляться в интерфейсах тарифов.
|