Files
Solo_bot/handlers/payments/fast_payment_flow.py
T

270 lines
9.8 KiB
Python

from typing import Any
from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import USE_NEW_PAYMENT_FLOW
from core.bootstrap import PAYMENTS_CONFIG, MONEY_CONFIG
from database.temporary_data import create_temporary_data
from handlers import buttons as btn
from handlers.payments.currency_flow import (
build_currency_choice_kb,
shortfall_lead_text,
currency_label,
)
from handlers.payments.providers import get_providers_with_hooks
from handlers.texts import FAST_PAY_CHOOSE_CURRENCY, FAST_PAY_CHOOSE_PROVIDER
from handlers.utils import edit_or_send_message
from logger import logger
router = Router()
async def get_payment_providers_config() -> dict[str, bool]:
config = PAYMENTS_CONFIG or {}
return dict(config)
def get_currency_mode() -> str:
mode_cfg = MONEY_CONFIG.get("CURRENCY_MODE", "RUB")
mode = str(mode_cfg or "RUB").upper()
if mode not in ("RUB", "USD", "RUB+USD"):
mode = "RUB"
return mode
async def _run_provider_flow(
provider: str,
callback_query: CallbackQuery,
session: Any,
state: FSMContext,
required_amount: int | None,
) -> bool:
import importlib
payment_config = await get_payment_providers_config()
providers_map = await get_providers_with_hooks(payment_config)
provider_upper = provider.upper()
cfg = providers_map.get(provider_upper) or {}
fast_handler_name = cfg.get("fast")
if not fast_handler_name:
return False
module_name_from_config = cfg.get("module")
if module_name_from_config:
module_name = f"handlers.payments.{module_name_from_config}.handlers"
else:
module_name = f"handlers.payments.{provider_upper.lower()}.handlers"
try:
module = importlib.import_module(module_name)
func = getattr(module, fast_handler_name)
except Exception as error:
logger.error(f"[FAST_FLOW] Импорт {provider_upper}.{fast_handler_name} из {module_name} не удался: {error}")
return False
try:
if provider_upper == "STARS":
try:
await callback_query.message.delete()
except Exception as error:
logger.warning(f"[FAST_FLOW] Не удалось удалить меню перед STARS: {error}")
await func(callback_query, session)
return True
except Exception as error:
logger.error(f"[FAST_FLOW] Ошибка при вызове {provider_upper}.{fast_handler_name}(): {error}")
return False
async def try_fast_payment_flow(
callback_query: CallbackQuery,
session: Any,
state: FSMContext,
*,
tg_id: int,
temp_key: str,
temp_payload: dict,
required_amount: int | None = None,
) -> bool:
await create_temporary_data(session, tg_id, temp_key, temp_payload)
if not USE_NEW_PAYMENT_FLOW:
return False
payment_config = await get_payment_providers_config()
providers_map = await get_providers_with_hooks(payment_config)
providers = (
[USE_NEW_PAYMENT_FLOW]
if isinstance(USE_NEW_PAYMENT_FLOW, str)
else [str(provider) for provider in (USE_NEW_PAYMENT_FLOW or [])]
)
providers = [
provider
for provider in providers
if (providers_map.get(str(provider).upper()) or {}).get("fast")
and (providers_map.get(str(provider).upper()) or {}).get("enabled", True)
]
mode = get_currency_mode()
multicurrency_enabled = mode == "RUB+USD"
if not multicurrency_enabled:
allowed_currency = "RUB" if mode == "RUB" else "USD"
providers = [
provider
for provider in providers
if (providers_map.get(provider.upper()) or {}).get("currency") in (allowed_currency, "RUB+USD")
]
if not providers:
return False
if len(providers) == 1 and not multicurrency_enabled:
single_provider = providers[0].upper()
cfg = providers_map.get(single_provider) or {}
currency = cfg.get("currency")
if currency:
await state.update_data(chosen_currency=currency)
if await _run_provider_flow(single_provider, callback_query, session, state, required_amount):
return True
return False
if multicurrency_enabled:
show_stars = bool((providers_map.get("STARS") or {}).get("enabled"))
show_tribute = bool((providers_map.get("TRIBUTE") or {}).get("enabled"))
keyboard = build_currency_choice_kb(show_stars=show_stars, show_tribute=show_tribute)
lead_text = await shortfall_lead_text(
session,
tg_id,
required_amount,
getattr(callback_query.from_user, "language_code", None),
)
text = f"{lead_text}.\n\n{FAST_PAY_CHOOSE_CURRENCY}"
await state.update_data(
temp_key=temp_key,
temp_payload=temp_payload,
required_amount=required_amount,
fastflow_providers=providers,
)
await edit_or_send_message(
target_message=callback_query.message,
text=text,
reply_markup=keyboard.as_markup(),
)
return True
keyboard = InlineKeyboardBuilder()
for provider in providers:
provider_upper = provider.upper()
button_text = getattr(btn, provider_upper, provider_upper)
keyboard.row(InlineKeyboardButton(text=button_text, callback_data=f"choose_payment_provider|{provider_upper}"))
keyboard.row(InlineKeyboardButton(text=btn.MAIN_MENU, callback_data="profile"))
lead_text = await shortfall_lead_text(
session,
tg_id,
required_amount,
getattr(callback_query.from_user, "language_code", None),
)
await state.update_data(temp_key=temp_key, temp_payload=temp_payload, required_amount=required_amount)
await edit_or_send_message(
target_message=callback_query.message,
text=f"{lead_text}.\n\n{FAST_PAY_CHOOSE_PROVIDER}",
reply_markup=keyboard.as_markup(),
)
return True
@router.callback_query(F.data.startswith("choose_payment_currency|"))
async def choose_payment_currency(callback_query: CallbackQuery, state: FSMContext, session: Any):
payment_config = await get_payment_providers_config()
providers_map = await get_providers_with_hooks(payment_config)
currency = callback_query.data.split("|")[1]
data = await state.get_data()
providers = data.get("fastflow_providers") or (
[USE_NEW_PAYMENT_FLOW]
if isinstance(USE_NEW_PAYMENT_FLOW, str)
else [str(provider) for provider in (USE_NEW_PAYMENT_FLOW or [])]
)
filtered = [
provider_upper
for provider_upper in (provider.upper() for provider in providers)
if (providers_map.get(provider_upper) or {}).get("currency") == currency
and (providers_map.get(provider_upper) or {}).get("fast")
and (providers_map.get(provider_upper) or {}).get("enabled", True)
]
await state.update_data(chosen_currency=currency)
if not filtered:
keyboard = InlineKeyboardBuilder().row(InlineKeyboardButton(text="← Назад", callback_data="profile"))
await edit_or_send_message(
target_message=callback_query.message,
text="Для выбранной валюты нет доступных касс. Выберите другую валюту или вернитесь в меню.",
reply_markup=keyboard.as_markup(),
)
return
if len(filtered) == 1:
only_provider = filtered[0]
if await _run_provider_flow(only_provider, callback_query, session, state, data.get("required_amount")):
return
await edit_or_send_message(
target_message=callback_query.message,
text="Этот способ временно недоступен.",
reply_markup=InlineKeyboardBuilder()
.row(InlineKeyboardButton(text=btn.MAIN_MENU, callback_data="profile"))
.as_markup(),
)
return
keyboard = InlineKeyboardBuilder()
for provider_upper in filtered:
button_text = getattr(btn, provider_upper, provider_upper)
keyboard.row(
InlineKeyboardButton(text=button_text, callback_data=f"choose_payment_provider|{provider_upper}")
)
keyboard.row(InlineKeyboardButton(text=btn.MAIN_MENU, callback_data="profile"))
lead_text = await shortfall_lead_text(
session,
callback_query.from_user.id,
data.get("required_amount"),
getattr(callback_query.from_user, "language_code", None),
force_currency=currency,
)
text = f"{lead_text}.\n\nВалюта: {currency_label(currency)}\n{FAST_PAY_CHOOSE_PROVIDER}"
await edit_or_send_message(target_message=callback_query.message, text=text, reply_markup=keyboard.as_markup())
@router.callback_query(F.data.startswith("choose_payment_provider|"))
async def choose_payment_provider(callback_query: CallbackQuery, state: FSMContext, session: Any):
payment_config = await get_payment_providers_config()
providers_map = await get_providers_with_hooks(payment_config)
provider = callback_query.data.split("|")[1].upper()
cfg = providers_map.get(provider) or {}
if not cfg.get("fast") or not cfg.get("enabled", True):
await edit_or_send_message(
target_message=callback_query.message,
text="Этот способ временно недоступен.",
reply_markup=InlineKeyboardBuilder()
.row(InlineKeyboardButton(text=btn.MAIN_MENU, callback_data="profile"))
.as_markup(),
)
return
currency = cfg.get("currency")
if currency:
await state.update_data(chosen_currency=currency)
data = await state.get_data()
await _run_provider_flow(provider, callback_query, session, state, data.get("required_amount"))