from aiogram import F, Router from aiogram.filters import Command, CommandStart from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message from app.api import ApiError, ShopApi router = Router() def _catalog_keyboard(products: list[dict]) -> InlineKeyboardMarkup: rows = [ [InlineKeyboardButton(text=f"{item['title']} — {item['price']} ₽", callback_data=f"prod:{item['id']}")] for item in products ] return InlineKeyboardMarkup(inline_keyboard=rows) def _product_keyboard(product_id: int) -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton(text="В корзину", callback_data=f"add:{product_id}"), InlineKeyboardButton(text="Оформить", callback_data="checkout"), ] ] ) def _cart_text(cart: dict) -> str: if not cart["items"]: return "Корзина пуста." lines = [f"{item['title']} × {item['qty']} = {item['line_total']} ₽" for item in cart["items"]] lines.append(f"Итого: {cart['total']} ₽") lines.append("Оформление: /checkout") return "\n".join(lines) @router.message(CommandStart()) async def start(message: Message) -> None: await message.answer("Магазин. /catalog — товары, /cart — корзина, /checkout — заказ без оплаты.") @router.message(Command("catalog")) async def catalog(message: Message, api: ShopApi) -> None: products = await api.products() if not products: await message.answer("Каталог пуст.") return await message.answer("Каталог", reply_markup=_catalog_keyboard(products)) @router.callback_query(F.data.startswith("prod:")) async def show_product(query: CallbackQuery, api: ShopApi) -> None: product_id = int(query.data.split(":", 1)[1]) try: product = await api.product(product_id) except ApiError as exc: await query.answer(exc.detail, show_alert=True) return text = f"{product['title']}\n{product['description']}\n{product['price']} ₽\n{product['category_name']}" if query.message is not None: await query.message.answer(text, reply_markup=_product_keyboard(product_id)) await query.answer() @router.callback_query(F.data.startswith("add:")) async def add_product(query: CallbackQuery, api: ShopApi) -> None: if query.from_user is None: await query.answer() return product_id = int(query.data.split(":", 1)[1]) try: cart = await api.add(query.from_user.id, product_id) except ApiError as exc: await query.answer(exc.detail, show_alert=True) return await query.answer("Добавлено") if query.message is not None: await query.message.answer(_cart_text(cart)) @router.message(Command("cart")) async def cart(message: Message, api: ShopApi) -> None: if message.from_user is None: return data = await api.cart(message.from_user.id) await message.answer(_cart_text(data)) @router.message(Command("checkout")) @router.callback_query(F.data == "checkout") async def checkout_start(event: Message | CallbackQuery, api: ShopApi) -> None: user = event.from_user if user is None: return await api.save_dialog(user.id, "name", None) text = "Как вас зовут?" if isinstance(event, CallbackQuery): await event.answer() if event.message is not None: await event.message.answer(text) return await event.answer(text) @router.message(F.text) async def dialog(message: Message, api: ShopApi) -> None: if message.from_user is None or message.text is None or message.text.startswith("/"): return state = await api.dialog(message.from_user.id) if state is None: return if state["step"] == "name": await api.save_dialog(message.from_user.id, "phone", message.text.strip()[:120]) await message.answer("Телефон? Можно цифры, пробелы и +()-.") return if state["step"] != "phone" or not state.get("name"): await api.clear_dialog(message.from_user.id) return try: order = await api.order(message.from_user.id, state["name"], message.text.strip()) except ApiError as exc: await message.answer(exc.detail) return await api.clear_dialog(message.from_user.id) await message.answer( f"Заказ {order['id']} создан, без оплаты.\nСумма: {order['total']} ₽\nИмя: {order['customer_name']}" )