Перейти к содержимому

Веб-приложение / Mini App

Веб-приложение собирается в отдельный образ frontend и отдается через nginx. Статические запросы Mini App идут в frontend:80; frontend nginx проксирует /api/*, /auth/* и ассеты тем/логотипов во внутренний WebApp-сервер backend на backend:8081. Telegram, платежные и панельные webhook-маршруты остаются на backend-сервере вебхуков backend:8080.

  • текущую ссылку подключения;
  • статус и дату окончания подписки;
  • использованный и доступный трафик;
  • отдельную карточку premium-трафика, если у активного тарифа настроены premium-сквады и premium-лимит;
  • доступные тарифы, способы оплаты и платежный статус;
  • карточку состава period-тарифа и, если администратор включил соответствующие возможности, слайдеры итогового числа устройств и периодических лимитов обычного/premium-трафика прямо при оформлении;
  • смену тарифа, обычную докупку трафика и докупку premium-трафика при настроенном каталоге тарифов; предложение докупки по умолчанию появляется после 80% расхода соответствующего лимита, если у тарифа не включен переключатель «Докупка доступна всегда» (подробнее: когда показывается докупка);
  • встроенную инструкцию установки: подбор платформы, список приложений, deeplink-кнопки, QR и действия со ссылкой подписки;
  • раздел “Мои устройства” при MY_DEVICES_SECTION_ENABLED=True;
  • раздел “Поддержка” с тикетами и внешней ссылкой SUPPORT_LINK при включенном SUPPORT_TICKETS_ENABLED;
  • раздел статуса серверов при SERVER_STATUS_ENABLED=True: внешнюю страницу статуса или встроенные данные Uptime Kuma и xray-checker;
  • отдельный подраздел Настройки → Уведомления для рекламных и системных уведомлений по email и Telegram; прямой путь /settings/notifications и startapp=notifications можно использовать в кнопках сообщений и меню;
  • реферальную ссылку и статистику приглашений;
  • раздел Партнёрство при включённой партнёрской программе: заявку, отдельные ссылки, клиентов, комиссии, раздельные балансы, выплаты и полную или частичную оплату покупок из баланса;
  • привязку email и Telegram к одному аккаунту.

Для аккаунтов с ролью owner или admin веб-приложение также показывает админ-панель: статистику, пользователей (поиск, фильтры, premium-трафик), партнёров, поддержку, рассылки, промокоды, логи, настройки и редактор тарифов. Подробности: админ-панель.

Перед выбором срока Mini App показывает компактную карточку тарифа: название, описание, устройства, обычный трафик и premium-трафик. Для безлимитного ресурса выводится «Безлимит» без лишнего упоминания периода сброса. Если хотя бы один checkout-слайдер доступен, карточка раскрывается по нажатию или кнопке изменения; параметры, для которых слайдер выключен, остаются только информационными.

Выбор устройств и лимитов сохраняется при переключении срока подписки. Цена анимированно обновляется в общей кнопке, во всех карточках периодов и в подписи стоимости за месяц. Во время непрерывного перетаскивания расчёт выполняется локально, а после отпускания слайдера Mini App получает авторитетную котировку /api/subscription/quote. Применённая процентная скидка пересчитывается от всей корзины, а не только от базового периода.

Способы оплаты по умолчанию собраны в выпадающий список с иконками; администратор может вернуть отдельные кнопки через PAYMENT_METHODS_DISPLAY_MODE=buttons. Метод ниже минимальной суммы остаётся видимым, но заблокированным, а hover/focus/tap показывает подсказку с минимумом. Подробности расчёта и ограничений провайдеров: платежи и докупки при оформлении.

WEBAPP_ENABLED=True
WEBAPP_SERVER_HOST=0.0.0.0
WEBAPP_SERVER_PORT=8081
SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/
WEBAPP_API_BASE_URL=/api
WEBAPP_BACKEND_UPSTREAM=http://backend:8081
WEBAPP_BACKEND_UPSTREAM_HOST=backend
MINISHOP_EDGE_TOKEN=
SUBSCRIPTION_GUIDES_ENABLED=True
SUBSCRIPTION_GUIDES_BOT_MENU_ENABLED=True
SUBSCRIPTION_PAGE_CONFIG_PANEL_ENABLED=True
SUBSCRIPTION_PAGE_CONFIG_JSON_OVERRIDE_ENABLED=False
WEBAPP_TITLE="/minishop"
WEBAPP_THEMES_DIR=data/themes
WEBAPP_DEFAULT_THEME=
WEBAPP_SESSION_SECRET=<stable-random-secret>
WEBHOOK_SECRET_TOKEN=<stable-random-secret>
WEBAPP_SESSION_TTL_SECONDS=2592000
WEBAPP_AUTH_MAX_AGE_SECONDS=86400
WEBAPP_LOGIN_TOKEN_TTL_SECONDS=600

SUPPORT_LINK=https://t.me/your_support_link
SUPPORT_TICKETS_ENABLED=True
SUPPORT_TICKET_RATE_LIMIT_PER_HOUR=5

SERVER_STATUS_ENABLED=False
SERVER_STATUS_PROVIDER=url
SERVER_STATUS_URL=
SERVER_STATUS_KUMA_URL=
SERVER_STATUS_XRAY_CHECKER_URL=
SERVER_STATUS_CACHE_TTL_SECONDS=30
SERVER_STATUS_STALE_TTL_SECONDS=300
SERVER_STATUS_TIMEOUT_SECONDS=5

SUBSCRIPTION_MINI_APP_URL - это публичный HTTPS URL именно frontend/Mini App, обычно отдельный домен вроде https://app.domain.com/. Его указывают в BotFather в Mini Apps, а бот использует его для кнопок личного кабинета, реферальных ссылок и входа по email. Не добавляйте в него /api, /webhook или путь конкретной страницы.

WEBAPP_API_BASE_URL - это browser-visible base URL для frontend-запросов. Оставляйте /api и для обычного compose, и для разнесенных frontend/backend серверов. Разнесение делается server-side настройкой WEBAPP_BACKEND_UPSTREAM у frontend nginx, а не публичным backend origin в JavaScript.

WEBAPP_BACKEND_UPSTREAM - приватный/protected upstream, куда frontend nginx проксирует /api, /auth, /open-app и ассеты Web App. По умолчанию это http://backend:8081. Для split-сервера используйте защищенный backend-домен с MINISHOP_EDGE_TOKEN, private IP/VPN или Rathole tunnel.

SERVER_STATUS_ENABLED=True добавляет пользовательский раздел статуса. Источник url открывает SERVER_STATUS_URL, а uptime-kuma и xray-checker открывают внутренний экран Mini App и не используют SERVER_STATUS_URL. Настройка опубликованной страницы статуса, кэша, безопасности и диагностики: статус серверов.

Публичные документы управляются в отдельном разделе Система → Документы. Для каждого документа задаются название, slug, Markdown-текст, порядок и необязательная группа. Публичный URL документа — /<slug>; slug может состоять из вложенных lower-case сегментов с буквами, цифрами и дефисами, например /guides/install/ios. Для уже занятых системных корней сохраняется совместимый адрес /docs/<slug>. Пустой Markdown можно сохранить как черновик, но его URL будет отвечать 404.

  • «Показывать в настройках» постоянно добавляет непустой документ в пользовательский экран «Настройки»;
  • «Показывать в сайдбаре» добавляет его в навигацию на страницах документов;
  • группа объединяет ссылки под общим заголовком;
  • роль privacy_policy или user_agreement подставляет нативный документ в ссылки регистрации, оплаты и Telegram. Каждая роль может быть назначена только одному документу.

Метаданные и Markdown хранятся раздельно в persistent-каталоге data/docs; запись выполняется атомарно. Markdown рендерится без raw HTML, а размер одного документа ограничен 512 KiB.

Старые /legal/policy, /legal/terms и произвольные файловые страницы из data/pages остаются доступными для совместимости. Если в data/legal есть непустой старый файл, а документа с соответствующей ролью ещё нет, сервис безопасно импортирует его в новый каталог, не удаляя исходник. Каждая юридическая роль импортируется один раз: удаление документа, очистка текста или смена роли в админке не восстанавливают старую публикацию. Заготовки data/legal/*.example.md не импортируются и не публикуются.

Визуальный редактор сохраняет таблицы, изображения, списки задач и блоки с указанием языка как исходный Markdown. Эти фрагменты можно изменить в режиме «Markdown»; переключение режимов и правка соседнего текста не удаляют их.

Для ручной проверки созданного документа:

curl -fsS https://app.example.com/api/documents/guides/install/ios
curl -I https://app.example.com/guides/install/ios

Старые произвольные Markdown-страницы по-прежнему можно добавлять в Telegram-меню через Система → Настройки → Кнопки меню. Эквивалент в MENU_BUTTONS_JSON:

[
  {
    "id": "about",
    "kind": "page",
    "target": "/company/about",
    "webapp_icon": "Info",
    "telegram_emoji": "ℹ️",
    "labels": { "ru": "О сервисе", "en": "About" },
    "show_in_bot": true,
    "show_in_webapp": true
  }
]

Стандартный frontend nginx проксирует такие extensionless-пути в backend, поэтому он подтверждает наличие файла и отдаёт тот же Mini App shell с темой и CSP. Не добавляйте HTML, JavaScript URLs или секреты в Markdown: raw HTML показывается как текст, а небезопасные ссылки и изображения не рендерятся.

Если SUBSCRIPTION_GUIDES_ENABLED=True, кнопка Установить и настроить в личном кабинете открывает внутренний экран /install. Если инструкции выключены, конфиг не загрузился или не прошел валидацию, сохраняется старое поведение: кнопка открывает финальную ссылку подключения из панели.

Экран /install доступен только авторизованному пользователю Web App. Он получает данные из /api/subscription-guides, определяет платформу по Telegram Mini Apps platform, navigator.userAgentData.platform и navigator.userAgent, а затем показывает приложения и шаги из Remnawave Subscription Page v1 config. Ссылки типа happ://... и другие deeplink-кнопки открываются прямо из Mini App; в шаблонах заменяются {{SUBSCRIPTION_LINK}}, {{USERNAME}}, {{HAPP_CRYPT3_LINK}}, {{HAPP_CRYPT4_LINK}} и {{INCY_CRYPT1_LINK}}. Крипто-шаблоны шифруют исходный HTTP(S) URL подписки для соответствующего клиента.

Если загрузка инструкций завершилась ошибкой, экран предлагает Загрузить снова. Повторное открытие экрана также заново запрашивает данные: ошибка не кешируется до перезагрузки Mini App. Повторная загрузка публичной ссылки /s/<token> использует ту же подписку и не требует авторизации. Успешно загруженные и явно отключённые инструкции сохраняют обычное кеширование.

Конфиг инструкций загружается в таком порядке:

  1. JSON из админки, только если включен SUBSCRIPTION_PAGE_CONFIG_JSON_OVERRIDE_ENABLED.
  2. Resolved Subscription Page config конкретной подписки из Remnawave Panel, если включен SUBSCRIPTION_PAGE_CONFIG_PANEL_ENABLED и у активной подписки найден shortUuid.
  3. Default Subscription Page config из Remnawave Panel.
  4. Локальный файл SUBSCRIPTION_PAGE_CONFIG_PATH как fallback.

По умолчанию используются инструкции из Remnawave Panel, чтобы не дублировать настройку страницы подписки в Minishop. Если пользователю в панели назначен External Squad со своим Subpage Config, встроенный экран /install и публичная ссылка /s/<token> получают уже примененный к этой подписке конфиг. Локальный файл в data/subpage-config/multiapp.json не создается автоматически. Общий fallback-конфиг кешируется на backend и обновляется при изменении связанных настроек; ошибки загрузки кешируются кратко, чтобы не дергать панель на каждый пользовательский запрос.

Личный экран показывает QR-код разрешённой ссылки кнопки подключения выбранного приложения, кнопку копирования той же ссылки и кнопку Поделиться. Если у приложения нет безопасной кнопки подключения, QR-блок скрывается. Для передачи инструкции генерируется публичная ссылка /s/<token>: она открывает тот же интерфейс инструкций без авторизации и нижней навигации, но без QR-блока. Публичный payload отдается через /api/subscription-guides/public/{share_token} только для активной локальной подписки с валидным share token.

SUBSCRIPTION_GUIDES_BOT_MENU_ENABLED=True включает такое же поведение в Telegram-боте: кнопки подключения открывают Mini App /install, а после успешной оплаты, пробного периода или промокода пользователь получает публичную ссылку /s/<token>. Если настройку выключить, бот снова отправляет пользователя на финальную Remnawave Subscription Page.

Конфиг совместим с Remnawave Subscription Page v1 (version, locales, brandingSettings, uiConfig, baseSettings, baseTranslations, svgLibrary, platforms). Backend проверяет обязательные locale-строки, допустимые платформы и типы кнопок, ссылки на svgIconKey, а SVG из svgLibrary санитизирует перед отдачей в UI.

Для иконок и SVG-файлов тем действует общая политика статической разметки: поддерживаются градиенты, маски, фильтры, паттерны, маркеры, текст, symbol/use, vector-effect, paint-order, атрибуты шрифтов и доступности и inline style с разрешёнными свойствами оформления. href, xlink:href и url(...) могут ссылаться только на локальные #id. Скрипты, обработчики событий, анимация, внешние изображения, HTML, таблицы стилей, DTD и сущности запрещены; CSS-экранирование и комментарии в значениях отклоняются, чтобы скрытая ссылка не обходила проверку. Неподдерживаемая иконка из Remnawave Panel заменяется нейтральной SVG-иконкой, не отключая инструкции и остальные иконки. Редактор JSON и локальные файлы по-прежнему отклоняют некорректную разметку, чтобы оператор получил ошибку при проверке.

В brandingSettings.logoUrl поддерживаются HTTP(S)-ссылки и встроенные изображения data:image/… (PNG, JPEG, GIF, WebP, AVIF, BMP, ICO и SVG), включая base64 и URL-кодированные данные. Поле сохраняется для совместимости с конфигом панели; встроенные инструкции не отображают этот логотип. Иконки приложений и шагов берутся отдельно из svgLibrary. Это разрешение относится только к изображениям: supportUrl должен оставаться HTTP(S)-ссылкой, а data: в ссылках кнопок запрещен.

Если WEBAPP_ENABLED=False, пользовательское веб-приложение и админ-панель не регистрируются. Чтобы снова попасть в админку, включите WEBAPP_ENABLED=True в .env и перезапустите backend/frontend контейнеры.

Внешний вид настраивается в админке: раздел Внешний вид управляет логотипом, favicon, accent-цветом, выбранной темой и отдельным масштабом логотипа для desktop/mobile layout. Кастомные темы читаются из WEBAPP_THEMES_DIR, а WEBAPP_DEFAULT_THEME может принудительно выбрать тему по ключу. Подробный контракт theme.json, CSS/asset-роуты и пайплайн создания темы описаны в webapp-themes.md.

Mini App поддерживает email-код, email/пароль, Telegram Mini Apps initData, Telegram OAuth / OpenID Connect, Google, Yandex ID и passkey. Настройка всех вариантов собрана в разделе «Способы входа».

Если SMTP-настройки не заполнены, вход по email скрывается. Если Telegram OAuth не настроен, вход через Telegram продолжает работать внутри Telegram Mini App через initData, но внешняя браузерная авторизация не сможет стартовать.

Тикеты поддержки включаются через SUPPORT_TICKETS_ENABLED; внешний резервный контакт задается SUPPORT_LINK. Полный сценарий пользователя, админа и уведомлений описан в разделе поддержка пользователей / тикеты.

Рекомендуемая продакшен-схема - два публичных домена и две разные backend-плоскости:

  • WEBHOOK_BASE_URL, например https://webhooks.domain.com, целиком проксируется в backend:8080;
  • SUBSCRIPTION_MINI_APP_URL, например https://app.domain.com/, целиком проксируется в frontend:80.

frontend уже сам проксирует /api/*, /auth/*, /webapp-logo и ассеты тем/логотипов во внутренний WebApp API на backend:8081, поэтому внешний обратный прокси обычно не должен отправлять эти пути в backend:8081 напрямую.

Для split frontend/backend браузер всё равно обращается только к frontend-домену:

browser -> https://app.domain.com/api -> frontend nginx -> WEBAPP_BACKEND_UPSTREAM

Платежные provider webhook, Telegram webhook и Remnawave Panel webhook продолжают идти на WEBHOOK_BASE_URL и backend plane 8080; frontend-домен и MINISHOP_EDGE_TOKEN к ним не относятся.

Если WEBAPP_BACKEND_UPSTREAM=https://bot.domain.com, backend-side reverse proxy должен маршрутизировать /api/*, /auth/*, /open-app, logo/theme/favicon paths в backend:8081 и требовать X-Minishop-Edge-Token, который добавляет только frontend nginx. Webhook routes на этом же домене остаются на backend:8080 без edge token. Токен нельзя класть в frontend JS: любой browser-visible token виден пользователю в DevTools.

Готовые варианты описаны в разделе Развертывание:

  • Caddy - автоматический HTTPS;
  • Angie - автоматический HTTPS в Nginx-синтаксисе;
  • Nginx - сертификаты в соседней папке ssl/;
  • Pangolin/Newt - публикация без входящих портов на сервере приложения;
  • без обратного прокси - прямая публикация портов для проверки или внешней TLS-платформы.

В default docker-compose.yml наружу публикуются frontend и webhook/backend port, а внутри Docker network сервисы доступны друг другу по service DNS names:

services:
  frontend:
    expose:
      - "80"
  backend:
    expose:
      - "8080"

Реферальные ссылки доступны в двух форматах:

  • Telegram deep-link: https://t.me/<bot>?start=ref_u<code>;
  • Web App ссылка: https://app.domain.com/?ref=u<code>.

В разделе бонусов Web App показывает приветственный бонус за регистрацию и бонусы за оплату подписки. В legacy-режиме или при одном period-тарифе выводятся подробные строки по периодам. Если в JSON-каталоге включено несколько period-тарифов, Web App показывает, что бонус зависит от тарифа и периода оплаты друга, затем список тарифов с диапазоном “от N до N дней”; подробности по периодам раскрываются по иконке вопроса.

Веб-приложение учитывает ref, start, start_param и Telegram Mini Apps start_param, сохраняет найденный параметр до авторизации и передает его в Telegram OAuth или вход по email.

Если включён REGISTRATION_INVITE_ONLY_ENABLED, новая регистрация в Web App проходит только через такую ссылку; отдельного ручного поля для ввода кода нет. Существующие пользователи могут входить без реферального параметра.

Для новых email- и Telegram-регистраций основной пользователь Remnawave создаётся с username minishop_id вида ms_ плюс 32 символа UUID. Изменение способов входа не меняет этот username. Фактические старые em_* и tg_* сохраняются до отдельного подтверждённого переименования.

Свободную Telegram identity можно привязать к текущему аккаунту без смены внутреннего ID, подписки или роли. Если она уже принадлежит другому аккаунту, интерфейс сообщает о конфликте и не объединяет аккаунты автоматически.

Партнёрские ссылки используют отдельный код и не заменяют обычную реферальную связь. Telegram ссылка содержит payload p_<partner_code>, Web App ссылка — параметр partner=<partner_code>. Они закрепляют только нового пользователя и только за active-партнёром; повторное открытие другой ссылки не переносит уже созданного клиента. При REGISTRATION_INVITE_ONLY_ENABLED=True валидная партнёрская ссылка также разрешает регистрацию.

Раздел Партнёрство и прямой маршрут /partner доступны только при включённой программе. Сохранение переключателя в админке обновляет данные текущей Mini App без перезапуска приложения; другие открытые сессии получают новое состояние при следующей загрузке данных. Полные правила атрибуции, бонусов, комиссий и выплат описаны в руководстве по партнёрской программе.