Implement buy flow and migrate DTOs to schemas
Move DTO definitions from dto/ to schemas/ and update imports. Add buy handler with FSM state management for device selection, whitelist toggling, and duration choice. Include pricing logic, inline keyboards, and new configuration fields.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from .menus import router as menus_router
|
||||
from .support import router as support_router
|
||||
from .buy import router as buy_router
|
||||
|
||||
routers = [menus_router, support_router]
|
||||
routers = [menus_router, support_router, buy_router]
|
||||
|
||||
151
handlers/buy.py
151
handlers/buy.py
@@ -1,10 +1,153 @@
|
||||
from aiogram import Router
|
||||
from typing import Optional
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import CallbackQuery
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
from dto.di import DependenciesDTO
|
||||
|
||||
from keyboards.client import devices_selector, duration_selector
|
||||
from misc.utils import calculate_price, convert_int_to_duration
|
||||
from schemas.subscriptions import SubscriptionPlan
|
||||
from states.buy import SubscriptionStorage
|
||||
from texts import (
|
||||
CALLBACK_FALLBACK,
|
||||
CONTEXT_REINITIATED,
|
||||
NOTHING_PLACEHOLDER,
|
||||
SUBSCRIPTION_DEVICE_SELECTOR,
|
||||
SUBSCRIPTION_DURATION_SELECTOR,
|
||||
)
|
||||
from config import settings
|
||||
|
||||
router = Router()
|
||||
|
||||
# @router.callback_query()
|
||||
|
||||
@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")
|
||||
if not ctx:
|
||||
await state.set_state(SubscriptionPlan())
|
||||
await state.set_data({"ctx": ctx})
|
||||
await cb.answer(CALLBACK_FALLBACK)
|
||||
return
|
||||
ctx.whitelists = (
|
||||
not ctx.whitelists or ctx.devices >= settings.whitelist_device_threshold
|
||||
)
|
||||
|
||||
try:
|
||||
await cb.message.edit_reply_markup(
|
||||
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 state.set_state(SubscriptionPlan())
|
||||
await state.set_data({"ctx": 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 state.set_state()
|
||||
await state.set_data({"ctx": 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):
|
||||
await cb.answer("yay!!!! fuck off.")
|
||||
...
|
||||
|
||||
@@ -4,7 +4,7 @@ from aiogram.types import CallbackQuery, Message
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from dto.di import DependenciesDTO
|
||||
from schemas.di import DependenciesDTO
|
||||
from keyboards.client import main_menu
|
||||
from texts import build_main_menu_text
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from aiogram.types import CallbackQuery, Message
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import settings
|
||||
from dto.di import DependenciesDTO
|
||||
from schemas.di import DependenciesDTO
|
||||
from services.user_service import UserServiceException
|
||||
from states.support import SupportStorage
|
||||
from texts import GENERAL_PROCESSING, STANDARD_FALLBACK, SUPPORT_INIT, SUPPORT_MSG_SENT
|
||||
|
||||
Reference in New Issue
Block a user