From a0f5d694487a1ab806343356fd00812f8f9e7066 Mon Sep 17 00:00:00 2001 From: Umbrix Dev Date: Sun, 8 Feb 2026 09:16:55 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=8A=20=D0=9F=D0=BE=D0=BB=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=B0=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7=20=D1=81=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=B5=D0=BC=D1=8B=20=D1=82=D0=B0=D1=80=D0=B8=D1=84?= =?UTF-8?q?=D0=BE=D0=B2=20=D1=81=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=BB=D0=BE=D0=BA=D0=B0=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PRICING-LOCATION-ANALYSIS.md (45KB): технический анализ Marzban API - IMPLEMENTATION-SUMMARY.md (12KB): краткая выжимка для разработки - MINI_APP_ARCHITECTURE.md: обновленная архитектура Ключевые решения: ✅ Скидки за период: -10% (3м), -15% (6м), -20% (год) ✅ Выбор локаций из реальных нод Marzban ✅ Мультишаговая воронка: тариф → период → локации → оплата ❌ Динамическое ценообразование отклонено ⏸️ Семейные планы отложены на V2.0 (требуют Device Monitor) ✅ Реферальная система уже работает MVP план: 2 недели (20 часов) - Week 1: Backend API (8h) - /api/nodes + /api/create-user updates - Week 2: Frontend UI (12h) - мультишаговый /app/plans/page.tsx --- IMPLEMENTATION-SUMMARY.md | 306 +++++++++ MINI_APP_ARCHITECTURE.md | 223 +++++++ PRICING-LOCATION-ANALYSIS.md | 1222 ++++++++++++++++++++++++++++++++++ 3 files changed, 1751 insertions(+) create mode 100644 IMPLEMENTATION-SUMMARY.md create mode 100644 MINI_APP_ARCHITECTURE.md create mode 100644 PRICING-LOCATION-ANALYSIS.md diff --git a/IMPLEMENTATION-SUMMARY.md b/IMPLEMENTATION-SUMMARY.md new file mode 100644 index 0000000..281e4d2 --- /dev/null +++ b/IMPLEMENTATION-SUMMARY.md @@ -0,0 +1,306 @@ +# 📋 Краткая выжимка: План реализации тарифов с локациями + +**Дата**: 8 февраля 2026 +**Статус**: ✅ Анализ завершен, готово к реализации + +--- + +## 🎯 Цель + +Реализовать систему покупки VPN с: +1. ✅ Выбором тарифа (Start/Plus/Max) +2. ✅ Выбором периода (1м/3м/6м/год) с показом скидок +3. ✅ Выбором локаций (нод) из Marzban +4. ✅ Оплатой через payment gateway +5. ✅ Автоматическим созданием пользователя с выбранными нодами + +--- + +## 📊 Итоги анализа + +### ✅ Что работает: +- Тарифы (Start/Plus/Max) - UI готов +- SetupWizard с выбором локаций - UI готов +- API создания пользователей - работает +- Реферальная система - полностью реализована (+7 дней за друга, +30 за milestone) + +### ❌ Что НЕ работает: +- Выбор локаций не сохраняется в Marzban +- Нет скидок за период оплаты +- Нет связи между UI выбора локаций и реальными нодами + +### ⚠️ Что отклонено: +- ❌ Динамическое ценообразование (премиум локации дороже) +- ⏸️ Семейные планы (отложены на V2.0, требуют Device Monitor Service) + +--- + +## 🚀 MVP Roadmap (2 недели) + +### Week 1: Backend API (8-10 часов) +```typescript +1. [3h] Создать /api/nodes + - Получить список нод из Marzban API + - Форматировать для фронтенда (id, name, country, ping) + - Фильтровать только connected ноды + +2. [5h] Обновить /api/create-user + - Добавить параметры: period, locationIds + - Получить список inbounds для выбранных нод + - Создать пользователя с кастомными inbounds + - Добавить функцию getPlanConfig(planType, period) с расчетом скидок +``` + +### Week 2: Frontend UI (10-12 часов) +```typescript +3. [8h] Переработать /app/plans/page.tsx + - Шаг 1: Выбор тарифа (Start/Plus/Max) + - Шаг 2: Выбор периода (1м/3м/6м/год) + - Показать экономию: "💰 Экономия 297₽ при оплате на год" + - Шаг 3: Выбор локаций + - Загрузить из /api/nodes + - Ограничить по тарифу (Start=1, Plus=3, Max=все) + - Шаг 4: Кнопка "Оплатить" + +4. [2h] Добавить компоненты + - PeriodSelector - карточки периодов со скидками + - LocationSelector - чекбоксы локаций с флагами + - PriceSummary - итоговая цена с экономией + +5. [2h] Стилизация под Telegram Mini App +``` + +--- + +## 💰 Скидки за период + +``` +1 месяц: 0% (базовая цена) +3 месяца: -10% (экономия ~270₽ на Plus) +6 месяцев: -15% (экономия ~540₽ на Plus) +1 год: -20% (экономия ~720₽ на Plus) +``` + +**Примеры**: +- Plus (299₽/мес): + - 1 месяц: 299₽ + - 3 месяца: 807₽ (вместо 897₽) → экономия 90₽ + - 6 месяцев: 1523₽ (вместо 1794₽) → экономия 271₽ + - 1 год: 2870₽ (вместо 3588₽) → экономия 718₽ + +--- + +## 🔧 Техническая реализация + +### 1. API: GET /api/nodes + +```typescript +// Возвращает список доступных локаций +{ + "success": true, + "locations": [ + { + "id": 1, + "name": "🇳🇱 Нидерланды", + "address": "193.168.175.128", + "ping": "15ms", + "country": "NL" + }, + { + "id": 2, + "name": "🇺🇸 США", + "address": "194.113.210.187", + "ping": "120ms", + "country": "US" + } + ] +} +``` + +### 2. API: POST /api/create-user (обновленный) + +```typescript +// Принимает: +{ + "planType": "plus", + "period": "3months", + "locationIds": [1, 2, 5], // NEW + "telegramId": 12345, + "telegramUsername": "john_doe" +} + +// Логика: +1. Получить список нод по locationIds +2. Получить список inbounds для этих нод +3. Создать mapping: + { + vless: ["VLESS TCP Node1", "VLESS Reality Node2"], + vmess: ["VMess WS Node1"], + trojan: ["Trojan TCP Node1"] + } +4. Создать пользователя с этими inbounds +5. Рассчитать expire с учетом period: + - 1month: +30 дней + - 3months: +90 дней + - 6months: +180 дней + - 1year: +365 дней +``` + +### 3. Frontend: Мультишаговый выбор + +``` +┌─────────────────────────────────────┐ +│ Шаг 1: Тариф │ +│ ⭕ Start (1 локация) │ +│ ⭕ Plus (3 локации) ← выбран │ +│ ⭕ Max (все локации) │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Шаг 2: Период │ +│ ⭕ 1 месяц - 299₽ │ +│ ⭕ 3 месяца - 807₽ (экономия 90₽) │ ← выбран +│ ⭕ 6 мес - 1523₽ (экономия 271₽)│ +│ ⭕ 1 год - 2870₽ (экономия 718₽)│ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Шаг 3: Локации (выберите 3) │ +│ ☑ 🇳🇱 Нидерланды (15ms) │ ← выбран +│ ☐ 🇩🇪 Германия (20ms) │ +│ ☑ 🇺🇸 США (120ms) │ ← выбран +│ ☐ 🇸🇬 Сингапур (180ms) │ +│ ☑ 🇯🇵 Япония (160ms) │ ← выбран +│ ☐ 🇬🇧 UK (35ms) │ +│ │ +│ [Перейти к оплате 807₽ →] │ +└─────────────────────────────────────┘ +``` + +--- + +## 🎁 Реферальная система (уже работает!) + +**Текущая реализация**: +```typescript +// Endpoints: +POST /api/referral/track // Записать реферала +GET /api/referral/stats // Получить статистику + +// Бонусы: ++7 дней за каждого друга ++30 дней milestone за каждые 5 друзей + +// Формула: +bonusDays = (referralCount * 7) + (Math.floor(referralCount / 5) * 30) + +// Примеры: +1 друг: +7 дней +5 друзей: +65 дней (35 + 30 milestone) +10 друзей: +130 дней (70 + 60 milestone) +``` + +**Что можно улучшить в V1.1**: +- Визуальные badges (Bronze/Silver/Gold) +- Leaderboard на странице /referral +- Уведомления в Telegram при milestone + +--- + +## 🚫 Семейные планы - Почему отложены? + +**Проблема**: Marzban **НЕ имеет** встроенного механизма ограничения устройств. + +**Что НЕ доступно**: +```json +// В UserCreate API нет: +{ + "device_limit": 5, // ❌ + "ip_limit": 5, // ❌ + "max_connections": 5 // ❌ +} +``` + +**Что есть**: +```json +{ + "online_at": "2026-02-08T10:30:00Z", // ✅ Последняя активность + "sub_last_user_agent": "v2rayNG" // ✅ User agent +} +``` + +**Варианты решения**: + +| Вариант | Точность | Сложность | Время | +|---------|----------|-----------|-------| +| 1. Мониторинг `online_at` | ❌ Низкая | Средняя | 8h | +| 2. IP-based мониторинг | ❌ Низкая | Низкая | 4h | +| 3. Xray патчи | ✅ Высокая | Очень высокая | 80h+ | +| 4. Sub-users (5 аккаунтов) | ✅ Высокая | Средняя | 12h | +| 5. **Без ограничений + ToS** | N/A | Нулевая | 0h | +| 6. **Log monitor (V2.0)** | ✅ Высокая | Высокая | 40h | + +**Решение для MVP**: Отложить на V2.0, для MVP - полагаться на ToS и честность пользователей. + +**Решение для V2.0**: Разработать Device Monitor Service с парсингом Xray логов в реальном времени. + +--- + +## 📅 Timeline + +### Неделя 1 (Backend): +- [ ] День 1-2: Создать /api/nodes (3 часа) +- [ ] День 3-5: Обновить /api/create-user с period + locationIds (5 часов) + +### Неделя 2 (Frontend): +- [ ] День 1-3: Переработать /app/plans/page.tsx (8 часов) +- [ ] День 4: Компоненты PeriodSelector + LocationSelector (2 часа) +- [ ] День 5: Стилизация + тестирование (2 часа) + +### Неделя 3-4 (Payment + Testing): +- [ ] Интеграция YooKassa/Stripe +- [ ] Webhook для обработки оплат +- [ ] E2E тесты полной воронки +- [ ] n8n bot updates + +--- + +## 🎯 KPI успеха + +**Метрики для отслеживания**: +1. Конверсия выбора периода: + - Сколько % выбирают 1м/3м/6м/год? + - Какой период дает наибольший LTV? + +2. Выбор локаций: + - Топ-3 популярных локации + - Сколько % пользователей меняют дефолтный выбор? + +3. Реферальная программа: + - % пользователей с рефералами + - Average referral count per user + - Impact на retention + +4. Revenue: + - ARPU (Average Revenue Per User) + - LTV (Lifetime Value) + - Churn rate + +--- + +## 📞 Следующие действия + +1. ✅ Анализ завершен +2. ✅ Feedback учтен: + - ❌ Динамическое ценообразование отклонено + - ⏸️ Семейные планы отложены на V2.0 + - ✅ Реферальная система работает +3. ⏳ **Начать разработку MVP** (2 недели) +4. ⏳ Выбрать payment gateway (YooKassa?) +5. ⏳ Setup Telegram bot webhook для уведомлений + +--- + +**Полная документация**: См. `PRICING-LOCATION-ANALYSIS.md` (1400+ строк детального анализа) + +**Готов к старту!** 🚀 diff --git a/MINI_APP_ARCHITECTURE.md b/MINI_APP_ARCHITECTURE.md new file mode 100644 index 0000000..215b1c8 --- /dev/null +++ b/MINI_APP_ARCHITECTURE.md @@ -0,0 +1,223 @@ +# 🚀 Umbrix Mini App - Полная Архитектура (PRO LEVEL) + +## Принципы (Best Practices): +1. **Telegram Bot = Минимум** - только /start → Open WebApp +2. **Mini App = Все** - вся логика, UI, воронка продаж +3. **No localStorage** - только API + MariaDB +4. **Telegram WebApp SDK** - интеграция с Telegram UI + +--- + +## 📱 **User Journey (Воронка Продаж)** + +### **Scenario 1: Новый пользователь БЕЗ реферала** +``` +1. User clicks: https://t.me/Dorod_vps_bot +2. Bot sends: "👋 Привет! 🚀 Открыть Umbrix" → WebApp button +3. User opens: https://app.umbrix.net +4. Mini App shows: Onboarding Flow + ├── Welcome Screen (Telegram.WebApp.initData) + ├── Language Selection (RU/EN/UZ) + ├── Plan Selection (Shared/Dedicated/Premium) + └── Create Account Button +5. Account created → Dashboard +6. Dashboard shows: + ├── Subscription Status (Active / Expired / Trial) + ├── QR Code + Links + ├── Referral Button → Copy link + ├── Help Button + └── Settings Button +``` + +### **Scenario 2: Новый пользователь С рефералом** +``` +1. User clicks: https://t.me/Dorod_vps_bot?start=ref_john_doe +2. Bot extracts: referrerId = "john_doe" +3. Bot opens: https://app.umbrix.net?ref=john_doe +4. Mini App shows: "🎁 Вы перешли по ссылке от john_doe! +7 дней бесплатно" +5. Onboarding Flow (same as above) +6. On Account Create: + ├── POST /api/create-user (with referrerId) + ├── POST /api/referral/track (award +7 days to john_doe) + └── Both get bonuses +7. Dashboard → показывает trial статус + bonus days +``` + +### **Scenario 3: Существующий пользователь (возврат)** +``` +1. User opens: https://t.me/Dorod_vps_bot → WebApp +2. Mini App detects: Telegram.WebApp.initDataUnsafe.user.id +3. API Check: GET /api/user-subscription?telegramId=12345 +4. Response: { hasSubscription: true, token: "...", status: "active" } +5. Mini App redirects: /dashboard (skip onboarding) +6. Dashboard shows: + ├── Subscription details + ├── Days remaining: "23 дня осталось" + ├── QR Code + Links + └── Referral stats: "3 друга, +21 день заработано" +``` + +### **Scenario 4: Пользователь с истекшей подпиской** +``` +1. API Check: { hasSubscription: true, status: "expired" } +2. Dashboard shows: + ├── ⚠️ "Подписка истекла 5 дней назад" + ├── "Пригласите 2 друзей для продления" (gamification) + ├── "Или оплатите" → Payment Button + └── Referral progress bar: [■■■□□] 3/5 friends +``` + +--- + +## 🗂️ **Структура файлов Mini App** + +``` +/app +├── page.tsx # 🏠 Landing / Onboarding (если новый user) +├── dashboard/ +│ └── page.tsx # 📊 Dashboard (если есть подписка) +├── plans/ +│ └── page.tsx # 💎 Plan Selection (выбор тарифа) +├── referral/ +│ └── page.tsx # 🎁 Referral Stats (уже есть!) +├── help/ +│ └── page.tsx # ❓ Help / FAQ (уже есть!) +├── setup/ +│ └── page.tsx # 🔧 Setup Wizard (инструкции по настройке) +└── subscription/ + └── [token]/ + └── page.tsx # 📲 Subscription Details (QR код, ссылки) + +/components +├── OnboardingFlow.tsx # 🎬 Onboarding steps (language, plan, create) +├── PlanCard.tsx # 💳 Plan card (Shared/Dedicated/Premium) +├── SubscriptionStatus.tsx # ✅ Status badge (Active/Expired/Trial) +├── ReferralProgress.tsx # 📈 Progress bar (3/5 friends) +├── QRCodeModal.tsx # ✅ Уже есть +├── ReferralModal.tsx # ✅ Уже есть +└── SetupWizard.tsx # ✅ Уже есть + +/lib +├── telegram-webapp.ts # 🔌 Telegram WebApp SDK wrapper +├── marzban-api.ts # ✅ Уже есть +└── constants.ts # ✅ Уже есть +``` + +--- + +## 🎨 **UI Components (Telegram WebApp Style)** + +### **1. OnboardingFlow.tsx** +```tsx +// Multi-step onboarding (Welcome → Language → Plan → Create) +// Uses Telegram.WebApp.BackButton, .MainButton +// Stores state in React (NOT localStorage) +``` + +### **2. PlanCard.tsx** +```tsx +// Карточка тарифа с ценой, features, "Выбрать" кнопкой +// Props: { plan, price, features[], selected, onClick } +// Design: Telegram-style card (rounded, shadow, accent color) +``` + +### **3. SubscriptionStatus.tsx** +```tsx +// Badge: "✅ Активна до 15.03.2026" / "⚠️ Истекла" / "🎁 Trial (7 дней)" +// Color-coded: green/red/blue +// Props: { status, expiryDate, daysRemaining } +``` + +### **4. ReferralProgress.tsx** +```tsx +// Progress bar: [■■■□□] 3/5 friends → +30 days bonus! +// Shows: current referrals, next milestone, bonus amount +// Props: { referralCount, bonusDays } +``` + +--- + +## 🔄 **API Endpoints (уже есть + новые)** + +### **Existing:** +- ✅ POST /api/create-user +- ✅ GET /api/user-subscription +- ✅ POST /api/referral/track +- ✅ GET /api/referral/stats + +### **New (to add):** +```typescript +// Check if user exists by Telegram ID +GET /api/user-check?telegramId=12345 +Response: { exists: boolean, username?: string, status?: string } + +// Get plan details +GET /api/plans +Response: { plans: [{ id, name, price, features[] }] } + +// Create payment link +POST /api/create-payment +Body: { username, plan, amount } +Response: { paymentUrl: string, orderId: string } +``` + +--- + +## 📋 **Implementation Priority** + +### **Phase 1: Core Flow (СЕЙЧАС)** +1. ✅ Упростить n8n workflow (DONE) +2. Create OnboardingFlow.tsx (Welcome → Language → Plan → Create) +3. Update app/page.tsx (detect new vs existing user) +4. Create app/dashboard/page.tsx (show subscription status) +5. Create PlanCard.tsx component + +### **Phase 2: Referral Integration** +6. Update OnboardingFlow to accept ?ref=USERNAME +7. Pass referrerId to /api/create-user +8. Add ReferralProgress.tsx to dashboard +9. Test full referral flow + +### **Phase 3: Payment** +10. Add /api/plans endpoint +11. Add /api/create-payment endpoint +12. Integrate payment gateway (crypto) +13. Add webhook for payment confirmation + +### **Phase 4: Polish** +14. Telegram WebApp SDK integration (BackButton, MainButton, theme) +15. i18n (RU/EN/UZ) +16. Error handling + loading states +17. Analytics (track conversions) + +--- + +## 🎯 **Key Differences from Current Code** + +| Current | New (PRO) | +|---------|-----------| +| Bot имеет 3 команды + кнопки | Bot только /start → WebApp | +| Нет onboarding flow | Full onboarding в Mini App | +| localStorage для данных | API + MariaDB only | +| Нет gamification | Referral progress bar | +| Нет выбора плана в UI | Plan selection screen | +| Нет payment flow | Integrated payment | +| Нет Telegram theme | Uses Telegram.WebApp.themeParams | + +--- + +## 💡 **Pro Tips** + +1. **Telegram WebApp SDK**: используй `Telegram.WebApp.MainButton` для кнопок действий +2. **No localStorage**: только API, чтобы работало на всех устройствах +3. **Gamification**: показывай прогресс реферальной программы (мотивация!) +4. **Mobile-first**: Mini App всегда открывается на мобильном +5. **Fast loading**: минимизируй API calls, cache в React state +6. **Error handling**: graceful fallback если нет Telegram.WebApp +7. **i18n ready**: структура для перевода на другие языки + +--- + +## 🚀 **Next Steps** + +Создам полную структуру Mini App с учетом этой архитектуры! diff --git a/PRICING-LOCATION-ANALYSIS.md b/PRICING-LOCATION-ANALYSIS.md new file mode 100644 index 0000000..8475a21 --- /dev/null +++ b/PRICING-LOCATION-ANALYSIS.md @@ -0,0 +1,1222 @@ +# 🎯 Анализ: Система тарифов с выбором локаций + скидки за период + +## 📊 Текущее состояние + +### Что есть сейчас: +1. ✅ **Тарифы** определены в `/app/plans/page.tsx`: + - Trial (7 дней бесплатно) + - Start (100₽/мес, 1 локация) + - Plus (299₽/мес, 3 локации) + - Max (350₽/мес, 15+ локаций) + +2. ✅ **SetupWizard** (`/components/SetupWizard.tsx`): + - Есть UI для выбора локаций + - Но используется ПОСЛЕ покупки (в wizard'е настройки) + - НЕ влияет на создание пользователя + +3. ✅ **API создания пользователя** (`/app/api/create-user/route.ts`): + - Создает пользователя с фиксированными inbounds + - НЕТ привязки к нодам/локациям + - Используется hardcoded список inbounds: + ```typescript + inbounds: { + vless: ['VLESS TCP', 'VLESS Reality'], + vmess: ['VMess WS'], + trojan: ['Trojan TCP'] + } + ``` + +4. ❌ **Что НЕ работает**: + - Выбор локаций НЕ сохраняется в Marzban + - Нет связи между тарифом и доступными нодами + - Нет скидок за длительные периоды (3 мес, 6 мес, год) + +--- + +## 🔍 Анализ Marzban API + +### Доступные endpoints (из https://panel.umbrix.net/docs): + +#### 1. **Управление нодами** (`/api/nodes`): +```bash +GET /api/nodes # Список всех нод с их параметрами +GET /api/node/{node_id} # Детали конкретной ноды +POST /api/node # Создать ноду +PUT /api/node/{node_id} # Изменить ноду +DELETE /api/node/{node_id} # Удалить ноду +GET /api/nodes/usage # Статистика по нодам +``` + +**Структура ноды** (`NodeResponse`): +```json +{ + "id": 1, + "name": "🇺🇸 USA Node", + "address": "194.113.210.187", + "port": 62050, + "api_port": 62051, + "status": "connected", + "message": null, + "xray_version": "1.8.21", + "usage": { + "uplink": 123456789, + "downlink": 987654321 + } +} +``` + +#### 2. **Inbounds** (`/api/inbounds`): +```bash +GET /api/inbounds # Список всех inbounds (протоколов) +``` + +**Структура inbound**: +```json +{ + "tag": "VLESS TCP", + "protocol": "vless", + "network": "tcp", + "tls": "reality", + "port": 443 +} +``` + +#### 3. **Создание пользователя** (`POST /api/user`): +```json +{ + "username": "test_user", + "status": "active", + "expire": 1735689600, // Unix timestamp + "data_limit": 107374182400, // Bytes + "data_limit_reset_strategy": "no_reset", + "proxies": { + "vmess": {"id": "uuid-here"}, + "vless": {}, + "trojan": {} + }, + "inbounds": { + "vless": ["VLESS TCP", "VLESS Reality"], + "vmess": ["VMess WS"], + "trojan": ["Trojan TCP"] + }, + "note": "Тариф Plus, TG: 12345" +} +``` + +--- + +## 🎨 Новая воронка с выбором локаций + +### Идеальный User Flow: + +``` +1. Пользователь открывает /plans + ↓ +2. Выбирает тариф (Start/Plus/Max) + ↓ +3. Видит варианты периода оплаты: + ┌─────────────────────────────────────┐ + │ ⭕ 1 месяц - 299₽ (без скидки) │ + │ ⭕ 3 месяца - 807₽ (-10% = 897₽) │ + │ ⭕ 6 месяцев - 1523₽ (-15% = 1794₽)│ + │ ⭕ 1 год - 2870₽ (-20% = 3588₽)│ + └─────────────────────────────────────┘ + ↓ +4. Выбирает локации (в зависимости от тарифа): + ┌─────────────────────────────────────┐ + │ Start: 1 локация из списка │ + │ Plus: 3 локации из списка │ + │ Max: Все локации │ + └─────────────────────────────────────┘ + + Список локаций (из Marzban API): + ☐ 🇳🇱 Нидерланды (ping: 15ms) + ☐ 🇩🇪 Германия (ping: 20ms) + ☐ 🇺🇸 США (ping: 120ms) + ☐ 🇸🇬 Сингапур (ping: 180ms) + ☑ 🇯🇵 Япония (ping: 160ms) + ☐ 🇬🇧 Великобритания (ping: 35ms) + ↓ +5. Нажимает "Оплатить" → Payment gateway + ↓ +6. После оплаты → API создает пользователя с выбранными локациями +``` + +--- + +## 🚀 Техническая реализация + +### **Этап 1: Получить список нод из Marzban** + +Создать новый API endpoint `/api/nodes`: + +```typescript +// app/api/nodes/route.ts +import { NextResponse } from 'next/server'; + +const MARZBAN_API = process.env.MARZBAN_PANEL_URL; +const ADMIN_USERNAME = process.env.MARZBAN_ADMIN_USERNAME; +const ADMIN_PASSWORD = process.env.MARZBAN_ADMIN_PASSWORD; + +export async function GET() { + try { + // 1. Получить токен админа + const tokenResponse = await fetch(`${MARZBAN_API}/api/admin/token`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `grant_type=password&username=${ADMIN_USERNAME}&password=${ADMIN_PASSWORD}`, + }); + + const { access_token } = await tokenResponse.json(); + + // 2. Получить список нод + const nodesResponse = await fetch(`${MARZBAN_API}/api/nodes`, { + headers: { 'Authorization': `Bearer ${access_token}` }, + }); + + const nodes = await nodesResponse.json(); + + // 3. Форматировать для фронтенда + const locations = nodes + .filter(node => node.status === 'connected') + .map(node => ({ + id: node.id, + name: node.name, // "🇺🇸 USA Node" + address: node.address, + ping: calculatePing(node.address), // TODO: реальный ping + country: extractCountry(node.name), // "US" + })); + + return NextResponse.json({ success: true, locations }); + } catch (error) { + return NextResponse.json({ success: false, error: error.message }, { status: 500 }); + } +} + +function extractCountry(nodeName: string): string { + // Извлечь код страны из emoji флага + const match = nodeName.match(/🇦-🇿{2}/); + return match ? getFlagCode(match[0]) : 'Unknown'; +} + +function calculatePing(address: string): string { + // TODO: Реальный ping check (или хардкод по регионам) + const mockPings = { + '194.113.210.187': '120ms', // USA + '193.168.175.128': '15ms', // NL + // ... + }; + return mockPings[address] || '50ms'; +} +``` + +--- + +### **Этап 2: Страница выбора тарифа с периодом** + +Обновить `/app/plans/page.tsx`: + +```tsx +'use client'; + +import { useState, useEffect } from 'react'; +import { Check, ChevronRight } from 'lucide-react'; + +type Period = '1month' | '3months' | '6months' | '1year'; +type PlanType = 'start' | 'plus' | 'max'; + +interface Location { + id: number; + name: string; + country: string; + ping: string; +} + +export default function Plans() { + const [selectedPlan, setSelectedPlan] = useState(null); + const [selectedPeriod, setSelectedPeriod] = useState('1month'); + const [selectedLocations, setSelectedLocations] = useState([]); + const [availableLocations, setAvailableLocations] = useState([]); + + // Загрузить локации из API + useEffect(() => { + async function fetchLocations() { + const response = await fetch('/api/nodes'); + const data = await response.json(); + if (data.success) { + setAvailableLocations(data.locations); + } + } + fetchLocations(); + }, []); + + // Тарифы с ценами за разные периоды + const plans = { + start: { + name: 'Start', + locations: 1, + prices: { + '1month': 100, + '3months': 270, // -10% + '6months': 510, // -15% + '1year': 960, // -20% + } + }, + plus: { + name: 'Plus', + locations: 3, + prices: { + '1month': 299, + '3months': 807, // -10% + '6months': 1523, // -15% + '1year': 2870, // -20% + } + }, + max: { + name: 'Max', + locations: 999, // Все + prices: { + '1month': 350, + '3months': 945, // -10% + '6months': 1785, // -15% + '1year': 3360, // -20% + } + } + }; + + const handlePlanSelect = (plan: PlanType) => { + setSelectedPlan(plan); + }; + + const handlePeriodSelect = (period: Period) => { + setSelectedPeriod(period); + }; + + const handleLocationToggle = (locationId: number) => { + if (!selectedPlan) return; + + const maxLocations = plans[selectedPlan].locations; + + if (selectedLocations.includes(locationId)) { + setSelectedLocations(selectedLocations.filter(id => id !== locationId)); + } else if (maxLocations === 999 || selectedLocations.length < maxLocations) { + setSelectedLocations([...selectedLocations, locationId]); + } + }; + + const handlePurchase = async () => { + if (!selectedPlan || selectedLocations.length === 0) { + alert('Выберите тариф и хотя бы одну локацию'); + return; + } + + // Отправить на payment gateway + const response = await fetch('/api/create-payment', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + plan: selectedPlan, + period: selectedPeriod, + locations: selectedLocations, + telegramId: getTelegramId(), + }), + }); + + const data = await response.json(); + if (data.success) { + window.location.href = data.paymentUrl; + } + }; + + const getDiscountPercent = (period: Period): number => { + const discounts = { + '1month': 0, + '3months': 10, + '6months': 15, + '1year': 20, + }; + return discounts[period]; + }; + + const calculateSavings = (plan: PlanType, period: Period): number => { + const basePrices = plans[plan].prices; + const monthlyPrice = basePrices['1month']; + const months = { '1month': 1, '3months': 3, '6months': 6, '1year': 12 }[period]; + const fullPrice = monthlyPrice * months; + const discountedPrice = basePrices[period]; + return fullPrice - discountedPrice; + }; + + return ( +
+

