diff --git a/handlers/payments/__init__.py b/handlers/payments/__init__.py index 963de680..8e370f60 100644 --- a/handlers/payments/__init__.py +++ b/handlers/payments/__init__.py @@ -9,8 +9,8 @@ from .cryptobot import router as cryptobot_router from .fast_payment_flow import router as fast_payment_flow_router from .freekassa.freekassa_pay import router as freekassa_router from .gift import router as gift_router -from .heleket.heleket import router as heleket_router -from .kassai.kassai import router as kassai_router +from .heleket import router as heleket_router +from .kassai import router as kassai_router from .pay import router as pay_router from .robokassa import router as robokassa_router from .stars import router as stars_router diff --git a/handlers/payments/currency_rates.py b/handlers/payments/currency_rates.py index 331e3a37..c730c835 100644 --- a/handlers/payments/currency_rates.py +++ b/handlers/payments/currency_rates.py @@ -69,7 +69,8 @@ async def get_rub_rate(quote: str, *, session: aiohttp.ClientSession | None = No rate = _q(Decimal("1") / rub_per_unit) if code != "RUB" and FX_MARKUP: - rate = _q(rate * (Decimal("1") + Decimal(str(FX_MARKUP)))) + pct = Decimal(str(FX_MARKUP)) / Decimal("100") + rate = _q(rate * (Decimal("1") + pct)) cache[code] = (now, rate) return rate diff --git a/handlers/payments/fast_payment_flow.py b/handlers/payments/fast_payment_flow.py index ffa88969..349d8b7b 100644 --- a/handlers/payments/fast_payment_flow.py +++ b/handlers/payments/fast_payment_flow.py @@ -39,7 +39,11 @@ async def _run_provider_flow( if not fast_name: return False - module_name = f"handlers.payments.{up.lower()}.handlers" + 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.{up.lower()}.handlers" try: module = importlib.import_module(module_name) diff --git a/handlers/payments/heleket/__init__.py b/handlers/payments/heleket/__init__.py index e69de29b..8a870356 100644 --- a/handlers/payments/heleket/__init__.py +++ b/handlers/payments/heleket/__init__.py @@ -0,0 +1,3 @@ +__all__ = ("router",) + +from .handlers import router diff --git a/handlers/payments/heleket/handlers.py b/handlers/payments/heleket/handlers.py new file mode 100644 index 00000000..f84d3cf0 --- /dev/null +++ b/handlers/payments/heleket/handlers.py @@ -0,0 +1,80 @@ +from aiogram import F, Router, types +from aiogram.fsm.context import FSMContext +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from sqlalchemy.ext.asyncio import AsyncSession + +from handlers.buttons import PAY_2, MAIN_MENU +from handlers.texts import DEFAULT_PAYMENT_MESSAGE +from handlers.utils import edit_or_send_message +from handlers.payments.currency_rates import format_for_user +from database import get_temporary_data +from logger import logger + +from .service import HELEKET_PAYMENT_METHODS, generate_heleket_payment_link, process_callback_pay_heleket +from .service import router as service_router + +router = Router(name="heleket_router") +router.include_router(service_router) + + +@router.callback_query(F.data == "pay_heleket_crypto") +async def handle_pay_heleket_crypto(callback_query: types.CallbackQuery, state: FSMContext, session: AsyncSession): + await process_callback_pay_heleket(callback_query, state, session, method_name="crypto") + + +async def handle_custom_amount_input_heleket( + event, + session: AsyncSession, + pay_button_text: str = PAY_2, + main_menu_text: str = MAIN_MENU, +): + """ + Функция быстрого потока для Heleket - принимает недостающую сумму и формирует платеж. + Работает с временными данными из fast_payment_flow для создания/продления/подарка. + """ + message = event.message + from_user = event.from_user + tg_id = from_user.id + + temp_data = await get_temporary_data(session, tg_id) + if not temp_data or temp_data["state"] not in ["waiting_for_payment", "waiting_for_renewal_payment", "waiting_for_gift_payment"]: + await edit_or_send_message(target_message=message, text="❌ Не удалось получить данные для оплаты.") + return + + amount = int(temp_data["data"].get("required_amount", 0)) + if amount <= 0: + await edit_or_send_message(target_message=message, text="❌ Не удалось определить сумму оплаты.") + return + + if amount < 10: + await edit_or_send_message(target_message=message, text="❌ Минимальная сумма для оплаты криптовалютой — 10 рублей.") + return + + enabled_methods = [m for m in HELEKET_PAYMENT_METHODS if m["enable"]] + if not enabled_methods: + await edit_or_send_message(target_message=message, text="❌ Способ оплаты Heleket временно недоступен.") + return + method = enabled_methods[0] + + try: + payment_url = await generate_heleket_payment_link(amount, tg_id, method) + + markup = InlineKeyboardMarkup( + inline_keyboard=[ + [InlineKeyboardButton(text=pay_button_text, url=payment_url)], + [InlineKeyboardButton(text=main_menu_text, callback_data="profile")], + ] + ) + + language_code = getattr(from_user, "language_code", None) + amount_text = await format_for_user(session, tg_id, float(amount), language_code, force_currency="RUB") + text_out = DEFAULT_PAYMENT_MESSAGE.format(amount=amount_text) + + await edit_or_send_message(target_message=message, text=text_out, reply_markup=markup) + except Exception as e: + logger.error(f"Ошибка при создании платежа Heleket для пользователя {tg_id}: {e}") + await edit_or_send_message( + target_message=message, + text="Произошла ошибка при создании платежа. Попробуйте позже.", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), + ) diff --git a/handlers/payments/heleket/heleket.py b/handlers/payments/heleket/service.py similarity index 98% rename from handlers/payments/heleket/heleket.py rename to handlers/payments/heleket/service.py index 7523eeb0..0432d262 100644 --- a/handlers/payments/heleket/heleket.py +++ b/handlers/payments/heleket/service.py @@ -52,7 +52,6 @@ HELEKET_PAYMENT_METHODS = [ ] -@router.callback_query(F.data == "pay_heleket_crypto") async def process_callback_pay_heleket( callback_query: types.CallbackQuery, state: FSMContext, session: AsyncSession, method_name: str = None ): @@ -61,6 +60,11 @@ async def process_callback_pay_heleket( logger.info(f"User {tg_id} initiated Heleket payment.") await state.clear() + if not method_name: + enabled_methods = [m["name"] for m in HELEKET_PAYMENT_METHODS if m["enable"]] + if len(enabled_methods) == 1: + method_name = enabled_methods[0] + if method_name: method = next((m for m in HELEKET_PAYMENT_METHODS if m["name"] == method_name and m["enable"]), None) if not method: @@ -175,7 +179,7 @@ async def process_method_selection(callback_query: types.CallbackQuery, state: F ) ) builder.row(InlineKeyboardButton(text="Ввести сумму", callback_data=f"heleket_custom_amount|{method_name}")) - builder.row(InlineKeyboardButton(text=BACK, callback_data="pay_heleket_crypto")) + builder.row(InlineKeyboardButton(text=BACK, callback_data="pay")) await edit_or_send_message( target_message=callback_query.message, @@ -193,7 +197,7 @@ async def process_custom_amount_button(callback_query: types.CallbackQuery, stat await state.update_data(heleket_method=method_name) builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text=BACK, callback_data=f"pay_heleket_{method_name}")) + builder.row(InlineKeyboardButton(text=BACK, callback_data="pay_heleket_crypto")) await edit_or_send_message( target_message=callback_query.message, diff --git a/handlers/payments/heleket/webhook.py b/handlers/payments/heleket/webhook.py new file mode 100644 index 00000000..9c7c31dd --- /dev/null +++ b/handlers/payments/heleket/webhook.py @@ -0,0 +1,97 @@ +import base64 +import hashlib +import json +from logger import logger +from config import HELEKET_API_KEY +from database import async_session_maker, update_payment_status, add_balance_to_user + + +def verify_heleket_signature(data: dict) -> bool: + try: + received_signature = data.get('sign') + if not received_signature: + logger.error("Heleket webhook: отсутствует подпись") + return False + + data_without_sign = data.copy() + del data_without_sign['sign'] + + json_data = json.dumps(data_without_sign, ensure_ascii=False, separators=(',', ':')) + json_data = json_data.replace('/', '\\/') + base64_data = base64.b64encode(json_data.encode('utf-8')).decode('utf-8') + sign_string = base64_data + HELEKET_API_KEY + calculated_signature = hashlib.md5(sign_string.encode('utf-8')).hexdigest() + is_valid = calculated_signature.lower() == received_signature.lower() + + if not is_valid: + logger.error(f"Heleket webhook: неверная подпись. Ожидалось: {calculated_signature}, получено: {received_signature}") + logger.error(f"Heleket webhook: строка для подписи: {sign_string}") + else: + logger.info("Heleket webhook: подпись успешно проверена") + return is_valid + except Exception as e: + logger.error(f"Ошибка проверки подписи Heleket webhook: {e}") + return False + + +async def process_heleket_webhook(data: dict) -> bool: + try: + logger.info(f"Processing Heleket webhook: {data}") + + webhook_type = data.get('type') + uuid = data.get('uuid') + order_id = data.get('order_id') + status = data.get('status') + amount = data.get('amount') + payment_amount = data.get('payment_amount') + merchant_amount = data.get('merchant_amount') + currency = data.get('currency') + payer_currency = data.get('payer_currency') + additional_data = data.get('additional_data') + is_final = data.get('is_final', False) + + logger.info(f"Heleket webhook - Type: {webhook_type}, UUID: {uuid}, Order: {order_id}, Status: {status}") + if webhook_type != 'payment': + logger.warning(f"Heleket webhook: неизвестный тип {webhook_type}") + return False + if status in ['paid', 'paid_over']: + logger.info(f"Heleket: успешный платёж {order_id} на сумму {payment_amount} {payer_currency}") + tg_id = None + rub_amount = None + if additional_data: + try: + for part in additional_data.split(','): + if part.startswith('tg_id:'): + tg_id = int(part.split(':')[1]) + elif part.startswith('rub_amount:'): + rub_amount = float(part.split(':')[1]) + except Exception as e: + logger.error(f"Ошибка парсинга additional_data: {e}") + if not tg_id and '_' in order_id: + try: + tg_id = int(order_id.split('_')[1]) + except Exception as e: + logger.error(f"Ошибка извлечения tg_id из order_id: {e}") + if not tg_id: + logger.error(f"Не удалось извлечь tg_id из Heleket webhook: {data}") + return False + balance_amount = rub_amount if rub_amount else float(merchant_amount) + async with async_session_maker() as session: + await update_payment_status(session, order_id, "success") + await add_balance_to_user(session, tg_id, balance_amount) + await session.commit() + logger.info(f"Heleket: платёж {order_id} для пользователя {tg_id} успешно обработан, баланс пополнен на {balance_amount} RUB") + return True + elif status in ['fail', 'wrong_amount', 'cancel', 'system_fail']: + logger.warning(f"Heleket: неудачный платёж {order_id}, статус: {status}") + + async with async_session_maker() as session: + await update_payment_status(session, order_id, "failed") + await session.commit() + return True + else: + logger.info(f"Heleket: промежуточный статус {status} для платежа {order_id}") + return True + except Exception as e: + logger.error(f"Ошибка обработки Heleket webhook: {e}") + return False diff --git a/handlers/payments/kassai/__init__.py b/handlers/payments/kassai/__init__.py index e69de29b..8a870356 100644 --- a/handlers/payments/kassai/__init__.py +++ b/handlers/payments/kassai/__init__.py @@ -0,0 +1,3 @@ +__all__ = ("router",) + +from .handlers import router diff --git a/handlers/payments/kassai/handlers.py b/handlers/payments/kassai/handlers.py new file mode 100644 index 00000000..717d5885 --- /dev/null +++ b/handlers/payments/kassai/handlers.py @@ -0,0 +1,137 @@ +from aiogram import F, Router, types +from aiogram.fsm.context import FSMContext +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from sqlalchemy.ext.asyncio import AsyncSession + +from handlers.buttons import PAY_2, MAIN_MENU +from handlers.texts import DEFAULT_PAYMENT_MESSAGE +from handlers.utils import edit_or_send_message +from handlers.payments.currency_rates import format_for_user +from database import get_temporary_data +from logger import logger + +from .service import KASSAI_PAYMENT_METHODS, generate_kassai_payment_link, process_callback_pay_kassai +from .service import router as service_router + +router = Router(name="kassai_router") +router.include_router(service_router) + + +@router.callback_query(F.data == "pay_kassai_cards") +async def handle_pay_kassai_cards(callback_query: types.CallbackQuery, state: FSMContext, session: AsyncSession): + await process_callback_pay_kassai(callback_query, state, session, method_name="cards") + + +@router.callback_query(F.data == "pay_kassai_sbp") +async def handle_pay_kassai_sbp(callback_query: types.CallbackQuery, state: FSMContext, session: AsyncSession): + await process_callback_pay_kassai(callback_query, state, session, method_name="sbp") + + +async def handle_custom_amount_input_kassai_cards( + event, + session: AsyncSession, + pay_button_text: str = PAY_2, + main_menu_text: str = MAIN_MENU, +): + """ + Функция быстрого потока для KassaI Cards - принимает недостающую сумму и формирует платеж картами. + Работает с временными данными из fast_payment_flow для создания/продления/подарка. + """ + message = event.message + from_user = event.from_user + tg_id = from_user.id + + temp_data = await get_temporary_data(session, tg_id) + if not temp_data or temp_data["state"] not in ["waiting_for_payment", "waiting_for_renewal_payment", "waiting_for_gift_payment"]: + await edit_or_send_message(target_message=message, text="❌ Не удалось получить данные для оплаты.") + return + + amount = int(temp_data["data"].get("required_amount", 0)) + if amount <= 0: + await edit_or_send_message(target_message=message, text="❌ Не удалось определить сумму оплаты.") + return + + method = next((m for m in KASSAI_PAYMENT_METHODS if m["name"] == "cards" and m["enable"]), None) + if not method: + await edit_or_send_message(target_message=message, text="❌ Оплата картами KassaAI временно недоступна.") + return + + try: + payment_url = await generate_kassai_payment_link(amount, tg_id, method) + + markup = InlineKeyboardMarkup( + inline_keyboard=[ + [InlineKeyboardButton(text=pay_button_text, url=payment_url)], + [InlineKeyboardButton(text=main_menu_text, callback_data="profile")], + ] + ) + + language_code = getattr(from_user, "language_code", None) + amount_text = await format_for_user(session, tg_id, float(amount), language_code, force_currency="RUB") + text_out = DEFAULT_PAYMENT_MESSAGE.format(amount=amount_text) + + await edit_or_send_message(target_message=message, text=text_out, reply_markup=markup) + except Exception as e: + logger.error(f"Ошибка при создании платежа KassaAI Cards для пользователя {tg_id}: {e}") + await edit_or_send_message( + target_message=message, + text="Произошла ошибка при создании платежа. Попробуйте позже.", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), + ) + + +async def handle_custom_amount_input_kassai_sbp( + event, + session: AsyncSession, + pay_button_text: str = PAY_2, + main_menu_text: str = MAIN_MENU, +): + """ + Функция быстрого потока для KassaI SBP - принимает недостающую сумму и формирует платеж через СБП. + Работает с временными данными из fast_payment_flow для создания/продления/подарка. + """ + message = event.message + from_user = event.from_user + tg_id = from_user.id + + temp_data = await get_temporary_data(session, tg_id) + if not temp_data or temp_data["state"] not in ["waiting_for_payment", "waiting_for_renewal_payment", "waiting_for_gift_payment"]: + await edit_or_send_message(target_message=message, text="❌ Не удалось получить данные для оплаты.") + return + + amount = int(temp_data["data"].get("required_amount", 0)) + if amount <= 0: + await edit_or_send_message(target_message=message, text="❌ Не удалось определить сумму оплаты.") + return + + if amount < 10: + await edit_or_send_message(target_message=message, text="❌ Минимальная сумма для оплаты через СБП — 10 рублей.") + return + + method = next((m for m in KASSAI_PAYMENT_METHODS if m["name"] == "sbp" and m["enable"]), None) + if not method: + await edit_or_send_message(target_message=message, text="❌ Оплата через СБП KassaAI временно недоступна.") + return + + try: + payment_url = await generate_kassai_payment_link(amount, tg_id, method) + + markup = InlineKeyboardMarkup( + inline_keyboard=[ + [InlineKeyboardButton(text=pay_button_text, url=payment_url)], + [InlineKeyboardButton(text=main_menu_text, callback_data="profile")], + ] + ) + + language_code = getattr(from_user, "language_code", None) + amount_text = await format_for_user(session, tg_id, float(amount), language_code, force_currency="RUB") + text_out = DEFAULT_PAYMENT_MESSAGE.format(amount=amount_text) + + await edit_or_send_message(target_message=message, text=text_out, reply_markup=markup) + except Exception as e: + logger.error(f"Ошибка при создании платежа KassaAI SBP для пользователя {tg_id}: {e}") + await edit_or_send_message( + target_message=message, + text="Произошла ошибка при создании платежа. Попробуйте позже.", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), + ) diff --git a/handlers/payments/kassai/kassai.py b/handlers/payments/kassai/service.py similarity index 94% rename from handlers/payments/kassai/kassai.py rename to handlers/payments/kassai/service.py index c0905805..cba05a1a 100644 --- a/handlers/payments/kassai/kassai.py +++ b/handlers/payments/kassai/service.py @@ -15,7 +15,6 @@ from config import ( KASSAI_DOMAIN, KASSAI_FAILURE_URL, KASSAI_IP, - KASSAI_SECRET_KEY, KASSAI_SHOP_ID, KASSAI_SUCCESS_URL, PROVIDERS_ENABLED, @@ -379,21 +378,3 @@ async def generate_kassai_payment_link(amount: int, tg_id: int, method: dict) -> except Exception as e: logger.error(f"Error creating KassaAI order: {e}") return "https://fk.life/" - - -def verify_kassai_signature(data: dict, signature: str) -> bool: - """ - Проверка подписи вебхука FreeKassa (используемой KassaAI): - MERCHANT_ID:AMOUNT:SECRET_KEY2:MERCHANT_ORDER_ID - """ - try: - sign_string = f"{KASSAI_SHOP_ID}:{data.get('AMOUNT', '')}:{KASSAI_SECRET_KEY}:{data.get('MERCHANT_ORDER_ID', '')}" - expected_signature = hashlib.md5(sign_string.encode("utf-8")).hexdigest() - result = signature.upper() == expected_signature.upper() - if not result: - logger.error(f"KassaAI signature mismatch. Expected: {expected_signature}, Got: {signature}") - logger.error(f"Sign string: {sign_string}") - return result - except Exception as e: - logger.error(f"Ошибка проверки подписи KassaAI: {e}") - return False diff --git a/handlers/payments/kassai/webhook.py b/handlers/payments/kassai/webhook.py new file mode 100644 index 00000000..d3c44f00 --- /dev/null +++ b/handlers/payments/kassai/webhook.py @@ -0,0 +1,87 @@ +import hashlib +from aiohttp import web +from logger import logger +from config import KASSAI_SHOP_ID, KASSAI_SECRET_KEY +from database import add_payment, async_session_maker, get_payment_by_payment_id, update_balance, update_payment_status +from handlers.payments.utils import send_payment_success_notification + + +def verify_kassai_signature(data: dict, signature: str) -> bool: + try: + sign_string = f"{KASSAI_SHOP_ID}:{data.get('AMOUNT', '')}:{KASSAI_SECRET_KEY}:{data.get('MERCHANT_ORDER_ID', '')}" + expected_signature = hashlib.md5(sign_string.encode("utf-8")).hexdigest() + result = signature.upper() == expected_signature.upper() + if not result: + logger.error(f"KassaAI signature mismatch. Expected: {expected_signature}, Got: {signature}") + logger.error(f"Sign string: {sign_string}") + else: + logger.info("KassaAI webhook: подпись успешно проверена") + return result + except Exception as e: + logger.error(f"Ошибка проверки подписи KassaAI: {e}") + return False + + +async def kassai_webhook(request: web.Request): + try: + data = await request.post() + logger.info(f"KassaAI webhook received: {dict(data)}") + signature = data.get('SIGN', '') + if not signature: + logger.error("KassaAI webhook: отсутствует подпись") + return web.Response(status=400) + if not verify_kassai_signature(data, signature): + logger.error("KassaAI webhook: неверная подпись") + return web.Response(status=400) + + amount_raw = data.get('AMOUNT') + order_id = data.get('MERCHANT_ORDER_ID') + status = data.get('STATUS', '') + + if not amount_raw or not order_id: + logger.error("KassaAI webhook: отсутствуют обязательные параметры") + return web.Response(status=400) + if status.upper() != 'SUCCESS': + logger.warning(f"KassaAI webhook: неуспешный статус {status} для заказа {order_id}") + return web.Response(text="OK") + + amount = float(amount_raw) + + try: + tg_id = int(order_id.split('_')[1]) + except (IndexError, ValueError) as e: + logger.error(f"KassaAI webhook: не удалось извлечь tg_id из order_id {order_id}: {e}") + return web.Response(status=400) + + logger.info(f"KassaAI: успешный платёж {order_id} на сумму {amount} RUB для пользователя {tg_id}") + + async with async_session_maker() as session: + payment = await get_payment_by_payment_id(session, order_id) + if payment: + if payment.get("status") == "success": + logger.info(f"KassaAI: платёж {order_id} уже обработан") + return web.Response(text="OK") + ok = await update_payment_status(session=session, internal_id=int(payment["id"]), new_status="success") + if not ok: + logger.error(f"KassaAI: не удалось обновить статус платежа {order_id}") + return web.Response(status=500) + else: + await add_payment( + session=session, + tg_id=tg_id, + amount=amount, + payment_system="KASSAI", + status="success", + currency="RUB", + payment_id=order_id, + metadata=None, + ) + + await update_balance(session, tg_id, amount) + await send_payment_success_notification(tg_id, amount, session) + await session.commit() + logger.info(f"KassaAI: платёж {order_id} успешно обработан, баланс пользователя {tg_id} пополнен на {amount} RUB") + return web.Response(text="OK") + except Exception as e: + logger.error(f"Ошибка обработки KassaAI webhook: {e}") + return web.Response(status=500) diff --git a/handlers/payments/pay.py b/handlers/payments/pay.py index 216e539a..1a9a0960 100644 --- a/handlers/payments/pay.py +++ b/handlers/payments/pay.py @@ -15,8 +15,6 @@ from database import get_last_payments from database.models import User from handlers import buttons as btn -from handlers.payments.heleket.heleket import process_callback_pay_heleket -from handlers.payments.kassai.kassai import process_callback_pay_kassai from handlers.payments.stars.handlers import process_callback_pay_stars from handlers.payments.tribute.handlers import process_callback_pay_tribute from handlers.payments.wata.wata import process_callback_pay_wata @@ -181,22 +179,6 @@ async def handle_pay_wata_sbp(callback_query: CallbackQuery, state: FSMContext, async def handle_pay_wata_int(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession): await process_callback_pay_wata(callback_query, state, session, cassa_name="int") - -@router.callback_query(F.data == "pay_kassai_cards") -async def handle_pay_kassai_cards(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession): - await process_callback_pay_kassai(callback_query, state, session, method_name="cards") - - -@router.callback_query(F.data == "pay_kassai_sbp") -async def handle_pay_kassai_sbp(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession): - await process_callback_pay_kassai(callback_query, state, session, method_name="sbp") - - -@router.callback_query(F.data == "pay_heleket_crypto") -async def handle_pay_heleket_crypto(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession): - await process_callback_pay_heleket(callback_query, state, session, method_name="crypto") - - @router.callback_query(F.data == "pay_tribute") async def handle_pay_tribute(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession): await process_callback_pay_tribute(callback_query, state, session) diff --git a/handlers/payments/providers.py b/handlers/payments/providers.py index e2c2e040..f44e7e73 100644 --- a/handlers/payments/providers.py +++ b/handlers/payments/providers.py @@ -20,12 +20,14 @@ PROVIDERS_BASE: Dict[str, dict] = { "KASSAI_CARDS": { "currency": "RUB", "value": "pay_kassai_cards", - "fast": None, + "fast": "handle_custom_amount_input_kassai_cards", + "module": "kassai", }, "KASSAI_SBP": { "currency": "RUB", "value": "pay_kassai_sbp", - "fast": None, + "fast": "handle_custom_amount_input_kassai_sbp", + "module": "kassai", }, "WATA_RU": { "currency": "RUB", @@ -45,7 +47,7 @@ PROVIDERS_BASE: Dict[str, dict] = { "HELEKET": { "currency": "USD", "value": "pay_heleket_crypto", - "fast": None, + "fast": "handle_custom_amount_input_heleket", }, "CRYPTOBOT": { "currency": "USD",