Files
remnawave-bedolaga-telegram…/app/handlers/referral.py
gy9vin 6392033579 fix(referral): обновлены методы отправки уведомлений админам
- Заменён метод send_notification на send_to_admins в AdminNotificationService
  - Исправлена настройка NOTIFICATIONS_CHAT_ID на ADMIN_NOTIFICATIONS_CHAT_ID для отправки в топик
2026-01-07 17:00:34 +03:00

922 lines
33 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 json
import logging
from pathlib import Path
import qrcode
from aiogram import Dispatcher, F, types
from aiogram.exceptions import TelegramBadRequest
from aiogram.fsm.context import FSMContext
from aiogram.types import FSInputFile
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import User
from app.keyboards.inline import get_referral_keyboard
from app.localization.texts import get_texts
from app.services.referral_withdrawal_service import referral_withdrawal_service
from app.services.admin_notification_service import AdminNotificationService
from app.states import ReferralWithdrawalStates
from app.utils.photo_message import edit_or_answer_photo
from app.utils.user_utils import (
get_detailed_referral_list,
get_effective_referral_commission_percent,
get_referral_analytics,
get_user_referral_summary,
)
logger = logging.getLogger(__name__)
async def show_referral_info(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession
):
# Проверяем, включена ли реферальная программа
if not settings.is_referral_program_enabled():
texts = get_texts(db_user.language)
await callback.answer(
texts.t("REFERRAL_PROGRAM_DISABLED", "Реферальная программа отключена"),
show_alert=True
)
return
texts = get_texts(db_user.language)
summary = await get_user_referral_summary(db, db_user.id)
bot_username = (await callback.bot.get_me()).username
referral_link = f"https://t.me/{bot_username}?start={db_user.referral_code}"
referral_text = (
texts.t("REFERRAL_PROGRAM_TITLE", "👥 <b>Реферальная программа</b>")
+ "\n\n"
+ texts.t("REFERRAL_STATS_HEADER", "📊 <b>Ваша статистика:</b>")
+ "\n"
+ texts.t(
"REFERRAL_STATS_INVITED",
"• Приглашено пользователей: <b>{count}</b>",
).format(count=summary['invited_count'])
+ "\n"
+ texts.t(
"REFERRAL_STATS_FIRST_TOPUPS",
"• Сделали первое пополнение: <b>{count}</b>",
).format(count=summary['paid_referrals_count'])
+ "\n"
+ texts.t(
"REFERRAL_STATS_ACTIVE",
"• Активных рефералов: <b>{count}</b>",
).format(count=summary['active_referrals_count'])
+ "\n"
+ texts.t(
"REFERRAL_STATS_CONVERSION",
"• Конверсия: <b>{rate}%</b>",
).format(rate=summary['conversion_rate'])
+ "\n"
+ texts.t(
"REFERRAL_STATS_TOTAL_EARNED",
"• Заработано всего: <b>{amount}</b>",
).format(amount=texts.format_price(summary['total_earned_kopeks']))
+ "\n"
+ texts.t(
"REFERRAL_STATS_MONTH_EARNED",
"За последний месяц: <b>{amount}</b>",
).format(amount=texts.format_price(summary['month_earned_kopeks']))
+ "\n\n"
+ texts.t("REFERRAL_REWARDS_HEADER", "🎁 <b>Как работают награды:</b>")
+ "\n"
+ texts.t(
"REFERRAL_REWARD_NEW_USER",
"• Новый пользователь получает: <b>{bonus}</b> при первом пополнении от <b>{minimum}</b>",
).format(
bonus=texts.format_price(settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS),
minimum=texts.format_price(settings.REFERRAL_MINIMUM_TOPUP_KOPEKS),
)
+ "\n"
+ texts.t(
"REFERRAL_REWARD_INVITER",
"• Вы получаете при первом пополнении реферала: <b>{bonus}</b>",
).format(bonus=texts.format_price(settings.REFERRAL_INVITER_BONUS_KOPEKS))
+ "\n"
+ texts.t(
"REFERRAL_REWARD_COMMISSION",
"• Комиссия с каждого пополнения реферала: <b>{percent}%</b>",
).format(percent=get_effective_referral_commission_percent(db_user))
+ "\n\n"
+ texts.t("REFERRAL_LINK_TITLE", "🔗 <b>Ваша реферальная ссылка:</b>")
+ f"\n<code>{referral_link}</code>\n\n"
+ texts.t("REFERRAL_CODE_TITLE", "🆔 <b>Ваш код:</b> <code>{code}</code>").format(code=db_user.referral_code)
+ "\n\n"
)
if summary['recent_earnings']:
meaningful_earnings = [
earning for earning in summary['recent_earnings'][:5]
if earning['amount_kopeks'] > 0
]
if meaningful_earnings:
referral_text += texts.t(
"REFERRAL_RECENT_EARNINGS_HEADER",
"💰 <b>Последние начисления:</b>",
) + "\n"
for earning in meaningful_earnings[:3]:
reason_text = {
"referral_first_topup": texts.t(
"REFERRAL_EARNING_REASON_FIRST_TOPUP",
"🎉 Первое пополнение",
),
"referral_commission_topup": texts.t(
"REFERRAL_EARNING_REASON_COMMISSION_TOPUP",
"💰 Комиссия с пополнения",
),
"referral_commission": texts.t(
"REFERRAL_EARNING_REASON_COMMISSION_PURCHASE",
"💰 Комиссия с покупки",
),
}.get(earning['reason'], earning['reason'])
referral_text += texts.t(
"REFERRAL_RECENT_EARNINGS_ITEM",
"{reason}: <b>{amount}</b> от {referral_name}",
).format(
reason=reason_text,
amount=texts.format_price(earning['amount_kopeks']),
referral_name=earning['referral_name'],
) + "\n"
referral_text += "\n"
if summary['earnings_by_type']:
referral_text += texts.t(
"REFERRAL_EARNINGS_BY_TYPE_HEADER",
"📈 <b>Доходы по типам:</b>",
) + "\n"
if 'referral_first_topup' in summary['earnings_by_type']:
data = summary['earnings_by_type']['referral_first_topup']
if data['total_amount_kopeks'] > 0:
referral_text += texts.t(
"REFERRAL_EARNINGS_FIRST_TOPUPS",
"• Бонусы за первые пополнения: <b>{count}</b> ({amount})",
).format(
count=data['count'],
amount=texts.format_price(data['total_amount_kopeks']),
) + "\n"
if 'referral_commission_topup' in summary['earnings_by_type']:
data = summary['earnings_by_type']['referral_commission_topup']
if data['total_amount_kopeks'] > 0:
referral_text += texts.t(
"REFERRAL_EARNINGS_TOPUPS",
"• Комиссии с пополнений: <b>{count}</b> ({amount})",
).format(
count=data['count'],
amount=texts.format_price(data['total_amount_kopeks']),
) + "\n"
if 'referral_commission' in summary['earnings_by_type']:
data = summary['earnings_by_type']['referral_commission']
if data['total_amount_kopeks'] > 0:
referral_text += texts.t(
"REFERRAL_EARNINGS_PURCHASES",
"• Комиссии с покупок: <b>{count}</b> ({amount})",
).format(
count=data['count'],
amount=texts.format_price(data['total_amount_kopeks']),
) + "\n"
referral_text += "\n"
referral_text += texts.t(
"REFERRAL_INVITE_FOOTER",
"📢 Приглашайте друзей и зарабатывайте!",
)
await edit_or_answer_photo(
callback,
referral_text,
get_referral_keyboard(db_user.language),
)
await callback.answer()
async def show_referral_qr(
callback: types.CallbackQuery,
db_user: User,
):
await callback.answer()
texts = get_texts(db_user.language)
bot_username = (await callback.bot.get_me()).username
referral_link = f"https://t.me/{bot_username}?start={db_user.referral_code}"
qr_dir = Path("data") / "referral_qr"
qr_dir.mkdir(parents=True, exist_ok=True)
file_path = qr_dir / f"{db_user.id}.png"
if not file_path.exists():
img = qrcode.make(referral_link)
img.save(file_path)
photo = FSInputFile(file_path)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[[types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_referrals")]]
)
try:
await callback.message.edit_media(
types.InputMediaPhoto(
media=photo,
caption=texts.t(
"REFERRAL_LINK_CAPTION",
"🔗 Ваша реферальная ссылка:\n{link}",
).format(link=referral_link),
),
reply_markup=keyboard,
)
except TelegramBadRequest:
await callback.message.delete()
await callback.message.answer_photo(
photo,
caption=texts.t(
"REFERRAL_LINK_CAPTION",
"🔗 Ваша реферальная ссылка:\n{link}",
).format(link=referral_link),
reply_markup=keyboard,
)
async def show_detailed_referral_list(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
page: int = 1
):
texts = get_texts(db_user.language)
referrals_data = await get_detailed_referral_list(db, db_user.id, limit=10, offset=(page - 1) * 10)
if not referrals_data['referrals']:
await edit_or_answer_photo(
callback,
texts.t(
"REFERRAL_LIST_EMPTY",
"📋 У вас пока нет рефералов.\n\nПоделитесь своей реферальной ссылкой, чтобы начать зарабатывать!",
),
types.InlineKeyboardMarkup(
inline_keyboard=[[types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_referrals")]]
),
parse_mode=None,
)
await callback.answer()
return
text = texts.t(
"REFERRAL_LIST_HEADER",
"👥 <b>Ваши рефералы</b> (стр. {current}/{total})",
).format(
current=referrals_data['current_page'],
total=referrals_data['total_pages'],
) + "\n\n"
for i, referral in enumerate(referrals_data['referrals'], 1):
status_emoji = "🟢" if referral['status'] == 'active' else "🔴"
topup_emoji = "💰" if referral['has_made_first_topup'] else ""
text += texts.t(
"REFERRAL_LIST_ITEM_HEADER",
"{index}. {status} <b>{name}</b>",
).format(index=i, status=status_emoji, name=referral['full_name']) + "\n"
text += texts.t(
"REFERRAL_LIST_ITEM_TOPUPS",
" {emoji} Пополнений: {count}",
).format(emoji=topup_emoji, count=referral['topups_count']) + "\n"
text += texts.t(
"REFERRAL_LIST_ITEM_EARNED",
" 💎 Заработано с него: {amount}",
).format(amount=texts.format_price(referral['total_earned_kopeks'])) + "\n"
text += texts.t(
"REFERRAL_LIST_ITEM_REGISTERED",
" 📅 Регистрация: {days} дн. назад",
).format(days=referral['days_since_registration']) + "\n"
if referral['days_since_activity'] is not None:
text += texts.t(
"REFERRAL_LIST_ITEM_ACTIVITY",
" 🕐 Активность: {days} дн. назад",
).format(days=referral['days_since_activity']) + "\n"
else:
text += texts.t(
"REFERRAL_LIST_ITEM_ACTIVITY_LONG_AGO",
" 🕐 Активность: давно",
) + "\n"
text += "\n"
keyboard = []
nav_buttons = []
if referrals_data['has_prev']:
nav_buttons.append(types.InlineKeyboardButton(
text=texts.t("REFERRAL_LIST_PREV_PAGE", "⬅️ Назад"),
callback_data=f"referral_list_page_{page - 1}"
))
if referrals_data['has_next']:
nav_buttons.append(types.InlineKeyboardButton(
text=texts.t("REFERRAL_LIST_NEXT_PAGE", "Вперед ➡️"),
callback_data=f"referral_list_page_{page + 1}"
))
if nav_buttons:
keyboard.append(nav_buttons)
keyboard.append([types.InlineKeyboardButton(
text=texts.BACK,
callback_data="menu_referrals"
)])
await edit_or_answer_photo(
callback,
text,
types.InlineKeyboardMarkup(inline_keyboard=keyboard),
)
await callback.answer()
async def show_referral_analytics(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession
):
texts = get_texts(db_user.language)
analytics = await get_referral_analytics(db, db_user.id)
text = texts.t("REFERRAL_ANALYTICS_TITLE", "📊 <b>Аналитика рефералов</b>") + "\n\n"
text += texts.t(
"REFERRAL_ANALYTICS_EARNINGS_HEADER",
"💰 <b>Доходы по периодам:</b>",
) + "\n"
text += texts.t(
"REFERRAL_ANALYTICS_EARNINGS_TODAY",
"• Сегодня: {amount}",
).format(amount=texts.format_price(analytics['earnings_by_period']['today'])) + "\n"
text += texts.t(
"REFERRAL_ANALYTICS_EARNINGS_WEEK",
"За неделю: {amount}",
).format(amount=texts.format_price(analytics['earnings_by_period']['week'])) + "\n"
text += texts.t(
"REFERRAL_ANALYTICS_EARNINGS_MONTH",
"За месяц: {amount}",
).format(amount=texts.format_price(analytics['earnings_by_period']['month'])) + "\n"
text += texts.t(
"REFERRAL_ANALYTICS_EARNINGS_QUARTER",
"За квартал: {amount}",
).format(amount=texts.format_price(analytics['earnings_by_period']['quarter'])) + "\n\n"
if analytics['top_referrals']:
text += texts.t(
"REFERRAL_ANALYTICS_TOP_TITLE",
"🏆 <b>Топ-{count} рефералов:</b>",
).format(count=len(analytics['top_referrals'])) + "\n"
for i, ref in enumerate(analytics['top_referrals'], 1):
text += texts.t(
"REFERRAL_ANALYTICS_TOP_ITEM",
"{index}. {name}: {amount} ({count} начислений)",
).format(
index=i,
name=ref['referral_name'],
amount=texts.format_price(ref['total_earned_kopeks']),
count=ref['earnings_count'],
) + "\n"
text += "\n"
text += texts.t(
"REFERRAL_ANALYTICS_FOOTER",
"📈 Продолжайте развивать свою реферальную сеть!",
)
await edit_or_answer_photo(
callback,
text,
types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_referrals")]
]),
)
await callback.answer()
async def create_invite_message(
callback: types.CallbackQuery,
db_user: User
):
texts = get_texts(db_user.language)
bot_username = (await callback.bot.get_me()).username
referral_link = f"https://t.me/{bot_username}?start={db_user.referral_code}"
invite_text = (
texts.t("REFERRAL_INVITE_TITLE", "🎉 Присоединяйся к VPN сервису!")
+ "\n\n"
+ texts.t(
"REFERRAL_INVITE_BONUS",
"💎 При первом пополнении от {minimum} ты получишь {bonus} бонусом на баланс!",
).format(
minimum=texts.format_price(settings.REFERRAL_MINIMUM_TOPUP_KOPEKS),
bonus=texts.format_price(settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS),
)
+ "\n\n"
+ texts.t("REFERRAL_INVITE_FEATURE_FAST", "🚀 Быстрое подключение")
+ "\n"
+ texts.t("REFERRAL_INVITE_FEATURE_SERVERS", "🌍 Серверы по всему миру")
+ "\n"
+ texts.t("REFERRAL_INVITE_FEATURE_SECURE", "🔒 Надежная защита")
+ "\n\n"
+ texts.t("REFERRAL_INVITE_LINK_PROMPT", "👇 Переходи по ссылке:")
+ f"\n{referral_link}"
)
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text=texts.t("REFERRAL_SHARE_BUTTON", "📤 Поделиться"),
switch_inline_query=invite_text
)],
[types.InlineKeyboardButton(
text=texts.BACK,
callback_data="menu_referrals"
)]
])
await edit_or_answer_photo(
callback,
(
texts.t("REFERRAL_INVITE_CREATED_TITLE", "📝 <b>Приглашение создано!</b>")
+ "\n\n"
+ texts.t(
"REFERRAL_INVITE_CREATED_INSTRUCTION",
"Нажмите кнопку «📤 Поделиться» чтобы отправить приглашение в любой чат, или скопируйте текст ниже:",
)
+ "\n\n"
f"<code>{invite_text}</code>"
),
keyboard,
)
await callback.answer()
async def show_withdrawal_info(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
state: FSMContext
):
"""Показывает информацию о выводе реферального баланса."""
texts = get_texts(db_user.language)
if not settings.is_referral_withdrawal_enabled():
await callback.answer(
texts.t("REFERRAL_WITHDRAWAL_DISABLED", "Функция вывода отключена"),
show_alert=True
)
return
# Получаем детальную статистику баланса
stats = await referral_withdrawal_service.get_referral_balance_stats(db, db_user.id)
min_amount = settings.REFERRAL_WITHDRAWAL_MIN_AMOUNT_KOPEKS
cooldown_days = settings.REFERRAL_WITHDRAWAL_COOLDOWN_DAYS
# Проверяем возможность вывода
can_request, reason = await referral_withdrawal_service.can_request_withdrawal(db, db_user.id)
text = texts.t("REFERRAL_WITHDRAWAL_TITLE", "💸 <b>Вывод реферального баланса</b>") + "\n\n"
# Показываем детальную статистику
text += referral_withdrawal_service.format_balance_stats_for_user(stats, texts)
text += "\n"
text += texts.t(
"REFERRAL_WITHDRAWAL_MIN_AMOUNT",
"📊 Минимальная сумма: <b>{amount}</b>"
).format(amount=texts.format_price(min_amount)) + "\n"
text += texts.t(
"REFERRAL_WITHDRAWAL_COOLDOWN",
"⏱ Частота вывода: раз в <b>{days}</b> дней"
).format(days=cooldown_days) + "\n\n"
keyboard = []
if can_request:
text += texts.t(
"REFERRAL_WITHDRAWAL_READY",
"✅ Вы можете запросить вывод средств"
) + "\n"
keyboard.append([types.InlineKeyboardButton(
text=texts.t("REFERRAL_WITHDRAWAL_REQUEST_BUTTON", "📝 Оформить заявку"),
callback_data="referral_withdrawal_start"
)])
else:
text += f"{reason}\n"
keyboard.append([types.InlineKeyboardButton(
text=texts.BACK,
callback_data="menu_referrals"
)])
await edit_or_answer_photo(
callback,
text,
types.InlineKeyboardMarkup(inline_keyboard=keyboard)
)
await callback.answer()
async def start_withdrawal_request(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
state: FSMContext
):
"""Начинает процесс оформления заявки на вывод."""
texts = get_texts(db_user.language)
# Повторная проверка
can_request, reason = await referral_withdrawal_service.can_request_withdrawal(db, db_user.id)
if not can_request:
await callback.answer(reason, show_alert=True)
return
available = await referral_withdrawal_service.get_available_for_withdrawal(db, db_user.id)
# Сохраняем доступный баланс в состоянии
await state.update_data(available_balance=available)
await state.set_state(ReferralWithdrawalStates.waiting_for_amount)
text = texts.t(
"REFERRAL_WITHDRAWAL_ENTER_AMOUNT",
"💸 Введите сумму для вывода в рублях\n\nДоступно: <b>{amount}</b>"
).format(amount=texts.format_price(available))
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text=texts.t("REFERRAL_WITHDRAWAL_ALL", f"Вывести всё ({available / 100:.0f}₽)"),
callback_data=f"referral_withdrawal_amount_{available}"
)],
[types.InlineKeyboardButton(
text=texts.t("CANCEL", "❌ Отмена"),
callback_data="referral_withdrawal_cancel"
)]
])
await edit_or_answer_photo(callback, text, keyboard)
await callback.answer()
async def process_withdrawal_amount(
message: types.Message,
db_user: User,
db: AsyncSession,
state: FSMContext
):
"""Обрабатывает ввод суммы для вывода."""
texts = get_texts(db_user.language)
data = await state.get_data()
available = data.get("available_balance", 0)
try:
# Парсим сумму (в рублях)
amount_text = message.text.strip().replace(",", ".").replace("", "").replace(" ", "")
amount_rubles = float(amount_text)
amount_kopeks = int(amount_rubles * 100)
if amount_kopeks <= 0:
await message.answer(texts.t("REFERRAL_WITHDRAWAL_INVALID_AMOUNT", "❌ Введите положительную сумму"))
return
min_amount = settings.REFERRAL_WITHDRAWAL_MIN_AMOUNT_KOPEKS
if amount_kopeks < min_amount:
await message.answer(
texts.t(
"REFERRAL_WITHDRAWAL_MIN_ERROR",
"❌ Минимальная сумма: {amount}"
).format(amount=texts.format_price(min_amount))
)
return
if amount_kopeks > available:
await message.answer(
texts.t(
"REFERRAL_WITHDRAWAL_INSUFFICIENT",
"❌ Недостаточно средств. Доступно: {amount}"
).format(amount=texts.format_price(available))
)
return
# Сохраняем сумму и переходим к вводу реквизитов
await state.update_data(withdrawal_amount=amount_kopeks)
await state.set_state(ReferralWithdrawalStates.waiting_for_payment_details)
text = texts.t(
"REFERRAL_WITHDRAWAL_ENTER_DETAILS",
"💳 Введите реквизиты для перевода:\n\n"
"Например:\n"
"• СБП: +7 999 123-45-67 (Сбербанк)"
)
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text=texts.t("CANCEL", "❌ Отмена"),
callback_data="referral_withdrawal_cancel"
)]
])
await message.answer(text, reply_markup=keyboard)
except ValueError:
await message.answer(texts.t("REFERRAL_WITHDRAWAL_INVALID_AMOUNT", "❌ Введите корректную сумму"))
async def process_withdrawal_amount_callback(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
state: FSMContext
):
"""Обрабатывает выбор суммы для вывода через кнопку."""
texts = get_texts(db_user.language)
# Получаем сумму из callback_data
amount_kopeks = int(callback.data.split("_")[-1])
# Сохраняем сумму и переходим к вводу реквизитов
await state.update_data(withdrawal_amount=amount_kopeks)
await state.set_state(ReferralWithdrawalStates.waiting_for_payment_details)
text = texts.t(
"REFERRAL_WITHDRAWAL_ENTER_DETAILS",
"💳 Введите реквизиты для перевода:\n\n"
"Например:\n"
"• СБП: +7 999 123-45-67 (Сбербанк)"
)
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text=texts.t("CANCEL", "❌ Отмена"),
callback_data="referral_withdrawal_cancel"
)]
])
await edit_or_answer_photo(callback, text, keyboard)
await callback.answer()
async def process_payment_details(
message: types.Message,
db_user: User,
db: AsyncSession,
state: FSMContext
):
"""Обрабатывает ввод реквизитов и показывает подтверждение."""
texts = get_texts(db_user.language)
data = await state.get_data()
amount_kopeks = data.get("withdrawal_amount", 0)
payment_details = message.text.strip()
if len(payment_details) < 10:
await message.answer(
texts.t("REFERRAL_WITHDRAWAL_DETAILS_TOO_SHORT", "❌ Реквизиты слишком короткие")
)
return
# Сохраняем реквизиты
await state.update_data(payment_details=payment_details)
await state.set_state(ReferralWithdrawalStates.confirming)
text = texts.t("REFERRAL_WITHDRAWAL_CONFIRM_TITLE", "📋 <b>Подтверждение заявки</b>") + "\n\n"
text += texts.t(
"REFERRAL_WITHDRAWAL_CONFIRM_AMOUNT",
"💰 Сумма: <b>{amount}</b>"
).format(amount=texts.format_price(amount_kopeks)) + "\n\n"
text += texts.t(
"REFERRAL_WITHDRAWAL_CONFIRM_DETAILS",
"💳 Реквизиты:\n<code>{details}</code>"
).format(details=payment_details) + "\n\n"
text += texts.t(
"REFERRAL_WITHDRAWAL_CONFIRM_WARNING",
"⚠️ После отправки заявка будет рассмотрена администрацией"
)
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text=texts.t("REFERRAL_WITHDRAWAL_CONFIRM_BUTTON", "✅ Подтвердить"),
callback_data="referral_withdrawal_confirm"
)],
[types.InlineKeyboardButton(
text=texts.t("CANCEL", "❌ Отмена"),
callback_data="referral_withdrawal_cancel"
)]
])
await message.answer(text, reply_markup=keyboard)
async def confirm_withdrawal_request(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
state: FSMContext
):
"""Подтверждает и создаёт заявку на вывод."""
texts = get_texts(db_user.language)
data = await state.get_data()
amount_kopeks = data.get("withdrawal_amount", 0)
payment_details = data.get("payment_details", "")
await state.clear()
# Создаём заявку
request, error = await referral_withdrawal_service.create_withdrawal_request(
db, db_user.id, amount_kopeks, payment_details
)
if error:
await callback.answer(f"{error}", show_alert=True)
return
# Отправляем уведомление админам
analysis = json.loads(request.risk_analysis) if request.risk_analysis else {}
admin_text = f"""
🔔 <b>Новая заявка на вывод #{request.id}</b>
👤 Пользователь: {db_user.full_name or 'Без имени'}
🆔 ID: <code>{db_user.telegram_id}</code>
💰 Сумма: <b>{amount_kopeks / 100:.0f}₽</b>
💳 Реквизиты:
<code>{payment_details}</code>
{referral_withdrawal_service.format_analysis_for_admin(analysis)}
"""
admin_keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[
types.InlineKeyboardButton(
text="✅ Одобрить",
callback_data=f"admin_withdrawal_approve_{request.id}"
),
types.InlineKeyboardButton(
text="❌ Отклонить",
callback_data=f"admin_withdrawal_reject_{request.id}"
)
],
[types.InlineKeyboardButton(
text="👤 Профиль пользователя",
callback_data=f"admin_user_{db_user.telegram_id}"
)]
])
try:
notification_service = AdminNotificationService(callback.bot)
await notification_service.send_to_admins(
admin_text,
keyboard=admin_keyboard
)
except Exception as e:
logger.error(f"Ошибка отправки уведомления админам о заявке на вывод: {e}")
# Уведомление в топик, если настроено
topic_id = settings.REFERRAL_WITHDRAWAL_NOTIFICATIONS_TOPIC_ID
if topic_id and settings.ADMIN_NOTIFICATIONS_CHAT_ID:
try:
await callback.bot.send_message(
chat_id=settings.ADMIN_NOTIFICATIONS_CHAT_ID,
message_thread_id=topic_id,
text=admin_text,
reply_markup=admin_keyboard,
parse_mode="HTML"
)
except Exception as e:
logger.error(f"Ошибка отправки уведомления в топик о заявке на вывод: {e}")
# Отвечаем пользователю
text = texts.t(
"REFERRAL_WITHDRAWAL_SUCCESS",
"✅ <b>Заявка #{id} создана!</b>\n\n"
"Сумма: <b>{amount}</b>\n\n"
"Ваша заявка будет рассмотрена администрацией. "
"Мы уведомим вас о результате."
).format(id=request.id, amount=texts.format_price(amount_kopeks))
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text=texts.BACK,
callback_data="menu_referrals"
)]
])
await edit_or_answer_photo(callback, text, keyboard)
await callback.answer()
async def cancel_withdrawal_request(
callback: types.CallbackQuery,
db_user: User,
state: FSMContext
):
"""Отменяет процесс создания заявки на вывод."""
await state.clear()
texts = get_texts(db_user.language)
await callback.answer(texts.t("CANCELLED", "Отменено"))
# Возвращаем в меню партнёрки
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text=texts.BACK,
callback_data="menu_referrals"
)]
])
await edit_or_answer_photo(
callback,
texts.t("REFERRAL_WITHDRAWAL_CANCELLED", "❌ Заявка отменена"),
keyboard
)
def register_handlers(dp: Dispatcher):
dp.callback_query.register(
show_referral_info,
F.data == "menu_referrals"
)
dp.callback_query.register(
create_invite_message,
F.data == "referral_create_invite"
)
dp.callback_query.register(
show_referral_qr,
F.data == "referral_show_qr"
)
dp.callback_query.register(
show_detailed_referral_list,
F.data == "referral_list"
)
dp.callback_query.register(
show_referral_analytics,
F.data == "referral_analytics"
)
async def handle_referral_list_page(callback: types.CallbackQuery, db_user: User, db: AsyncSession):
page = int(callback.data.split('_')[-1])
await show_detailed_referral_list(callback, db_user, db, page)
dp.callback_query.register(
handle_referral_list_page,
F.data.startswith("referral_list_page_")
)
# Хендлеры вывода реферального баланса
dp.callback_query.register(
show_withdrawal_info,
F.data == "referral_withdrawal"
)
dp.callback_query.register(
start_withdrawal_request,
F.data == "referral_withdrawal_start"
)
dp.callback_query.register(
process_withdrawal_amount_callback,
F.data.startswith("referral_withdrawal_amount_")
)
dp.callback_query.register(
confirm_withdrawal_request,
F.data == "referral_withdrawal_confirm"
)
dp.callback_query.register(
cancel_withdrawal_request,
F.data == "referral_withdrawal_cancel"
)
# Обработка текстового ввода суммы
dp.message.register(
process_withdrawal_amount,
ReferralWithdrawalStates.waiting_for_amount
)
# Обработка текстового ввода реквизитов
dp.message.register(
process_payment_details,
ReferralWithdrawalStates.waiting_for_payment_details
)