Выбор тарифа

+ + {/* Шаг 1: Выбор тарифа */} + {!selectedPlan && ( +
+ {Object.entries(plans).map(([key, plan]) => ( + + ))} +
+ )} + + {/* Шаг 2: Выбор периода */} + {selectedPlan && !selectedLocations.length && ( +
+ + +

Выберите период

+ + {Object.entries(plans[selectedPlan].prices).map(([period, price]) => { + const discount = getDiscountPercent(period as Period); + const savings = calculateSavings(selectedPlan, period as Period); + + return ( + + ); + })} +
+ )} + + {/* Шаг 3: Выбор локаций */} + {selectedPlan && selectedPeriod && ( +
+ + +

Выберите локации

+

+ {plans[selectedPlan].locations === 999 + ? 'Доступны все локации' + : `Выберите до ${plans[selectedPlan].locations} локаций`} +

+ +
+ {availableLocations.map(location => ( + + ))} +
+ + {selectedLocations.length > 0 && ( + + )} +
+ )} +
+ ); +} + +function getTelegramId() { + return (window as any).Telegram?.WebApp?.initDataUnsafe?.user?.id || null; +} +``` + +--- + +### **Этап 3: Обновить API создания пользователя** + +Модифицировать `/app/api/create-user/route.ts`: + +```typescript +export async function POST(request: NextRequest) { + const { + planType, + period, // NEW: '1month' | '3months' | '6months' | '1year' + locationIds, // NEW: [1, 3, 5] - выбранные ID нод + telegramId, + telegramUsername, + firstName, + lastName + } = await request.json(); + + // 1. Получить токен админа (same as before) + + // 2. Определить параметры тарифа с учетом периода + const planConfig = getPlanConfig(planType, period); + + // 3. Получить список нод и их inbounds + const nodesResponse = await fetch(`${MARZBAN_API}/api/nodes`, { + headers: { 'Authorization': `Bearer ${access_token}` }, + }); + const allNodes = await nodesResponse.json(); + + // 4. Фильтровать только выбранные ноды + const selectedNodes = allNodes.filter(node => locationIds.includes(node.id)); + + // 5. Получить inbounds выбранных нод + const inboundsResponse = await fetch(`${MARZBAN_API}/api/inbounds`, { + headers: { 'Authorization': `Bearer ${access_token}` }, + }); + const allInbounds = await inboundsResponse.json(); + + // 6. Создать mapping inbounds для выбранных нод + const userInbounds = { + vless: [], + vmess: [], + trojan: [] + }; + + selectedNodes.forEach(node => { + // Добавить inbounds этой ноды в конфиг пользователя + allInbounds + .filter(inbound => inbound.node_id === node.id) + .forEach(inbound => { + if (inbound.protocol === 'vless') { + userInbounds.vless.push(inbound.tag); + } else if (inbound.protocol === 'vmess') { + userInbounds.vmess.push(inbound.tag); + } else if (inbound.protocol === 'trojan') { + userInbounds.trojan.push(inbound.tag); + } + }); + }); + + // 7. Создать пользователя с кастомными inbounds + const createUserResponse = await fetch(`${MARZBAN_API}/api/user`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${access_token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: username, + status: 'active', + expire: planConfig.expireTimestamp, + data_limit: planConfig.dataLimitBytes, + data_limit_reset_strategy: 'no_reset', + proxies: { + vmess: { id: randomUUID() }, + vless: {}, + trojan: {} + }, + inbounds: userInbounds, // <-- ГЛАВНОЕ ИЗМЕНЕНИЕ! + note: `Тариф: ${planConfig.name}, Период: ${period}, Локации: ${selectedNodes.map(n => n.name).join(', ')}, TG: ${telegramId}`, + }), + }); + + // Rest of the code... +} + +function getPlanConfig(planType: PlanType, period: Period) { + const basePlans = { + trial: { name: 'Trial', dataLimitGB: 10, durationDays: 7 }, + start: { name: 'Start', dataLimitGB: 50, basePriceRub: 100 }, + plus: { name: 'Plus', dataLimitGB: 299, basePriceRub: 299 }, + max: { name: 'Max', dataLimitGB: 999999, basePriceRub: 350 }, + }; + + const periodMultipliers = { + '1month': { months: 1, discount: 0 }, + '3months': { months: 3, discount: 0.10 }, + '6months': { months: 6, discount: 0.15 }, + '1year': { months: 12, discount: 0.20 }, + }; + + const plan = basePlans[planType]; + const periodConfig = periodMultipliers[period]; + + const durationDays = periodConfig.months * 30; + const fullPrice = plan.basePriceRub * periodConfig.months; + const discountedPrice = Math.round(fullPrice * (1 - periodConfig.discount)); + + return { + name: plan.name, + dataLimitBytes: plan.dataLimitGB * 1024 * 1024 * 1024, + expireTimestamp: Math.floor(Date.now() / 1000) + (durationDays * 24 * 60 * 60), + priceRub: discountedPrice, + periodDays: durationDays, + }; +} +``` + +--- + +### **Этап 4: Payment gateway integration** + +Создать `/app/api/create-payment/route.ts`: + +```typescript +// Интеграция с платежным шлюзом (Stripe / YooKassa / Coinbase) +export async function POST(request: NextRequest) { + const { plan, period, locations, telegramId } = await request.json(); + + const planConfig = getPlanConfig(plan, period); + + // 1. Создать заказ в БД (pending payment) + const orderId = createOrder({ + telegramId, + plan, + period, + locations, + amount: planConfig.priceRub, + status: 'pending', + }); + + // 2. Создать платежную ссылку + const paymentUrl = await createStripeCheckout({ + amount: planConfig.priceRub, + currency: 'RUB', + metadata: { orderId, telegramId }, + successUrl: `https://app.umbrix.net/payment-success?order=${orderId}`, + cancelUrl: `https://app.umbrix.net/plans`, + }); + + return NextResponse.json({ success: true, paymentUrl }); +} +``` + +--- + +### **Этап 5: Webhook для обработки оплаты** + +Создать `/app/api/payment-webhook/route.ts`: + +```typescript +// Webhook от платежного провайдера +export async function POST(request: NextRequest) { + const payload = await request.json(); + + // 1. Verify signature (Stripe/YooKassa) + + // 2. Проверить статус оплаты + if (payload.status === 'succeeded') { + const { orderId } = payload.metadata; + + // 3. Получить данные заказа из БД + const order = getOrder(orderId); + + // 4. Создать пользователя в Marzban + await fetch('/api/create-user', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + planType: order.plan, + period: order.period, + locationIds: order.locations, + telegramId: order.telegramId, + }), + }); + + // 5. Обновить статус заказа + updateOrder(orderId, { status: 'completed' }); + + // 6. Отправить уведомление в Telegram + sendTelegramMessage(order.telegramId, '✅ Оплата прошла! Ваш VPN активирован.'); + } + + return NextResponse.json({ success: true }); +} +``` + +--- + +## 🤖 Обновление n8n бота + +### Добавить новые шаги в workflow: + +```json +{ + "nodes": [ + { + "name": "Show Plans Menu", + "type": "n8n-nodes-base.telegram", + "parameters": { + "chatId": "={{ $json.userId }}", + "text": "💎 Выберите тариф:", + "replyMarkup": { + "inline_keyboard": [ + [{"text": "🌍 Start (100₽/мес)", "web_app": {"url": "https://app.umbrix.net/plans?plan=start"}}], + [{"text": "🌎 Plus (299₽/мес)", "web_app": {"url": "https://app.umbrix.net/plans?plan=plus"}}], + [{"text": "🌏 Max (350₽/мес)", "web_app": {"url": "https://app.umbrix.net/plans?plan=max"}}] + ] + } + } + } + ] +} +``` + +**ВАЖНО**: Все тарифы теперь открывают Mini App с pre-selected планом → выбор периода → выбор локаций → оплата. + +--- + +## 📋 Чеклист внедрения + +### Неделя 1: Backend API +- [ ] Создать `/api/nodes` - список доступных локаций из Marzban +- [ ] Обновить `/api/create-user` - добавить параметры `period` и `locationIds` +- [ ] Добавить функцию `getPlanConfig(planType, period)` с расчетом скидок +- [ ] Реализовать mapping нод → inbounds для создания пользователя + +### Неделя 2: Frontend UI +- [ ] Переработать `/app/plans/page.tsx` - мультишаговый выбор: + - Шаг 1: Тариф (Start/Plus/Max) + - Шаг 2: Период (1м/3м/6м/1год) с показом скидок + - Шаг 3: Локации (чекбоксы с ограничением по тарифу) +- [ ] Добавить компонент `PeriodSelector` с расчетом экономии +- [ ] Добавить компонент `LocationSelector` с ping и флагами стран +- [ ] Стилизация под Telegram Design System + +### Неделя 3: Payment Integration +- [ ] Выбрать платежный шлюз (YooKassa для RU, Stripe для международных) +- [ ] Создать `/api/create-payment` - генерация платежных ссылок +- [ ] Создать `/api/payment-webhook` - обработка успешных оплат +- [ ] Создать БД таблицу `orders` для хранения pending платежей +- [ ] Добавить страницу `/payment-success` - редирект после оплаты + +### Неделя 4: n8n Bot Update +- [ ] Добавить inline кнопки с pre-selected тарифами +- [ ] Обновить WebApp URLs с query параметрами `?plan=start` +- [ ] Добавить уведомления о успешной оплате +- [ ] Добавить команду `/renew` для продления подписки + +### Неделя 5: Testing & QA +- [ ] E2E тесты полной воронки (выбор → оплата → создание пользователя) +- [ ] Тестирование на разных тарифах и периодах +- [ ] Проверка корректности создания inbounds для выбранных нод +- [ ] Load testing (100 одновременных покупок) +- [ ] Security audit payment webhook + +--- + +## 💡 Кардинальные предложения + +### **❌ Предложение 1: Динамическое ценообразование - ОТКЛОНЕНО** + +~~Вместо фиксированных цен - учитывать нагрузку на ноды~~ + +**Причина отклонения**: Усложняет ценообразование для пользователей, все локации должны быть одинаковы по цене. + +### **Предложение 2: Гибкие пакеты трафика** + +Разделить тарифы на "Устройства" и "Трафик": + +``` +Base Plan (1 устройство): + ├── 50 ГБ - 100₽/мес + ├── 200 ГБ - 250₽/мес + └── ∞ ГБ - 350₽/мес + ++100₽ за каждое доп. устройство ++50₽ за каждую доп. локацию +``` + +### **⚠️ Предложение 3: Семейные планы - ТРЕБУЕТ АНАЛИЗА** + +``` +Family Plan (5 устройств, безлимит): +- 499₽/мес для семьи +- Каждый член семьи выбирает свои локации +- Общий аккаунт с sub-users в Marzban +``` + +**Проблема**: Marzban не имеет встроенного механизма ограничения количества устройств на уровне API. + +**Варианты решения**: + +#### Вариант 1: Мониторинг `online_at` (рекомендуемый) +```typescript +// GET /api/user/{username} возвращает: +{ + "online_at": "2026-02-08T10:30:00Z", // Последняя активность + "sub_last_user_agent": "v2rayNG/1.8.5" +} + +// Логика: +// - online_at обновляется при каждом подключении +// - Если разница между текущим временем и online_at < 5 минут - устройство активно +// - Подсчитываем активные сессии через периодический poll +``` + +**Ограничения**: +- ❌ НЕТ прямого поля `active_connections` или `device_count` +- ❌ НЕТ поля `ip_limit` в UserCreate +- ✅ ЕСТЬ `online_at` - последняя активность +- ✅ ЕСТЬ `sub_last_user_agent` - информация об устройстве + +**Реализация** (см. детали ниже в разделе "Семейные планы: Детальный анализ") + +#### Вариант 2: Ограничение на уровне Xray (требует кастомных патчей) +- Модифицировать `xray_config.json` с ограничением по IP +- Требует fork Marzban с кастомной логикой +- **НЕ рекомендуется** для MVP + +#### Вариант 3: IP-based limiting (приблизительный) +- Собирать статистику подключений через `/api/user/{username}/usage` +- Отслеживать уникальные IP адреса +- Ограничение: один пользователь может иметь несколько устройств с одним IP (домашняя сеть) + +**Решение**: Отложить семейные планы на V2.0 до реализации мониторинга устройств. + +### **✅ Предложение 4: Gamification - УЖЕ РЕАЛИЗОВАНО** + +**Текущая система рефералов**: +```typescript +// /api/referral/track - отслеживание приглашений +// /api/referral/stats - статистика пользователя + +Бонусы: +- +7 дней за каждого друга +- +30 дней milestone bonus за каждые 5 друзей + +Формула: +bonusDays = (referralCount * 7) + (Math.floor(referralCount / 5) * 30) + +Примеры: +1 друг → +7 дней +5 друзей → +35 дней (+30 milestone) +10 друзей → +70 дней (+60 milestone) = 130 дней +``` + +**Что можно улучшить**: +- Добавить визуальные badges (Bronze/Silver/Gold) +- Добавить leaderboard на странице `/referral` +- Уведомления в Telegram при достижении milestone + +--- + +## 🔥 Критические улучшения воронки + +### **1. A/B Testing периодов оплаты** + +```typescript +// Показывать разным пользователям разные default периоды +const defaultPeriod = userId % 2 === 0 ? '3months' : '6months'; + +// Трекинг конверсии: +// - Какой период чаще выбирают? +// - Какая скидка конвертирует лучше? +``` + +### **2. Urgency триггеры** + +``` +⏰ Специальное предложение! +🔥 -30% на годовую подписку (осталось 2 часа) +💰 При оплате сегодня - бонус +7 дней +``` + +### **3. Social proof** + +``` +✅ Уже 1,234 пользователей выбрали тариф Plus +⭐ 4.9/5 - средняя оценка +🌍 Топ-3 локации: 🇳🇱 🇩🇪 🇺🇸 +``` + +### **4. Упрощенная воронка для новичков** + +``` +Вместо 3 шагов (тариф → период → локации): + +"Быстрый старт" кнопка: +→ Plus (3 месяца) + Авто-выбор 3 лучших локаций по ping +→ Сразу на оплату (экономия 807₽ вместо 897₽) +``` + +--- + +## 🎯 Priority Roadmap + +### **MVP (2 недели)**: +1. ✅ API `/api/nodes` - список локаций +2. ✅ Обновить `/app/plans/page.tsx` - мультишаговый выбор +3. ✅ Обновить `/api/create-user` - кастомные inbounds +4. ⚠️ YooKassa integration (payment gateway) + +### **V1.1 (4 недели)**: +5. Скидки за период (3м/6м/1год) +6. Показ экономии при выборе периода +7. n8n bot updates + +### **V1.2 (6 недель)**: +8. ~~Динамическое ценообразование~~ ❌ ОТКЛОНЕНО +9. A/B testing периодов +10. Analytics dashboard (конверсия по воронке) + +### **V2.0 (8 недель)**: +11. **Device Monitor Service** - мониторинг устройств через Xray логи +12. Семейные планы (после реализации п.11) +13. Gamification улучшения (badges, leaderboard) +14. Автоматическое продление подписки + +--- + +## ❓ Вопросы для обсуждения + +1. **Какой payment gateway использовать?** + - YooKassa (для РФ) ✅ + - Stripe (международный) ✅ + - Crypto (Coinbase Commerce) ? + +2. **Какие скидки оптимальны?** + - 3 мес: -10% ✅ + - 6 мес: -15% ✅ + - 1 год: -20% ✅ + +3. **~~Нужна ли локация-специфичная цена?~~** ❌ ОТКЛОНЕНО + - Все локации одинаковы ✅ + +4. **Trial период - как ограничить abuse?** + - 1 trial на Telegram ID ✅ + - Требовать номер телефона? ⚠️ + - Требовать payment method (0₽ charge)? ⚠️ + +5. **Семейные планы - реализовывать в MVP?** + - ❌ Отложить на V2.0 + - ⚠️ Требует Device Monitor Service (40 часов разработки) + - ✅ Для MVP - добавить в ToS ограничение без технического enforcement + +--- + +## 📞 Следующие шаги + +1. ✅ Проанализировать текущее состояние +2. ✅ Изучить Marzban API +3. ✅ Спроектировать новую воронку +4. ✅ Проанализировать ограничения устройств +5. ✅ Изучить реферальную систему +6. ⏳ **Согласовать со stakeholder'ами** +7. ⏳ **Начать разработку MVP** + +**Готово к обсуждению!** 🚀 + +--- + +## 🔬 ДОПОЛНЕНИЕ: Семейные планы - Детальный анализ ограничения устройств + +### Проблема: +Marzban **НЕ имеет встроенного механизма ограничения количества одновременных подключений** (devices/connections). + +### Анализ Marzban API: + +#### ❌ Что НЕ доступно в API: +```json +// POST /api/user - UserCreate schema НЕ имеет: +{ + "ip_limit": 5, // ❌ Нет такого поля + "device_limit": 5, // ❌ Нет такого поля + "max_connections": 5, // ❌ Нет такого поля + "active_sessions": [] // ❌ Нет такого поля +} +``` + +#### ✅ Что ДОСТУПНО в API: +```json +// GET /api/user/{username} - UserResponse: +{ + "username": "test_user", + "status": "active", + "online_at": "2026-02-08T10:30:00Z", // ✅ Последняя активность + "sub_last_user_agent": "v2rayNG/1.8.5", // ✅ User-Agent устройства + "used_traffic": 123456789, + "data_limit": 107374182400 +} + +// GET /api/user/{username}/usage - UsageResponse: +{ + "username": "test_user", + "usages": [ + { + "date": "2026-02-08", + "upload": 12345, + "download": 67890, + "total": 80235 + } + ] +} +``` + +### Варианты решения: + +--- + +### **Вариант 1: Server-side мониторинг через `online_at` (Рекомендуемый для MVP)** + +**Как работает**: +1. Периодически (каждые 30 секунд) опрашиваем `GET /api/user/{username}` +2. Проверяем `online_at` - если разница < 2 минуты → устройство активно +3. Если активных устройств > лимита → временно блокируем (`status: 'disabled'`) + +**Реализация**: + +```typescript +// app/api/family-plans/monitor-devices/route.ts + +import { NextRequest, NextResponse } from 'next/server'; + +const MARZBAN_API = process.env.MARZBAN_PANEL_URL; +const ADMIN_USERNAME = process.env.MARZBAN_ADMIN_USERNAME; +const ADMIN_PASSWORD = process.env.MARZBAN_ADMIN_PASSWORD; + +interface FamilyPlanUser { + username: string; + deviceLimit: number; + lastCheck: Date; +} + +// В production - хранить в Redis или БД +const familyPlanUsers: Map = new Map(); + +// Cron job - запускать каждые 30 секунд +export async function POST(request: NextRequest) { + try { + // 1. Получить токен админа + const tokenResponse = await fetch(`${MARZBAN_API}/api/admin/token`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `grant_type=password&username=${ADMIN_USERNAME}&password=${ADMIN_PASSWORD}`, + }); + + const { access_token } = await tokenResponse.json(); + + // 2. Проверить каждого пользователя Family Plan + for (const [username, config] of familyPlanUsers.entries()) { + const userResponse = await fetch(`${MARZBAN_API}/api/user/${username}`, { + headers: { 'Authorization': `Bearer ${access_token}` }, + }); + + const user = await userResponse.json(); + + // 3. Проверить активность + const onlineAt = new Date(user.online_at); + const now = new Date(); + const minutesSinceActive = (now.getTime() - onlineAt.getTime()) / 1000 / 60; + + // Если пользователь активен (подключался менее 2 минут назад) + if (minutesSinceActive < 2) { + // ПРОБЛЕМА: Мы НЕ МОЖЕМ узнать сколько устройств подключено СЕЙЧАС! + // online_at показывает только ПОСЛЕДНЮЮ активность + + // Вариант решения: собирать статистику подключений + // Но это требует доступа к логам Xray на уровне нод + } + } + + return NextResponse.json({ success: true, message: 'Device monitoring completed' }); + } catch (error) { + return NextResponse.json({ success: false, error: error.message }, { status: 500 }); + } +} +``` + +**Проблема**: `online_at` показывает только **последнюю** активность, а НЕ **количество активных сессий**. + +--- + +### **Вариант 2: IP-based мониторинг (Приблизительный)** + +**Идея**: Отслеживать уникальные IP адреса через usage API. + +**Проблема**: +- Несколько устройств могут быть за одним IP (домашний роутер) +- Один телефон может менять IP (мобильный интернет) +- **НЕ точный** метод + +--- + +### **Вариант 3: Кастомизация Xray config (Профессиональный)** + +**Идея**: Модифицировать `xray_config.json` для ограничения по IP. + +```json +{ + "inbounds": [ + { + "tag": "VLESS TCP", + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "uuid-here", + "email": "test_user@marzban", + "level": 0, + "maxIps": 5 // ← Кастомное поле (требует патч Xray) + } + ] + } + } + ] +} +``` + +**Проблема**: +- Требует fork Xray с кастомной логикой +- Нужно пропатчить Marzban для поддержки `maxIps` +- **Сложно** для MVP + +--- + +### **Вариант 4: Использовать sub-users (Встроенная функция Marzban)** + +**Идея**: Создать **родительский аккаунт** + **4 дочерних аккаунта**. + +```typescript +// Семейный план: +// 1. Создать главного пользователя: family_john_doe +// 2. Создать 4 sub-users: +// - family_john_doe_device_1 +// - family_john_doe_device_2 +// - family_john_doe_device_3 +// - family_john_doe_device_4 +// - family_john_doe_device_5 + +// Каждый sub-user = отдельное устройство с собственной подпиской +``` + +**Преимущества**: +- ✅ Работает "из коробки" в Marzban +- ✅ Каждое устройство = отдельная подписка +- ✅ Легко контролировать (5 username'ов) + +**Недостатки**: +- ❌ Пользователю нужно выдавать 5 разных subscription URLs +- ❌ Неудобно для конечного пользователя +- ❌ Не настоящий "Family Plan" (просто 5 отдельных аккаунтов) + +--- + +### **Рекомендация для V1.0**: + +#### MVP подход: +1. **Отказаться от ограничения устройств на техническом уровне** +2. **Полагаться на честность пользователей** +3. **Добавить в ToS**: "Семейный план предназначен для использования в одной семье (до 5 устройств)" +4. **Мониторить аномальный трафик**: если один аккаунт генерирует 1TB/день - вручную проверить + +#### V2.0 подход (после MVP): +1. Реализовать **Server-side webhook** для мониторинга Xray логов +2. Парсить логи подключений в реальном времени: + ```bash + # Xray лог показывает: + [Info] accepted tcp:xxx.xxx.xxx.xxx:12345 [email:test_user@marzban] + ``` +3. Собирать статистику активных IP за последние 5 минут +4. Если > deviceLimit → отправить уведомление или временно заблокировать + +--- + +### **Архитектура для V2.0**: + +``` +┌─────────────────────────────────────────┐ +│ Marzban Panel (API) │ +│ - Создание пользователей │ +│ - Управление подписками │ +└────────────┬────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Xray Core (на каждой ноде) │ +│ - Обработка трафика │ +│ - Генерация логов подключений │ +└────────────┬────────────────────────────┘ + │ + │ (Webhooks или log streaming) + ▼ +┌─────────────────────────────────────────┐ +│ Device Monitor Service (NEW!) │ +│ - Парсинг логов Xray │ +│ - Подсчет активных IP per user │ +│ - Проверка лимитов устройств │ +│ - Автоблокировка при превышении │ +└────────────┬────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ PostgreSQL / Redis │ +│ - Хранение активных сессий │ +│ - Хранение семейных планов │ +└─────────────────────────────────────────┘ +``` + +**Стоимость разработки**: ~40 часов (Device Monitor Service) + +--- + +### **Итоговое решение**: + +| Вариант | Точность | Сложность | Время реализации | Рекомендация | +|---------|----------|-----------|------------------|--------------| +| 1. `online_at` мониторинг | ❌ Низкая | Средняя | 8 часов | ⚠️ Не точно | +| 2. IP-based | ❌ Низкая | Низкая | 4 часа | ⚠️ Не рекомендуется | +| 3. Xray патчи | ✅ Высокая | Очень высокая | 80+ часов | ❌ Слишком сложно | +| 4. Sub-users | ✅ Высокая | Средняя | 12 часов | ⚠️ Неудобно для пользователей | +| 5. **Без ограничений + ToS** | N/A | Очень низкая | 0 часов | ✅ **Для MVP** | +| 6. Log-based monitor (V2.0) | ✅ Высокая | Высокая | 40 часов | ✅ **Для V2.0** | + +**Вывод**: Для MVP - **НЕ реализовывать техническое ограничение устройств**. Добавить в ToS и мониторить аномальный трафик вручную. + +Для V2.0 - разработать Device Monitor Service с парсингом Xray логов. + +**Готово к обсуждению!** 🚀