From 0444ff325490f24e9a8d35f83ba37a0bd95ab6c5 Mon Sep 17 00:00:00 2001 From: Tolmachev Igor Date: Mon, 23 Mar 2026 22:17:24 +0300 Subject: Add pay_invoice button --- handlers/user/pay_invoice.py | 160 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 handlers/user/pay_invoice.py (limited to 'handlers/user/pay_invoice.py') diff --git a/handlers/user/pay_invoice.py b/handlers/user/pay_invoice.py new file mode 100644 index 0000000..98f80a6 --- /dev/null +++ b/handlers/user/pay_invoice.py @@ -0,0 +1,160 @@ +from datetime import UTC, datetime + +from aiogram import Bot, F, Router +from aiogram.enums import ButtonStyle +from aiogram.exceptions import TelegramAPIError +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup +from aiogram.types import ( + CallbackQuery, + InlineKeyboardButton, + InlineKeyboardMarkup, + KeyboardButton, + Message, + ReplyKeyboardMarkup, + ReplyKeyboardRemove, +) +from pydantic import BaseModel +from sqlalchemy import and_, select +from sqlalchemy.ext.asyncio import AsyncSession + +from libs.fsm import get_data, set_data +from libs.user import mention +from models import Payment, PaymentStatus, ReceiptFile, ReceiptFileType, User, UserRole +from models.callback_data import PayInvoiceClb, PaymentStatusClb + +router = Router(name="pay_invoice") + + +class PayInvoiceStates(StatesGroup): + receipt = State() + + +class PayInvoiceData(BaseModel): + invoice_id: int + + +CANCEL_BUTTON = "Отмена оплаты" + + +@router.callback_query(PayInvoiceClb.filter()) +async def button( + clb: CallbackQuery, + bot: Bot, + state: FSMContext, + callback_data: PayInvoiceClb, + session: AsyncSession, +) -> None: + payment = await session.scalar( + select(Payment).where( + and_( + Payment.user_id == clb.from_user.id, + Payment.invoice_id == callback_data.invoice_id, + Payment.status != PaymentStatus.REJECTED, + ) + ) + ) + + if payment is not None: + await clb.answer( + "Вы уже оплатили данный счёт.", + show_alert=True, + ) + return + + await bot.send_message( + clb.from_user.id, + "Укажите подтверждение оплаты (скриншот, pdf чека и т.п.)", + reply_markup=ReplyKeyboardMarkup( + keyboard=[[KeyboardButton(text=CANCEL_BUTTON, style=ButtonStyle.DANGER)]], + resize_keyboard=True, + ), + ) + + await state.set_state(PayInvoiceStates.receipt) + await set_data(state, PayInvoiceData(invoice_id=callback_data.invoice_id)) + + await clb.answer() + + +@router.message(PayInvoiceStates.receipt, F.text == CANCEL_BUTTON) +async def cancel(msg: Message, state: FSMContext) -> None: + await msg.answer( + "Отправка подтверждений оплаты отменена.", + reply_markup=ReplyKeyboardRemove(), + ) + await state.clear() + + +@router.message(PayInvoiceStates.receipt) +async def receipt( + msg: Message, + bot: Bot, + state: FSMContext, + session: AsyncSession, +) -> None: + if msg.document is not None: + receipt_file = ReceiptFile( + type=ReceiptFileType.DOCUMENT, + file_id=msg.document.file_id, + ) + elif msg.photo is not None: + receipt_file = ReceiptFile( + type=ReceiptFileType.PHOTO, + file_id=max(msg.photo, key=lambda p: (p.width, p.height)).file_id, + ) + else: + await msg.answer("Вы должны прислать файл или фото.") + return + + data = await get_data(state, PayInvoiceData) + payment = Payment( + user_id=msg.chat.id, + invoice_id=data.invoice_id, + receipt_file=receipt_file, + datetime=datetime.now(UTC), + ) + session.add(payment) + await session.flush() + + await msg.answer( + "Файл подтверждения оплаты прикреплен.", + reply_markup=ReplyKeyboardRemove(), + ) + await state.clear() + + admin_ids = await session.scalars( + select(User.id).where(User.role == UserRole.ADMIN) + ) + reply_markup = InlineKeyboardMarkup( + inline_keyboard=[ + [ + InlineKeyboardButton( + text="Подтвердить", + callback_data=PaymentStatusClb( + payment_id=payment.id, + payment_status=PaymentStatus.ACCEPTED, + ).pack(), + style=ButtonStyle.SUCCESS, + ), + InlineKeyboardButton( + text="Отклонить", + callback_data=PaymentStatusClb( + payment_id=payment.id, + payment_status=PaymentStatus.REJECTED, + ).pack(), + style=ButtonStyle.DANGER, + ), + ] + ] + ) + + for admin_id in admin_ids: + try: + await bot.send_message( + admin_id, + f"Новое подтверждение оплаты:\nПользователь: {mention(msg.chat)}", + ) + await receipt_file.send(bot, admin_id, reply_markup=reply_markup) + except TelegramAPIError as e: + print(e) -- cgit v1.3