feat: premium squads inside one tariff

This commit is contained in:
3252a8
2026-05-10 12:51:53 +03:00
parent 6f9a87cf98
commit 990eba1e1e
34 changed files with 1898 additions and 135 deletions
+51 -7
View File
@@ -41,10 +41,6 @@ JSON-каталог может содержать несколько тариф
```json
{
"default_tariff": "standard",
"topup_packages_default": {
"rub": [{ "gb": 10, "price": 99 }],
"stars": [{ "gb": 10, "price": 2500 }]
},
"tariffs": [
{
"key": "standard",
@@ -55,6 +51,10 @@ JSON-каталог может содержать несколько тариф
"monthly_gb": 500,
"prices_rub": { "1": 150, "3": 400 },
"enabled_periods": [1, 3],
"topup_packages": {
"rub": [{ "gb": 10, "price": 99 }],
"stars": [{ "gb": 10, "price": 2500 }]
},
"enabled": true
}
]
@@ -66,12 +66,14 @@ JSON-каталог может содержать несколько тариф
| Поле | Назначение |
| --- | --- |
| `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[].premium_squad_uuids` | Internal Squads с отдельным premium-лимитом. Ноды для учета берутся автоматически из accessible nodes этих сквадов через API панели. |
| `tariffs[].premium_monthly_gb` | Отдельный месячный лимит трафика по premium-сквадам. `0` или отсутствие поля отключает отдельное ограничение. |
| `tariffs[].premium_topup_packages` | Пакеты докупки premium-трафика: `{ "gb": 10, "price": 99 }`. |
| `tariffs[].billing_model` | Модель тарифа: `period` или `traffic`. |
| `tariffs[].hwid_device_limit` | Базовый лимит HWID-устройств. `0` означает безлимит, отсутствие поля использует `USER_HWID_DEVICE_LIMIT`. |
| `tariffs[].hwid_device_packages` | Пакеты докупки устройств: `{ "count": 1, "price": 99 }`. |
@@ -84,7 +86,7 @@ JSON-каталог может содержать несколько тариф
| `prices_rub` | Цены периодов в рублях, ключ - количество месяцев. |
| `prices_stars` | Цены периодов в Telegram Stars. |
| `enabled_periods` | Периоды, доступные для покупки. |
| `topup_packages` | Пакеты докупки трафика именно для этого тарифа. |
| `topup_packages` | Пакеты докупки трафика именно для этого тарифа. Если поле не задано или списки пустые, докупка для тарифа не показывается в Web App и Telegram-боте. |
Для `traffic`-тарифа используются:
@@ -115,6 +117,48 @@ JSON-каталог может содержать несколько тариф
Докупка трафика для period-тарифа увеличивает `topup_balance_bytes` и общий `traffic_limit_bytes`. Этот баланс сохраняется в подписке и учитывается при продлении period-тарифа. В панель отправляется актуальный лимит, а доступ переводится в `ACTIVE`.
## Premium-сквады и отдельный лимит
Тариф может включать дополнительный набор Internal Squads с отдельным лимитом трафика. Это удобно для сценария “обычные серверы без изменений, premium-серверы ограничены отдельно”.
```json
{
"squad_uuids": ["standard-squad-uuid"],
"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_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-лимиту отправляются отдельно от обычного трафика на тех же процентах `TARIFF_TRAFFIC_WARNING_LEVELS`. Сообщение объясняет, что это именно premium-серверы, перечисляет серверы/сквады и ведет пользователя в докупку premium-трафика.
В Web App админке premium-сквады можно выбрать из выпадающего списка. Список берется из API Remnawave, поэтому UUID не нужно копировать вручную.
## Traffic-тарифы
`traffic` продает объем трафика без пользовательского срока действия.
@@ -197,7 +241,7 @@ limit_after = current_used + balance_after
| Поле | Назначение |
| --- | --- |
| `sale_mode` | Тип продажи: `subscription`, `traffic_package`, `topup`, `tariff_upgrade`, `hwid_devices`. |
| `sale_mode` | Тип продажи: `subscription`, `traffic_package`, `topup`, `premium_topup`, `tariff_upgrade`, `hwid_devices`. |
| `tariff_key` | Ключ тарифа, к которому относится платеж. |
| `purchased_gb` | Купленный объем GB для traffic-пакетов и докупки трафика. |
| `purchased_hwid_devices` | Количество устройств при докупке HWID. |