diff --git a/.env.example b/.env.example index e2fc368c..a8d57a98 100644 --- a/.env.example +++ b/.env.example @@ -509,6 +509,10 @@ BOT_RUN_MODE=polling # polling, webhook или both CONTESTS_ENABLED=false CONTESTS_BUTTON_VISIBLE=false +# ===== КНОПКА АКТИВАЦИИ ===== +ACTIVATE_BUTTON_VISIBLE=false +# ACTIVATE_BUTTON_TEXT=активировать + # ===== ЕДИНЫЙ ВЕБ-СЕРВЕР ===== WEB_API_ENABLED=false WEB_API_HOST=0.0.0.0 diff --git a/app/config.py b/app/config.py index d6e70776..a87c8dd0 100644 --- a/app/config.py +++ b/app/config.py @@ -239,6 +239,8 @@ class Settings(BaseSettings): # Отключение превью ссылок в сообщениях бота DISABLE_WEB_PAGE_PREVIEW: bool = False + ACTIVATE_BUTTON_VISIBLE: bool = False + ACTIVATE_BUTTON_TEXT: str = "активировать" PAYMENT_BALANCE_DESCRIPTION: str = "Пополнение баланса" PAYMENT_SUBSCRIPTION_DESCRIPTION: str = "Оплата подписки" PAYMENT_SERVICE_NAME: str = "Интернет-сервис" diff --git a/app/handlers/menu.py b/app/handlers/menu.py index a4301d73..2db00d56 100644 --- a/app/handlers/menu.py +++ b/app/handlers/menu.py @@ -1223,6 +1223,85 @@ async def get_main_menu_text(user, texts, db: AsyncSession): return base_text +async def handle_activate_button( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession +): + texts = get_texts(db_user.language) + + # Получить подписку пользователя + from app.database.crud.subscription import get_subscription_by_user_id + subscription = await get_subscription_by_user_id(db, db_user.id) + + if subscription and subscription.status == "ACTIVE" and subscription.end_date > datetime.utcnow(): + await callback.answer( + texts.t("SUBSCRIPTION_ALREADY_ACTIVE", "✅ Подписка уже активна!"), + show_alert=True, + ) + return + + # Параметры из подписки или дефолтные + device_limit = subscription.device_limit if subscription else settings.DEFAULT_DEVICE_LIMIT + traffic_limit_gb = subscription.traffic_limit_gb if subscription else 0 + connected_squads = subscription.connected_squads if subscription else [] + + # Получить IDs серверов из UUIDs + from app.database.crud.server_squad import get_server_ids_by_uuids + server_ids = await get_server_ids_by_uuids(db, connected_squads) if connected_squads else [] + + balance = db_user.balance_kopeks + available_periods = [int(p) for p in settings.AVAILABLE_SUBSCRIPTION_PERIODS] + + best_period = None + best_price = 0 + + from app.services.subscription_service import SubscriptionService + subscription_service = SubscriptionService() + + # Найти максимальный период, цена которого <= баланса + for period in sorted(available_periods, reverse=True): + price, _ = await subscription_service.calculate_subscription_price_with_months( + period, + traffic_limit_gb, + server_ids, + device_limit, + db, + user=db_user + ) + if price <= balance: + best_period = period + best_price = price + break + + if best_period: + # Создать новую подписку + from app.database.crud.subscription import create_paid_subscription + new_subscription = await create_paid_subscription( + db, + db_user.id, + best_period, + traffic_limit_gb=traffic_limit_gb, + device_limit=device_limit, + connected_squads=connected_squads, + update_server_counters=True + ) + + # Списать деньги + db_user.balance_kopeks -= best_price + await db.commit() + + await callback.answer( + texts.t("ACTIVATION_SUCCESS", f"✅ Подписка активирована на {best_period} дней за {best_price//100} руб!"), + show_alert=True, + ) + else: + await callback.answer( + texts.t("INSUFFICIENT_FUNDS", "❌ Недостаточно средств для активации подписки"), + show_alert=True, + ) + + def register_handlers(dp: Dispatcher): dp.callback_query.register( @@ -1295,3 +1374,8 @@ def register_handlers(dp: Dispatcher): handle_add_traffic, F.data == "buy_traffic" ) + + dp.callback_query.register( + handle_activate_button, + F.data == "activate_button" + ) diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py index f9db008b..9cfaea45 100644 --- a/app/keyboards/inline.py +++ b/app/keyboards/inline.py @@ -445,7 +445,7 @@ def get_main_menu_keyboard( ) # Добавляем кнопку конкурсов - if settings.CONTESTS_BUTTON_VISIBLE: + if settings.CONTESTS_ENABLED and settings.CONTESTS_BUTTON_VISIBLE: paired_buttons.append( InlineKeyboardButton(text=texts.t("CONTESTS_BUTTON", "🎲 Конкурсы"), callback_data="contests_menu") ) @@ -461,6 +461,12 @@ def get_main_menu_keyboard( InlineKeyboardButton(text=texts.MENU_SUPPORT, callback_data="menu_support") ) + # Добавляем кнопку активации + if settings.ACTIVATE_BUTTON_VISIBLE: + paired_buttons.append( + InlineKeyboardButton(text=settings.ACTIVATE_BUTTON_TEXT, callback_data="activate_button") + ) + paired_buttons.append( InlineKeyboardButton( text=texts.t("MENU_INFO", "ℹ️ Инфо"), diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json index 757e8220..0e7385ed 100644 --- a/app/localization/locales/ru.json +++ b/app/localization/locales/ru.json @@ -104,6 +104,7 @@ "ADMIN_INVALID_NUMBER": "Некорректное число", "ADMIN_INVALID_JSON": "Некорректный JSON", "CONTEST_MENU_TITLE": "🎲 Игры/Конкурсы\nВыберите игру:", + "CONTESTS_BUTTON": "🎲 Конкурсы", "CONTEST_EMPTY": "Сейчас игр нет", "CONTEST_NOT_ELIGIBLE": "Игры доступны только с активной или триальной подпиской.", "CONTEST_ROUND_FINISHED": "Раунд завершён или недоступен.",