Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03fc4abd2a | |||
| f280a37818 | |||
| f100ec62d4 | |||
| cd827612d9 | |||
| 50da2f82c8 | |||
| 4c814ec5f4 | |||
| fb8c02d5ab | |||
| 465ad3d1ad | |||
| 2bfaab7d16 | |||
| e356542df7 | |||
| a3dfbdce3b | |||
| a666e8d248 | |||
| 9ef8df22a8 | |||
| a1471c8b9a | |||
| a99a14922c | |||
| ce954ad775 | |||
| efba7c10d0 | |||
| cd10c131b2 | |||
| 3e4cb8dfa2 | |||
| 6471ab547c | |||
| c10bf07124 | |||
| 4e05882522 | |||
| 061b292d92 | |||
| 779a6c309a | |||
| c79c71f209 | |||
| d4dbce5606 | |||
| e53d99223d | |||
| ec7274e0c9 | |||
| 8fe631a98e | |||
| bd831bd92b | |||
| 9b31ba8f3d | |||
| 3759be333a | |||
| 555894b26c | |||
| bb636c003c | |||
| 40c7b4aebd | |||
| 028bd4888b | |||
| 27d64e361a | |||
| 6fc7dd49dd | |||
| b835ba1d65 | |||
| 811e985087 | |||
| c16733b93d | |||
| 23ab3c5198 | |||
| 9b8f121e69 | |||
| 86b1b95fb9 | |||
| 9418681a00 | |||
| 7ebd937e2c | |||
| 25c404f999 |
+3
-1
@@ -21,8 +21,10 @@ POSTGRES_USER=remnawave_user
|
||||
POSTGRES_PASSWORD=secure_password_123
|
||||
|
||||
# ===== REMNAWAVE API =====
|
||||
REMNAWAVE_API_URL=
|
||||
REMNAWAVE_API_URL=https://panel.example.com
|
||||
REMNAWAVE_API_KEY=
|
||||
# Для панелей установленных скриптом eGames прописывать ключ в формате XXXXXXX:DDDDDDDD - https://panel.example.com/auth/login?XXXXXXX=DDDDDDDD
|
||||
REMNAWAVE_SECRET_KEY=your_secret_key_here
|
||||
|
||||
# ===== ТРИАЛ ПОДПИСКА =====
|
||||
TRIAL_DURATION_DAYS=3
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
- 📈 **Масштабируемость**
|
||||
- 🔧 **Мониторинг** - Prometheus, Grafana, health checks
|
||||
- 🔧 **Режим технических работ** - Ручное включение + Мониторинг системы, который в случае падении панели Remnawave переведет бота в режим технических работ и обратно - отключит его, если панель поднимется.
|
||||
- Интеграция с системой защиты панели Remnawave через куки-аутентификацию, которая используется в [remnawave-reverse-proxy](https://github.com/eGamesAPI/remnawave-reverse-proxy) для скрытия панели от несанкционированного доступа.
|
||||
|
||||
---
|
||||
|
||||
@@ -113,8 +114,10 @@ POSTGRES_USER=remnawave_user
|
||||
POSTGRES_PASSWORD=secure_password_123
|
||||
|
||||
# ===== REMNAWAVE API =====
|
||||
REMNAWAVE_API_URL=
|
||||
REMNAWAVE_API_URL=https://panel.example.com
|
||||
REMNAWAVE_API_KEY=
|
||||
# Для панелей установленных скриптом eGames прописывать ключ в формате XXXXXXX:DDDDDDDD - https://panel.example.com/auth/login?XXXXXXX=DDDDDDDD
|
||||
REMNAWAVE_SECRET_KEY=your_secret_key_here
|
||||
|
||||
# ===== ТРИАЛ ПОДПИСКА =====
|
||||
TRIAL_DURATION_DAYS=3
|
||||
@@ -431,16 +434,16 @@ your-domain.com {
|
||||
|
||||
```bash
|
||||
# Быстрый старт
|
||||
docker compose up -d --build
|
||||
docker compose up -d
|
||||
|
||||
# Статус сервисов
|
||||
docker compose ps
|
||||
|
||||
# Логи
|
||||
docker compose logs -f bot
|
||||
docker compose logs
|
||||
|
||||
# Перезапуск
|
||||
docker compose restart bot
|
||||
docker compose restart
|
||||
|
||||
# Остановка
|
||||
docker compose down
|
||||
@@ -462,26 +465,30 @@ docker compose down -v --remove-orphans
|
||||
🛒 **Умная покупка подписок**
|
||||
- 📅 Выбор периода (14-360 дней) (С возможностью настройки выводимых периодов 14/30/60/90/180/360 дней)
|
||||
- 📊 Настройка трафика (5GB - безлимит) (Можно откоючить данный шаг, всем подпискам выдавать единый)
|
||||
- 🌍 Выбор стран через сквады
|
||||
- 📱 Количество устройств (1-10)
|
||||
- 🌍 Выбор стран через сквады (Доступно при наличие двух стран(сквадов) в продаже)
|
||||
- 📱 Количество устройств (1-5)
|
||||
|
||||
🧪 **Тестовая подписка**
|
||||
- Получение настраиваемой разовой тестовой подписки
|
||||
- Уведомления об истечении с предложением о переходе н платную версию
|
||||
|
||||
💰 **Удобные платежи**
|
||||
- ⭐ Telegram Stars
|
||||
- 💳 Tribute
|
||||
- 💳 YooKassa
|
||||
- 🎁 Реферальные бонусы
|
||||
- Детальная история транзакций
|
||||
|
||||
📱 **Управление подписками**
|
||||
- 📈 Просмотр статистики использования
|
||||
- 🔄 Автопродление с баланса
|
||||
- 🔄 Сброс/увеличение трафика
|
||||
- 🌍 Смена стран на лету
|
||||
- Устройства с возможностью докупки/сброса устройств)
|
||||
- 🌍 Смена стран на лету (Доступно при наличие двух стран(сквадов) в продаже)
|
||||
- 📱 Настройка устройств с возможностью докупки(до 10ти устройств) + сброса устройств
|
||||
|
||||
🎁 **Бонусная система**
|
||||
- 🎫 Промокоды на деньги/дни
|
||||
- 👥 Реферальная программа
|
||||
- 🆓 Бесплатный триал
|
||||
- 🔔 Ежедневные уведомления
|
||||
|
||||
</td>
|
||||
@@ -503,7 +510,7 @@ docker compose down -v --remove-orphans
|
||||
🎫 **Промо-система**
|
||||
- 🎁 Создание промокодов (деньги/дни/длинный триал)
|
||||
- 📊 Статистика использования
|
||||
- ⚙️ Полное редактирование промокодов
|
||||
- ⚙️ Полное редактирование промокодов (Изминение условий, активация/деактивация/удаление + статистика применения)
|
||||
|
||||
🖥️ **Мониторинг системы**
|
||||
- 💚 Состояние Remnawave панели
|
||||
@@ -517,6 +524,16 @@ docker compose down -v --remove-orphans
|
||||
- 🔔 Автоуведомления о продлении
|
||||
- 💬 Система поддержки
|
||||
- 📝 Настройка правил сервиса
|
||||
- Поддержка HTML разметки
|
||||
|
||||
🚧 **Режим технических работ**
|
||||
- Ручное включение/отключение с указанием причины
|
||||
- Включение/выключение мониторинга состояния панели (проверяет соеденение до панели раз в 30 сек(можно изменить промежуток)
|
||||
- Принудительаная проверка API
|
||||
|
||||
📖 **Правила сервиса**
|
||||
- Настройка правил сервиса
|
||||
- Просмотр текущих правил
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -16,6 +16,7 @@ class Settings(BaseSettings):
|
||||
|
||||
REMNAWAVE_API_URL: str
|
||||
REMNAWAVE_API_KEY: str
|
||||
REMNAWAVE_SECRET_KEY: Optional[str] = None
|
||||
|
||||
TRIAL_DURATION_DAYS: int = 3
|
||||
TRIAL_TRAFFIC_LIMIT_GB: int = 10
|
||||
|
||||
@@ -512,7 +512,7 @@ async def get_subscription_renewal_cost(
|
||||
if subscription.traffic_limit_gb > 0:
|
||||
traffic_cost = TRAFFIC_PRICES.get(subscription.traffic_limit_gb, 0)
|
||||
|
||||
devices_cost = max(0, subscription.device_limit - 1) * settings.PRICE_PER_DEVICE
|
||||
devices_cost = max(0, subscription.device_limit - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
|
||||
|
||||
total_cost = base_price + servers_cost + traffic_cost + devices_cost
|
||||
|
||||
|
||||
Vendored
+34
-11
@@ -8,6 +8,7 @@ import aiohttp
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from urllib.parse import urlparse, urljoin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -33,7 +34,7 @@ class RemnaWaveUser:
|
||||
username: str
|
||||
status: UserStatus
|
||||
used_traffic_bytes: int
|
||||
lifetime_used_traffic_bytes: int # Новое поле
|
||||
lifetime_used_traffic_bytes: int
|
||||
traffic_limit_bytes: int
|
||||
traffic_limit_strategy: TrafficLimitStrategy
|
||||
expire_at: datetime
|
||||
@@ -92,10 +93,12 @@ class RemnaWaveAPIError(Exception):
|
||||
|
||||
class RemnaWaveAPI:
|
||||
|
||||
def __init__(self, base_url: str, api_key: str):
|
||||
def __init__(self, base_url: str, api_key: str, secret_key: Optional[str] = None):
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.api_key = api_key
|
||||
self.secret_key = secret_key
|
||||
self.session: Optional[aiohttp.ClientSession] = None
|
||||
self.authenticated = False
|
||||
|
||||
def _detect_connection_type(self) -> str:
|
||||
parsed = urlparse(self.base_url)
|
||||
@@ -124,18 +127,29 @@ class RemnaWaveAPI:
|
||||
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.api_key}',
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-Forwarded-Proto': 'https',
|
||||
'X-Forwarded-For': '127.0.0.1',
|
||||
'X-Real-IP': '127.0.0.1'
|
||||
}
|
||||
|
||||
cookies = None
|
||||
if self.secret_key:
|
||||
if ':' in self.secret_key:
|
||||
key_name, key_value = self.secret_key.split(':', 1)
|
||||
cookies = {key_name: key_value}
|
||||
logger.debug(f"🍪 Используем куки: {key_name}=***")
|
||||
else:
|
||||
cookies = {self.secret_key: self.secret_key}
|
||||
logger.debug(f"🍪 Используем куки: {self.secret_key}=***")
|
||||
|
||||
connector_kwargs = {}
|
||||
|
||||
if conn_type == "local":
|
||||
logger.debug("🏠 Использую локальные заголовки proxy")
|
||||
headers.update({
|
||||
'X-Forwarded-For': '127.0.0.1',
|
||||
'X-Forwarded-Proto': 'https',
|
||||
'X-Forwarded-Host': 'localhost',
|
||||
'X-Real-IP': '127.0.0.1',
|
||||
'Host': 'localhost'
|
||||
})
|
||||
|
||||
@@ -152,11 +166,18 @@ class RemnaWaveAPI:
|
||||
|
||||
connector = aiohttp.TCPConnector(**connector_kwargs)
|
||||
|
||||
self.session = aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(total=30),
|
||||
headers=headers,
|
||||
connector=connector
|
||||
)
|
||||
session_kwargs = {
|
||||
'timeout': aiohttp.ClientTimeout(total=30),
|
||||
'headers': headers,
|
||||
'connector': connector
|
||||
}
|
||||
|
||||
if cookies:
|
||||
session_kwargs['cookies'] = cookies
|
||||
|
||||
self.session = aiohttp.ClientSession(**session_kwargs)
|
||||
self.authenticated = True
|
||||
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
@@ -194,6 +215,8 @@ class RemnaWaveAPI:
|
||||
|
||||
if response.status >= 400:
|
||||
error_message = response_data.get('message', f'HTTP {response.status}')
|
||||
logger.error(f"API Error {response.status}: {error_message}")
|
||||
logger.error(f"Response: {response_text[:500]}")
|
||||
raise RemnaWaveAPIError(
|
||||
error_message,
|
||||
response.status,
|
||||
|
||||
@@ -198,7 +198,7 @@ async def get_subscription_cost(subscription, db: AsyncSession) -> int:
|
||||
)
|
||||
|
||||
traffic_cost = TRAFFIC_PRICES.get(subscription.traffic_limit_gb, 0)
|
||||
devices_cost = max(0, subscription.device_limit - 1) * settings.PRICE_PER_DEVICE
|
||||
devices_cost = max(0, subscription.device_limit - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
|
||||
|
||||
total_cost = base_cost + servers_cost + traffic_cost + devices_cost
|
||||
|
||||
@@ -328,7 +328,7 @@ async def start_subscription_purchase(
|
||||
initial_data = {
|
||||
'period_days': None,
|
||||
'countries': [],
|
||||
'devices': 1,
|
||||
'devices': settings.DEFAULT_DEVICE_LIMIT,
|
||||
'total_price': 0
|
||||
}
|
||||
|
||||
@@ -412,11 +412,11 @@ async def get_countries_price_by_uuids_fallback(country_uuids: List[str], db: As
|
||||
total_price += price
|
||||
prices_list.append(price)
|
||||
else:
|
||||
default_price = 1000
|
||||
default_price = 0
|
||||
total_price += default_price
|
||||
prices_list.append(default_price)
|
||||
except Exception:
|
||||
default_price = 1000
|
||||
default_price = 0
|
||||
total_price += default_price
|
||||
prices_list.append(default_price)
|
||||
|
||||
@@ -424,7 +424,7 @@ async def get_countries_price_by_uuids_fallback(country_uuids: List[str], db: As
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка fallback функции: {e}")
|
||||
default_prices = [1000] * len(country_uuids)
|
||||
default_prices = [0] * len(country_uuids)
|
||||
return sum(default_prices), default_prices
|
||||
|
||||
async def handle_manage_country(
|
||||
@@ -1369,7 +1369,7 @@ async def select_devices(
|
||||
if c['uuid'] in data['countries']
|
||||
)
|
||||
|
||||
devices_price = (devices - 1) * settings.PRICE_PER_DEVICE
|
||||
devices_price = max(0, devices - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
|
||||
|
||||
data['devices'] = devices
|
||||
data['total_price'] = base_price + countries_price + devices_price
|
||||
@@ -1414,7 +1414,7 @@ async def devices_continue(
|
||||
else:
|
||||
traffic_price = TRAFFIC_PRICES.get(data['traffic_gb'], 0)
|
||||
|
||||
devices_price = (data['devices'] - 1) * settings.PRICE_PER_DEVICE
|
||||
devices_price = max(0, data['devices'] - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
|
||||
total_price = base_price + traffic_price + countries_price + devices_price
|
||||
|
||||
data['total_price'] = total_price
|
||||
@@ -1474,7 +1474,7 @@ async def confirm_purchase(
|
||||
countries_price += country['price_kopeks']
|
||||
server_prices.append(country['price_kopeks'])
|
||||
|
||||
devices_price = (data['devices'] - 1) * settings.PRICE_PER_DEVICE
|
||||
devices_price = max(0, data['devices'] - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
|
||||
|
||||
if settings.is_traffic_fixed():
|
||||
traffic_price = TRAFFIC_PRICES.get(settings.get_fixed_traffic_limit(), 0)
|
||||
@@ -1885,7 +1885,7 @@ async def _get_available_countries():
|
||||
countries.append({
|
||||
"uuid": squad["uuid"],
|
||||
"name": squad_name,
|
||||
"price_kopeks": 1000,
|
||||
"price_kopeks": 0,
|
||||
"is_available": True
|
||||
})
|
||||
|
||||
@@ -2243,7 +2243,7 @@ async def handle_connect_subscription(
|
||||
subscription = db_user.subscription
|
||||
|
||||
if not subscription or not subscription.subscription_url:
|
||||
await callback.answer("❌ У вас нет активной подписки или ссылка еще генерируется", show_alert=True)
|
||||
await callback.answer("⚠ У вас нет активной подписки или ссылка еще генерируется", show_alert=True)
|
||||
return
|
||||
|
||||
connect_mode = settings.CONNECT_BUTTON_MODE
|
||||
@@ -2252,13 +2252,10 @@ async def handle_connect_subscription(
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text="🔗 Открыть подписку",
|
||||
text="🔗 Открыть подписку в мини-приложении",
|
||||
web_app=types.WebAppInfo(url=subscription.subscription_url)
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text="📋 Показать ссылку", callback_data="open_subscription_link")
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text="⬅️ Назад", callback_data="menu_subscription")
|
||||
]
|
||||
@@ -2266,9 +2263,9 @@ async def handle_connect_subscription(
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"""
|
||||
🔗 <b>Подключить подписку</b>
|
||||
📱 <b>Подключить подписку</b>
|
||||
|
||||
📱 Нажмите кнопку ниже, чтобы открыть подписку в мини-приложении Telegram:
|
||||
🚀 Нажмите кнопку ниже, чтобы открыть подписку в мини-приложении Telegram:
|
||||
""",
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML"
|
||||
@@ -2276,7 +2273,7 @@ async def handle_connect_subscription(
|
||||
|
||||
elif connect_mode == "miniapp_custom":
|
||||
if not settings.MINIAPP_CUSTOM_URL:
|
||||
await callback.answer("❌ Кастомная ссылка для мини-приложения не настроена", show_alert=True)
|
||||
await callback.answer("⚠ Кастомная ссылка для мини-приложения не настроена", show_alert=True)
|
||||
return
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
@@ -2286,9 +2283,6 @@ async def handle_connect_subscription(
|
||||
web_app=types.WebAppInfo(url=settings.MINIAPP_CUSTOM_URL)
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text="📋 Показать ссылку подписки", callback_data="open_subscription_link")
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text="⬅️ Назад", callback_data="menu_subscription")
|
||||
]
|
||||
@@ -2299,9 +2293,6 @@ async def handle_connect_subscription(
|
||||
🚀 <b>Подключить подписку</b>
|
||||
|
||||
📱 Нажмите кнопку ниже, чтобы открыть приложение:
|
||||
|
||||
📋 <b>Ссылка подписки:</b>
|
||||
<code>{subscription.subscription_url}</code>
|
||||
""",
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML"
|
||||
|
||||
+27
-10
@@ -1,4 +1,5 @@
|
||||
from typing import List, Optional
|
||||
from aiogram import types
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
from app.config import settings, PERIOD_PRICES, TRAFFIC_PRICES
|
||||
@@ -107,15 +108,28 @@ def get_subscription_keyboard(
|
||||
connect_mode = settings.CONNECT_BUTTON_MODE
|
||||
|
||||
if connect_mode == "miniapp_subscription":
|
||||
button_text = "🚀 Открыть в мини-приложении"
|
||||
keyboard.append([
|
||||
InlineKeyboardButton(
|
||||
text="🚀 Открыть в мини-приложении",
|
||||
web_app=types.WebAppInfo(url=subscription.subscription_url)
|
||||
)
|
||||
])
|
||||
elif connect_mode == "miniapp_custom":
|
||||
button_text = "🚀 Открыть приложение"
|
||||
if settings.MINIAPP_CUSTOM_URL:
|
||||
keyboard.append([
|
||||
InlineKeyboardButton(
|
||||
text="🚀 Открыть приложение",
|
||||
web_app=types.WebAppInfo(url=settings.MINIAPP_CUSTOM_URL)
|
||||
)
|
||||
])
|
||||
else:
|
||||
keyboard.append([
|
||||
InlineKeyboardButton(text="🔗 Подключиться", callback_data="subscription_connect")
|
||||
])
|
||||
else:
|
||||
button_text = "🔗 Подключиться"
|
||||
|
||||
keyboard.append([
|
||||
InlineKeyboardButton(text=button_text, callback_data="subscription_connect")
|
||||
])
|
||||
keyboard.append([
|
||||
InlineKeyboardButton(text="🔗 Подключиться", callback_data="subscription_connect")
|
||||
])
|
||||
|
||||
if not is_trial and subscription and subscription.days_left <= 3:
|
||||
keyboard.append([
|
||||
@@ -291,9 +305,12 @@ def get_devices_keyboard(current: int, language: str = "ru") -> InlineKeyboardMa
|
||||
texts = get_texts(language)
|
||||
keyboard = []
|
||||
|
||||
for devices in range(1, 6):
|
||||
price = (devices - 1) * settings.PRICE_PER_DEVICE
|
||||
price_text = f" (+{texts.format_price(price)})" if price > 0 else ""
|
||||
start_devices = settings.DEFAULT_DEVICE_LIMIT
|
||||
end_devices = max(start_devices + 3, 6)
|
||||
|
||||
for devices in range(start_devices, end_devices):
|
||||
price = max(0, devices - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
|
||||
price_text = f" (+{texts.format_price(price)})" if price > 0 else " (включено)"
|
||||
emoji = "✅" if devices == current else "⚪"
|
||||
|
||||
device_word = _get_device_declension(devices)
|
||||
|
||||
@@ -25,7 +25,8 @@ class RemnaWaveService:
|
||||
def __init__(self):
|
||||
self.api = RemnaWaveAPI(
|
||||
base_url=settings.REMNAWAVE_API_URL,
|
||||
api_key=settings.REMNAWAVE_API_KEY
|
||||
api_key=settings.REMNAWAVE_API_KEY,
|
||||
secret_key=settings.REMNAWAVE_SECRET_KEY
|
||||
)
|
||||
|
||||
def _parse_remnawave_date(self, date_str: str) -> datetime:
|
||||
|
||||
@@ -249,7 +249,7 @@ class SubscriptionService:
|
||||
server_prices.append(0)
|
||||
logger.warning(f"⚠️ Сервер ID {server_id} недоступен")
|
||||
|
||||
devices_price = max(0, devices - 1) * settings.PRICE_PER_DEVICE
|
||||
devices_price = max(0, devices - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
|
||||
|
||||
total_price = base_price + traffic_price + total_servers_price + devices_price
|
||||
|
||||
@@ -277,7 +277,7 @@ class SubscriptionService:
|
||||
subscription.connected_squads, db
|
||||
)
|
||||
|
||||
devices_price = max(0, subscription.device_limit - 1) * settings.PRICE_PER_DEVICE
|
||||
devices_price = max(0, subscription.device_limit - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
|
||||
|
||||
traffic_price = TRAFFIC_PRICES.get(subscription.traffic_limit_gb, 0)
|
||||
|
||||
@@ -364,7 +364,7 @@ class SubscriptionService:
|
||||
prices_list.append(price)
|
||||
logger.debug(f"🏷️ Страна {server.display_name}: {price/100}₽")
|
||||
else:
|
||||
default_price = 1000
|
||||
default_price = 0
|
||||
total_price += default_price
|
||||
prices_list.append(default_price)
|
||||
logger.warning(f"⚠️ Сервер {country_uuid} недоступен, используем базовую цену: {default_price/100}₽")
|
||||
@@ -374,7 +374,7 @@ class SubscriptionService:
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка получения цен стран: {e}")
|
||||
default_prices = [1000] * len(country_uuids)
|
||||
default_prices = [0] * len(country_uuids)
|
||||
return sum(default_prices), default_prices
|
||||
|
||||
async def _get_countries_price(self, country_uuids: List[str], db: AsyncSession) -> int:
|
||||
|
||||
Reference in New Issue
Block a user