import logging from typing import Optional from aiogram import Router, F from aiogram.types import CallbackQuery from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from keyboards.client import ( devices_selector, duration_selector, return_to_menu, payment_gateways, ) from misc.utils import ( calculate_price, convert_int_to_duration, ) from schemas.di import DependenciesDTO from schemas.subscriptions import SubscriptionPlan from services.user_service import UserServiceException from states.buy import SubscriptionStorage from texts import ( CALLBACK_FALLBACK, CHECKOUT_PAYMENT_CHOICE, CONTEXT_REINITIATED, GENERAL_PROCESSING, NOTHING_PLACEHOLDER, STANDARD_FALLBACK, SUBSCRIPTION_DEVICE_SELECTOR, SUBSCRIPTION_DURATION_SELECTOR, SUPPORT_MSG_SENT, ) from config import settings router = Router() logger = logging.getLogger(__name__) @router.callback_query(F.data == "buy") async def buy_init(cb: CallbackQuery, state: FSMContext): await state.clear() ctx = SubscriptionPlan() await cb.message.edit_text( SUBSCRIPTION_DEVICE_SELECTOR.format(price=calculate_price(ctx)), reply_markup=devices_selector(settings.min_devices), ) await state.set_state(SubscriptionStorage.devices) await state.set_data({"ctx": ctx}) @router.callback_query(F.data.startswith("devices:")) async def device_count_select(cb: CallbackQuery, state: FSMContext): data = await state.get_data() await state.clear() ctx: Optional[SubscriptionPlan] = data.get("ctx", SubscriptionPlan()) cb_data = cb.data.split(":") devices = int(cb_data[1]) if cb_data[1].isdigit() else settings.min_devices ctx.devices = devices ctx.whitelists = ctx.whitelists or devices >= settings.whitelist_device_threshold await cb.message.edit_text( SUBSCRIPTION_DEVICE_SELECTOR.format(price=calculate_price(ctx)), reply_markup=devices_selector(current=ctx.devices, whitelists=ctx.whitelists), ) await state.set_state(SubscriptionStorage.devices) await state.set_data({"ctx": ctx}) @router.callback_query(SubscriptionStorage.devices, F.data == "whitelist:toggle") async def toggle_whitelists(cb: CallbackQuery, state: FSMContext): data = await state.get_data() await state.clear() ctx: Optional[SubscriptionPlan] = data.get("ctx", SubscriptionPlan()) ctx.whitelists = ( not ctx.whitelists or ctx.devices >= settings.whitelist_device_threshold ) try: await cb.message.edit_text( SUBSCRIPTION_DEVICE_SELECTOR.format(price=calculate_price(ctx)), reply_markup=devices_selector( current=ctx.devices, whitelists=ctx.whitelists ), ) except Exception as exc: if "message is not modified:" in str(exc): await cb.answer(NOTHING_PLACEHOLDER) else: await cb.answer(CALLBACK_FALLBACK) await state.set_state(SubscriptionStorage.devices) await state.set_data({"ctx": ctx}) @router.callback_query(SubscriptionStorage.devices, F.data == "confirm") async def select_duration_init(cb: CallbackQuery, state: FSMContext): data = await state.get_data() await state.clear() ctx: Optional[SubscriptionPlan] = data.get("ctx") if not ctx: await cb.answer(CALLBACK_FALLBACK) return try: await cb.message.edit_text( SUBSCRIPTION_DURATION_SELECTOR.format(price=calculate_price(ctx)), reply_markup=duration_selector( current=ctx.duration, back_cb=f"devices:{ctx.devices}" ), ) except Exception as exc: if "message is not modified:" in str(exc): await cb.answer(NOTHING_PLACEHOLDER) else: await cb.answer(CALLBACK_FALLBACK) raise await state.set_state(SubscriptionStorage.duration) await state.set_data({"ctx": ctx}) @router.callback_query(F.data.startswith("duration:")) async def select_duration(cb: CallbackQuery, state: FSMContext): data = await state.get_data() await state.clear() ctx: Optional[SubscriptionPlan] = data.get("ctx") if not ctx: await cb.answer(CONTEXT_REINITIATED) ctx = SubscriptionPlan() cb_data = cb.data.split(":") duration = int(cb_data[1]) if cb_data[1].isdigit() else 1 ctx.duration = convert_int_to_duration(duration) try: await cb.message.edit_text( SUBSCRIPTION_DURATION_SELECTOR.format(price=calculate_price(ctx)), reply_markup=duration_selector( current=ctx.duration, back_cb=f"devices:{ctx.devices}" ), ) except Exception as exc: if "message is not modified:" in str(exc): await cb.answer(NOTHING_PLACEHOLDER) else: await cb.answer(CALLBACK_FALLBACK) raise await state.set_state(SubscriptionStorage.duration) await state.set_data({"ctx": ctx}) @router.callback_query(SubscriptionStorage.duration, F.data == "confirm") async def proceed_to_checkout(cb: CallbackQuery, state: FSMContext): data = await state.get_data() await state.clear() ctx: Optional[SubscriptionPlan] = data.get("ctx") if not ctx: await cb.message.edit_text(STANDARD_FALLBACK, reply_markup=return_to_menu) return await cb.message.edit_text( CHECKOUT_PAYMENT_CHOICE, reply_markup=payment_gateways, ) # FIXME: temp solution that's incredibly stupid await state.set_state(SubscriptionStorage.checkout) await state.set_data({"ctx": ctx}) @router.callback_query(F.data == "checkout") async def checkout_support( cb: CallbackQuery, state: FSMContext, session: AsyncSession, deps: DependenciesDTO ): data = await state.get_data() await state.clear() ctx: Optional[SubscriptionPlan] = data.get("ctx") if not ctx: await cb.message.edit_text(STANDARD_FALLBACK, reply_markup=return_to_menu) return await cb.message.edit_text(GENERAL_PROCESSING) try: await deps.user_service.create_subscription_ticket( session, cb.from_user, ctx, cb.bot ) await cb.message.edit_text(SUPPORT_MSG_SENT) except UserServiceException: await cb.message.edit_text(STANDARD_FALLBACK) raise except Exception: await cb.message.edit_text(STANDARD_FALLBACK) logger.exception("Exception occured while trying to forward a msg to support") raise