from datetime import datetime import html import pytz from typing import Any from aiogram import F, Router from aiogram.enums import ParseMode from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.types import ( CallbackQuery, InlineQuery, InlineQueryResultArticle, InputTextMessageContent, Message, ) from aiogram.utils.keyboard import InlineKeyboardBuilder from config import INLINE_MODE, USERNAME_BOT from database import ( add_connection, check_connection_exists, create_coupon, create_coupon_usage, delete_coupon, get_all_coupons, get_keys, update_key_expiry, ) from filters.admin import IsAdminFilter from handlers.buttons import BACK from handlers.keys.key_utils import renew_key_in_cluster from handlers.profile import process_callback_view_profile from handlers.utils import format_days from logger import logger from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb from .keyboard import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb, format_coupons_list router = Router() class AdminCouponsState(StatesGroup): waiting_for_coupon_type = State() waiting_for_balance_data = State() waiting_for_days_data = State() waiting_for_key_selection = State() @router.callback_query( AdminPanelCallback.filter(F.action == "coupons"), IsAdminFilter(), ) async def handle_coupons(callback_query: CallbackQuery): await callback_query.message.edit_text(text="πŸ›  МСню управлСния ΠΊΡƒΠΏΠΎΠ½Π°ΠΌΠΈ:", reply_markup=build_coupons_kb()) @router.callback_query( AdminPanelCallback.filter(F.action == "coupons_create"), IsAdminFilter(), ) async def handle_coupons_create(callback_query: CallbackQuery, state: FSMContext): text = "🎫 Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ Ρ‚ΠΈΠΏ ΠΊΡƒΠΏΠΎΠ½Π°:" kb = InlineKeyboardBuilder() kb.button(text="πŸ’° Баланс", callback_data="coupon_type_balance") kb.button(text="⏳ ВрСмя", callback_data="coupon_type_days") kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) kb.adjust(1) await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup()) await state.set_state(AdminCouponsState.waiting_for_coupon_type) @router.callback_query(F.data == "coupon_type_balance") async def handle_balance_coupon_selection(callback_query: CallbackQuery, state: FSMContext): text = ( "🎫 Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ для создания ΠΊΡƒΠΏΠΎΠ½Π° Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅:\n\n" "πŸ“ ΠΊΠΎΠ΄ πŸ’° сумма πŸ”’ Π»ΠΈΠΌΠΈΡ‚\n\n" "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: 'COUPON1 50 5' πŸ‘ˆ\n\n" ) kb = InlineKeyboardBuilder() kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup()) await state.set_state(AdminCouponsState.waiting_for_balance_data) @router.callback_query(F.data == "coupon_type_days") async def handle_days_coupon_selection(callback_query: CallbackQuery, state: FSMContext): text = ( "🎫 Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ для создания ΠΊΡƒΠΏΠΎΠ½Π° Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅:\n\n" "πŸ“ ΠΊΠΎΠ΄ ⏳ Π΄Π½ΠΈ πŸ”’ Π»ΠΈΠΌΠΈΡ‚\n\n" "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: 'DAYS10 10 50' πŸ‘ˆ\n\n" ) kb = InlineKeyboardBuilder() kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup()) await state.set_state(AdminCouponsState.waiting_for_days_data) @router.message(AdminCouponsState.waiting_for_balance_data, IsAdminFilter()) async def handle_balance_coupon_input(message: Message, state: FSMContext, session: Any): text = message.text.strip() parts = text.split() kb = InlineKeyboardBuilder() kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) if len(parts) != 3: text = ( "❌ НСкоррСктный Ρ„ΠΎΡ€ΠΌΠ°Ρ‚! πŸ“ ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Π²Π΅Π΄ΠΈΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅:\n" "🏷️ ΠΊΠΎΠ΄ πŸ’° сумма πŸ”’ Π»ΠΈΠΌΠΈΡ‚\n" "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: 'COUPON1 50 5' πŸ‘ˆ" ) await message.answer(text=text, reply_markup=kb.as_markup()) return try: coupon_code = parts[0] coupon_amount = int(parts[1]) usage_limit = int(parts[2]) if coupon_amount <= 0: raise ValueError("Π‘ΡƒΠΌΠΌΠ° Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ большС 0") except ValueError: text = "⚠️ ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΡΡ‚ΡŒ Π²Π²Π΅Π΄Π΅Π½Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ…!\nπŸ’± Π‘ΡƒΠΌΠΌΠ° Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ числом, Π° Π»ΠΈΠΌΠΈΡ‚ β€” Ρ†Π΅Π»Ρ‹ΠΌ числом." await message.answer(text=text, reply_markup=kb.as_markup()) return try: await create_coupon(coupon_code, coupon_amount, usage_limit, session, days=None) coupon_link = f"https://t.me/{USERNAME_BOT}?start=coupons_{coupon_code}" text = ( f"βœ… ΠšΡƒΠΏΠΎΠ½ с ΠΊΠΎΠ΄ΠΎΠΌ {coupon_code} ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ создан!\n" f"πŸ’° Π‘ΡƒΠΌΠΌΠ°: {coupon_amount} Ρ€ΡƒΠ±Π»Π΅ΠΉ\n" f"πŸ”’ Π›ΠΈΠΌΠΈΡ‚ использования: {usage_limit} Ρ€Π°Π·\n" f"πŸ”— Бсылка: {coupon_link}\n" ) kb = InlineKeyboardBuilder() if INLINE_MODE: kb.button(text="πŸ“€ ΠŸΠΎΠ΄Π΅Π»ΠΈΡ‚ΡŒΡΡ", switch_inline_query=f"coupon_{coupon_code}") kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) kb.adjust(1) await message.answer(text=text, reply_markup=kb.as_markup()) await state.clear() except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ создании ΠΊΡƒΠΏΠΎΠ½Π°: {e}") await message.answer("❌ ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка ΠΏΡ€ΠΈ создании ΠΊΡƒΠΏΠΎΠ½Π°.", reply_markup=kb.as_markup()) @router.message(AdminCouponsState.waiting_for_days_data, IsAdminFilter()) async def handle_days_coupon_input(message: Message, state: FSMContext, session: Any): text = message.text.strip() parts = text.split() kb = InlineKeyboardBuilder() kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) if len(parts) != 3: text = ( "❌ НСкоррСктный Ρ„ΠΎΡ€ΠΌΠ°Ρ‚! πŸ“ ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Π²Π΅Π΄ΠΈΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅:\n" "🏷️ ΠΊΠΎΠ΄ ⏳ Π΄Π½ΠΈ πŸ”’ Π»ΠΈΠΌΠΈΡ‚\n" "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: 'DAYS10 10 50' πŸ‘ˆ" ) await message.answer(text=text, reply_markup=kb.as_markup()) return try: coupon_code = parts[0] days = int(parts[1]) usage_limit = int(parts[2]) if days <= 0: raise ValueError("ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π΄Π½Π΅ΠΉ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ большС 0") except ValueError: text = "⚠️ ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΡΡ‚ΡŒ Π²Π²Π΅Π΄Π΅Π½Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ…!\nπŸ’± Π”Π½ΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ числом, Π° Π»ΠΈΠΌΠΈΡ‚ β€” Ρ†Π΅Π»Ρ‹ΠΌ числом." await message.answer(text=text, reply_markup=kb.as_markup()) return try: await create_coupon(coupon_code, 0, usage_limit, session, days=days) coupon_link = f"https://t.me/{USERNAME_BOT}?start=coupons_{coupon_code}" text = ( f"βœ… ΠšΡƒΠΏΠΎΠ½ с ΠΊΠΎΠ΄ΠΎΠΌ {coupon_code} ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ создан!\n" f"⏳ {format_days(days)}\n" f"πŸ”’ Π›ΠΈΠΌΠΈΡ‚ использования: {usage_limit} Ρ€Π°Π·\n" f"πŸ”— Бсылка: {coupon_link}\n" ) kb = InlineKeyboardBuilder() if INLINE_MODE: kb.button(text="πŸ“€ ΠŸΠΎΠ΄Π΅Π»ΠΈΡ‚ΡŒΡΡ", switch_inline_query=f"coupon_{coupon_code}") kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) kb.adjust(1) await message.answer(text=text, reply_markup=kb.as_markup()) await state.clear() except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ создании ΠΊΡƒΠΏΠΎΠ½Π°: {e}") await message.answer("❌ ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка ΠΏΡ€ΠΈ создании ΠΊΡƒΠΏΠΎΠ½Π°.", reply_markup=kb.as_markup()) @router.callback_query( AdminPanelCallback.filter(F.action == "coupons_list"), IsAdminFilter(), ) async def handle_coupons_list(callback_query: CallbackQuery, session: Any): try: data = AdminPanelCallback.unpack(callback_query.data) page = data.page if data.page is not None else 1 await update_coupons_list(callback_query.message, session, page) except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ списка ΠΊΡƒΠΏΠΎΠ½ΠΎΠ²: {e}") await callback_query.message.edit_text("ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка ΠΏΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ списка ΠΊΡƒΠΏΠΎΠ½ΠΎΠ².") @router.callback_query(AdminCouponDeleteCallback.filter(F.confirm.is_(None)), IsAdminFilter()) async def handle_coupon_delete(callback_query: CallbackQuery, callback_data: AdminCouponDeleteCallback, session: Any): coupon_code = callback_data.coupon_code kb = InlineKeyboardBuilder() kb.button( text="βœ… Π”Π°, ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ", callback_data=AdminCouponDeleteCallback(coupon_code=coupon_code, confirm=True).pack() ) kb.button( text="❌ НСт, ΠΎΡ‚ΠΌΠ΅Π½ΠΈΡ‚ΡŒ", callback_data=AdminCouponDeleteCallback(coupon_code=coupon_code, confirm=False).pack() ) kb.adjust(1) await callback_query.message.edit_text( f"Π’Ρ‹ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹, Ρ‡Ρ‚ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ ΠΊΡƒΠΏΠΎΠ½ {coupon_code}?", reply_markup=kb.as_markup() ) @router.callback_query(AdminCouponDeleteCallback.filter(F.confirm.is_not(None)), IsAdminFilter()) async def confirm_coupon_delete(callback_query: CallbackQuery, callback_data: AdminCouponDeleteCallback, session: Any): coupon_code = callback_data.coupon_code confirm = callback_data.confirm if confirm: try: result = await delete_coupon(coupon_code, session) if not result: await callback_query.message.edit_text( f"❌ ΠšΡƒΠΏΠΎΠ½ с ΠΊΠΎΠ΄ΠΎΠΌ {coupon_code} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½.", reply_markup=build_admin_back_kb("coupons") ) return except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ ΠΊΡƒΠΏΠΎΠ½Π°: {e}") await callback_query.message.edit_text( "ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка ΠΏΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ ΠΊΡƒΠΏΠΎΠ½Π°.", reply_markup=build_admin_back_kb("coupons") ) return await update_coupons_list(callback_query.message, session) async def update_coupons_list(message, session: Any, page: int = 1): per_page = 10 result = await get_all_coupons(session, page, per_page) coupons = result["coupons"] if not coupons: await message.edit_text( text="❌ На Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π½Π΅Ρ‚ доступных ΠΊΡƒΠΏΠΎΠ½ΠΎΠ²!", reply_markup=build_admin_back_kb("coupons"), ) return kb = build_coupons_list_kb(coupons, result["current_page"], result["pages"]) text = format_coupons_list(coupons, USERNAME_BOT) await message.edit_text(text=text, reply_markup=kb) @router.inline_query(F.query.startswith("coupon_")) async def inline_coupon_handler(inline_query: InlineQuery, session: Any): if not INLINE_MODE: return coupon_code = inline_query.query.split("coupon_")[1] coupon_link = f"https://t.me/{USERNAME_BOT}?start=coupons_{coupon_code}" coupons = await get_all_coupons(session, page=1, per_page=10) coupon = next((c for c in coupons["coupons"] if c["code"] == coupon_code), None) if not coupon: await inline_query.answer( results=[], switch_pm_text="ΠšΡƒΠΏΠΎΠ½ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½", switch_pm_parameter="coupons", cache_time=1, ) return title = f"ΠšΡƒΠΏΠΎΠ½ {coupon['code']}" description = f"ΠŸΠΎΠ»ΡƒΡ‡ΠΈ {coupon['amount']} Ρ€ΡƒΠ±Π»Π΅ΠΉ!" if coupon["amount"] > 0 else f"ΠŸΡ€ΠΎΠ΄Π»ΠΈ подписку Π½Π° {format_days(coupon['days'])}!" message_text = ( f"🎫 ΠšΡƒΠΏΠΎΠ½: {coupon['code']}\n" f"{'πŸ’° Бонус: ' + str(coupon['amount']) + ' Ρ€ΡƒΠ±Π»Π΅ΠΉ' if coupon['amount'] > 0 else '⏳ ΠŸΡ€ΠΎΠ΄Π»Π΅Π½ΠΈΠ΅: ' + format_days(coupon['days'])}\n" f"πŸ‘‡ НаТми, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ!" ) builder = InlineKeyboardBuilder() builder.button(text="ΠΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΊΡƒΠΏΠΎΠ½", url=coupon_link) result = InlineQueryResultArticle( id=coupon_code, title=title, description=description, input_message_content=InputTextMessageContent( message_text=message_text, parse_mode=ParseMode.HTML ), reply_markup=builder.as_markup(), ) await inline_query.answer( results=[result], cache_time=86400, is_personal=True ) @router.message(F.text.regexp(r"^/start coupons_(.+)$")) async def handle_coupon_activation(message: Message, state: FSMContext, session: Any, admin: bool = False): coupon_code = message.text.split("coupons_")[1] coupons = await get_all_coupons(session, page=1, per_page=10) coupon = next((c for c in coupons["coupons"] if c["code"] == coupon_code), None) if not coupon: await message.answer("❌ ΠšΡƒΠΏΠΎΠ½ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½.") return if coupon["usage_count"] >= coupon["usage_limit"] or coupon["is_used"]: await message.answer("❌ Π›ΠΈΠΌΠΈΡ‚ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΉ ΠΊΡƒΠΏΠΎΠ½Π° исчСрпан.") return usage = await session.fetchrow( "SELECT * FROM coupon_usages WHERE coupon_id = $1 AND user_id = $2", coupon["id"], message.from_user.id ) if usage: await message.answer("❌ Π’Ρ‹ ΡƒΠΆΠ΅ Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Π»ΠΈ этот ΠΊΡƒΠΏΠΎΠ½.") return connection_exists = await check_connection_exists(message.from_user.id) if not connection_exists: await add_connection(tg_id=message.from_user.id, session=session) if coupon["amount"] > 0: await session.execute( "UPDATE connections SET balance = balance + $1 WHERE tg_id = $2", coupon["amount"], message.from_user.id ) await session.execute( "UPDATE coupons SET usage_count = usage_count + 1, is_used = $1 WHERE id = $2", coupon["usage_count"] + 1 >= coupon["usage_limit"], coupon["id"] ) await create_coupon_usage(coupon["id"], message.from_user.id, session) await message.answer(f"βœ… ΠšΡƒΠΏΠΎΠ½ Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Π½, Π½Π° баланс начислСно {coupon['amount']} Ρ€ΡƒΠ±Π»Π΅ΠΉ.") await process_callback_view_profile(message, state, admin) return if coupon["days"] is not None and coupon["days"] > 0: keys = await get_keys(message.from_user.id, session) active_keys = [k for k in keys if not k["is_frozen"]] if not active_keys: await message.answer("❌ Π£ вас Π½Π΅Ρ‚ Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… подписок для продлСния.") return builder = InlineKeyboardBuilder() moscow_tz = pytz.timezone("Europe/Moscow") response_message = "πŸ”‘ Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ подписку для продлСния:\n\n
" for key in active_keys: alias = key.get("alias") email = key["email"] client_id = key["client_id"] expiry_time = key.get("expiry_time") key_display = html.escape(alias.strip() if alias else email) expiry_date = datetime.fromtimestamp(expiry_time / 1000, tz=moscow_tz).strftime("Π΄ΠΎ %d.%m.%y, %H:%M") response_message += f"β€’ {key_display} ({expiry_date})\n" builder.button(text=key_display, callback_data=f"extend_key|{client_id}|{coupon['id']}") response_message += "
" builder.button(text="ΠžΡ‚ΠΌΠ΅Π½Π°", callback_data="cancel_coupon_activation") builder.adjust(1) await message.answer(response_message, reply_markup=builder.as_markup()) await state.set_state(AdminCouponsState.waiting_for_key_selection) await state.update_data(coupon_id=coupon["id"]) return await message.answer("❌ ΠšΡƒΠΏΠΎΠ½ нСдСйствитСлСн (Π½Π΅Ρ‚ суммы ΠΈΠ»ΠΈ Π΄Π½Π΅ΠΉ).") @router.callback_query(F.data.startswith("extend_key|")) async def handle_key_extension(callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool = False): parts = callback_query.data.split("|") client_id = parts[1] coupon_id = int(parts[2]) coupon = await session.fetchrow("SELECT * FROM coupons WHERE id = $1", coupon_id) if not coupon or coupon["usage_count"] >= coupon["usage_limit"]: await callback_query.message.edit_text("❌ ΠšΡƒΠΏΠΎΠ½ нСдСйствитСлСн ΠΈΠ»ΠΈ Π»ΠΈΠΌΠΈΡ‚ исчСрпан.") await state.clear() return usage = await session.fetchrow( "SELECT * FROM coupon_usages WHERE coupon_id = $1 AND user_id = $2", coupon_id, callback_query.from_user.id ) if usage: await callback_query.message.edit_text("❌ Π’Ρ‹ ΡƒΠΆΠ΅ Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Π»ΠΈ этот ΠΊΡƒΠΏΠΎΠ½.") await state.clear() return key = await session.fetchrow( "SELECT * FROM keys WHERE tg_id = $1 AND client_id = $2", callback_query.from_user.id, client_id ) if not key or key["is_frozen"]: await callback_query.message.edit_text("❌ Выбранная подписка Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Π° ΠΈΠ»ΠΈ Π·Π°ΠΌΠΎΡ€ΠΎΠΆΠ΅Π½Π°.") await state.clear() return now_ms = int(datetime.now().timestamp() * 1000) current_expiry = key["expiry_time"] new_expiry = max(now_ms, current_expiry) + (coupon["days"] * 86400 * 1000) try: await renew_key_in_cluster( cluster_id=key["server_id"], email=key["email"], client_id=client_id, new_expiry_time=new_expiry, total_gb=0 ) await update_key_expiry(client_id, new_expiry, session) await session.execute( "UPDATE coupons SET usage_count = usage_count + 1, is_used = $1 WHERE id = $2", coupon["usage_count"] + 1 >= coupon["usage_limit"], coupon["id"] ) await create_coupon_usage(coupon["id"], callback_query.from_user.id, session) alias = key.get("alias") or key["email"] expiry_date = datetime.fromtimestamp(new_expiry / 1000, tz=pytz.timezone("Europe/Moscow")).strftime("%d.%m.%y, %H:%M") text = f"βœ… ΠšΡƒΠΏΠΎΠ½ Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Π½, подписка {alias} ΠΏΡ€ΠΎΠ΄Π»Π΅Π½Π° Π½Π° {format_days(coupon['days'])}⏳ Π΄ΠΎ {expiry_date}πŸ“†." await callback_query.message.answer(text) await process_callback_view_profile(callback_query.message, state, admin) await state.clear() except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ ΠΏΡ€ΠΎΠ΄Π»Π΅Π½ΠΈΠΈ ΠΊΠ»ΡŽΡ‡Π°: {e}") await callback_query.message.edit_text("❌ ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка ΠΏΡ€ΠΈ ΠΏΡ€ΠΎΠ΄Π»Π΅Π½ΠΈΠΈ подписки.") await state.clear() @router.callback_query(F.data == "cancel_coupon_activation") async def cancel_coupon_activation(callback_query: CallbackQuery, state: FSMContext, admin: bool = False): await callback_query.message.answer("⚠️ Активация ΠΊΡƒΠΏΠΎΠ½Π° ΠΎΡ‚ΠΌΠ΅Π½Π΅Π½Π°.") await process_callback_view_profile(callback_query.message, state, admin) await state.clear()