diff --git a/README.md b/README.md
index 621cbcd..290c333 100644
--- a/README.md
+++ b/README.md
@@ -1,564 +1,82 @@
# Remnawave Minishop
-Remnawave Minishop — это Telegram-бот **и** Web App (Mini App) для автоматизации продажи и управления подписками панели **Remnawave**. Бот закрывает сценарий покупки, продления и работы с поддержкой прямо в чате, а Web App в едином интерфейсе показывает ссылку подключения, остаток времени, трафик, оплату и устройства, поддерживая вход через Telegram Mini Apps `initData`, новый Telegram OAuth / OpenID Connect Login и одноразовый код по email. Под капотом — интеграция с API Remnawave для управления пользователями и подписками и набор платёжных шлюзов для приёма платежей.
+Remnawave Minishop - Telegram-бот и Web App (Mini App) для продажи и управления подписками Remnawave. Бот обрабатывает регистрацию, оплату, продление, пробный период, промокоды, рефералов и поддержку в чате. Web App показывает ссылку подключения, срок действия, трафик, оплату, устройства и вход по Telegram Mini Apps `initData`, Telegram OAuth / OpenID Connect и одноразовому email-коду.
-> 🍴 **Это глубоко переработанный форк [kavore/remnawave-tg-shop](https://github.com/kavore/remnawave-tg-shop).** Здесь добавлены полноценный Web App / Mini App, вход по email и многое другое. Возможна миграция.
+Проект является переработанным форком [kavore/remnawave-tg-shop](https://github.com/kavore/remnawave-tg-shop). Для переноса данных из прежнего стека используйте [инструкцию по миграции](docs/migration-to-minishop.md).
-## ✨ Ключевые возможности
+## Возможности
-### Для пользователей:
-- **Регистрация и выбор языка:** Поддержка русского и английского языков.
-- **Просмотр подписки:** Пользователи могут видеть статус своей подписки, дату окончания и ссылку на конфигурацию.
-- **Web App (Mini App):** отдельный веб-интерфейс для просмотра ссылки подключения, остатка времени и оплаты подписки.
-- **Вход по email:** вход и регистрация в Web App по коду из письма, а также привязка email и Telegram к одному аккаунту.
-- **Мои устройства:** Опциональный раздел для просмотра и отключения подключенных устройств (активируется через переменную `MY_DEVICES_SECTION_ENABLED`).
-- **Пробная подписка:** Система пробных подписок для новых пользователей (активируется вручную по кнопке).
-- **Промокоды:** Возможность применять промокоды для получения скидок или бонусных дней.
-- **Реферальная программа:** Пользователи могут приглашать друзей и получать за это бонусные дни подписки.
-- **Оплата:** Поддержка оплаты через YooKassa, FreeKassa (REST API), Platega, SeverPay, CryptoPay и Telegram Stars.
+Для пользователей:
-### Для администраторов:
-- **Защищенная админ-панель:** Доступ только для администраторов, указанных в `ADMIN_IDS`.
-- **Статистика:** Просмотр статистики использования бота (общее количество пользователей, забаненные, активные подписки), недавние платежи и статус синхронизации с панелью.
-- **Управление пользователями:** Блокировка/разблокировка пользователей, просмотр списка забаненных и детальной информации о пользователе.
-- **Рассылка:** Отправка сообщений всем пользователям, пользователям с активной или истекшей подпиской.
-- **Управление промокодами:** Создание и просмотр промокодов.
-- **Синхронизация с панелью:** Ручной запуск синхронизации пользователей и подписок с панелью Remnawave.
-- **Логи действий:** Просмотр логов всех действий пользователей.
+- регистрация с выбором русского или английского языка;
+- просмотр статуса подписки, даты окончания, ссылки подключения и трафика;
+- покупка подписок, пакетов трафика, докупка трафика и устройств по настроенному каталогу тарифов;
+- Web App / Mini App с входом через Telegram или email;
+- пробный период, промокоды и реферальная программа;
+- оплата через YooKassa, FreeKassa, Platega, SeverPay, CryptoPay и Telegram Stars;
+- раздел "Мои устройства" при включенном `MY_DEVICES_SECTION_ENABLED`.
-## 🚀 Технологии
+Для администраторов:
-- **Python 3.12**
-- **Aiogram 3.x:** Асинхронный фреймворк для Telegram ботов.
-- **aiohttp:** Для запуска веб-сервера (вебхуки).
-- **SQLAlchemy 2.x & asyncpg:** Асинхронная работа с базой данных PostgreSQL.
-- **YooKassa, FreeKassa API, Platega, SeverPay, aiocryptopay:** Интеграции с платежными системами.
-- **Pydantic:** Для управления настройками из `.env` файла.
-- **Docker & Docker Compose:** Для контейнеризации и развертывания.
+- админ-панель для пользователей из `ADMIN_IDS`;
+- статистика пользователей, подписок, платежей и синхронизации с Remnawave;
+- блокировка пользователей, рассылки, промокоды и логи действий;
+- ручная синхронизация пользователей и подписок с панелью.
-## ⚙️ Установка и запуск
+## Документация
-### Предварительные требования
+- [Настройка окружения](docs/configuration.md) - основные переменные `.env`, платежи, Remnawave, пробный период и секреты.
+- [Тарифы](docs/tariffs.md) - каталог тарифов, period- и traffic-модели, докупки, смена тарифа, HWID-лимиты и обработка трафика.
+- [Web App / Mini App](docs/webapp.md) - отдельный порт, домен, Telegram OAuth, email-вход и реферальные ссылки.
+- [Развертывание](docs/deployment.md) - Docker Compose, reverse proxy, Nginx, Caddy, вебхуки и запуск из образа.
+- [Миграция с remnawave-tg-shop](docs/migration-to-minishop.md) - перенос данных из прежнего стека.
-- Установленные Docker и Docker Compose.
-- Рабочая панель Remnawave.
-- Токен Telegram-бота.
-- Данные для подключения к платежным системам (YooKassa, CryptoPay и т.д.).
+## Быстрый старт
-### Шаги установки
+Требования:
-1. **Клонируйте репозиторий:**
- ```bash
- git clone https://github.com/3252a8/remnawave-minishop
- cd remnawave-minishop
- ```
-
-2. **Создайте и настройте файл `.env`:**
- Скопируйте `.env.example` в `.env` и заполните своими данными.
- ```bash
- cp .env.example .env
- nano .env
- ```
- Ниже перечислены ключевые переменные.
-
-
- Основные настройки
-
- | Переменная | Описание | Пример |
- | --- | --- | --- |
- | `BOT_TOKEN` | **Обязательно.** Токен вашего Telegram-бота. | `1234567890:ABC-DEF1234ghIkl-zyx57W2v1u123ew11` |
- | `ADMIN_IDS` | **Обязательно.** ID администраторов в Telegram через запятую. | `12345678,98765432` |
- | `DEFAULT_LANGUAGE` | Язык по умолчанию для новых пользователей. | `ru` |
- | `SUPPORT_LINK` | (Опционально) Ссылка на поддержку. | `https://t.me/your_support` |
- | `PRIVACY_POLICY_URL` | (Опционально) Ссылка на политику конфиденциальности, показывается внизу Web App. | `https://example.com/privacy` |
- | `USER_AGREEMENT_URL` | (Опционально) Ссылка на пользовательское соглашение, показывается внизу Web App. | `https://example.com/agreement` |
- | `SUBSCRIPTION_MINI_APP_URL` | (Опционально) Публичный URL Mini App для показа подписки. Если задан, кнопка «Моя подписка» откроет Web App. | `https://app.domain.com/` |
- | `WEBAPP_ENABLED` | Включить Web App в том же контейнере, но на отдельном порту. | `true` |
- | `WEBAPP_SERVER_PORT` | Внутренний порт Web App. | `8081` |
- | `WEBAPP_TITLE` | Заголовок Web App. | `Моя подписка` |
- | `WEBAPP_PRIMARY_COLOR` | Основной цвет Web App. | `#00fe7a` |
- | `WEBAPP_LOGO_URL` | (Опционально) URL логотипа Web App. Если значение пустое, логотип не показывается вообще; если задано, он отображается в шапке и на экране логина. | `https://domain.com/logo.png` |
- | `TELEGRAM_OAUTH_CLIENT_ID` | Client ID для нового Telegram OAuth / OpenID Connect Login из BotFather. Если пусто, используется числовой ID из `BOT_TOKEN`. | `1234567890` |
- | `TELEGRAM_OAUTH_CLIENT_SECRET` | Client Secret из BotFather для Telegram OAuth Authorization Code Flow. | `tg_oauth_secret` |
- | `TELEGRAM_OAUTH_REQUEST_ACCESS` | Дополнительные разрешения Telegram Login через запятую: `write`, `phone`. Пустое значение запрашивает только OpenID profile. | `write` |
- | `SMTP_HOST` | SMTP-сервер для кодов входа по email. Для Brevo: `smtp-relay.brevo.com`. | `smtp-relay.brevo.com` |
- | `SMTP_PORT` | SMTP-порт. Для Brevo обычно используется 587 с STARTTLS. | `587` |
- | `SMTP_FALLBACK_PORTS` | Дополнительные SMTP-порты через запятую. Пробуются после `SMTP_PORT`; порт `465` автоматически используется через SSL. Для Brevo удобно оставить `2525,465`. | `2525,465` |
- | `SMTP_TIMEOUT_SECONDS` | Timeout для каждой SMTP-попытки подключения и отправки. | `30` |
- | `SMTP_USERNAME` / `SMTP_PASSWORD` | Логин и SMTP key/password из Brevo. Если не заданы вместе с `SMTP_FROM_EMAIL`, вход по email скрывается. | `user@smtp-brevo.com` |
- | `SMTP_FROM_EMAIL` / `SMTP_FROM_NAME` | Подтвержденный отправитель и отображаемое имя отправителя для писем с кодом. | `no-reply@example.com` |
- | `EMAIL_CODE_TTL_SECONDS` | Срок действия кода подтверждения email. | `600` |
- | `EMAIL_CODE_RESEND_SECONDS` | Минимальная пауза между отправками кода на один email. | `60` |
- | `EMAIL_CODE_MAX_ATTEMPTS` | Максимум попыток на один конкретный код. | `5` |
- | `BRUTE_FORCE_MAX_FAILURES` | Максимум неудачных попыток в окне защиты от перебора. | `5` |
- | `BRUTE_FORCE_WINDOW_SECONDS` | Длительность окна, в котором считаются неудачные попытки. | `900` |
- | `BRUTE_FORCE_LOCK_SECONDS` | Время временной блокировки после превышения лимита. | `1800` |
- | `MY_DEVICES_SECTION_ENABLED` | Включить раздел «Мои устройства» в меню подписки (`true`/`false`). | `false` |
- | `WEBAPP_SESSION_SECRET` | (Опционально) HMAC-секрет для подписи сессий Web App. Если пусто — генерируется при старте, но тогда сессии станут невалидными после перезапуска контейнера. Для прода задайте явно. | `см. раздел «Генерация секретов»` |
- | `WEBHOOK_SECRET_TOKEN` | (Опционально) Secret token для проверки подлинности вебхуков Telegram. Если пусто — генерируется при старте. Для прода задайте явно, чтобы значение пережило рестарт. | `см. раздел «Генерация секретов»` |
- | `REQUIRED_CHANNEL_ID` | (Опционально) ID канала, на который пользователь должен подписаться перед использованием. Оставьте пустым, если проверка не нужна. | `-1001234567890` |
- | `REQUIRED_CHANNEL_LINK` | (Опционально) Публичная ссылка или invite на канал для кнопки «Проверить подписку». | `https://t.me/your_channel` |
-
-
-
- Настройки платежей и вебхуков
-
- | Переменная | Описание |
- | --- | --- |
- | `WEBHOOK_BASE_URL` | **Обязательно.** Базовый URL для вебхуков, например `https://your.domain.com`. |
- | `WEB_SERVER_HOST` | Хост для веб-сервера (по умолчанию `0.0.0.0`). |
- | `WEB_SERVER_PORT` | Порт для веб-сервера (по умолчанию `8080`). |
- | `WEBAPP_SERVER_HOST` | Хост отдельного веб-сервера Mini App (по умолчанию `0.0.0.0`). |
- | `WEBAPP_SERVER_PORT` | Порт отдельного веб-сервера Mini App (по умолчанию `8081`). |
- | `PAYMENT_METHODS_ORDER` | (Опционально) Порядок отображения кнопок оплаты через запятую. Поддерживаемые ключи: `severpay`, `freekassa`, `platega`, `yookassa`, `stars`, `cryptopay`. Первый будет сверху. |
- | `YOOKASSA_ENABLED` | Включить/выключить YooKassa (`true`/`false`). |
- | `YOOKASSA_SHOP_ID` | ID вашего магазина в YooKassa. |
- | `YOOKASSA_SECRET_KEY` | Секретный ключ магазина YooKassa. |
- | `YOOKASSA_AUTOPAYMENTS_ENABLED` | Включить автопродление (сохранение карт, автосписания, управление способами оплаты). |
- | `YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING` | Требовать обязательную привязку карты при оплате с автосписанием. Установите `false`, чтобы пользователю показывался чекбокс «Сохранить карту». |
- | `NALOGO_INN` | ИНН для авторизации в nalog.ru (самозанятый). |
- | `NALOGO_PASSWORD` | Пароль для авторизации в nalog.ru (самозанятый). |
- | `CRYPTOPAY_ENABLED` | Включить/выключить CryptoPay (`true`/`false`). |
- | `CRYPTOPAY_TOKEN` | Токен из вашего CryptoPay App. |
- | `FREEKASSA_ENABLED` | Включить/выключить FreeKassa (`true`/`false`). |
- | `FREEKASSA_MERCHANT_ID` | ID вашего магазина в FreeKassa. |
- | `FREEKASSA_API_KEY` | API-ключ для запросов к FreeKassa REST API. |
- | `FREEKASSA_SECOND_SECRET` | Секретное слово №2 — используется для проверки уведомлений от FreeKassa. |
- | `FREEKASSA_PAYMENT_URL` | (Опционально, legacy SCI) Базовый URL платёжной формы FreeKassa. По умолчанию `https://pay.freekassa.ru/`. |
- | `FREEKASSA_PAYMENT_IP` | Внешний IP вашего сервера, который будет передаваться в запрос оплаты. |
- | `FREEKASSA_PAYMENT_METHOD_ID` | ID метода оплаты через магазин FreeKassa. По умолчанию `44`. |
- | `STARS_ENABLED` | Включить/выключить Telegram Stars (`true`/`false`). |
- | `PLATEGA_ENABLED` | Включить/выключить Platega (`true`/`false`). |
- | `PLATEGA_MERCHANT_ID` | MerchantId из личного кабинета Platega. |
- | `PLATEGA_SECRET` | API секрет для запросов Platega. |
- | `PLATEGA_PAYMENT_METHOD` | ID способа оплаты (2 — SBP QR, 10 — РФ карты, 12 — международные карты, 13 — crypto). |
- | `PLATEGA_RETURN_URL` | (Опционально) URL редиректа после успешной оплаты. По умолчанию ссылка на бота. |
- | `PLATEGA_FAILED_URL` | (Опционально) URL редиректа при ошибке/отмене. По умолчанию как `PLATEGA_RETURN_URL`. |
- | `SEVERPAY_ENABLED` | Включить/выключить SeverPay (`true`/`false`). |
- | `SEVERPAY_MID` | MID магазина в SeverPay. |
- | `SEVERPAY_TOKEN` | Секрет/токен для подписи запросов SeverPay. |
- | `SEVERPAY_BASE_URL` | (Опционально) Базовый URL API SeverPay. По умолчанию `https://severpay.io/api/merchant`. |
- | `SEVERPAY_RETURN_URL` | (Опционально) URL редиректа после оплаты (по умолчанию ссылка на бота). |
- | `SEVERPAY_LIFETIME_MINUTES` | (Опционально) Время жизни платежной ссылки в минутах (30–4320). |
-
-
-
- Настройки тарифов
-
- Бот умеет продавать **подписку на срок** (1/3/6/12 мес.) или **пакеты трафика** (`TRAFFIC_PACKAGES=10:199,50:799`). Эти режимы взаимоисключающие — наличие непустой `TRAFFIC_PACKAGES` (или `STARS_TRAFFIC_PACKAGES`) автоматически переключает бот в режим продажи трафика.
-
- Полное описание обоих режимов, переменных, что происходит при покупке, как ведут себя автопродление, реф-бонусы и триал — вынесено в [docs/tariffs.md](docs/tariffs.md).
-
-
-
- Настройки панели Remnawave
-
- | Переменная | Описание |
- | --- | --- |
- | `PANEL_API_URL` | URL API вашей панели Remnawave. |
- | `PANEL_API_KEY` | API ключ для доступа к панели. |
- | `PANEL_WEBHOOK_SECRET`| Секретный ключ для проверки вебхуков от панели. |
- | `USER_SQUAD_UUIDS` | ID отрядов для новых пользователей. |
- | `USER_EXTERNAL_SQUAD_UUID` | Опционально. UUID внешнего отряда (External Squad) из [документации Remnawave](https://docs.rw/api), куда автоматически добавляются новые пользователи. |
- | `USER_TRAFFIC_LIMIT_GB`| Лимит трафика в ГБ (0 - безлимит). |
- | `USER_HWID_DEVICE_LIMIT`| Лимит устройств (HWID) для новых пользователей (0 - безлимит). |
-
- > Раздел "Мои устройства" становится доступен пользователям только при включении `MY_DEVICES_SECTION_ENABLED`. Значение лимита устройств при создании записей в панели берётся из `USER_HWID_DEVICE_LIMIT`.
-
-
-
- Настройки пробного периода
-
- | Переменная | Описание |
- | --- | --- |
- | `TRIAL_ENABLED` | Включить/выключить пробный период (`true`/`false`). |
- | `TRIAL_DURATION_DAYS`| Длительность пробного периода в днях. |
- | `TRIAL_TRAFFIC_LIMIT_GB`| Лимит трафика для пробного периода в ГБ. |
-
-
-3. **Сгенерируйте секреты (рекомендуется):**
-
- Переменные `WEBAPP_SESSION_SECRET` и `WEBHOOK_SECRET_TOKEN` могут быть пустыми — тогда они автоматически сгенерируются при каждом старте контейнера. Однако в проде это означает, что после рестарта все сессии Web App станут невалидными, а Telegram придётся перерегистрировать webhook. Поэтому для боевого окружения задайте оба значения вручную.
-
- Сгенерировать криптостойкие значения можно одной из команд:
-
- ```bash
- # вариант 1 — Python (есть в любом окружении с Python 3)
- python -c "import secrets; print(secrets.token_urlsafe(32))"
-
- # вариант 2 — openssl
- openssl rand -base64 32 | tr -d '=+/' | cut -c1-43
-
- # вариант 3 — /dev/urandom (Linux/macOS)
- head -c 32 /dev/urandom | base64 | tr -d '=+/' | cut -c1-43
- ```
-
- Запустите команду дважды и подставьте полученные значения в `.env`:
-
- ```env
- WEBAPP_SESSION_SECRET=<первое_значение>
- WEBHOOK_SECRET_TOKEN=<второе_значение>
- ```
-
- > ⚠️ Не используйте одно и то же значение для обеих переменных и не коммитьте `.env` в git.
-
-4. **Запустите контейнеры:**
- ```bash
- docker compose up -d
- ```
- Эта команда соберёт образ из `Dockerfile` (Python + сборка Web App на Node) и запустит сервис в фоновом режиме. Если нужен запуск из готового образа GHCR — используйте `docker-compose-remote-server.yml`.
-
-5. **Настройка вебхуков (Обязательно):**
- Вебхуки являются **обязательным** компонентом для работы бота, так как они используются для получения уведомлений от платежных систем (YooKassa, FreeKassa, CryptoPay, Platega, SeverPay) и панели Remnawave.
-
- Вам понадобится обратный прокси (например, Nginx) для обработки HTTPS-трафика и перенаправления запросов на контейнер с ботом.
-
- **Пути для перенаправления:**
- - `https://<ваш_домен>/webhook/yookassa` → `http://remnawave-minishop:/webhook/yookassa`
- - `https://<ваш_домен>/webhook/freekassa` → `http://remnawave-minishop:/webhook/freekassa`
- - `https://<ваш_домен>/webhook/platega` → `http://remnawave-minishop:/webhook/platega`
- - `https://<ваш_домен>/webhook/severpay` → `http://remnawave-minishop:/webhook/severpay`
- - `https://<ваш_домен>/webhook/cryptopay` → `http://remnawave-minishop:/webhook/cryptopay`
- - `https://<ваш_домен>/webhook/panel` → `http://remnawave-minishop:/webhook/panel`
- - **Для Telegram:** Бот автоматически установит вебхук, если в `.env` указан `WEBHOOK_BASE_URL`. Путь будет `https://<ваш_домен>/`.
-
- Где `remnawave-minishop` — это имя сервиса из `docker-compose.yml`, а `` — порт, указанный в `.env`.
-
- **Отдельный порт Web App:**
- - `https://<домен_web_app>/` → `http://remnawave-minishop:/`
-
- Web App не должен проксироваться на `WEB_SERVER_PORT`: этот порт оставьте для Telegram, платежных и Remnawave webhooks.
-
-6. **Просмотр логов:**
- ```bash
- docker compose logs -f remnawave-minishop
- ```
-
- > 💡 Если включена проверка подписки на канал (`REQUIRED_CHANNEL_ID`), добавьте бота администратором в этот канал. Пользователь увидит кнопку «Проверить подписку», и, после первого успешного подтверждения, дальнейшие действия блокироваться не будут.
-
-### Настройка Web App / Mini App
-
-Web App запускается в том же контейнере, что и бот, но слушает отдельный порт `WEBAPP_SERVER_PORT` (по умолчанию `8081`). Внутри Telegram пользователь авторизуется через Telegram Mini Apps `initData`; если страницу открыть вне Telegram, используется новый Telegram OAuth / OpenID Connect Authorization Code Flow с PKCE, callback `/auth/telegram/callback`, `nonce` и серверной проверкой `id_token` по JWKS Telegram. Старый Login Widget больше не используется в UI. Также доступен вход по email через одноразовый код из письма, если настроен SMTP: после отправки письма код вводится в отдельном модальном окне подтверждения. После успешного входа страница обновляет данные сразу, без сообщений боту.
-
-1. Укажите в `.env` публичный URL Web App и порт:
-
- ```env
- WEBAPP_ENABLED=True
- WEBAPP_SERVER_HOST=0.0.0.0
- WEBAPP_SERVER_PORT=8081
- SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/
- WEBAPP_TITLE="Моя подписка"
- WEBAPP_PRIMARY_COLOR="#00fe7a"
- WEBAPP_LOGO_URL=
- TELEGRAM_OAUTH_CLIENT_ID=
- TELEGRAM_OAUTH_CLIENT_SECRET=
- TELEGRAM_OAUTH_REQUEST_ACCESS=write
- SMTP_HOST=smtp-relay.brevo.com
- SMTP_PORT=587
- SMTP_FALLBACK_PORTS=2525,465
- SMTP_USERNAME=
- SMTP_PASSWORD=
- SMTP_FROM_EMAIL=no-reply@domain.com
- ```
-
- Если основной порт не отвечает, отправка письма автоматически пробует fallback-порты из `SMTP_FALLBACK_PORTS`. Для Brevo типичная схема: `587` с STARTTLS, затем `2525`, затем `465` через SSL.
-
-2. Убедитесь, что `docker-compose.yml` публикует порт Web App:
-
- ```yaml
- ports:
- - 127.0.0.1:8080:8080
- - 127.0.0.1:${WEBAPP_SERVER_PORT:-8081}:${WEBAPP_SERVER_PORT:-8081}
- ```
-
-3. Проксируйте отдельный домен или location на порт Web App:
-
- ```nginx
- upstream remnawave-minishop-webapp {
- server remnawave-minishop:8081;
- }
-
- server {
- server_name app.domain.com;
- listen 443 ssl;
- http2 on;
-
- ssl_certificate "/etc/nginx/ssl/app_fullchain.pem";
- ssl_certificate_key "/etc/nginx/ssl/app_privkey.key";
-
- location / {
- proxy_pass http://remnawave-minishop-webapp;
- proxy_http_version 1.1;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }
- }
- ```
-
-4. В BotFather настройте бота, Mini App и Telegram OAuth Login:
-
- - `@BotFather` → `/mybots` → выберите бота.
- - **Bot Settings → Domain**: укажите домен без протокола и пути, например `app.domain.com`.
- - **Bot Settings → Mini Apps**: задайте URL Mini App, например `https://app.domain.com/`.
- - **Bot Settings → Web Login**: если BotFather показывает кнопку `Switch to OpenID Connect Login`, нажмите ее.
- - **Bot Settings → Web Login**: скопируйте Client ID и Client Secret в `TELEGRAM_OAUTH_CLIENT_ID` и `TELEGRAM_OAUTH_CLIENT_SECRET`.
- - **Web Login → Allowed URLs**: добавьте:
- `https://app.domain.com/`
- `https://app.domain.com/auth/telegram/callback`
- - `TELEGRAM_OAUTH_REQUEST_ACCESS=write` разрешает боту написать пользователю после логина. Если дополнительные разрешения не нужны, оставьте переменную пустой.
-
-5. Перезапустите контейнер:
-
- ```bash
- docker compose up -d --build
- ```
-
-После этого кнопка «Личный кабинет» в меню бота откроет Web App. Рядом доступна кнопка «Бот-меню» для открытия расширенного интерфейса в чате без команды `/tg`, но основной сценарий управления подпиской удобнее проходить в личном кабинете. Web App показывает текущую ссылку подключения, остаток времени, трафик, оплату и блок аккаунта. Пользователь может привязать email к Telegram-аккаунту через код из письма или привязать Telegram к email-аккаунту через Telegram OAuth Login. После привязки вход работает обоими способами.
-
-Реферальные ссылки доступны в двух форматах: Telegram deep-link `https://t.me/?start=ref_u` и Web App ссылка с query-параметром `ref=u`. Web App учитывает `ref`, `start`, `start_param` и Telegram Mini Apps `start_param`, сохраняет найденный параметр до авторизации и передаёт его в Telegram OAuth и email-вход, чтобы регистрация корректно привязалась к пригласившему.
-
-Для email-регистраций пользователь в панели Remnawave создается с анонимным username вида `em_`; email добавляется в описание пользователя панели и, если API панели принимает поле email, передается отдельным полем. Для Telegram-регистраций сохраняется существующая схема `tg_`.
-
-## Подробная инструкция для развертывания на сервере с панелью Remnawave
-
-### 1. Клонирование репозитория
+- Docker и Docker Compose;
+- рабочая панель Remnawave;
+- токен Telegram-бота;
+- параметры хотя бы одного платежного провайдера.
```bash
-git clone https://github.com/3252a8/remnawave-minishop && cd remnawave-minishop
+git clone https://github.com/3252a8/remnawave-minishop
+cd remnawave-minishop
+cp .env.example .env
+nano .env
+docker compose up -d --build
+docker compose logs -f remnawave-minishop
```
-### 2. Настройка переменных окружения
+Минимально заполните в `.env`:
+
+- `BOT_TOKEN` - токен Telegram-бота;
+- `ADMIN_IDS` - Telegram ID администраторов через запятую;
+- `WEBHOOK_BASE_URL` - публичный URL вебхуков;
+- `PANEL_API_URL`, `PANEL_API_KEY`, `PANEL_WEBHOOK_SECRET` - доступ к Remnawave;
+- `USER_SQUAD_UUIDS` - Internal Squads для пользователей;
+- настройки платежного провайдера;
+- `SUBSCRIPTION_MINI_APP_URL`, если используется Web App.
+
+Для каталога тарифов используется `TARIFFS_CONFIG_PATH` со значением по умолчанию `config/tariffs.json`. Пример формата лежит в [config/tariffs.example.json](config/tariffs.example.json), подробности - в [docs/tariffs.md](docs/tariffs.md).
+
+## Полезные команды
```bash
-cp .env.example .env && nano .env
-```
+# Локальная сборка и запуск
+docker compose up -d --build
-**Обязательные поля для заполнения:**
-- `BOT_TOKEN` - токен телеграмм бота, например, `234567890:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`
-- `ADMIN_IDS` - TG ID администраторов, например, `12345678,98765432` и т.д. (через запятую без пробелов)
-- `WEBHOOK_BASE_URL` - Обязательно. Базовый URL для вебхуков, например `https://webhook.domain.com`
-- `PANEL_API_URL` - URL API вашей панели Remnawave (например, `http://remnawave:3000/api` или `https://panel.domain.com/api`)
-- `PANEL_API_KEY` - API ключ для доступа к панели (генерируется из UI-интерфейса панели)
-- `PANEL_WEBHOOK_SECRET` - Секретный ключ для проверки вебхуков от панели (берётся из `.env` самой панели)
-- `USER_SQUAD_UUIDS` - ID отрядов для новых пользователей
+# Логи приложения
+docker compose logs -f remnawave-minishop
-### 3. Настройка Reverse Proxy (Nginx)
+# Запуск с Caddy
+docker compose -f docker-compose-caddy.yml up -d --build
-Перейдите в директорию конфигурации Nginx панели Remnawave:
-
-```bash
-cd /opt/remnawave/nginx && nano nginx.conf
-```
-
-Добавьте в `nginx.conf` следующую конфигурацию:
-
-```nginx
-upstream remnawave-minishop {
- server remnawave-minishop:8080;
-}
-
-map $http_upgrade $connection_upgrade {
- default upgrade;
- "" close;
-}
-
-server {
- server_name webhook.domain.com; # Домен для отправки Webhook'ов
- listen 443 ssl;
- http2 on;
-
- ssl_certificate "/etc/nginx/ssl/webhook_fullchain.pem";
- ssl_certificate_key "/etc/nginx/ssl/webhook_privkey.key";
- ssl_trusted_certificate "/etc/nginx/ssl/webhook_fullchain.pem";
-
- proxy_http_version 1.1;
- proxy_set_header Host $host;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection $connection_upgrade;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_set_header X-Forwarded-Host $host;
- proxy_set_header X-Forwarded-Port $server_port;
- proxy_send_timeout 60s;
- proxy_read_timeout 60s;
- proxy_intercept_errors on;
- error_page 400 404 500 502 @redirect;
-
- location / {
- proxy_pass http://remnawave-minishop$request_uri;
- }
-
- location @redirect {
- return 404;
- }
-}
-```
-
-### 4. Выпуск SSL-сертификата для домена webhook
-
-Убедитесь, что установлены необходимые компоненты, а также откройте 80 порт:
-
-```bash
-sudo apt-get install cron socat
-curl https://get.acme.sh | sh -s email=EMAIL && source ~/.bashrc
-ufw allow 80/tcp && ufw reload
-```
-
-Выпустите сертификат:
-
-```bash
-acme.sh --set-default-ca --server letsencrypt
-acme.sh --issue --standalone -d 'webhook.domain.com' \
- --key-file /opt/remnawave/nginx/webhook_privkey.key \
- --fullchain-file /opt/remnawave/nginx/webhook_fullchain.pem
-```
-
-### 5. Добавление сертификатов в Docker Compose Nginx
-
-Отредактируйте `docker-compose.yml` панели Nginx:
-
-```bash
-cd /opt/remnawave/nginx && nano docker-compose.yml
-```
-
-Добавьте две строки в секцию `volumes`:
-
-```yaml
-services:
- remnawave-nginx:
- image: nginx:1.26
- container_name: remnawave-nginx
- hostname: remnawave-nginx
- volumes:
- - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- - ./fullchain.pem:/etc/nginx/ssl/fullchain.pem:ro
- - ./privkey.key:/etc/nginx/ssl/privkey.key:ro
- - ./subdomain_fullchain.pem:/etc/nginx/ssl/subdomain_fullchain.pem:ro
- - ./subdomain_privkey.key:/etc/nginx/ssl/subdomain_privkey.key:ro
- - ./webhook_fullchain.pem:/etc/nginx/ssl/webhook_fullchain.pem:ro # Добавьте эту строку
- - ./webhook_privkey.key:/etc/nginx/ssl/webhook_privkey.key:ro # Добавьте эту строку
- restart: always
- ports:
- - '0.0.0.0:443:443'
- networks:
- - remnawave-network
-
-networks:
- remnawave-network:
- name: remnawave-network
- driver: bridge
- external: true
-```
-
-### 6. Запуск бота и перезапуск Nginx
-
-Запустите бота:
-
-```bash
-cd /root/remnawave-minishop && docker compose up -d && docker compose logs -f -t
-```
-
-Перезапустите Nginx:
-
-```bash
-cd /opt/remnawave/nginx && docker compose down && docker compose up -d && docker compose logs -f -t
-```
-
-## 🐳 Docker
-
-Файлы `Dockerfile` и `docker-compose.yml` уже настроены для локальной сборки и запуска проекта.
-
-Если нужен запуск из готового образа, используйте `docker-compose-remote-server.yml` как шаблон и укажите свой `image:` вместо локальной сборки. По умолчанию он тянет `ghcr.io/3252a8/remnawave-minishop:latest`, а для закрепления версии можно задать `IMAGE_TAG=3.1.0`.
-
-В GHCR доступны теги `3.1.0` и `latest`.
-
-Чтобы использовать сохранённый образ, можно запустить:
-```bash
+# Запуск из готового образа
IMAGE_TAG=3.1.0 docker compose -f docker-compose-remote-server.yml up -d
```
-### Вариант с Caddy
+## Поддержка
-Если нужен reverse proxy на Caddy, используйте `docker-compose-caddy.yml` вместе с `Caddyfile`. Это удобный вариант, когда хочется, чтобы Caddy сам выпускал TLS-сертификаты и проксировал и webhook'и, и Mini App без ручной настройки Nginx.
-
-В этой схеме:
-- Caddy публикует наружу `80` и `443`.
-- Бот остается доступным только внутри docker-сети.
-- `WEBHOOK_BASE_URL` должен указывать на домен вебхуков, а `SUBSCRIPTION_MINI_APP_URL` - на домен Mini App.
-
-Пример `Caddyfile`:
-
-```caddyfile
-webhook.domain.com {
- encode zstd gzip
- reverse_proxy remnawave-minishop:{$WEB_SERVER_PORT:8080}
-}
-
-app.domain.com {
- encode zstd gzip
- reverse_proxy remnawave-minishop:{$WEBAPP_SERVER_PORT:8081}
-}
-```
-
-Что нужно поменять под себя:
-- заменить `webhook.domain.com` и `app.domain.com` на свои домены;
-- убедиться, что в `.env` заданы `WEBHOOK_BASE_URL=https://webhook.domain.com` и `SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/`;
-- при необходимости скорректировать `WEB_SERVER_PORT` и `WEBAPP_SERVER_PORT`, если они отличаются от стандартных `8080` и `8081`.
-- в BotFather укажите домен Mini App через `/setdomain`, чтобы он совпадал с `SUBSCRIPTION_MINI_APP_URL`.
-
-Запуск:
-
-```bash
-docker compose -f docker-compose-caddy.yml up -d --build
-```
-
-После этого Caddy сам выпустит сертификаты и будет проксировать webhook'и на порт `8080`, а Mini App - на `8081`.
-
-## 🔄 Миграция с `remnawave-tg-shop` на `remnawave-minishop`
-
-Короткая инструкция, автоматический запуск helper'а из `raw` и ручной вариант переноса вынесены в отдельный документ: [docs/migration-to-minishop.md](docs/migration-to-minishop.md).
-
-## 📁 Структура проекта
-
-```
-.
-├── bot/
-│ ├── app/ # Сборка приложения (фабрики, контроллеры, Web App)
-│ │ ├── controllers/ # Запуск Aiogram dispatcher
-│ │ ├── factories/ # Фабрики сервисов (платежи, панель и т.д.)
-│ │ └── web/ # Web App / Mini App (сервер, аутентификация, шаблоны)
-│ ├── filters/ # Пользовательские фильтры Aiogram
-│ ├── handlers/ # Обработчики сообщений и колбэков (admin/, user/)
-│ ├── keyboards/ # Клавиатуры
-│ ├── middlewares/ # Промежуточные слои (i18n, проверка бана и т.д.)
-│ ├── services/ # Бизнес-логика (платёжные шлюзы, API панели, email и т.д.)
-│ ├── states/ # Состояния FSM (admin/user)
-│ ├── utils/ # Вспомогательные утилиты
-│ ├── routers.py # Регистрация всех роутеров Aiogram
-│ └── main_bot.py # Основная логика бота
-├── config/
-│ └── settings.py # Настройки Pydantic
-├── db/
-│ ├── dal/ # Слой доступа к данным (DAL)
-│ ├── database_setup.py # Настройка БД и подключения
-│ ├── migrator.py # Миграции схемы при старте
-│ └── models.py # Модели SQLAlchemy
-├── locales/ # Файлы локализации (ru.json, en.json)
-├── scripts/ # Сборка JS Web App, обновление копий Telegram JS и миграционный helper
-├── tests/ # Pytest-тесты
-├── .env.example # Пример файла с переменными окружения
-├── Caddyfile # Пример конфигурации Caddy
-├── Dockerfile # Multi-stage сборка (Python + Node для Web App)
-├── docker-compose.yml # Локальная сборка и запуск
-├── docker-compose-caddy.yml # Запуск с Caddy в качестве reverse proxy
-├── docker-compose-remote-server.yml # Запуск из готового образа GHCR
-├── package.json # Frontend-зависимости (Tailwind, esbuild) и сборка Web App
-├── requirements.txt # Зависимости Python
-└── main.py # Точка входа в приложение
-```
-
-## ❤️ Поддержка
- Crypto: `USDT/Other ERC-20 0xeD506D44aae634fEc0E01C8835744fBedb7B2a44 (Ethereum/Polygon/Gnosis)`
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 0000000..0605819
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,135 @@
+# Настройка окружения
+
+Конфигурация читается из `.env`. За основу удобно взять `.env.example` и заполнить значения под свою панель, домены и платежные провайдеры.
+
+```bash
+cp .env.example .env
+nano .env
+```
+
+## Основные настройки
+
+| Переменная | Назначение |
+| --- | --- |
+| `BOT_TOKEN` | Токен Telegram-бота. |
+| `ADMIN_IDS` | Telegram ID администраторов через запятую. |
+| `DEFAULT_LANGUAGE` | Язык по умолчанию для пользователей: `ru` или `en`. |
+| `SUPPORT_LINK` | Ссылка на поддержку. |
+| `PRIVACY_POLICY_URL` | Ссылка на политику конфиденциальности в Web App. |
+| `USER_AGREEMENT_URL` | Ссылка на пользовательское соглашение в Web App. |
+| `REQUIRED_CHANNEL_ID` | ID канала, на который пользователь должен подписаться перед использованием. |
+| `REQUIRED_CHANNEL_LINK` | Ссылка на канал для кнопки проверки подписки. |
+
+Если используется проверка подписки на канал, добавьте бота администратором в этот канал. После первой успешной проверки пользователь продолжает работу без повторной блокировки действий.
+
+## Remnawave
+
+| Переменная | Назначение |
+| --- | --- |
+| `PANEL_API_URL` | URL API панели Remnawave, например `https://panel.domain.com/api`. |
+| `PANEL_API_KEY` | API-ключ панели. |
+| `PANEL_WEBHOOK_SECRET` | Секрет для проверки вебхуков Remnawave. |
+| `USER_SQUAD_UUIDS` | Internal Squads, в которые добавляются пользователи. |
+| `USER_EXTERNAL_SQUAD_UUID` | External Squad для пользователей, если он используется. |
+| `USER_TRAFFIC_LIMIT_GB` | Лимит трафика для режима без JSON-каталога тарифов. `0` означает безлимит. |
+| `USER_TRAFFIC_STRATEGY` | Стратегия лимита трафика для режима без JSON-каталога тарифов. |
+| `USER_HWID_DEVICE_LIMIT` | Лимит HWID-устройств для пользователей. `0` означает безлимит. |
+
+При включенном каталоге тарифов значения `squad_uuids`, `monthly_gb`, `traffic_packages` и `hwid_device_limit` берутся из выбранного тарифа. Подробно это описано в [tariffs.md](tariffs.md).
+
+## Платежи
+
+| Переменная | Назначение |
+| --- | --- |
+| `PAYMENT_METHODS_ORDER` | Порядок кнопок оплаты через запятую: `severpay`, `freekassa`, `platega`, `yookassa`, `stars`, `cryptopay`. |
+| `YOOKASSA_ENABLED` | Включает YooKassa. |
+| `YOOKASSA_SHOP_ID` / `YOOKASSA_SECRET_KEY` | Данные магазина YooKassa. |
+| `YOOKASSA_AUTOPAYMENTS_ENABLED` | Включает автопродление через сохраненные способы оплаты YooKassa. |
+| `YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING` | Управляет обязательной привязкой карты при оплате. |
+| `FREEKASSA_ENABLED` | Включает FreeKassa. |
+| `FREEKASSA_MERCHANT_ID` / `FREEKASSA_API_KEY` / `FREEKASSA_SECOND_SECRET` | Данные FreeKassa и секрет уведомлений. |
+| `FREEKASSA_PAYMENT_IP` | Внешний IP сервера для запроса оплаты FreeKassa. |
+| `FREEKASSA_PAYMENT_METHOD_ID` | ID метода оплаты FreeKassa. |
+| `PLATEGA_ENABLED` | Включает Platega. |
+| `PLATEGA_MERCHANT_ID` / `PLATEGA_SECRET` | Данные Platega. |
+| `PLATEGA_PAYMENT_METHOD` | ID способа оплаты Platega. |
+| `PLATEGA_RETURN_URL` / `PLATEGA_FAILED_URL` | URL возврата после оплаты или ошибки. |
+| `SEVERPAY_ENABLED` | Включает SeverPay. |
+| `SEVERPAY_MID` / `SEVERPAY_TOKEN` | Данные SeverPay. |
+| `SEVERPAY_BASE_URL` | Базовый URL API SeverPay. |
+| `SEVERPAY_RETURN_URL` | URL возврата после оплаты. |
+| `SEVERPAY_LIFETIME_MINUTES` | Время жизни платежной ссылки. |
+| `CRYPTOPAY_ENABLED` | Включает CryptoPay. |
+| `CRYPTOPAY_TOKEN` | Токен CryptoPay App. |
+| `STARS_ENABLED` | Включает Telegram Stars. |
+
+Вебхуки платежных систем должны проксироваться на порт `WEB_SERVER_PORT`. Примеры маршрутов есть в [deployment.md](deployment.md).
+
+## Тарифы
+
+| Переменная | Назначение |
+| --- | --- |
+| `TARIFFS_CONFIG_PATH` | Путь к JSON-каталогу тарифов. По умолчанию `config/tariffs.json`. |
+| `TARIFF_TRAFFIC_WARNING_LEVELS` | Проценты предупреждений по трафику, например `85,90,95`. |
+| `RUB_PRICE_1_MONTH`, `RUB_PRICE_3_MONTHS`, `RUB_PRICE_6_MONTHS`, `RUB_PRICE_12_MONTHS` | Цены подписок в рублях для режима без JSON-каталога. |
+| `STARS_PRICE_1_MONTH`, `STARS_PRICE_3_MONTHS`, `STARS_PRICE_6_MONTHS`, `STARS_PRICE_12_MONTHS` | Цены подписок в Telegram Stars для режима без JSON-каталога. |
+| `1_MONTH_ENABLED`, `3_MONTHS_ENABLED`, `6_MONTHS_ENABLED`, `12_MONTHS_ENABLED` | Доступность периодов подписки для режима без JSON-каталога. |
+| `TRAFFIC_PACKAGES` | Пакеты трафика в рублях для режима без JSON-каталога, например `10:199,50:799`. |
+| `STARS_TRAFFIC_PACKAGES` | Пакеты трафика в Telegram Stars для режима без JSON-каталога. |
+
+Если файл из `TARIFFS_CONFIG_PATH` существует, бот использует каталог тарифов. Если файла нет, применяется конфигурация из переменных `.env`.
+
+## Web App и email-вход
+
+| Переменная | Назначение |
+| --- | --- |
+| `WEBAPP_ENABLED` | Включает Web App в том же контейнере. |
+| `WEBAPP_SERVER_HOST` / `WEBAPP_SERVER_PORT` | Хост и порт Web App. По умолчанию порт `8081`. |
+| `SUBSCRIPTION_MINI_APP_URL` | Публичный URL Web App. |
+| `WEBAPP_TITLE` | Заголовок Web App. |
+| `WEBAPP_PRIMARY_COLOR` | Основной цвет интерфейса. |
+| `WEBAPP_LOGO_URL` | URL логотипа Web App. |
+| `WEBAPP_SESSION_SECRET` | HMAC-секрет сессий Web App. |
+| `TELEGRAM_OAUTH_CLIENT_ID` / `TELEGRAM_OAUTH_CLIENT_SECRET` | Данные Telegram OAuth / OpenID Connect из BotFather. |
+| `TELEGRAM_OAUTH_REQUEST_ACCESS` | Дополнительные разрешения Telegram Login, например `write`. |
+| `SMTP_HOST`, `SMTP_PORT`, `SMTP_FALLBACK_PORTS` | SMTP-подключение для email-кодов. |
+| `SMTP_USERNAME` / `SMTP_PASSWORD` | Логин и пароль или SMTP key. |
+| `SMTP_FROM_EMAIL` / `SMTP_FROM_NAME` | Отправитель писем с кодом. |
+| `EMAIL_CODE_TTL_SECONDS` | Срок действия email-кода. |
+| `EMAIL_CODE_RESEND_SECONDS` | Пауза перед повторной отправкой кода. |
+| `EMAIL_CODE_MAX_ATTEMPTS` | Максимум попыток ввода одного кода. |
+| `BRUTE_FORCE_MAX_FAILURES` | Количество неудачных попыток до временной блокировки. |
+| `BRUTE_FORCE_WINDOW_SECONDS` | Окно учета неудачных попыток. |
+| `BRUTE_FORCE_LOCK_SECONDS` | Длительность временной блокировки. |
+| `MY_DEVICES_SECTION_ENABLED` | Показывает раздел "Мои устройства" и включает API устройств. |
+
+Настройка домена, BotFather и callback URL описана в [webapp.md](webapp.md).
+
+## Пробный период
+
+| Переменная | Назначение |
+| --- | --- |
+| `TRIAL_ENABLED` | Включает пробный период. |
+| `TRIAL_DURATION_DAYS` | Длительность пробного периода в днях. |
+| `TRIAL_TRAFFIC_LIMIT_GB` | Лимит трафика пробного периода. `0` означает безлимит. |
+| `TRIAL_TRAFFIC_STRATEGY` | Стратегия лимита трафика пробного периода. |
+
+## Реферальная программа
+
+| Переменная | Назначение |
+| --- | --- |
+| `REFERRAL_WELCOME_BONUS_DAYS` | Бонус пользователю, который пришел по реферальной ссылке. |
+| `REFERRAL_ONE_BONUS_PER_REFEREE` | Ограничивает бонусы одним успешным платежом приглашенного пользователя. |
+| `REFERRAL_BONUS_DAYS_*` | Бонусные дни пригласившему по периодам подписки. |
+| `REFEREE_BONUS_DAYS_*` | Бонусные дни приглашенному по периодам подписки. |
+| `LEGACY_REFS` | Разрешает ссылки формата `ref_`. |
+
+В режиме продажи трафика без JSON-каталога бонусы по периодам не отображаются, потому что покупка не привязана к сроку подписки.
+
+## Секреты
+
+`WEBAPP_SESSION_SECRET` и `WEBHOOK_SECRET_TOKEN` могут генерироваться при старте, но для рабочего окружения их лучше задать явно. Иначе после рестарта сессии Web App станут невалидными, а Telegram webhook будет установлен с другим secret token.
+
+```bash
+openssl rand -hex 32
+```
diff --git a/docs/deployment.md b/docs/deployment.md
new file mode 100644
index 0000000..8e7bf9b
--- /dev/null
+++ b/docs/deployment.md
@@ -0,0 +1,176 @@
+# Развертывание
+
+Документ описывает запуск через Docker Compose, маршруты вебхуков и варианты reverse proxy. Перед запуском заполните `.env` по [configuration.md](configuration.md).
+
+## Docker Compose
+
+Локальная сборка:
+
+```bash
+docker compose up -d --build
+docker compose logs -f remnawave-minishop
+```
+
+Запуск из готового образа:
+
+```bash
+IMAGE_TAG=3.1.0 docker compose -f docker-compose-remote-server.yml up -d
+```
+
+`docker-compose-remote-server.yml` можно использовать как шаблон и заменить `image:` на нужный образ. По умолчанию используется `ghcr.io/3252a8/remnawave-minishop:latest`.
+
+## Порты
+
+| Порт | Назначение |
+| --- | --- |
+| `WEB_SERVER_PORT` (`8080`) | Telegram webhook, платежные вебхуки, Remnawave webhook. |
+| `WEBAPP_SERVER_PORT` (`8081`) | Web App / Mini App. |
+
+Web App не должен проксироваться на `WEB_SERVER_PORT`.
+
+## Маршруты вебхуков
+
+Проксируйте платежные и системные вебхуки на `WEB_SERVER_PORT`:
+
+- `https:///webhook/yookassa` -> `http://remnawave-minishop:/webhook/yookassa`;
+- `https:///webhook/freekassa` -> `http://remnawave-minishop:/webhook/freekassa`;
+- `https:///webhook/platega` -> `http://remnawave-minishop:/webhook/platega`;
+- `https:///webhook/severpay` -> `http://remnawave-minishop:/webhook/severpay`;
+- `https:///webhook/cryptopay` -> `http://remnawave-minishop:/webhook/cryptopay`;
+- `https:///webhook/panel` -> `http://remnawave-minishop:/webhook/panel`.
+
+Telegram webhook устанавливается приложением, если задан `WEBHOOK_BASE_URL`. Путь формируется как `https:///`.
+
+## Nginx рядом с Remnawave
+
+Пример upstream и server-блока для домена вебхуков:
+
+```nginx
+upstream remnawave-minishop {
+ server remnawave-minishop:8080;
+}
+
+map $http_upgrade $connection_upgrade {
+ default upgrade;
+ "" close;
+}
+
+server {
+ server_name webhook.domain.com;
+ listen 443 ssl;
+ http2 on;
+
+ ssl_certificate "/etc/nginx/ssl/webhook_fullchain.pem";
+ ssl_certificate_key "/etc/nginx/ssl/webhook_privkey.key";
+ ssl_trusted_certificate "/etc/nginx/ssl/webhook_fullchain.pem";
+
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Port $server_port;
+ proxy_send_timeout 60s;
+ proxy_read_timeout 60s;
+ proxy_intercept_errors on;
+ error_page 400 404 500 502 @redirect;
+
+ location / {
+ proxy_pass http://remnawave-minishop$request_uri;
+ }
+
+ location @redirect {
+ return 404;
+ }
+}
+```
+
+Для Web App используйте отдельный upstream на `WEBAPP_SERVER_PORT`; пример есть в [webapp.md](webapp.md).
+
+## SSL для домена вебхуков
+
+Пример выпуска сертификата через `acme.sh`:
+
+```bash
+sudo apt-get install cron socat
+curl https://get.acme.sh | sh -s email=EMAIL
+source ~/.bashrc
+ufw allow 80/tcp
+ufw reload
+
+acme.sh --set-default-ca --server letsencrypt
+acme.sh --issue --standalone -d 'webhook.domain.com' \
+ --key-file /opt/remnawave/nginx/webhook_privkey.key \
+ --fullchain-file /opt/remnawave/nginx/webhook_fullchain.pem
+```
+
+Если Nginx панели Remnawave запускается в Docker, добавьте сертификаты в `volumes` сервиса Nginx:
+
+```yaml
+services:
+ remnawave-nginx:
+ volumes:
+ - ./webhook_fullchain.pem:/etc/nginx/ssl/webhook_fullchain.pem:ro
+ - ./webhook_privkey.key:/etc/nginx/ssl/webhook_privkey.key:ro
+```
+
+После изменения конфигурации перезапустите Nginx:
+
+```bash
+cd /opt/remnawave/nginx
+docker compose down
+docker compose up -d
+docker compose logs -f -t
+```
+
+## Caddy
+
+Для схемы с Caddy используйте `docker-compose-caddy.yml` и `Caddyfile`. Caddy публикует наружу `80` и `443`, выпускает TLS-сертификаты и проксирует вебхуки и Web App на разные внутренние порты.
+
+Пример `Caddyfile`:
+
+```caddyfile
+webhook.domain.com {
+ encode zstd gzip
+ reverse_proxy remnawave-minishop:{$WEB_SERVER_PORT:8080}
+}
+
+app.domain.com {
+ encode zstd gzip
+ reverse_proxy remnawave-minishop:{$WEBAPP_SERVER_PORT:8081}
+}
+```
+
+В `.env` укажите:
+
+```env
+WEBHOOK_BASE_URL=https://webhook.domain.com
+SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/
+```
+
+Запуск:
+
+```bash
+docker compose -f docker-compose-caddy.yml up -d --build
+```
+
+В BotFather укажите домен Mini App через настройки домена, чтобы он совпадал с `SUBSCRIPTION_MINI_APP_URL`.
+
+## Проверка
+
+```bash
+docker compose ps
+docker compose logs -f remnawave-minishop
+```
+
+Проверьте:
+
+- бот отвечает в Telegram;
+- Telegram webhook установлен без ошибок в логах;
+- платежные вебхуки доходят до приложения;
+- Remnawave webhook проходит проверку `PANEL_WEBHOOK_SECRET`;
+- Web App открывается по домену из `SUBSCRIPTION_MINI_APP_URL`;
+- в BotFather разрешены URL Web App и callback.
diff --git a/docs/tariffs.md b/docs/tariffs.md
index fc3fc30..0d6bebf 100644
--- a/docs/tariffs.md
+++ b/docs/tariffs.md
@@ -1,99 +1,227 @@
-# Тарифы 2.0
+# Тарифы
-Бот поддерживает каталог тарифов в JSON-файле. Путь задается через `TARIFFS_CONFIG_PATH`, по умолчанию используется `config/tariffs.json`.
+Бот поддерживает два способа описания продаж:
-Если файл отсутствует, включается legacy-режим: используются старые `.env` поля `RUB_PRICE_*`, `STARS_PRICE_*`, `USER_TRAFFIC_LIMIT_GB`, `USER_SQUAD_UUIDS`, а также старый режим продажи трафика через `TRAFFIC_PACKAGES`.
+- JSON-каталог тарифов из `TARIFFS_CONFIG_PATH` (по умолчанию `config/tariffs.json`);
+- конфигурация через переменные `.env`, если JSON-файл отсутствует.
-## Конфиг
+JSON-каталог может содержать несколько тарифов разных моделей: подписки на срок, пакеты трафика без срока действия, разные наборы Internal Squads, лимиты устройств и пакеты докупки. Пример формата: [config/tariffs.example.json](../config/tariffs.example.json).
-Пример конфига: [`config/tariffs.example.json`](../config/tariffs.example.json).
+## Как выбирается режим
+
+Если файл из `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` | Тариф по умолчанию для миграции существующих подписок и первичного выбора |
-| `topup_packages_default` | Пакеты докупки трафика для period-тарифов без собственных пакетов |
-| `tariffs[].billing_model` | Модель тарификации: `period` или `traffic` |
-| `tariffs[].squad_uuids` | Internal Squads Remnawave, которые получает пользователь на этом тарифе |
-| `tariffs[].hwid_device_limit` | Базовый лимит HWID-устройств для тарифа; `0` означает без ограничений, отсутствие поля использует `USER_HWID_DEVICE_LIMIT` |
-| `tariffs[].hwid_device_packages` | Пакеты докупки HWID-устройств, например `{ "count": 1, "price": 99 }` |
-| `prices_rub` / `prices_stars` | Цены period-тарифов по месяцам |
-| `traffic_packages` | Пакеты GB для traffic-тарифов |
+| `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-Тариф
+Для `period`-тарифа также используются:
-Period-тариф продает доступ на срок и лимит трафика с календарным ежемесячным сбросом.
+| Поле | Назначение |
+| --- | --- |
+| `monthly_gb` | Базовый месячный лимит трафика тарифа. `0` означает безлимит. |
+| `prices_rub` | Цены периодов в рублях, ключ - количество месяцев. |
+| `prices_stars` | Цены периодов в Telegram Stars. |
+| `enabled_periods` | Периоды, доступные для покупки. |
+| `topup_packages` | Пакеты докупки трафика именно для этого тарифа. |
-- `monthly_gb` превращается в `tier_baseline_bytes`.
-- Докупленные пакеты трафика хранятся в `topup_balance_bytes`.
-- В Remnawave отправляется `trafficLimitBytes = tier_baseline_bytes + topup_balance_bytes`.
-- Для period-тарифов бот выставляет `trafficLimitStrategy = MONTH`, а дальнейший сброс выполняет сама панель.
-- Дата сброса больше не считается в боте.
-- Если покупка или продление были в середине месяца, сброс все равно произойдет по правилам панели для `MONTH`.
-- Бот меняет только лимиты в GB и следит за предупреждениями на основе текущего usage из панели.
+Для `traffic`-тарифа используются:
-## Traffic-Тариф
+| Поле | Назначение |
+| --- | --- |
+| `traffic_packages` | Пакеты трафика в GB для рублей и Telegram Stars. |
+| `conversion_rate_rub_per_gb` | Курс для конвертации оставшихся дней period-тарифа в GB при смене на traffic-тариф. |
-Traffic-тариф продает GB без срока действия.
+Если у traffic-тарифа нет RUB-пакетов, `conversion_rate_rub_per_gb` обязателен.
-- `end_date` технически ставится в `2099-01-01 UTC`.
-- `period_start_at = NULL`.
-- `trafficLimitStrategy = NO_RESET`.
-- Новая покупка добавляет GB к фактическому остатку: `limit = used + remaining + purchased`.
-- Доступ ограничивается только при исчерпании купленного трафика.
+## Period-тарифы
-## HWID-Устройства
+`period` продает доступ на срок с месячным лимитом трафика.
-Тарифы поддерживают лимит HWID-устройств и платную докупку устройств.
+При покупке или продлении:
-- При покупке или продлении тарифа бот отправляет в Remnawave `hwidDeviceLimit`.
-- `subscriptions.hwid_device_limit` хранит базовый лимит тарифа.
-- `subscriptions.extra_hwid_devices` хранит количество докупленных устройств.
-- Эффективный лимит равен `hwid_device_limit + extra_hwid_devices`.
-- Если базовый лимит равен `0`, это безлимит; докупка не нужна, а в панель отправляется `0`.
-- Докупка устройств использует `sale_mode=hwid_devices`.
-- Количество купленных устройств сохраняется в `payments.purchased_hwid_devices`.
-- История докупок пишется в `hwid_device_purchases`.
-- Докупка доступна в Web App через `/api/devices/topup-options` и `/api/payments`.
-- Докупка доступна в Telegram-боте из раздела устройств.
-- При смене тарифа базовый HWID-лимит берется из нового тарифа, а уже докупленные устройства сохраняются.
+- дата начала берется от текущей активной подписки, если она еще действует, иначе от текущего времени;
+- срок считается календарными месяцами через `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-тарифов.
-Смена тарифа пишется в `tariff_changes`.
+Докупка трафика для period-тарифа увеличивает `topup_balance_bytes` и общий `traffic_limit_bytes`. Этот баланс сохраняется в подписке и учитывается при продлении period-тарифа. В панель отправляется актуальный лимит, а доступ переводится в `ACTIVE`.
-- `period -> period`: расчет идет от `effective_monthly_price_rub`; пересчет дней использует `floor`.
-- `period -> traffic`: остаток оплаченных дней конвертируется в GB по `conversion_rate_rub_per_gb` или по минимальной цене GB в RUB-пакетах.
-- `traffic -> period`: пользователь покупает период, а остаток GB сохраняется как top-up поверх нового тарифа.
-- При смене тарифа обновляются Internal Squads, лимит трафика, стратегия сброса и базовый HWID-лимит.
+## 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-покупок;
-- `purchased_hwid_devices` для докупки HWID-устройств.
+| Поле | Назначение |
+| --- | --- |
+| `sale_mode` | Тип продажи: `subscription`, `traffic_package`, `topup`, `tariff_upgrade`, `hwid_devices`. |
+| `tariff_key` | Ключ тарифа, к которому относится платеж. |
+| `purchased_gb` | Купленный объем GB для traffic-пакетов и докупки трафика. |
+| `purchased_hwid_devices` | Количество устройств при докупке HWID. |
+| `subscription_duration_months` | Количество месяцев для подписки на срок; также используется платежными обработчиками как числовое поле покупки. |
-Legacy-поле `subscription_duration_months` остается для совместимости с существующими платежными обработчиками.
+В callback и metadata платежных провайдеров `sale_mode` может передаваться с суффиксом тарифа, например `subscription@standard` или `topup@standard`. При активации платежа тариф сохраняется отдельно в `tariff_key`.
-## Поведение При Исчерпании Трафика
+## Предупреждения и исчерпание трафика
-Remnawave сама ограничивает пользователя при достижении `trafficLimitBytes`: панель переводит пользователя в статус `LIMITED`. Бот не должен удалять пользователя из Internal Squads при 100% использования трафика.
+Remnawave ограничивает доступ при достижении `trafficLimitBytes`, переводя пользователя в статус `LIMITED`. Бот не удаляет пользователя из Internal Squads при 100% использования трафика.
-Текущее поведение воркера:
+`TariffTrafficWorker` запускается, когда активен JSON-каталог тарифов. Раз в 300 секунд он:
- синхронизирует из панели `status`, `trafficLimitBytes`, `usedTrafficBytes` и `trafficLimitStrategy`;
+- для period-тарифов выставляет `trafficLimitStrategy = MONTH`, если панель еще показывает другую стратегию;
- отправляет предупреждения на уровнях из `TARIFF_TRAFFIC_WARNING_LEVELS` (по умолчанию `85,90,95`);
-- оставляет блокировку и разблокировку доступа штатной логике Remnawave.
+- не отправляет `status=ACTIVE` при простой синхронизации стратегии, чтобы не снять статус `LIMITED`, выставленный Remnawave;
+- дедуплицирует предупреждения через `traffic_warnings`.
-## Воркеры
+Для period-тарифов дедупликация предупреждений привязана к началу текущего месяца. Для traffic-тарифов она учитывает текущий `trafficLimitBytes`, чтобы после покупки очередного пакета пользователь мог получить следующий набор предупреждений.
-`TariffTrafficWorker` запускается только при активном `tariffs.json`.
+Подписки, которые были ограничены логикой предыдущих запусков бота (`is_throttled=True`), восстанавливаются воркером только когда лимит снова больше использованного трафика.
-- Раз в несколько минут синхронизирует `trafficLimitStrategy = MONTH` для period-тарифов, если панель еще не переключена.
-- При синхронизации стратегии не отправляет `status=ACTIVE`, чтобы случайно не снять статус `LIMITED`, выставленный Remnawave.
-- Дедуплицирует предупреждения через таблицу `traffic_warnings`.
-- Для traffic-тарифов дедупликация предупреждений учитывает текущий `trafficLimitBytes`, чтобы новая покупка трафика могла создать новый набор предупреждений.
+## Автопродление, пробный период и бонусы
+
+Автопродление через 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` берется из последнего успешного платежа или из цены тарифа по умолчанию.
+
+Это позволяет существующим активным подпискам отображаться и управляться в интерфейсах тарифов.
diff --git a/docs/webapp.md b/docs/webapp.md
new file mode 100644
index 0000000..e4258f9
--- /dev/null
+++ b/docs/webapp.md
@@ -0,0 +1,119 @@
+# Web App / Mini App
+
+Web App запускается в том же контейнере, что и бот, но слушает отдельный порт `WEBAPP_SERVER_PORT` (по умолчанию `8081`). Порт `WEB_SERVER_PORT` остается для Telegram, платежных и Remnawave вебхуков.
+
+## Что показывает Web App
+
+- текущую ссылку подключения;
+- статус и дату окончания подписки;
+- использованный и доступный трафик;
+- доступные тарифы, способы оплаты и платежный статус;
+- смену тарифа и докупку трафика при настроенном каталоге тарифов;
+- раздел "Мои устройства" при `MY_DEVICES_SECTION_ENABLED=True`;
+- реферальную ссылку и статистику приглашений;
+- привязку email и Telegram к одному аккаунту.
+
+## Настройки `.env`
+
+```env
+WEBAPP_ENABLED=True
+WEBAPP_SERVER_HOST=0.0.0.0
+WEBAPP_SERVER_PORT=8081
+SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/
+WEBAPP_TITLE="Моя подписка"
+WEBAPP_PRIMARY_COLOR="#00fe7a"
+WEBAPP_LOGO_URL=
+WEBAPP_SESSION_SECRET=
+
+TELEGRAM_OAUTH_CLIENT_ID=
+TELEGRAM_OAUTH_CLIENT_SECRET=
+TELEGRAM_OAUTH_REQUEST_ACCESS=write
+
+SMTP_HOST=smtp-relay.brevo.com
+SMTP_PORT=587
+SMTP_FALLBACK_PORTS=2525,465
+SMTP_USERNAME=
+SMTP_PASSWORD=
+SMTP_FROM_EMAIL=no-reply@domain.com
+SMTP_FROM_NAME=Remnawave Minishop
+```
+
+Если `WEBAPP_LOGO_URL` пустой, логотип в Web App не показывается. Если SMTP-настройки не заполнены, вход по email скрывается.
+
+## Telegram-авторизация
+
+Внутри Telegram Mini App пользователь авторизуется через Telegram Mini Apps `initData`. При открытии страницы вне Telegram используется Telegram OAuth / OpenID Connect Authorization Code Flow с PKCE, callback `/auth/telegram/callback`, `nonce` и серверной проверкой `id_token` по JWKS Telegram.
+
+Настройка в BotFather:
+
+1. Откройте `@BotFather` -> `/mybots` -> выберите бота.
+2. В `Bot Settings` -> `Domain` укажите домен Web App без протокола и пути, например `app.domain.com`.
+3. В `Bot Settings` -> `Mini Apps` укажите URL, например `https://app.domain.com/`.
+4. В `Bot Settings` -> `Web Login` включите OpenID Connect Login, если BotFather предлагает переключение.
+5. Скопируйте Client ID и Client Secret в `TELEGRAM_OAUTH_CLIENT_ID` и `TELEGRAM_OAUTH_CLIENT_SECRET`.
+6. В `Web Login` -> `Allowed URLs` добавьте:
+
+```text
+https://app.domain.com/
+https://app.domain.com/auth/telegram/callback
+```
+
+`TELEGRAM_OAUTH_REQUEST_ACCESS=write` разрешает боту написать пользователю после логина. Если дополнительные разрешения не нужны, оставьте переменную пустой.
+
+## Email-вход
+
+Email-вход работает через одноразовый код:
+
+1. Пользователь вводит email.
+2. Бот отправляет код через SMTP.
+3. Код вводится в модальном окне Web App.
+4. После подтверждения создается или находится пользователь, а email можно связать с Telegram-аккаунтом.
+
+Для Brevo обычно подходит порт `587` с STARTTLS. Если основной порт недоступен, приложение пробует порты из `SMTP_FALLBACK_PORTS`; порт `465` используется через SSL.
+
+## Проксирование
+
+Web App должен проксироваться отдельно от вебхуков:
+
+```nginx
+upstream remnawave-minishop-webapp {
+ server remnawave-minishop:8081;
+}
+
+server {
+ server_name app.domain.com;
+ listen 443 ssl;
+ http2 on;
+
+ ssl_certificate "/etc/nginx/ssl/app_fullchain.pem";
+ ssl_certificate_key "/etc/nginx/ssl/app_privkey.key";
+
+ location / {
+ proxy_pass http://remnawave-minishop-webapp;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
+```
+
+В `docker-compose.yml` порт Web App публикуется отдельно:
+
+```yaml
+ports:
+ - 127.0.0.1:8080:8080
+ - 127.0.0.1:${WEBAPP_SERVER_PORT:-8081}:${WEBAPP_SERVER_PORT:-8081}
+```
+
+## Реферальные ссылки
+
+Реферальные ссылки доступны в двух форматах:
+
+- Telegram deep-link: `https://t.me/?start=ref_u`;
+- Web App ссылка: `https://app.domain.com/?ref=u`.
+
+Web App учитывает `ref`, `start`, `start_param` и Telegram Mini Apps `start_param`, сохраняет найденный параметр до авторизации и передает его в Telegram OAuth или email-вход.
+
+Для email-регистраций пользователь в Remnawave создается с username вида `em_`. Email добавляется в описание пользователя панели и, если API панели принимает поле `email`, передается отдельным полем. Для Telegram-регистраций используется username `tg_`.