From d15d58df3651e20f3306a153897cfe3c526b3e4d Mon Sep 17 00:00:00 2001
From: 3252a8 <3252a8@proton.me>
Date: Tue, 28 Apr 2026 10:47:59 +0300
Subject: [PATCH] docs: tariffs documentation
---
README.md | 10 ++--
docs/tariffs.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 129 insertions(+), 6 deletions(-)
create mode 100644 docs/tariffs.md
diff --git a/README.md b/README.md
index a3881a6..ea2bdbc 100644
--- a/README.md
+++ b/README.md
@@ -140,13 +140,11 @@ Remnawave Minishop — это Telegram-бот **и** Web App (Mini App) для
- Настройки подписок
+ Настройки тарифов
- Для каждого периода (1, 3, 6, 12 месяцев) можно настроить доступность и цены:
- - `1_MONTH_ENABLED`: `true` или `false`
- - `RUB_PRICE_1_MONTH`: Цена в рублях
- - `STARS_PRICE_1_MONTH`: Цена в Telegram Stars
- Аналогичные переменные есть для `3_MONTHS`, `6_MONTHS`, `12_MONTHS`.
+ Бот умеет продавать **подписку на срок** (1/3/6/12 мес.) или **пакеты трафика** (`TRAFFIC_PACKAGES=10:199,50:799`). Эти режимы взаимоисключающие — наличие непустой `TRAFFIC_PACKAGES` (или `STARS_TRAFFIC_PACKAGES`) автоматически переключает бот в режим продажи трафика.
+
+ Полное описание обоих режимов, переменных, что происходит при покупке, как ведут себя автопродление, реф-бонусы и триал — вынесено в [docs/tariffs.md](docs/tariffs.md).
diff --git a/docs/tariffs.md b/docs/tariffs.md
new file mode 100644
index 0000000..0543efb
--- /dev/null
+++ b/docs/tariffs.md
@@ -0,0 +1,125 @@
+# Тарифы
+
+В Minishop два режима тарификации, и они **взаимоисключающие**: бот в каждый момент времени продаёт либо подписку на срок (1 / 3 / 6 / 12 месяцев), либо пакеты трафика. Ниже — что именно настраивается, что происходит при покупке и как переключаться.
+
+## Краткое сравнение
+
+| | Подписка по времени | Пакеты трафика |
+| --- | --- | --- |
+| Что покупает пользователь | Период доступа (1/3/6/12 мес.) | Объём трафика в ГБ |
+| Срок действия в панели | До конца купленного периода | До 01.01.2099 (фактически бессрочно) |
+| Лимит трафика | Из `USER_TRAFFIC_LIMIT_GB`, сбрасывается по `USER_TRAFFIC_STRATEGY` | Кумулятивно: каждый платёж **прибавляет** ГБ к лимиту |
+| Стратегия сброса | `NO_RESET` / `WEEK` / `MONTH` / `DAY` | Принудительно `NO_RESET` |
+| Автопродление YooKassa | Поддерживается | Выключено |
+| Реферальные бонусы | Доступны (бонусные дни) | Не выдаются |
+| Триал | Доступен (`TRIAL_*`) | Доступен (включается отдельно) |
+
+Бот определяет режим по наличию переменных `TRAFFIC_PACKAGES` или `STARS_TRAFFIC_PACKAGES`: если хотя бы одна из них непустая — включается режим продажи трафика и переменные `*_MONTHS_ENABLED` / `RUB_PRICE_*` игнорируются.
+
+## Режим «Подписка по времени» (по умолчанию)
+
+Используется, когда `TRAFFIC_PACKAGES` и `STARS_TRAFFIC_PACKAGES` пусты.
+
+### Цены и периоды
+
+Для каждого периода настраивается доступность и две цены — в основной валюте (`DEFAULT_CURRENCY_SYMBOL`, по умолчанию RUB) и в Telegram Stars:
+
+| Переменная | Описание |
+| --- | --- |
+| `1_MONTH_ENABLED` / `3_MONTHS_ENABLED` / `6_MONTHS_ENABLED` / `12_MONTHS_ENABLED` | `true`/`false` — показывать ли период в выборе |
+| `RUB_PRICE_1_MONTH` / `RUB_PRICE_3_MONTHS` / `RUB_PRICE_6_MONTHS` / `RUB_PRICE_12_MONTHS` | Цена в рублях. Если не задана — кнопка не появится |
+| `STARS_PRICE_1_MONTH` / `STARS_PRICE_3_MONTHS` / `STARS_PRICE_6_MONTHS` / `STARS_PRICE_12_MONTHS` | Цена в Stars. Используется, когда оплата идёт через `STARS_ENABLED` |
+
+Если `RUB_PRICE_*` равно `0` — соответствующий период просто скрывается.
+
+### Трафик пользователя
+
+Лимит трафика и стратегия его сброса настраиваются глобально и применяются ко всем платным пользователям (в том числе при продлении):
+
+| Переменная | Описание |
+| --- | --- |
+| `USER_TRAFFIC_LIMIT_GB` | Лимит трафика в ГБ. `0` — безлимит |
+| `USER_TRAFFIC_STRATEGY` | Когда сбрасывается счётчик: `NO_RESET`, `DAY`, `WEEK`, `MONTH` |
+
+### Что происходит при оплате
+
+1. Если у пользователя ещё нет активной подписки — стартовая дата = «сейчас». Если есть — новая длительность прибавляется к её `end_date` (продление, а не перезапись).
+2. К итогу могут добавиться бонусные дни промокода (`promo_codes`) и реферальной программы (`REFERRAL_BONUS_DAYS_*`, `REFEREE_BONUS_DAYS_*`).
+3. В панели Remnawave у пользователя обновляются `expireAt`, `trafficLimitBytes` и `trafficLimitStrategy` под текущие настройки.
+
+### Автопродление (только YooKassa)
+
+Включается через `YOOKASSA_AUTOPAYMENTS_ENABLED=true`. Если включено и пользователь сохранил карту, то за `SUBSCRIPTION_NOTIFY_DAYS_BEFORE` дней до окончания бот пытается списать сумму, равную `RUB_PRICE_*` для длительности из последнего платежа. См. `YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING` для управления чекбоксом «сохранить карту».
+
+В режиме трафика автопродление принудительно выключается, даже если YooKassa-настройки разрешают его.
+
+### Уведомления
+
+Управляются `SUBSCRIPTION_NOTIFICATIONS_ENABLED`, `SUBSCRIPTION_NOTIFY_ON_EXPIRE`, `SUBSCRIPTION_NOTIFY_AFTER_EXPIRE`, `SUBSCRIPTION_NOTIFY_DAYS_BEFORE`. Работают по `end_date` подписки — поэтому в режиме трафика, где `end_date` всегда «2099», они эффективно не отправляются.
+
+## Режим «Пакеты трафика»
+
+Включается заданием переменной `TRAFFIC_PACKAGES` (и/или `STARS_TRAFFIC_PACKAGES`) в `.env`.
+
+### Формат
+
+```env
+# Пакеты в основной валюте (RUB), формат ":<цена>", через запятую
+TRAFFIC_PACKAGES=10:199,50:799,200:1999
+
+# Пакеты в Telegram Stars (опционально, если включён STARS_ENABLED)
+STARS_TRAFFIC_PACKAGES=10:2500,50:9000
+```
+
+Для дробных размеров допускается `0.5:99`, `1.5:299` и т. п. Цены допускают `0` (например, для теста).
+
+Если задана только `STARS_TRAFFIC_PACKAGES`, при выборе пакета бот покажет цену в звёздах и автоматически подменит символ валюты на `⭐`. Если фиатные провайдеры (`YOOKASSA_ENABLED`, `FREEKASSA_ENABLED`, `PLATEGA_ENABLED`, `SEVERPAY_ENABLED`, `CRYPTOPAY_ENABLED`) включены, но `TRAFFIC_PACKAGES` не задана — бот сочтёт это ошибкой конфигурации и не пропустит покупку.
+
+### Что происходит при оплате
+
+1. К текущему лимиту трафика пользователя в панели **прибавляется** купленный объём (`new_limit = current_limit + purchase_bytes`). Это работает и для существующих подписчиков.
+2. Стратегия сброса принудительно ставится в `NO_RESET` — иначе докупленные ГБ обнулялись бы по расписанию.
+3. `end_date` ставится в `2099-01-01 UTC` (или сохраняется более поздняя, если по какой-то причине уже была). Это нужно, чтобы Remnawave не помечал пользователя как просроченного.
+4. Реферальные бонусы и автопродление при таких платежах отключены: даже если в `.env` они настроены, в этом режиме бот их не применяет.
+5. Поле `subscription_duration_months` в БД и в CSV-выгрузке платежей в этом режиме интерпретируется как «количество ГБ», а не как месяцы. В UI и админке это учитывается, но имейте в виду при ручных запросах к БД.
+
+### Триал в режиме трафика
+
+Триал работает по той же логике, что и в режиме подписок: задаётся `TRIAL_ENABLED`, `TRIAL_DURATION_DAYS`, `TRIAL_TRAFFIC_LIMIT_GB`, `TRIAL_TRAFFIC_STRATEGY`. То есть пробный период всё ещё ограничен по времени, даже если основная продажа — это пакеты трафика.
+
+## Как переключиться между режимами
+
+1. В `.env` либо заполните `TRAFFIC_PACKAGES` (включится режим трафика), либо очистите её (включится режим подписок).
+2. Перезапустите контейнер: `docker compose up -d`.
+3. Существующие подписчики не теряют доступ:
+ - При переключении в режим трафика их `end_date` остаётся прежним до следующей покупки. Покупка пакета установит `end_date` = «2099» и переведёт стратегию в `NO_RESET`.
+ - При обратном переключении следующий платёж задаст обычный `end_date` от «сейчас» (или продлит существующий).
+
+> ⚠️ Одновременно совмещать продажу подписок и трафика бот **не умеет**. Проверка идёт по одному булевому флагу `traffic_sale_mode`.
+
+## Чеки для самозанятых (nalog.ru)
+
+Для каждого режима используется отдельный шаблон названия в чеке:
+
+| Переменная | Назначение |
+| --- | --- |
+| `NALOGO_RECEIPT_NAME_SUBSCRIPTION` | Шаблон для подписки. Поддерживает `{months}` |
+| `NALOGO_RECEIPT_NAME_TRAFFIC` | Шаблон для пакета трафика. Поддерживает `{gb}` |
+
+Пример: `NALOGO_RECEIPT_NAME_TRAFFIC=traffic package {gb} GB`.
+
+## Связанные переменные
+
+| Переменная | Описание |
+| --- | --- |
+| `DEFAULT_CURRENCY_SYMBOL` | Что показывается рядом с фиатной ценой (`RUB`, `USD`, `EUR`, …) |
+| `PAYMENT_METHODS_ORDER` | Порядок кнопок оплаты — общий для обоих режимов |
+| `STARS_ENABLED` | Без него Stars-цены не предложатся, даже если заданы |
+| `USER_HWID_DEVICE_LIMIT` | Лимит устройств — общий для обоих режимов, к тарификации не относится |
+
+## Где это всё в коде
+
+- Чтение и парсинг переменных: [config/settings.py](../config/settings.py) — `subscription_options`, `traffic_packages`, `stars_traffic_packages`, `traffic_sale_mode`.
+- Активация платежа: [bot/services/subscription_service.py](../bot/services/subscription_service.py) — `activate_subscription` (ветка по `sale_mode`) и `_activate_traffic_package`.
+- Выбор тарифа в боте: [bot/handlers/user/subscription/core.py](../bot/handlers/user/subscription/core.py) и [bot/handlers/user/subscription/payments_subscription.py](../bot/handlers/user/subscription/payments_subscription.py).
+- Web App: [bot/app/web/subscription_webapp.py](../bot/app/web/subscription_webapp.py) — поле `traffic_mode` в payload и логика отображения трафика.