Files
remnawave-bedolaga-telegram…/app/handlers/balance/main.py
2026-01-17 03:31:39 +03:00

1032 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import html
import logging
from aiogram import Dispatcher, types, F
from aiogram.exceptions import TelegramBadRequest
from aiogram.fsm.context import FSMContext
from aiogram.types import InaccessibleMessage
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.states import BalanceStates
from app.database.crud.user import add_user_balance
from app.utils.price_display import calculate_user_price, format_price_button
from app.utils.pricing_utils import format_period_description
from app.database.crud.transaction import (
get_user_transactions, get_user_transactions_count,
create_transaction
)
from app.database.models import User, TransactionType, PaymentMethod
from app.keyboards.inline import (
get_balance_keyboard, get_payment_methods_keyboard,
get_back_keyboard, get_pagination_keyboard
)
from app.localization.texts import get_texts
from app.services.payment_service import PaymentService
from app.utils.pagination import paginate_list
from app.utils.decorators import error_handler
logger = logging.getLogger(__name__)
TRANSACTIONS_PER_PAGE = 10
async def route_payment_by_method(
message: types.Message,
db_user: User,
amount_kopeks: int,
state: FSMContext,
payment_method: str
) -> bool:
"""
Роутер платежей по методу оплаты.
Args:
message: Сообщение для ответа
db_user: Пользователь БД
amount_kopeks: Сумма в копейках
state: FSM состояние
payment_method: Метод оплаты (yookassa, stars, cryptobot и т.д.)
Returns:
True если платеж обработан, False если метод неизвестен
"""
if payment_method == "stars":
from .stars import process_stars_payment_amount
await process_stars_payment_amount(message, db_user, amount_kopeks, state)
return True
# Все остальные методы требуют сессию БД
from app.database.database import AsyncSessionLocal
if payment_method == "yookassa":
from .yookassa import process_yookassa_payment_amount
async with AsyncSessionLocal() as db:
await process_yookassa_payment_amount(message, db_user, db, amount_kopeks, state)
return True
if payment_method == "yookassa_sbp":
from .yookassa import process_yookassa_sbp_payment_amount
async with AsyncSessionLocal() as db:
await process_yookassa_sbp_payment_amount(message, db_user, db, amount_kopeks, state)
return True
if payment_method == "mulenpay":
from .mulenpay import process_mulenpay_payment_amount
async with AsyncSessionLocal() as db:
await process_mulenpay_payment_amount(message, db_user, db, amount_kopeks, state)
return True
if payment_method == "platega":
from .platega import process_platega_payment_amount
async with AsyncSessionLocal() as db:
await process_platega_payment_amount(message, db_user, db, amount_kopeks, state)
return True
if payment_method == "wata":
from .wata import process_wata_payment_amount
async with AsyncSessionLocal() as db:
await process_wata_payment_amount(message, db_user, db, amount_kopeks, state)
return True
if payment_method == "pal24":
from .pal24 import process_pal24_payment_amount
async with AsyncSessionLocal() as db:
await process_pal24_payment_amount(message, db_user, db, amount_kopeks, state)
return True
if payment_method == "cryptobot":
from .cryptobot import process_cryptobot_payment_amount
async with AsyncSessionLocal() as db:
await process_cryptobot_payment_amount(message, db_user, db, amount_kopeks, state)
return True
if payment_method == "heleket":
from .heleket import process_heleket_payment_amount
async with AsyncSessionLocal() as db:
await process_heleket_payment_amount(message, db_user, db, amount_kopeks, state)
return True
if payment_method == "cloudpayments":
from .cloudpayments import process_cloudpayments_payment_amount
async with AsyncSessionLocal() as db:
await process_cloudpayments_payment_amount(message, db_user, db, amount_kopeks, state)
return True
if payment_method == "freekassa":
from .freekassa import process_freekassa_payment_amount
async with AsyncSessionLocal() as db:
await process_freekassa_payment_amount(message, db_user, db, amount_kopeks, state)
return True
return False
async def get_quick_amount_buttons(language: str, user: User) -> list:
"""
Generate quick amount buttons with user-specific pricing and discounts.
Args:
language: User's language for formatting
user: User object to calculate personalized discounts
Returns:
List of button rows for inline keyboard
"""
if not settings.is_quick_amount_buttons_enabled():
return []
from app.localization.texts import get_texts
from app.config import PERIOD_PRICES
texts = get_texts(language)
# В режиме тарифов получаем цены из тарифа пользователя
tariff_prices = None
tariff_periods = None
if settings.is_tariffs_mode():
from app.database.database import AsyncSessionLocal
from app.database.crud.subscription import get_subscription_by_user_id
from app.database.crud.tariff import get_tariff_by_id
async with AsyncSessionLocal() as db:
subscription = await get_subscription_by_user_id(db, user.id)
if subscription and subscription.tariff_id:
tariff = await get_tariff_by_id(db, subscription.tariff_id)
if tariff and tariff.period_prices:
tariff_prices = {int(k): v for k, v in tariff.period_prices.items()}
tariff_periods = sorted(tariff_prices.keys())
buttons = []
# Используем периоды тарифа в режиме тарифов, иначе стандартные
if tariff_periods:
periods = tariff_periods[:6]
else:
periods = settings.get_available_subscription_periods()[:6]
for period in periods:
# Получаем цену из тарифа или из PERIOD_PRICES
if tariff_prices and period in tariff_prices:
base_price_kopeks = tariff_prices[period]
else:
base_price_kopeks = PERIOD_PRICES.get(period, 0)
if base_price_kopeks > 0:
# Calculate price with user's promo group discount using unified system
price_info = calculate_user_price(user, base_price_kopeks, period, "period")
callback_data = f"quick_amount_{price_info.final_price}"
# Format button text with discount display
period_label = f"{period} дней"
# For balance buttons, use simpler format without emoji and period label prefix
if price_info.has_discount:
button_text = (
f"{texts.format_price(price_info.base_price)}"
f"{texts.format_price(price_info.final_price)} "
f"(-{price_info.discount_percent}%) • {period_label}"
)
else:
button_text = f"{texts.format_price(price_info.final_price)}{period_label}"
buttons.append(
types.InlineKeyboardButton(
text=button_text,
callback_data=callback_data
)
)
keyboard_rows = []
for i in range(0, len(buttons), 2):
keyboard_rows.append(buttons[i:i + 2])
return keyboard_rows
@error_handler
async def show_balance_menu(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession
):
# Проверяем, доступно ли сообщение
if isinstance(callback.message, InaccessibleMessage):
await callback.answer()
return
texts = get_texts(db_user.language)
balance_text = texts.BALANCE_INFO.format(
balance=texts.format_price(db_user.balance_kopeks)
)
reply_markup = get_balance_keyboard(db_user.language)
try:
if callback.message and callback.message.text:
await callback.message.edit_text(
balance_text,
reply_markup=reply_markup
)
elif callback.message and callback.message.caption:
await callback.message.edit_caption(
balance_text,
reply_markup=reply_markup
)
else:
await callback.message.answer(
balance_text,
reply_markup=reply_markup
)
except TelegramBadRequest as error:
logger.warning(
"Failed to edit balance message, sending a new one instead: %s",
error,
)
await callback.message.answer(
balance_text,
reply_markup=reply_markup
)
await callback.answer()
@error_handler
async def show_balance_history(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
page: int = 1
):
texts = get_texts(db_user.language)
offset = (page - 1) * TRANSACTIONS_PER_PAGE
raw_transactions = await get_user_transactions(
db, db_user.id,
limit=TRANSACTIONS_PER_PAGE * 3,
offset=offset
)
seen_transactions = set()
unique_transactions = []
for transaction in raw_transactions:
rounded_time = transaction.created_at.replace(second=0, microsecond=0)
transaction_key = (
transaction.amount_kopeks,
transaction.description,
rounded_time
)
if transaction_key not in seen_transactions:
seen_transactions.add(transaction_key)
unique_transactions.append(transaction)
if len(unique_transactions) >= TRANSACTIONS_PER_PAGE:
break
all_transactions = await get_user_transactions(db, db_user.id, limit=1000)
seen_all = set()
total_unique = 0
for transaction in all_transactions:
rounded_time = transaction.created_at.replace(second=0, microsecond=0)
transaction_key = (
transaction.amount_kopeks,
transaction.description,
rounded_time
)
if transaction_key not in seen_all:
seen_all.add(transaction_key)
total_unique += 1
if not unique_transactions:
await callback.message.edit_text(
"📊 История операций пуста",
reply_markup=get_back_keyboard(db_user.language)
)
await callback.answer()
return
text = "📊 <b>История операций</b>\n\n"
for transaction in unique_transactions:
emoji = "💰" if transaction.type == TransactionType.DEPOSIT.value else "💸"
amount_text = f"+{texts.format_price(transaction.amount_kopeks)}" if transaction.type == TransactionType.DEPOSIT.value else f"-{texts.format_price(transaction.amount_kopeks)}"
text += f"{emoji} {amount_text}\n"
text += f"📝 {transaction.description}\n"
text += f"📅 {transaction.created_at.strftime('%d.%m.%Y %H:%M')}\n\n"
keyboard = []
total_pages = (total_unique + TRANSACTIONS_PER_PAGE - 1) // TRANSACTIONS_PER_PAGE
if total_pages > 1:
pagination_row = get_pagination_keyboard(
page, total_pages, "balance_history", db_user.language
)
keyboard.extend(pagination_row)
keyboard.append([
types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_balance")
])
await callback.message.edit_text(
text,
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard),
parse_mode="HTML"
)
await callback.answer()
@error_handler
async def handle_balance_history_pagination(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession
):
page = int(callback.data.split('_')[-1])
await show_balance_history(callback, db_user, db, page)
@error_handler
async def show_payment_methods(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
state: FSMContext
):
from app.utils.payment_utils import get_payment_methods_text
from app.database.crud.subscription import get_subscription_by_user_id
from app.utils.pricing_utils import calculate_months_from_days, apply_percentage_discount
from app.config import settings
from app.services.subscription_service import SubscriptionService
texts = get_texts(db_user.language)
# Проверка ограничения на пополнение
if getattr(db_user, 'restriction_topup', False):
reason = getattr(db_user, 'restriction_reason', None) or "Действие ограничено администратором"
support_url = settings.get_support_contact_url()
keyboard = []
if support_url:
keyboard.append([types.InlineKeyboardButton(text="🆘 Обжаловать", url=support_url)])
keyboard.append([types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_balance")])
await callback.message.edit_text(
f"🚫 <b>Пополнение ограничено</b>\n\n{reason}\n\n"
"Если вы считаете это ошибкой, вы можете обжаловать решение.",
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard)
)
await callback.answer()
return
payment_text = get_payment_methods_text(db_user.language)
# Добавляем информацию о текущем тарифе пользователя
subscription = await get_subscription_by_user_id(db, db_user.id)
tariff_info = ""
if subscription and not subscription.is_trial:
# Рассчитываем приблизительную стоимость продления на 30 дней
duration_days = 30 # Берем для примера 30 дней
current_traffic = subscription.traffic_limit_gb
current_connected_squads = subscription.connected_squads or []
current_device_limit = subscription.device_limit or settings.DEFAULT_DEVICE_LIMIT
try:
# Получаем цены для текущих параметров
from app.config import PERIOD_PRICES
from app.database.crud.tariff import get_tariff_by_id
# В режиме тарифов берём цену из тарифа пользователя
base_price_original = 0
if settings.is_tariffs_mode() and subscription.tariff_id:
tariff = await get_tariff_by_id(db, subscription.tariff_id)
if tariff and tariff.period_prices:
base_price_original = tariff.period_prices.get(str(duration_days), 0)
# Если не нашли в тарифе - используем PERIOD_PRICES
if base_price_original <= 0:
base_price_original = PERIOD_PRICES.get(duration_days, 0)
period_discount_percent = db_user.get_promo_discount("period", duration_days)
base_price, base_discount_total = apply_percentage_discount(
base_price_original,
period_discount_percent,
)
# Рассчитываем стоимость серверов
from app.services.subscription_service import SubscriptionService
subscription_service = SubscriptionService()
servers_price_per_month, per_server_monthly_prices = await subscription_service.get_countries_price_by_uuids(
current_connected_squads,
db,
promo_group_id=db_user.promo_group_id,
)
servers_discount_percent = db_user.get_promo_discount("servers", duration_days)
total_servers_price = 0
for server_price in per_server_monthly_prices:
discounted_per_month, discount_per_month = apply_percentage_discount(
server_price,
servers_discount_percent,
)
total_servers_price += discounted_per_month
# Рассчитываем стоимость трафика
traffic_price_per_month = settings.get_traffic_price(current_traffic)
traffic_discount_percent = db_user.get_promo_discount("traffic", duration_days)
traffic_discounted_per_month, traffic_discount_per_month = apply_percentage_discount(
traffic_price_per_month,
traffic_discount_percent,
)
# Рассчитываем стоимость устройств
additional_devices = max(0, (current_device_limit or 0) - settings.DEFAULT_DEVICE_LIMIT)
devices_price_per_month = additional_devices * settings.PRICE_PER_DEVICE
devices_discount_percent = db_user.get_promo_discount("devices", duration_days)
devices_discounted_per_month, devices_discount_per_month = apply_percentage_discount(
devices_price_per_month,
devices_discount_percent,
)
# Общая стоимость
months_in_period = calculate_months_from_days(duration_days)
total_price = (
base_price +
total_servers_price * months_in_period +
traffic_discounted_per_month * months_in_period +
devices_discounted_per_month * months_in_period
)
traffic_value = current_traffic or 0
if traffic_value <= 0:
traffic_display = texts.t("TRAFFIC_UNLIMITED_SHORT", "Безлимит")
else:
traffic_display = texts.format_traffic(traffic_value)
current_tariff_desc = (
f"📱 Подписка: {len(current_connected_squads)} серверов, "
f"{traffic_display}, {current_device_limit} устр."
)
estimated_price_info = f"💰 Стоимость продления (примерно): {texts.format_price(total_price)} за {duration_days} дней"
tariff_info = f"\n\n📋 <b>Ваш текущий тариф:</b>\n{current_tariff_desc}\n{estimated_price_info}"
except Exception as e:
logger.warning(f"Не удалось рассчитать стоимость текущей подписки для пользователя {db_user.id}: {e}")
tariff_info = ""
full_text = payment_text + tariff_info
keyboard = get_payment_methods_keyboard(0, db_user.language)
# Если сообщение недоступно, отправляем новое
if isinstance(callback.message, InaccessibleMessage):
await callback.message.answer(
full_text,
reply_markup=keyboard,
parse_mode="HTML"
)
await callback.answer()
return
try:
await callback.message.edit_text(
full_text,
reply_markup=keyboard,
parse_mode="HTML"
)
except TelegramBadRequest:
try:
await callback.message.edit_caption(
full_text,
reply_markup=keyboard,
parse_mode="HTML"
)
except TelegramBadRequest:
try:
await callback.message.delete()
except TelegramBadRequest:
pass
await callback.message.answer(
full_text,
reply_markup=keyboard,
parse_mode="HTML"
)
await callback.answer()
@error_handler
async def handle_payment_methods_unavailable(
callback: types.CallbackQuery,
db_user: User
):
texts = get_texts(db_user.language)
await callback.answer(
texts.t(
"PAYMENT_METHODS_UNAVAILABLE_ALERT",
"⚠️ В данный момент автоматические способы оплаты временно недоступны. Для пополнения баланса обратитесь в техподдержку.",
),
show_alert=True
)
@error_handler
async def handle_successful_topup_with_cart(
user_id: int,
amount_kopeks: int,
bot,
db: AsyncSession
):
from app.database.crud.user import get_user_by_id
from aiogram.fsm.context import FSMContext
from aiogram.fsm.storage.base import StorageKey
from app.bot import dp
user = await get_user_by_id(db, user_id)
if not user:
return
storage = dp.storage
key = StorageKey(bot_id=bot.id, chat_id=user.telegram_id, user_id=user.telegram_id)
try:
state_data = await storage.get_data(key)
current_state = await storage.get_state(key)
if (current_state == "SubscriptionStates:cart_saved_for_topup" and
state_data.get('saved_cart')):
texts = get_texts(user.language)
total_price = state_data.get('total_price', 0)
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text="🛒 Вернуться к оформлению подписки",
callback_data="return_to_saved_cart"
)],
[types.InlineKeyboardButton(
text="💰 Мой баланс",
callback_data="menu_balance"
)],
[types.InlineKeyboardButton(
text="🏠 Главное меню",
callback_data="back_to_menu"
)]
])
success_text = (
f"✅ Баланс пополнен на {texts.format_price(amount_kopeks)}!\n\n"
f"💰 Текущий баланс: {texts.format_price(user.balance_kopeks)}\n\n"
f"⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. "
f"Обязательно активируйте подписку отдельно!\n\n"
f"🔄 При наличии сохранённой корзины подписки и включенной автопокупке, "
f"подписка будет приобретена автоматически после пополнения баланса.\n\n"
f"🛒 У вас есть сохраненная корзина подписки\n"
f"Стоимость: {texts.format_price(total_price)}\n\n"
f"Хотите продолжить оформление?"
)
await bot.send_message(
chat_id=user.telegram_id,
text=success_text,
reply_markup=keyboard,
parse_mode="HTML"
)
except Exception as e:
logger.error(f"Ошибка обработки успешного пополнения с корзиной: {e}")
@error_handler
async def request_support_topup(
callback: types.CallbackQuery,
db_user: User
):
texts = get_texts(db_user.language)
if not settings.is_support_topup_enabled():
await callback.answer(
texts.t(
"SUPPORT_TOPUP_DISABLED",
"Пополнение через поддержку отключено. Попробуйте другой способ оплаты.",
),
show_alert=True,
)
return
support_text = f"""
🛠️ <b>Пополнение через поддержку</b>
Для пополнения баланса обратитесь в техподдержку:
{settings.get_support_contact_display_html()}
Укажите:
• ID: {db_user.telegram_id}
• Сумму пополнения
• Способ оплаты
⏰ Время обработки: 1-24 часа
<b>Доступные способы:</b>
• Криптовалюта
• Переводы между банками
• Другие платежные системы
"""
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text="💬 Написать в поддержку",
url=settings.get_support_contact_url() or "https://t.me/"
)],
[types.InlineKeyboardButton(text=texts.BACK, callback_data="balance_topup")]
])
await callback.message.edit_text(
support_text,
reply_markup=keyboard,
parse_mode="HTML"
)
await callback.answer()
@error_handler
async def process_topup_amount(
message: types.Message,
db_user: User,
state: FSMContext
):
texts = get_texts(db_user.language)
try:
if not message.text:
if message.successful_payment:
logger.info(
"Получено сообщение об успешном платеже без текста, "
"обработчик суммы пополнения завершает работу"
)
await state.clear()
return
await message.answer(
texts.INVALID_AMOUNT,
reply_markup=get_back_keyboard(db_user.language)
)
return
amount_text = message.text.strip()
if not amount_text:
await message.answer(
texts.INVALID_AMOUNT,
reply_markup=get_back_keyboard(db_user.language)
)
return
amount_rubles = float(amount_text.replace(',', '.'))
if amount_rubles < 1:
await message.answer("Минимальная сумма пополнения: 1 ₽")
return
if amount_rubles > 50000:
await message.answer("Максимальная сумма пополнения: 50,000 ₽")
return
amount_kopeks = int(amount_rubles * 100)
data = await state.get_data()
payment_method = data.get("payment_method", "stars")
if payment_method in ["yookassa", "yookassa_sbp"]:
if amount_kopeks < settings.YOOKASSA_MIN_AMOUNT_KOPEKS:
min_rubles = settings.YOOKASSA_MIN_AMOUNT_KOPEKS / 100
await message.answer(f"❌ Минимальная сумма для оплаты через YooKassa: {min_rubles:.0f}")
return
if amount_kopeks > settings.YOOKASSA_MAX_AMOUNT_KOPEKS:
max_rubles = settings.YOOKASSA_MAX_AMOUNT_KOPEKS / 100
await message.answer(f"❌ Максимальная сумма для оплаты через YooKassa: {max_rubles:,.0f}".replace(',', ' '))
return
if not await route_payment_by_method(message, db_user, amount_kopeks, state, payment_method):
await message.answer("Неизвестный способ оплаты")
except ValueError:
await message.answer(
texts.INVALID_AMOUNT,
reply_markup=get_back_keyboard(db_user.language)
)
@error_handler
async def handle_sbp_payment(
callback: types.CallbackQuery,
db: AsyncSession
):
try:
local_payment_id = int(callback.data.split('_')[-1])
from app.database.crud.yookassa import get_yookassa_payment_by_local_id
payment = await get_yookassa_payment_by_local_id(db, local_payment_id)
if not payment:
await callback.answer("❌ Платеж не найден", show_alert=True)
return
import json
metadata = json.loads(payment.metadata_json) if payment.metadata_json else {}
confirmation_token = metadata.get("confirmation_token")
if not confirmation_token:
await callback.answer("❌ Токен подтверждения не найден", show_alert=True)
return
await callback.message.answer(
f"Для оплаты через СБП откройте приложение вашего банка и подтвердите платеж.\\n\\n"
f"Если у вас не открылось банковское приложение автоматически, вы можете:\\n"
f"1. Скопировать этот токен: <code>{confirmation_token}</code>\\n"
f"2. Открыть приложение вашего банка\\n"
f"3. Найти функцию оплаты по токену\\n"
f"4. Вставить токен и подтвердить платеж",
parse_mode="HTML"
)
await callback.answer("Информация об оплате отправлена", show_alert=True)
except Exception as e:
logger.error(f"Ошибка обработки embedded платежа СБП: {e}")
await callback.answer("❌ Ошибка обработки платежа", show_alert=True)
@error_handler
async def handle_quick_amount_selection(
callback: types.CallbackQuery,
db_user: User,
state: FSMContext
):
"""
Обработчик выбора суммы через кнопки быстрого выбора
"""
# Проверяем, что пользователь в правильном состоянии FSM
current_state = await state.get_state()
if current_state != BalanceStates.waiting_for_amount:
await callback.answer("❌ Сначала выберите способ оплаты", show_alert=True)
return
# Извлекаем сумму из callback_data
try:
amount_kopeks = int(callback.data.split('_')[-1])
# Получаем метод оплаты из состояния
data = await state.get_data()
payment_method = data.get("payment_method", "yookassa")
# Роутим платеж на соответствующий обработчик
if not await route_payment_by_method(
callback.message, db_user, amount_kopeks, state, payment_method
):
await callback.answer("❌ Неизвестный способ оплаты", show_alert=True)
return
except ValueError:
await callback.answer("❌ Ошибка обработки суммы", show_alert=True)
except Exception as e:
logger.error(f"Ошибка обработки быстрого выбора суммы: {e}")
await callback.answer("❌ Ошибка обработки запроса", show_alert=True)
@error_handler
async def handle_topup_amount_callback(
callback: types.CallbackQuery,
db_user: User,
state: FSMContext,
):
try:
_, method, amount_str = callback.data.split("|", 2)
amount_kopeks = int(amount_str)
except ValueError:
await callback.answer("❌ Некорректный запрос", show_alert=True)
return
if amount_kopeks <= 0:
await callback.answer("❌ Некорректная сумма", show_alert=True)
return
try:
# Особые случаи, требующие специальной логики
if method == "platega":
from app.database.database import AsyncSessionLocal
from .platega import process_platega_payment_amount, start_platega_payment
data = await state.get_data()
method_code = int(data.get("platega_method", 0)) if data else 0
if method_code > 0:
async with AsyncSessionLocal() as db:
await process_platega_payment_amount(
callback.message, db_user, db, amount_kopeks, state
)
else:
await state.update_data(platega_pending_amount=amount_kopeks)
await start_platega_payment(callback, db_user, state)
elif method == "tribute":
from .tribute import start_tribute_payment
await start_tribute_payment(callback, db_user)
return
# Стандартные методы через роутер
elif not await route_payment_by_method(
callback.message, db_user, amount_kopeks, state, method
):
await callback.answer("❌ Неизвестный способ оплаты", show_alert=True)
return
await callback.answer()
except Exception as error:
logger.error(f"Ошибка быстрого пополнения: {error}")
await callback.answer("❌ Ошибка обработки запроса", show_alert=True)
def register_balance_handlers(dp: Dispatcher):
dp.callback_query.register(
show_balance_menu,
F.data == "menu_balance"
)
dp.callback_query.register(
show_balance_history,
F.data == "balance_history"
)
dp.callback_query.register(
handle_balance_history_pagination,
F.data.startswith("balance_history_page_")
)
dp.callback_query.register(
show_payment_methods,
F.data == "balance_topup"
)
from .stars import start_stars_payment
dp.callback_query.register(
start_stars_payment,
F.data == "topup_stars"
)
from .yookassa import start_yookassa_payment
dp.callback_query.register(
start_yookassa_payment,
F.data == "topup_yookassa"
)
from .yookassa import start_yookassa_sbp_payment
dp.callback_query.register(
start_yookassa_sbp_payment,
F.data == "topup_yookassa_sbp"
)
from .mulenpay import start_mulenpay_payment
dp.callback_query.register(
start_mulenpay_payment,
F.data == "topup_mulenpay"
)
from .wata import start_wata_payment
dp.callback_query.register(
start_wata_payment,
F.data == "topup_wata"
)
from .pal24 import start_pal24_payment
dp.callback_query.register(
start_pal24_payment,
F.data == "topup_pal24"
)
from .pal24 import handle_pal24_method_selection
dp.callback_query.register(
handle_pal24_method_selection,
F.data.startswith("pal24_method_"),
)
from .platega import start_platega_payment, handle_platega_method_selection
dp.callback_query.register(
start_platega_payment,
F.data == "topup_platega"
)
dp.callback_query.register(
handle_platega_method_selection,
F.data.startswith("platega_method_"),
)
from .yookassa import check_yookassa_payment_status
dp.callback_query.register(
check_yookassa_payment_status,
F.data.startswith("check_yookassa_")
)
from .tribute import start_tribute_payment
dp.callback_query.register(
start_tribute_payment,
F.data == "topup_tribute"
)
dp.callback_query.register(
request_support_topup,
F.data == "topup_support"
)
from .yookassa import check_yookassa_payment_status
dp.callback_query.register(
check_yookassa_payment_status,
F.data.startswith("check_yookassa_")
)
dp.message.register(
process_topup_amount,
BalanceStates.waiting_for_amount
)
from .cryptobot import start_cryptobot_payment
dp.callback_query.register(
start_cryptobot_payment,
F.data == "topup_cryptobot"
)
from .cryptobot import check_cryptobot_payment_status
dp.callback_query.register(
check_cryptobot_payment_status,
F.data.startswith("check_cryptobot_")
)
from .heleket import start_heleket_payment, check_heleket_payment_status
dp.callback_query.register(
start_heleket_payment,
F.data == "topup_heleket"
)
dp.callback_query.register(
check_heleket_payment_status,
F.data.startswith("check_heleket_")
)
from .cloudpayments import start_cloudpayments_payment, handle_cloudpayments_quick_amount
dp.callback_query.register(
start_cloudpayments_payment,
F.data == "topup_cloudpayments"
)
dp.callback_query.register(
handle_cloudpayments_quick_amount,
F.data.startswith("topup_amount|cloudpayments|")
)
from .freekassa import start_freekassa_topup, process_freekassa_quick_amount
dp.callback_query.register(
start_freekassa_topup,
F.data == "topup_freekassa"
)
dp.callback_query.register(
process_freekassa_quick_amount,
F.data.startswith("topup_amount|freekassa|")
)
from .mulenpay import check_mulenpay_payment_status
dp.callback_query.register(
check_mulenpay_payment_status,
F.data.startswith("check_mulenpay_")
)
from .wata import check_wata_payment_status
dp.callback_query.register(
check_wata_payment_status,
F.data.startswith("check_wata_")
)
from .pal24 import check_pal24_payment_status
dp.callback_query.register(
check_pal24_payment_status,
F.data.startswith("check_pal24_")
)
from .platega import check_platega_payment_status
dp.callback_query.register(
check_platega_payment_status,
F.data.startswith("check_platega_")
)
dp.callback_query.register(
handle_payment_methods_unavailable,
F.data == "payment_methods_unavailable"
)
# Регистрируем обработчик для кнопок быстрого выбора суммы
dp.callback_query.register(
handle_quick_amount_selection,
F.data.startswith("quick_amount_")
)
dp.callback_query.register(
handle_topup_amount_callback,
F.data.startswith("topup_amount|")
)