from aiogram import Router, F from aiogram.types import Message, CallbackQuery, LabeledPrice, InlineKeyboardMarkup, InlineKeyboardButton from aiogram.fsm.context import FSMContext from datetime import datetime import logging from config import CONFIG, PLANS from database import db from marzban import marzban from keyboards import main_keyboard router = Router() logger = logging.getLogger(__name__) async def grant_subscription(user_id: int, plan_id: str, promo_code: str, amount: int, bonus_days: int = 0): total_days = 0 data_limit = 0 plan_name = "VIP Sub" marzban_days = 0 if plan_id: plan = PLANS[plan_id] total_days = plan['days'] + bonus_days marzban_days = total_days data_limit = plan['data_limit'] plan_name = plan['name'] else: # VIP case without plan total_days = 365 * 99 # For DB (99 years) marzban_days = 0 # For Marzban (Unlimited/None) data_limit = 0 # Unlimited plan_name = "VIP" user = await db.get_user(user_id) tg_username = user['username'] note = f"@{tg_username}" if tg_username else "" sub_until = user['subscription_until'] if isinstance(sub_until, str): try: sub_until = datetime.strptime(sub_until, '%Y-%m-%d %H:%M:%S') except ValueError: try: sub_until = datetime.strptime(sub_until, '%Y-%m-%d %H:%M:%S.%f') except: pass # Marzban try: marzban_username = user['marzban_username'] resp = None # Если есть подписка и она активна (и не бесконечна, хотя тут не важно) is_sub_active = sub_until and sub_until > datetime.now() if is_sub_active: logger.info(f"Attempting to modify existing user: {marzban_username}") resp = await marzban.modify_user( marzban_username, data_limit, marzban_days, note ) if isinstance(resp, dict) and resp.get('detail') == 'User not found': logger.info(f"User {marzban_username} missing in Marzban, re-creating...") resp = await marzban.create_user( marzban_username, data_limit, marzban_days, note ) else: logger.info(f"Creating/Reactivating user: {marzban_username}") resp = await marzban.create_user( marzban_username, data_limit, marzban_days, note ) except Exception as e: logger.error(f"Marzban error in grant_subscription: {e}") # DB await db.update_subscription(user_id, total_days, data_limit) await db.add_payment( user_id, plan_id or "vip", # Store 'vip' as plan name/id amount, promo_code ) if promo_code: await db.decrement_promo_usage(promo_code) # Return dummy plan dict for display return {'name': plan_name, 'days': total_days, 'data_limit': data_limit}, total_days @router.callback_query(F.data == "pay_now") async def process_payment(callback: CallbackQuery, state: FSMContext): data = await state.get_data() plan_id = data.get('selected_plan') is_vip = data.get('is_unlimited_promo') if not plan_id and not is_vip: await callback.answer("❌ Сессия истекла. Пожалуйста, выберите тариф снова.", show_alert=True) return # Если VIP без плана, ставим дефолтные значения plan_name = "VIP" plan_days = 3650 plan_limit = 0 base_price = 0 if plan_id: plan = PLANS[plan_id] plan_name = plan['name'] plan_days = plan['days'] plan_limit = plan['data_limit'] base_price = plan['price'] final_price = int(data.get('final_price', base_price)) promo_code = data.get('promo_code') bonus_days = data.get('bonus_days', 0) if final_price <= 0: sticky_msg = "" if promo_code: p_data = await db.get_promo_code(promo_code) # Use explicit key access with fallback logic if needed, but keys exist if p_data and p_data['is_sticky']: user = await db.get_user(callback.from_user.id) u_disc = user['personal_discount'] if user and user['personal_discount'] else 0 if p_data['discount'] > u_disc: await db.set_user_discount(callback.from_user.id, p_data['discount']) sticky_msg = "\n🔐 Скидка закреплена навсегда!" plan, date_days = await grant_subscription(callback.from_user.id, plan_id, promo_code, 0, bonus_days) await callback.message.edit_text( f"✅ Подписка активирована бесплатно!\n\n" f"План: {plan['name']}\n" f"Срок: {date_days} дней\n" f"Трафик: {plan['data_limit'] if plan['data_limit'] > 0 else '∞'} ГБ\n" f"{sticky_msg}\n" f"Настройте подключение в меню: 📊 Моя подписка", reply_markup=main_keyboard(callback.from_user.id in CONFIG["ADMIN_IDS"]) ) await state.clear() else: # Создаем инвойс для Telegram Stars await callback.message.answer_invoice( title=f"Подписка VPN - {plan['name']}", description=f"Трафик: {plan['data_limit']} ГБ на {plan['days']} дней", payload=f"{plan_id}:{data.get('promo_code', '')}", currency="XTR", # Telegram Stars prices=[LabeledPrice(label=plan['name'], amount=final_price)], reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="💳 Оплатить", pay=True)] ]) ) @router.pre_checkout_query() async def checkout_process(pre_checkout_query): await pre_checkout_query.answer(ok=True) @router.message(F.successful_payment) async def successful_payment(message: Message): payment = message.successful_payment plan_id, promo_code = payment.invoice_payload.split(":") # We can reuse grant_subscription helper promo_code = promo_code if promo_code else None bonus_days = 0 sticky_text = "" if promo_code: # Fetch actual promo details promo_data = await db.get_promo_code(promo_code) if promo_data: bonus_days = promo_data['bonus_days'] # STICKY LOGIC # Access by key (sqlite3.Row has no .get method) is_sticky = False try: is_sticky = promo_data['is_sticky'] except IndexError: pass # Column missing? if is_sticky: user = await db.get_user(message.from_user.id) current_discount = user['personal_discount'] if user and user['personal_discount'] else 0 new_discount = promo_data['discount'] if new_discount > current_discount: await db.set_user_discount(message.from_user.id, new_discount) sticky_text = f"\n🔐 Скидка {new_discount}% закреплена за вами НАВСЕГДА!" plan, date_days = await grant_subscription( message.from_user.id, plan_id, promo_code, payment.total_amount, bonus_days ) await message.answer( f"✅ Оплата успешна!\n\n" f"Ваша подписка активирована на {date_days} дней.\n" f"Трафик: {plan['data_limit']} ГБ\n" f"{sticky_text}\n" f"Получите конфигурацию через меню: 📊 Моя подписка", reply_markup=main_keyboard(message.from_user.id in CONFIG["ADMIN_IDS"]), parse_mode="HTML" )