1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
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 models import (
Invoice,
Payment,
PaymentStatus,
ReceiptFile,
ReceiptFileType,
User,
UserCache,
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,
user: User,
) -> None:
if user.is_admin():
await clb.answer("Администраторы не могут оплачивать счета", show_alert=True)
return
invoice = await session.get(Invoice, callback_data.invoice_id)
assert invoice is not None
if user.datetime > invoice.datetime:
await clb.answer("Вы не можете оплатить данный счёт", show_alert=True)
return
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,
user_cache: UserCache,
) -> 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Пользователь: {user_cache.mention}",
)
await receipt_file.send(bot, admin_id, reply_markup=reply_markup)
except TelegramAPIError as e:
print(e)
|