diff --git a/handlers/admin/sender/keyboard.py b/handlers/admin/sender/keyboard.py index 79ea9d9b..82030d69 100644 --- a/handlers/admin/sender/keyboard.py +++ b/handlers/admin/sender/keyboard.py @@ -35,6 +35,12 @@ def build_sender_kb() -> InlineKeyboardMarkup: callback_data=AdminSenderCallback(type="untrial").pack(), ) ) + builder.row( + InlineKeyboardButton( + text="🧪 Триал", + callback_data=AdminSenderCallback(type="trial").pack(), + ) + ) builder.row( InlineKeyboardButton( text="🔥 Горячие лиды", diff --git a/handlers/admin/sender/sender_handler.py b/handlers/admin/sender/sender_handler.py index 1b10b338..81a4da49 100644 --- a/handlers/admin/sender/sender_handler.py +++ b/handlers/admin/sender/sender_handler.py @@ -12,7 +12,7 @@ from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMar from sqlalchemy import distinct, func, select from sqlalchemy.ext.asyncio import AsyncSession -from database.models import Key, Payment, Server, User +from database.models import Key, Payment, Server, Tariff, User from filters.admin import IsAdminFilter from logger import logger @@ -269,6 +269,13 @@ async def handle_send_confirm(callback_query: CallbackQuery, state: FSMContext, .where(Payment.status == "success") .where(~User.tg_id.in_(subquery)) ) + elif send_to == "trial": + trial_tariff_subquery = select(Tariff.id).where(Tariff.group_code == "trial") + + query = ( + select(distinct(Key.tg_id)) + .where(Key.tariff_id.in_(trial_tariff_subquery)) + ) else: query = select(distinct(User.tg_id)) diff --git a/handlers/keys/key_mode/key_create.py b/handlers/keys/key_mode/key_create.py index 06b52532..e2c88133 100644 --- a/handlers/keys/key_mode/key_create.py +++ b/handlers/keys/key_mode/key_create.py @@ -42,6 +42,7 @@ from handlers.texts import ( ) from handlers.utils import edit_or_send_message, get_least_loaded_cluster from logger import logger +from utils.modules_loader import load_module_fast_flow_handlers from .key_cluster_mode import key_cluster_mode from .key_country_mode import key_country_mode @@ -313,27 +314,38 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any, state: }, ) - if USE_NEW_PAYMENT_FLOW == "YOOKASSA": - await process_custom_amount_input(callback_query, session) - elif USE_NEW_PAYMENT_FLOW == "ROBOKASSA": - await handle_custom_amount_input(message=callback_query, session=session) - elif USE_NEW_PAYMENT_FLOW == "STARS": - await process_custom_amount_input_stars(callback_query, session) - elif USE_NEW_PAYMENT_FLOW == "YOOMONEY": - await process_custom_amount_input_yoomoney(callback_query, session) - elif USE_NEW_PAYMENT_FLOW == "WATA": - await state.update_data(wata_cassa="sbp", required_amount=required_amount) - await handle_custom_amount_input_wata(callback_query, state) + module_fast_flow_handlers = load_module_fast_flow_handlers() + flow_handled = False + + if USE_NEW_PAYMENT_FLOW in module_fast_flow_handlers: + try: + handler = module_fast_flow_handlers[USE_NEW_PAYMENT_FLOW] + await handler(callback_query, session, state) + flow_handled = True + except Exception as e: + logger.error(f"[CREATE] Ошибка в модульном обработчике быстрого флоу {USE_NEW_PAYMENT_FLOW}: {e}") - else: - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay")) - builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) - await edit_or_send_message( - target_message=callback_query.message, - text=INSUFFICIENT_FUNDS_MSG.format(required_amount=required_amount), - reply_markup=builder.as_markup(), - ) + if not flow_handled: + if USE_NEW_PAYMENT_FLOW == "YOOKASSA": + await process_custom_amount_input(callback_query, session) + elif USE_NEW_PAYMENT_FLOW == "ROBOKASSA": + await handle_custom_amount_input(message=callback_query, session=session) + elif USE_NEW_PAYMENT_FLOW == "STARS": + await process_custom_amount_input_stars(callback_query, session) + elif USE_NEW_PAYMENT_FLOW == "YOOMONEY": + await process_custom_amount_input_yoomoney(callback_query, session) + elif USE_NEW_PAYMENT_FLOW == "WATA": + await state.update_data(wata_cassa="sbp", required_amount=required_amount) + await handle_custom_amount_input_wata(callback_query, state) + else: + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay")) + builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) + await edit_or_send_message( + target_message=callback_query.message, + text=INSUFFICIENT_FUNDS_MSG.format(required_amount=required_amount), + reply_markup=builder.as_markup(), + ) return builder = InlineKeyboardBuilder() diff --git a/handlers/keys/key_renew.py b/handlers/keys/key_renew.py index 9ca625c3..622f9cb5 100644 --- a/handlers/keys/key_renew.py +++ b/handlers/keys/key_renew.py @@ -42,6 +42,7 @@ from handlers.texts import ( ) from handlers.utils import edit_or_send_message, get_russian_month from logger import logger +from utils.modules_loader import load_module_fast_flow_handlers router = Router() @@ -285,26 +286,38 @@ async def process_callback_renew_plan(callback_query: CallbackQuery, state: FSMC }, ) - if USE_NEW_PAYMENT_FLOW == "YOOKASSA": - await process_custom_amount_input(callback_query, session) - elif USE_NEW_PAYMENT_FLOW == "ROBOKASSA": - await handle_custom_amount_input(message=callback_query, session=session) - elif USE_NEW_PAYMENT_FLOW == "STARS": - await process_custom_amount_input_stars(callback_query, session) - elif USE_NEW_PAYMENT_FLOW == "YOOMONEY": - await process_custom_amount_input_yoomoney(callback_query, session) - elif USE_NEW_PAYMENT_FLOW == "WATA": - await state.update_data(wata_cassa="sbp", required_amount=required_amount) - await handle_custom_amount_input_wata(callback_query, state) - else: - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay")) - builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) - await edit_or_send_message( - target_message=callback_query.message, - text=INSUFFICIENT_FUNDS_RENEWAL_MSG.format(required_amount=required_amount), - reply_markup=builder.as_markup(), - ) + module_fast_flow_handlers = load_module_fast_flow_handlers() + flow_handled = False + + if USE_NEW_PAYMENT_FLOW in module_fast_flow_handlers: + try: + handler = module_fast_flow_handlers[USE_NEW_PAYMENT_FLOW] + await handler(callback_query, session, state) + flow_handled = True + except Exception as e: + logger.error(f"[RENEW] Ошибка в модульном обработчике быстрого флоу {USE_NEW_PAYMENT_FLOW}: {e}") + + if not flow_handled: + if USE_NEW_PAYMENT_FLOW == "YOOKASSA": + await process_custom_amount_input(callback_query, session) + elif USE_NEW_PAYMENT_FLOW == "ROBOKASSA": + await handle_custom_amount_input(message=callback_query, session=session) + elif USE_NEW_PAYMENT_FLOW == "STARS": + await process_custom_amount_input_stars(callback_query, session) + elif USE_NEW_PAYMENT_FLOW == "YOOMONEY": + await process_custom_amount_input_yoomoney(callback_query, session) + elif USE_NEW_PAYMENT_FLOW == "WATA": + await state.update_data(wata_cassa="sbp", required_amount=required_amount) + await handle_custom_amount_input_wata(callback_query, state) + else: + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay")) + builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) + await edit_or_send_message( + target_message=callback_query.message, + text=INSUFFICIENT_FUNDS_RENEWAL_MSG.format(required_amount=required_amount), + reply_markup=builder.as_markup(), + ) return logger.info(f"[RENEW] Продление ключа для пользователя {tg_id} на {duration_days} дней") diff --git a/utils/modules_loader.py b/utils/modules_loader.py index 218f2a27..b073ca47 100644 --- a/utils/modules_loader.py +++ b/utils/modules_loader.py @@ -52,3 +52,31 @@ def load_module_webhooks(folder: str = "modules") -> list[dict]: logger.error(f"[Modules] Ошибка при загрузке вебхуков из {module_path}: {e}") return webhooks + + +def load_module_fast_flow_handlers(folder: str = "modules") -> dict: + handlers = {} + base_path = Path(folder) + + if not base_path.exists(): + logger.warning(f"[Modules] Папка {folder} не найдена, пропускаем загрузку быстрого флоу.") + return {} + + for _finder, name, _ispkg in pkgutil.iter_modules([str(base_path)]): + module_path = f"{folder}.{name}" + try: + router_module = importlib.import_module(f"{module_path}.router") + if hasattr(router_module, "get_fast_flow_handler"): + fast_flow_data = router_module.get_fast_flow_handler() + if fast_flow_data and isinstance(fast_flow_data, dict) and "payment_key" in fast_flow_data and "handler" in fast_flow_data: + payment_key = fast_flow_data["payment_key"] + handler = fast_flow_data["handler"] + handlers[payment_key] = handler + logger.info(f"[Modules] Найден обработчик быстрого флоу в модуле {name}: {payment_key}") + elif fast_flow_data is None: + logger.info(f"[Modules] Быстрое флоу отключено в модуле {name}") + + except Exception as e: + logger.error(f"[Modules] Ошибка при загрузке быстрого флоу из {module_path}: {e}") + + return handlers