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)