diff --git a/docs/tariffs.md b/docs/tariffs.md index 0543efb..7b6f1bf 100644 --- a/docs/tariffs.md +++ b/docs/tariffs.md @@ -1,125 +1,67 @@ -# Тарифы +# Тарифы 2.0 -В Minishop два режима тарификации, и они **взаимоисключающие**: бот в каждый момент времени продаёт либо подписку на срок (1 / 3 / 6 / 12 месяцев), либо пакеты трафика. Ниже — что именно настраивается, что происходит при покупке и как переключаться. +Бот поддерживает каталог тарифов в JSON-файле. Путь задается через `TARIFFS_CONFIG_PATH`, по умолчанию `config/tariffs.json`. -## Краткое сравнение +Если файл отсутствует, включается legacy fallback: используются старые `.env` поля `RUB_PRICE_*`, `STARS_PRICE_*`, `USER_TRAFFIC_LIMIT_GB`, `USER_SQUAD_UUIDS`, а также старый режим `TRAFFIC_PACKAGES`. -| | Подписка по времени | Пакеты трафика | -| --- | --- | --- | -| Что покупает пользователь | Период доступа (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_*` игнорируются. +См. пример: [`config/tariffs.example.json`](../config/tariffs.example.json). -## Режим «Подписка по времени» (по умолчанию) +Основные поля: -Используется, когда `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` | +| `default_tariff` | Тариф для миграции существующих подписок и выбора по умолчанию | +| `topup_packages_default` | Пакеты докупки для period-тарифов без собственных пакетов | +| `tariffs[].billing_model` | `period` или `traffic` | +| `tariffs[].squad_uuids` | Internal squads Remnawave для тарифа | +| `prices_rub` / `prices_stars` | Цены period-тарифов по месяцам | +| `traffic_packages` | Пакеты GB для traffic-тарифов | -Если `RUB_PRICE_*` равно `0` — соответствующий период просто скрывается. +## Period-Тариф -### Трафик пользователя +Period-тариф продает доступ на срок и личный 30-дневный лимит трафика. -Лимит трафика и стратегия его сброса настраиваются глобально и применяются ко всем платным пользователям (в том числе при продлении): +- `monthly_gb` превращается в `tier_baseline_bytes`. +- Докупленные пакеты хранятся в `topup_balance_bytes`. +- В Remnawave пушится `trafficLimitBytes = tier_baseline_bytes + topup_balance_bytes`. +- При продлении до окончания подписки дата 30-дневного сброса не меняется. +- При покупке после лапса новый период начинается с момента оплаты. -| Переменная | Описание | -| --- | --- | -| `USER_TRAFFIC_LIMIT_GB` | Лимит трафика в ГБ. `0` — безлимит | -| `USER_TRAFFIC_STRATEGY` | Когда сбрасывается счётчик: `NO_RESET`, `DAY`, `WEEK`, `MONTH` | +## Traffic-Тариф -### Что происходит при оплате +Traffic-тариф продает GB без срока действия. -1. Если у пользователя ещё нет активной подписки — стартовая дата = «сейчас». Если есть — новая длительность прибавляется к её `end_date` (продление, а не перезапись). -2. К итогу могут добавиться бонусные дни промокода (`promo_codes`) и реферальной программы (`REFERRAL_BONUS_DAYS_*`, `REFEREE_BONUS_DAYS_*`). -3. В панели Remnawave у пользователя обновляются `expireAt`, `trafficLimitBytes` и `trafficLimitStrategy` под текущие настройки. +- `end_date` технически ставится в `2099-01-01 UTC`. +- `period_start_at = NULL`. +- `trafficLimitStrategy = NO_RESET`. +- Новая покупка добавляет GB к фактическому остатку: `limit = used + remaining + purchased`. +- Доступ ограничивается только при исчерпании купленного трафика. -### Автопродление (только YooKassa) +## Смена Тарифа -Включается через `YOOKASSA_AUTOPAYMENTS_ENABLED=true`. Если включено и пользователь сохранил карту, то за `SUBSCRIPTION_NOTIFY_DAYS_BEFORE` дней до окончания бот пытается списать сумму, равную `RUB_PRICE_*` для длительности из последнего платежа. См. `YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING` для управления чекбоксом «сохранить карту». +Смена пишется в `tariff_changes`. -В режиме трафика автопродление принудительно выключается, даже если YooKassa-настройки разрешают его. +- `period -> period`: расчет идет от `effective_monthly_price_rub`; пересчет дней использует `floor`. +- `period -> traffic`: остаток оплаченных дней конвертируется в GB по `conversion_rate_rub_per_gb` или минимальной цене GB в RUB-пакетах. +- `traffic -> period`: пользователь покупает период, а остаток GB сохраняется как топ-ап поверх нового тарифа. -### Уведомления +## Платежи -Управляются `SUBSCRIPTION_NOTIFICATIONS_ENABLED`, `SUBSCRIPTION_NOTIFY_ON_EXPIRE`, `SUBSCRIPTION_NOTIFY_AFTER_EXPIRE`, `SUBSCRIPTION_NOTIFY_DAYS_BEFORE`. Работают по `end_date` подписки — поэтому в режиме трафика, где `end_date` всегда «2099», они эффективно не отправляются. +Новые платежи сохраняют: -## Режим «Пакеты трафика» +- `sale_mode`: `subscription`, `traffic_package`, `topup`, `tariff_upgrade`; +- `tariff_key`; +- `purchased_gb` для GB-покупок. -Включается заданием переменной `TRAFFIC_PACKAGES` (и/или `STARS_TRAFFIC_PACKAGES`) в `.env`. +Legacy поле `subscription_duration_months` остается для совместимости. -### Формат +## Воркеры -```env -# Пакеты в основной валюте (RUB), формат ":<цена>", через запятую -TRAFFIC_PACKAGES=10:199,50:799,200:1999 +`TariffTrafficWorker` запускается, только если активен `tariffs.json`. -# Пакеты в 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 и логика отображения трафика. +- Раз в несколько минут проверяет 30-дневные сбросы. +- Отправляет/дедуплицирует уровни предупреждений 80/95/100 через `traffic_warnings`. +- При 100% удаляет пользователя из squad-ов тарифа и ставит `is_throttled`. +- Возвращает пользователя в squad-ы, когда лимит снова больше использованного трафика.