aboutsummaryrefslogtreecommitdiff
path: root/handlers/user/payments.py
diff options
context:
space:
mode:
authorTolmachev Igor <me@igorek.dev>2026-04-20 19:11:21 +0300
committerTolmachev Igor <me@igorek.dev>2026-04-20 19:11:21 +0300
commitf186fca0f1aa9bbe5eab7613f229df527b2ab774 (patch)
tree7d6a5c4c044dc22802da142854dba6586ede8dd4 /handlers/user/payments.py
parent079c578c4872b5bb50edf1dd5fcfa7cf4d420696 (diff)
downloadvpn_manager_bot-f186fca0f1aa9bbe5eab7613f229df527b2ab774.tar.gz
vpn_manager_bot-f186fca0f1aa9bbe5eab7613f229df527b2ab774.zip
Add payments command
Diffstat (limited to 'handlers/user/payments.py')
-rw-r--r--handlers/user/payments.py264
1 files changed, 264 insertions, 0 deletions
diff --git a/handlers/user/payments.py b/handlers/user/payments.py
new file mode 100644
index 0000000..87ea236
--- /dev/null
+++ b/handlers/user/payments.py
@@ -0,0 +1,264 @@
1from math import ceil
2
3from aiogram import Bot, Router
4from aiogram.enums import ButtonStyle
5from aiogram.filters import Command
6from aiogram.types import (
7 CallbackQuery,
8 InlineKeyboardButton,
9 InlineKeyboardMarkup,
10 Message,
11)
12from sqlalchemy import select
13from sqlalchemy.ext.asyncio import AsyncSession
14from sqlalchemy.sql.functions import count
15
16from libs.invoice import get_payment_status
17from libs.msg import eclipse_text
18from libs.user import mention
19from models import Invoice, Payment, PaymentStatus, User
20from models.callback_data import (
21 PaymentItemClb,
22 PaymentPageClb,
23 PaymentStatusClb,
24 PayInvoiceClb,
25)
26
27router = Router(name="payments")
28PAGE_SIZE = 5
29PAYMENT_STATUS = {
30 PaymentStatus.PENDING: "🟡",
31 PaymentStatus.ACCEPTED: "🟢",
32 PaymentStatus.REJECTED: "🔴",
33}
34
35
36def get_text(user: User) -> str:
37 if user.is_admin():
38 return "Выберете платёж для управления:"
39 else:
40 return "Выберете счёт для оплаты:"
41
42
43def get_no_data_text(user: User) -> str:
44 if user.is_admin():
45 return "Нету платежей."
46 else:
47 return "Нету счетов для оплаты."
48
49
50def build_markup(
51 page: int,
52 total_pages: int,
53 item_buttons: list[list[InlineKeyboardButton]],
54) -> InlineKeyboardMarkup:
55 page_buttons = []
56 if page > 0:
57 page_buttons.append(
58 InlineKeyboardButton(
59 text="◀️",
60 callback_data=PaymentPageClb(page=page - 1).pack(),
61 )
62 )
63 if page < total_pages - 1:
64 page_buttons.append(
65 InlineKeyboardButton(
66 text="▶️",
67 callback_data=PaymentPageClb(page=page + 1).pack(),
68 )
69 )
70
71 return InlineKeyboardMarkup(inline_keyboard=[*item_buttons, page_buttons])
72
73
74async def get_user_reply_markup(
75 page: int,
76 user: User,
77 session: AsyncSession,
78) -> InlineKeyboardMarkup | None:
79 total = await session.scalar(
80 select(count()).select_from(Invoice).where(Invoice.datetime >= user.datetime)
81 )
82 assert total is not None
83 total_pages = ceil(total / PAGE_SIZE)
84
85 if total == 0:
86 return None
87
88 page = max(0, min(page, total_pages - 1))
89 invoices = await session.scalars(
90 select(Invoice)
91 .where(Invoice.datetime >= user.datetime)
92 .offset(PAGE_SIZE * page)
93 .limit(PAGE_SIZE)
94 .order_by(Invoice.id.desc())
95 )
96
97 invoice_buttons = []
98 for i in invoices:
99 status = PAYMENT_STATUS[await get_payment_status(session, i.id, user.id)]
100 button = InlineKeyboardButton(
101 text=(
102 f"{status} "
103 f"{eclipse_text(i.message.text, 10)} "
104 f"({i.datetime.strftime('%d %b %y г.')})"
105 ),
106 callback_data=PayInvoiceClb(invoice_id=i.id).pack(),
107 )
108 invoice_buttons.append([button])
109
110 return build_markup(page, total_pages, invoice_buttons)
111
112
113async def get_admin_reply_markup(
114 page: int,
115 bot: Bot,
116 session: AsyncSession,
117) -> InlineKeyboardMarkup | None:
118 total = await session.scalar(select(count()).select_from(Payment))
119 assert total is not None
120 total_pages = ceil(total / PAGE_SIZE)
121
122 if total == 0:
123 return None
124
125 page = max(0, min(page, total_pages - 1))
126 payments = await session.scalars(
127 select(Payment)
128 .offset(PAGE_SIZE * page)
129 .limit(PAGE_SIZE)
130 .order_by(Payment.id.desc())
131 )
132
133 payment_buttons = []
134 for p in payments:
135 invoice = await session.get(Invoice, p.invoice_id)
136 assert invoice is not None
137 chat = await bot.get_chat(p.user_id)
138 button = InlineKeyboardButton(
139 text=(
140 f"{PAYMENT_STATUS[p.status]} "
141 f"{chat.full_name} - "
142 f"{eclipse_text(invoice.message.text, 10)} "
143 f"({p.datetime.strftime('%d %b %y г.')})"
144 ),
145 callback_data=PaymentItemClb(page=page, payment_id=p.id).pack(),
146 )
147 payment_buttons.append([button])
148
149 return build_markup(page, total_pages, payment_buttons)
150
151
152async def get_reply_markup(
153 page: int,
154 bot: Bot,
155 user: User,
156 session: AsyncSession,
157) -> InlineKeyboardMarkup | None:
158 if user.is_admin():
159 return await get_admin_reply_markup(page, bot, session)
160 else:
161 return await get_user_reply_markup(page, user, session)
162
163
164@router.message(Command("payments"))
165async def command(msg: Message, bot: Bot, session: AsyncSession, user: User) -> None:
166 reply_markup = await get_reply_markup(0, bot, user, session)
167
168 if reply_markup is None:
169 await msg.answer(get_no_data_text(user))
170 return
171
172 await msg.answer(get_text(user), reply_markup=reply_markup)
173
174
175@router.callback_query(PaymentPageClb.filter())
176async def page(
177 clb: CallbackQuery,
178 bot: Bot,
179 callback_data: PaymentPageClb,
180 session: AsyncSession,
181 user: User,
182) -> None:
183 assert isinstance(clb.message, Message)
184 text = get_text(user)
185 reply_markup = await get_reply_markup(callback_data.page, bot, user, session)
186
187 if clb.message.photo is not None or clb.message.document is not None:
188 await clb.message.delete()
189 await bot.send_message(clb.from_user.id, text, reply_markup=reply_markup)
190 else:
191 await clb.message.edit_text(text, reply_markup=reply_markup)
192
193 await clb.answer()
194
195
196@router.callback_query(PaymentItemClb.filter())
197async def item(
198 clb: CallbackQuery,
199 bot: Bot,
200 callback_data: PaymentItemClb,
201 session: AsyncSession,
202 user: User,
203) -> None:
204 assert isinstance(clb.message, Message)
205 if not user.is_admin():
206 await clb.answer("У вас нет прав для данного действия.", show_alert=True)
207 return
208
209 payment = await session.get(Payment, callback_data.payment_id)
210 if payment is None:
211 await clb.answer("Платёж был удален.", show_alert=True)
212 return
213
214 invoice = await session.get(Invoice, payment.invoice_id)
215 assert invoice is not None
216 chat = await bot.get_chat(payment.user_id)
217
218 status_buttons = []
219 if payment.status != PaymentStatus.ACCEPTED:
220 status_buttons.append(
221 InlineKeyboardButton(
222 text="Подтвердить",
223 style=ButtonStyle.SUCCESS,
224 callback_data=PaymentStatusClb(
225 payment_id=payment.id,
226 payment_status=PaymentStatus.ACCEPTED,
227 ).pack(),
228 )
229 )
230 if payment.status != PaymentStatus.REJECTED:
231 status_buttons.append(
232 InlineKeyboardButton(
233 text="Отклонить",
234 style=ButtonStyle.DANGER,
235 callback_data=PaymentStatusClb(
236 payment_id=payment.id,
237 payment_status=PaymentStatus.REJECTED,
238 ).pack(),
239 )
240 )
241 back_button = InlineKeyboardButton(
242 text="Назад к выбору",
243 callback_data=PaymentPageClb(page=callback_data.page).pack(),
244 )
245 reply_markup = InlineKeyboardMarkup(
246 inline_keyboard=[status_buttons, [back_button]]
247 )
248
249 caption = (
250 f"Платёж от {mention(chat)}\n"
251 f"Счёт: {eclipse_text(invoice.message.text, 30)}\n"
252 f"Дата: {payment.datetime.strftime('%d %b %y г.')}\n"
253 f"Статус: {PAYMENT_STATUS[payment.status]}"
254 )
255
256 await clb.message.delete()
257 await payment.receipt_file.send(
258 bot,
259 clb.from_user.id,
260 caption=caption,
261 reply_markup=reply_markup,
262 )
263
264 await clb.answer()