aboutsummaryrefslogtreecommitdiff
path: root/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'handlers')
-rw-r--r--handlers/admin/add_user.py2
-rw-r--r--handlers/user/__init__.py2
-rw-r--r--handlers/user/pay_invoice.py160
3 files changed, 163 insertions, 1 deletions
diff --git a/handlers/admin/add_user.py b/handlers/admin/add_user.py
index c58af2b..1d19834 100644
--- a/handlers/admin/add_user.py
+++ b/handlers/admin/add_user.py
@@ -95,5 +95,5 @@ async def set_vpn_link(
95 session.add(User(id=data.user_id, vpn_link=msg.text, datetime=datetime.now(UTC))) 95 session.add(User(id=data.user_id, vpn_link=msg.text, datetime=datetime.now(UTC)))
96 await session.flush() 96 await session.flush()
97 97
98 await msg.answer("Пользователь добавлен.") 98 await msg.answer("Пользователь добавлен.", reply_markup=ReplyKeyboardRemove())
99 await state.clear() 99 await state.clear()
diff --git a/handlers/user/__init__.py b/handlers/user/__init__.py
index ac4c0a3..4b43427 100644
--- a/handlers/user/__init__.py
+++ b/handlers/user/__init__.py
@@ -3,10 +3,12 @@ from aiogram import Router
3# isort: off 3# isort: off
4from . import info 4from . import info
5from . import vpn_link 5from . import vpn_link
6from . import pay_invoice
6# isort: on 7# isort: on
7 8
8router = Router(name="user") 9router = Router(name="user")
9router.include_routers( 10router.include_routers(
10 info.router, 11 info.router,
11 vpn_link.router, 12 vpn_link.router,
13 pay_invoice.router,
12) 14)
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 @@
1from datetime import UTC, datetime
2
3from aiogram import Bot, F, Router
4from aiogram.enums import ButtonStyle
5from aiogram.exceptions import TelegramAPIError
6from aiogram.fsm.context import FSMContext
7from aiogram.fsm.state import State, StatesGroup
8from aiogram.types import (
9 CallbackQuery,
10 InlineKeyboardButton,
11 InlineKeyboardMarkup,
12 KeyboardButton,
13 Message,
14 ReplyKeyboardMarkup,
15 ReplyKeyboardRemove,
16)
17from pydantic import BaseModel
18from sqlalchemy import and_, select
19from sqlalchemy.ext.asyncio import AsyncSession
20
21from libs.fsm import get_data, set_data
22from libs.user import mention
23from models import Payment, PaymentStatus, ReceiptFile, ReceiptFileType, User, UserRole
24from models.callback_data import PayInvoiceClb, PaymentStatusClb
25
26router = Router(name="pay_invoice")
27
28
29class PayInvoiceStates(StatesGroup):
30 receipt = State()
31
32
33class PayInvoiceData(BaseModel):
34 invoice_id: int
35
36
37CANCEL_BUTTON = "Отмена оплаты"
38
39
40@router.callback_query(PayInvoiceClb.filter())
41async def button(
42 clb: CallbackQuery,
43 bot: Bot,
44 state: FSMContext,
45 callback_data: PayInvoiceClb,
46 session: AsyncSession,
47) -> None:
48 payment = await session.scalar(
49 select(Payment).where(
50 and_(
51 Payment.user_id == clb.from_user.id,
52 Payment.invoice_id == callback_data.invoice_id,
53 Payment.status != PaymentStatus.REJECTED,
54 )
55 )
56 )
57
58 if payment is not None:
59 await clb.answer(
60 "Вы уже оплатили данный счёт.",
61 show_alert=True,
62 )
63 return
64
65 await bot.send_message(
66 clb.from_user.id,
67 "Укажите подтверждение оплаты (скриншот, pdf чека и т.п.)",
68 reply_markup=ReplyKeyboardMarkup(
69 keyboard=[[KeyboardButton(text=CANCEL_BUTTON, style=ButtonStyle.DANGER)]],
70 resize_keyboard=True,
71 ),
72 )
73
74 await state.set_state(PayInvoiceStates.receipt)
75 await set_data(state, PayInvoiceData(invoice_id=callback_data.invoice_id))
76
77 await clb.answer()
78
79
80@router.message(PayInvoiceStates.receipt, F.text == CANCEL_BUTTON)
81async def cancel(msg: Message, state: FSMContext) -> None:
82 await msg.answer(
83 "Отправка подтверждений оплаты отменена.",
84 reply_markup=ReplyKeyboardRemove(),
85 )
86 await state.clear()
87
88
89@router.message(PayInvoiceStates.receipt)
90async def receipt(
91 msg: Message,
92 bot: Bot,
93 state: FSMContext,
94 session: AsyncSession,
95) -> None:
96 if msg.document is not None:
97 receipt_file = ReceiptFile(
98 type=ReceiptFileType.DOCUMENT,
99 file_id=msg.document.file_id,
100 )
101 elif msg.photo is not None:
102 receipt_file = ReceiptFile(
103 type=ReceiptFileType.PHOTO,
104 file_id=max(msg.photo, key=lambda p: (p.width, p.height)).file_id,
105 )
106 else:
107 await msg.answer("Вы должны прислать файл или фото.")
108 return
109
110 data = await get_data(state, PayInvoiceData)
111 payment = Payment(
112 user_id=msg.chat.id,
113 invoice_id=data.invoice_id,
114 receipt_file=receipt_file,
115 datetime=datetime.now(UTC),
116 )
117 session.add(payment)
118 await session.flush()
119
120 await msg.answer(
121 "Файл подтверждения оплаты прикреплен.",
122 reply_markup=ReplyKeyboardRemove(),
123 )
124 await state.clear()
125
126 admin_ids = await session.scalars(
127 select(User.id).where(User.role == UserRole.ADMIN)
128 )
129 reply_markup = InlineKeyboardMarkup(
130 inline_keyboard=[
131 [
132 InlineKeyboardButton(
133 text="Подтвердить",
134 callback_data=PaymentStatusClb(
135 payment_id=payment.id,
136 payment_status=PaymentStatus.ACCEPTED,
137 ).pack(),
138 style=ButtonStyle.SUCCESS,
139 ),
140 InlineKeyboardButton(
141 text="Отклонить",
142 callback_data=PaymentStatusClb(
143 payment_id=payment.id,
144 payment_status=PaymentStatus.REJECTED,
145 ).pack(),
146 style=ButtonStyle.DANGER,
147 ),
148 ]
149 ]
150 )
151
152 for admin_id in admin_ids:
153 try:
154 await bot.send_message(
155 admin_id,
156 f"Новое подтверждение оплаты:\nПользователь: {mention(msg.chat)}",
157 )
158 await receipt_file.send(bot, admin_id, reply_markup=reply_markup)
159 except TelegramAPIError as e:
160 print(e)