src.handlers.payment
Действия с оплатой
1"""Действия с оплатой""" 2 3import logging 4import os 5from datetime import datetime, timedelta, timezone 6from typing import Union 7from uuid import uuid4 8 9from aiogram import Bot, F, Router 10from aiogram.filters.command import Command 11from aiogram.fsm.context import FSMContext 12from aiogram.types import CallbackQuery, FSInputFile, Message 13from pytils.numeral import get_plural 14from yoomoney import Client, Quickpay 15from yoomoney.exceptions import YooMoneyError 16 17import kb 18import text 19from core import exceptions as exc 20from core.config import settings 21from core.err import bot_except 22from core.metric import async_speed_metric 23from core.path import PATH 24from db import utils 25from db.models import Transactions, UserData 26from handlers.utils import find_user, get_table_from_df 27from handlers.wg_service import post_user_data 28from states import Service 29 30logger = logging.getLogger() 31router = Router() 32pay_keyboard = kb.get_pay_keyboard() 33router.message.filter(F.chat.type == "private") 34 35 36@router.message(Command("sub")) 37@router.callback_query(F.data == "user_payment") 38@router.message(F.text == "Подписка") 39@async_speed_metric 40@bot_except 41async def subscribe_manager(trigger: Union[Message, CallbackQuery], state: FSMContext): 42 """Управляет подпиской пользователя. 43 44 Args: 45 trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду. 46 state (FSMContext): Контекст состояния для управления состоянием пользователя. 47 48 Если подписка активна, отправляет информацию о текущем статусе подписки, тарифе и балансе пользователя. 49 """ 50 user_data: UserData = await find_user(trigger) 51 if not user_data: 52 return 53 54 sub_status, rate = text.get_sub_status(user_data) 55 56 if sub_status: 57 await getattr(trigger, "message", trigger).answer( 58 "\n".join( 59 ( 60 f"Подписка: <b>{sub_status}</b>", 61 f"Тариф: <b>{rate}</b>", 62 f"Баланс: <b>{user_data.fbalance()} руб</b>", 63 f"Вам осталось <b>{get_plural(text.get_end_sub(user_data), 'день, дня, дней')}</b>", 64 ) 65 ), 66 reply_markup=kb.get_subscr_buttons(user_data), 67 ) 68 69 70@router.message(Command("history")) 71@router.callback_query(F.data == "transact_history") 72@bot_except 73async def get_user_transact_choose(trigger: Union[Message, CallbackQuery]): 74 """Запрашивает у пользователя, какие операции он хочет увидеть. 75 76 Args: 77 trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду. 78 79 Отправляет сообщение с предложением выбрать тип операции для отображения истории транзакций. 80 """ 81 await getattr(trigger, "message", trigger).answer( 82 "Какие операции вам нужно увидеть?", reply_markup=kb.static_history_button 83 ) 84 85 86@router.callback_query(F.data.startswith("transact_history_")) 87@async_speed_metric 88@bot_except 89async def get_user_transact(callback: CallbackQuery): 90 """Получает и отображает историю транзакций пользователя. 91 92 Args: 93 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 94 95 Отправляет последние 10 транзакций пользователя и предоставляет возможность скачать полный список транзакций в формате Excel. 96 """ 97 try: 98 transactions: list[Transactions] = await utils.get_user_transactions( 99 callback.from_user.id 100 ) 101 102 transact_list: list[dict] = [] 103 *_, transact_type = callback.data.split("_") 104 105 for transact in sorted(transactions, key=lambda x: x.date): 106 params = transact.__udict__ 107 view_params = {"id", "date", "amount", "withdraw_amount"} 108 109 post_params = {key: params[key] for key in params.keys() & view_params} 110 111 if transact.date: 112 post_params["date"] = post_params["date"].astimezone() 113 post_params["Дата"] = post_params.get("date").ctime() 114 115 if not transact.transaction_id and datetime.now( 116 transact.date.tzinfo 117 ) - transact.date > timedelta(hours=12): 118 continue 119 120 if transact.amount > 0: 121 post_params["Описание"] = "Пополнение" 122 if transact.transaction_id: 123 post_params["Статус"] = "Исполнена" 124 if not transact.transaction_id.isnumeric(): 125 post_params["Описание"] = transact.transaction_id 126 127 else: 128 post_params["Статус"] = "Не исполнена" 129 else: 130 post_params["Описание"] = params.get("transaction_id", None) 131 132 if not post_params["withdraw_amount"]: 133 post_params.pop("withdraw_amount") 134 else: 135 post_params["Комиссия"] = ( 136 post_params.pop("withdraw_amount") - post_params["amount"] 137 ) 138 139 post_params["Сумма"] = round(float(post_params.pop("amount")), 2) 140 141 if transact_type == "in" and transact.amount > 0: 142 transact_list.append(post_params) 143 elif transact_type == "out" and transact.amount < 0: 144 transact_list.append(post_params) 145 146 if transact_list: 147 await callback.message.answer("Последние 10 транзакций") 148 else: 149 await callback.answer("У вас не было еще ни одной транзакции такого вида") 150 151 for transact in transact_list[-10:]: 152 await callback.message.answer( 153 "<pre>" 154 + "\n".join( 155 sorted( 156 ( 157 f"<b>{key}</b>:{' '*(10-len(key))}{val}" 158 for key, val in transact.items() 159 if key != "date" 160 ), 161 key=lambda x: len(x.split(":")[0]), 162 ) 163 ) 164 + "</pre>" 165 ) 166 167 if len(transact_list) > 10: 168 for tr in transact_list: 169 tr.pop("Дата") 170 171 tr_file = os.path.join(PATH, "tmp", f"{transact_type}_transactions.xlsx") 172 173 get_table_from_df(transact_list, tr_file) 174 175 await callback.message.answer_document( 176 FSInputFile(tr_file, "transactions.xlsx"), 177 caption="Полный список ваших транзакций", 178 ) 179 os.remove(tr_file) 180 except exc.DatabaseError: 181 await callback.answer(text=text.DB_ERROR, show_alert=True) 182 except Exception: 183 logger.exception("Ошибка при отображении истории транзакций") 184 await callback.answer( 185 text="К сожалению, сейчас мы не можем корректно отобразить вашу историю. " 186 "Попробуйте позже или обратитесь в техподдержку.", 187 show_alert=True, 188 ) 189 190 191@router.callback_query(F.data == "change_rate") 192@async_speed_metric 193@bot_except 194async def post_rate_list(callback: CallbackQuery): 195 """Отправляет список доступных тарифов пользователю. 196 197 Args: 198 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 199 200 Отправляет сообщение с предложением выбрать тариф. 201 """ 202 user_data: UserData = await find_user(callback) 203 if not user_data: 204 return 205 206 await callback.message.answer( 207 "Выберите тариф", 208 reply_markup=kb.get_subscr_buttons(user_data, force_rates=True), 209 ) 210 211 212@router.callback_query(F.data.startswith("rate_info_")) 213@async_speed_metric 214@bot_except 215async def choose_rate(callback: CallbackQuery): 216 """Отправляет информацию о выбранном тарифе. 217 218 Args: 219 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 220 221 Отправляет сообщение с описанием выбранного тарифа и информацией о комиссии. 222 """ 223 user_data: UserData = await find_user(callback) 224 rate_id = float(callback.data.split("_")[-1]) 225 226 if not user_data: 227 return 228 elif user_data.stage > rate_id: 229 tax = user_data.stage * 10 230 else: 231 tax = 0 232 233 await callback.message.answer( 234 text.get_rate_descr(rate_id) 235 + f"\n\n‼️ <b>Комиссия при переходе на данный тариф {tax} руб</b>", 236 reply_markup=kb.get_rate_button(callback.data.split("_")[-1]), 237 ) 238 239 240@router.callback_query(F.data.startswith("accept_rate_")) 241@async_speed_metric 242@bot_except 243async def change_rate(callback: CallbackQuery, bot: Bot): 244 """Изменяет тариф пользователя. 245 246 Args: 247 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 248 bot (Bot): Экземпляр бота для выполнения действий. 249 250 Обрабатывает изменение тарифа пользователя, включая комиссию и условия. 251 """ 252 user_data: UserData = await find_user(callback) 253 rate_id = float(callback.data.split("_")[-1]) 254 try: 255 if not user_data: 256 return 257 elif user_data.stage == rate_id: 258 await callback.answer("У вас уже подключен этот тариф") 259 return 260 elif user_data.stage == 0.3: 261 await callback.answer( 262 "Дождитесь окончания пробного периода или пополните баланс", 263 show_alert=True, 264 ) 265 return 266 elif rate_id == 0.3: 267 if user_data.free: 268 await utils.update_rate_user( 269 callback.from_user.id, stage=rate_id, trial=True 270 ) 271 else: 272 await callback.answer( 273 "К сожалению вы исчерпали возможность подключения пробного периода" 274 ) 275 return 276 elif user_data.stage > rate_id: 277 await utils.update_rate_user( 278 callback.from_user.id, stage=rate_id, tax=user_data.stage * 10 279 ) 280 else: 281 await utils.update_rate_user(callback.from_user.id, stage=rate_id) 282 except exc.DatabaseError: 283 await callback.answer(text=text.DB_ERROR, show_alert=True) 284 else: 285 await callback.answer( 286 f"Изменение тарифа на {text.rates[rate_id]} успешно!", show_alert=True 287 ) 288 await post_user_data(callback) 289 finally: 290 await bot.delete_message(callback.from_user.id, callback.message.message_id) 291 292 293@router.callback_query(F.data == "top_up_balance") 294@bot_except 295async def input_balance(callback: CallbackQuery, state: FSMContext): 296 """Запрашивает у пользователя сумму для пополнения баланса. 297 298 Args: 299 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 300 state (FSMContext): Контекст состояния для управления состоянием пользователя. 301 302 Отправляет сообщение с предложением выбрать сумму или ввести вручную. 303 """ 304 await callback.message.answer( 305 "Выберите сумму или введите вручную. Для отмены используйте /cancel." 306 f"<b>\n(Актуальная комиссия провайдера {settings.transfer_fee*100-100} %</b>", 307 reply_markup=pay_keyboard, 308 ) 309 await state.set_state(Service.balance) 310 311 312@router.message(Service.balance, Command("cancel")) 313@bot_except 314async def cancel_input_balance(message: Message, state: FSMContext): 315 """Отменяет процесс ввода суммы для пополнения баланса. 316 317 Args: 318 message (Message): Сообщение, инициировавшее команду. 319 state (FSMContext): Контекст состояния для управления состоянием пользователя. 320 321 Отправляет сообщение об отмене операции. 322 """ 323 await state.clear() 324 await state.set_state() 325 await message.answer("Отменено") 326 327 328@router.callback_query(F.data.startswith("pay_sub_")) 329@router.message(Service.balance) 330@async_speed_metric 331@bot_except 332async def pay(trigger: Union[Message, CallbackQuery], bot: Bot, state: FSMContext): 333 """Обрабатывает процесс оплаты. 334 335 Args: 336 trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду. 337 bot (Bot): Экземпляр бота для выполнения действий. 338 state (FSMContext): Контекст состояния для управления состоянием пользователя. 339 340 Создает платеж и отправляет ссылку для пополнения баланса. Обрабатывает возможные ошибки. 341 """ 342 try: 343 client = Client(settings.YOO_TOKEN.get_secret_value()) 344 transaction_label = uuid4() 345 SUM = ( 346 float( 347 getattr( 348 trigger, "text", getattr(trigger, "data", "_None").split("_")[-1] 349 ) 350 ) 351 * settings.transfer_fee 352 ) 353 354 quickpay = Quickpay( 355 receiver=client.account_info().account, 356 quickpay_form="shop", 357 targets=f"Sponsor this project ({SUM} rub)", 358 paymentType="SB", 359 sum=SUM, 360 label=transaction_label, 361 ) 362 363 await utils.insert_transaction( 364 dict( 365 user_id=trigger.from_user.id, 366 date=datetime.now(timezone.utc), 367 amount=SUM, 368 label=transaction_label, 369 transaction_reference=quickpay.redirected_url, 370 ) 371 ) 372 373 await getattr(trigger, "message", trigger).answer( 374 "Многоразовая ссылка на пополнение счета." 375 "\n<b>(Действительна в течение 12 часов)</b>", 376 reply_markup=kb.get_pay_url(SUM, quickpay.redirected_url), 377 ) 378 await getattr(trigger, "message", trigger).answer( 379 "В случае если у вас по каким-либо причинам не прошла оплата, обратитесь в техподдержку", 380 reply_markup=kb.static_support_button, 381 ) 382 383 except KeyError: 384 await getattr(trigger, "message", trigger).answer( 385 "Истек срок давности сообщения" 386 ) 387 except exc.DatabaseError: 388 await getattr(trigger, "message", trigger).answer( 389 text=text.DB_ERROR, show_alert=True 390 ) 391 except YooMoneyError: 392 await getattr(trigger, "message", trigger).answer( 393 text=text.YOO_ERROR, show_alert=True 394 ) 395 except ValueError: 396 await getattr(trigger, "message", trigger).answer( 397 text="Сумма должна быть числом", show_alert=True 398 ) 399 else: 400 await state.clear() 401 await state.set_state() 402 finally: 403 await bot.delete_message( 404 trigger.from_user.id, getattr(trigger, "message", trigger).message_id 405 )
37@router.message(Command("sub")) 38@router.callback_query(F.data == "user_payment") 39@router.message(F.text == "Подписка") 40@async_speed_metric 41@bot_except 42async def subscribe_manager(trigger: Union[Message, CallbackQuery], state: FSMContext): 43 """Управляет подпиской пользователя. 44 45 Args: 46 trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду. 47 state (FSMContext): Контекст состояния для управления состоянием пользователя. 48 49 Если подписка активна, отправляет информацию о текущем статусе подписки, тарифе и балансе пользователя. 50 """ 51 user_data: UserData = await find_user(trigger) 52 if not user_data: 53 return 54 55 sub_status, rate = text.get_sub_status(user_data) 56 57 if sub_status: 58 await getattr(trigger, "message", trigger).answer( 59 "\n".join( 60 ( 61 f"Подписка: <b>{sub_status}</b>", 62 f"Тариф: <b>{rate}</b>", 63 f"Баланс: <b>{user_data.fbalance()} руб</b>", 64 f"Вам осталось <b>{get_plural(text.get_end_sub(user_data), 'день, дня, дней')}</b>", 65 ) 66 ), 67 reply_markup=kb.get_subscr_buttons(user_data), 68 )
Управляет подпиской пользователя.
Arguments:
- trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду.
- state (FSMContext): Контекст состояния для управления состоянием пользователя.
Если подписка активна, отправляет информацию о текущем статусе подписки, тарифе и балансе пользователя.
71@router.message(Command("history")) 72@router.callback_query(F.data == "transact_history") 73@bot_except 74async def get_user_transact_choose(trigger: Union[Message, CallbackQuery]): 75 """Запрашивает у пользователя, какие операции он хочет увидеть. 76 77 Args: 78 trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду. 79 80 Отправляет сообщение с предложением выбрать тип операции для отображения истории транзакций. 81 """ 82 await getattr(trigger, "message", trigger).answer( 83 "Какие операции вам нужно увидеть?", reply_markup=kb.static_history_button 84 )
Запрашивает у пользователя, какие операции он хочет увидеть.
Arguments:
- trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду.
Отправляет сообщение с предложением выбрать тип операции для отображения истории транзакций.
87@router.callback_query(F.data.startswith("transact_history_")) 88@async_speed_metric 89@bot_except 90async def get_user_transact(callback: CallbackQuery): 91 """Получает и отображает историю транзакций пользователя. 92 93 Args: 94 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 95 96 Отправляет последние 10 транзакций пользователя и предоставляет возможность скачать полный список транзакций в формате Excel. 97 """ 98 try: 99 transactions: list[Transactions] = await utils.get_user_transactions( 100 callback.from_user.id 101 ) 102 103 transact_list: list[dict] = [] 104 *_, transact_type = callback.data.split("_") 105 106 for transact in sorted(transactions, key=lambda x: x.date): 107 params = transact.__udict__ 108 view_params = {"id", "date", "amount", "withdraw_amount"} 109 110 post_params = {key: params[key] for key in params.keys() & view_params} 111 112 if transact.date: 113 post_params["date"] = post_params["date"].astimezone() 114 post_params["Дата"] = post_params.get("date").ctime() 115 116 if not transact.transaction_id and datetime.now( 117 transact.date.tzinfo 118 ) - transact.date > timedelta(hours=12): 119 continue 120 121 if transact.amount > 0: 122 post_params["Описание"] = "Пополнение" 123 if transact.transaction_id: 124 post_params["Статус"] = "Исполнена" 125 if not transact.transaction_id.isnumeric(): 126 post_params["Описание"] = transact.transaction_id 127 128 else: 129 post_params["Статус"] = "Не исполнена" 130 else: 131 post_params["Описание"] = params.get("transaction_id", None) 132 133 if not post_params["withdraw_amount"]: 134 post_params.pop("withdraw_amount") 135 else: 136 post_params["Комиссия"] = ( 137 post_params.pop("withdraw_amount") - post_params["amount"] 138 ) 139 140 post_params["Сумма"] = round(float(post_params.pop("amount")), 2) 141 142 if transact_type == "in" and transact.amount > 0: 143 transact_list.append(post_params) 144 elif transact_type == "out" and transact.amount < 0: 145 transact_list.append(post_params) 146 147 if transact_list: 148 await callback.message.answer("Последние 10 транзакций") 149 else: 150 await callback.answer("У вас не было еще ни одной транзакции такого вида") 151 152 for transact in transact_list[-10:]: 153 await callback.message.answer( 154 "<pre>" 155 + "\n".join( 156 sorted( 157 ( 158 f"<b>{key}</b>:{' '*(10-len(key))}{val}" 159 for key, val in transact.items() 160 if key != "date" 161 ), 162 key=lambda x: len(x.split(":")[0]), 163 ) 164 ) 165 + "</pre>" 166 ) 167 168 if len(transact_list) > 10: 169 for tr in transact_list: 170 tr.pop("Дата") 171 172 tr_file = os.path.join(PATH, "tmp", f"{transact_type}_transactions.xlsx") 173 174 get_table_from_df(transact_list, tr_file) 175 176 await callback.message.answer_document( 177 FSInputFile(tr_file, "transactions.xlsx"), 178 caption="Полный список ваших транзакций", 179 ) 180 os.remove(tr_file) 181 except exc.DatabaseError: 182 await callback.answer(text=text.DB_ERROR, show_alert=True) 183 except Exception: 184 logger.exception("Ошибка при отображении истории транзакций") 185 await callback.answer( 186 text="К сожалению, сейчас мы не можем корректно отобразить вашу историю. " 187 "Попробуйте позже или обратитесь в техподдержку.", 188 show_alert=True, 189 )
Получает и отображает историю транзакций пользователя.
Arguments:
- callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
Отправляет последние 10 транзакций пользователя и предоставляет возможность скачать полный список транзакций в формате Excel.
192@router.callback_query(F.data == "change_rate") 193@async_speed_metric 194@bot_except 195async def post_rate_list(callback: CallbackQuery): 196 """Отправляет список доступных тарифов пользователю. 197 198 Args: 199 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 200 201 Отправляет сообщение с предложением выбрать тариф. 202 """ 203 user_data: UserData = await find_user(callback) 204 if not user_data: 205 return 206 207 await callback.message.answer( 208 "Выберите тариф", 209 reply_markup=kb.get_subscr_buttons(user_data, force_rates=True), 210 )
Отправляет список доступных тарифов пользователю.
Arguments:
- callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
Отправляет сообщение с предложением выбрать тариф.
213@router.callback_query(F.data.startswith("rate_info_")) 214@async_speed_metric 215@bot_except 216async def choose_rate(callback: CallbackQuery): 217 """Отправляет информацию о выбранном тарифе. 218 219 Args: 220 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 221 222 Отправляет сообщение с описанием выбранного тарифа и информацией о комиссии. 223 """ 224 user_data: UserData = await find_user(callback) 225 rate_id = float(callback.data.split("_")[-1]) 226 227 if not user_data: 228 return 229 elif user_data.stage > rate_id: 230 tax = user_data.stage * 10 231 else: 232 tax = 0 233 234 await callback.message.answer( 235 text.get_rate_descr(rate_id) 236 + f"\n\n‼️ <b>Комиссия при переходе на данный тариф {tax} руб</b>", 237 reply_markup=kb.get_rate_button(callback.data.split("_")[-1]), 238 )
Отправляет информацию о выбранном тарифе.
Arguments:
- callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
Отправляет сообщение с описанием выбранного тарифа и информацией о комиссии.
241@router.callback_query(F.data.startswith("accept_rate_")) 242@async_speed_metric 243@bot_except 244async def change_rate(callback: CallbackQuery, bot: Bot): 245 """Изменяет тариф пользователя. 246 247 Args: 248 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 249 bot (Bot): Экземпляр бота для выполнения действий. 250 251 Обрабатывает изменение тарифа пользователя, включая комиссию и условия. 252 """ 253 user_data: UserData = await find_user(callback) 254 rate_id = float(callback.data.split("_")[-1]) 255 try: 256 if not user_data: 257 return 258 elif user_data.stage == rate_id: 259 await callback.answer("У вас уже подключен этот тариф") 260 return 261 elif user_data.stage == 0.3: 262 await callback.answer( 263 "Дождитесь окончания пробного периода или пополните баланс", 264 show_alert=True, 265 ) 266 return 267 elif rate_id == 0.3: 268 if user_data.free: 269 await utils.update_rate_user( 270 callback.from_user.id, stage=rate_id, trial=True 271 ) 272 else: 273 await callback.answer( 274 "К сожалению вы исчерпали возможность подключения пробного периода" 275 ) 276 return 277 elif user_data.stage > rate_id: 278 await utils.update_rate_user( 279 callback.from_user.id, stage=rate_id, tax=user_data.stage * 10 280 ) 281 else: 282 await utils.update_rate_user(callback.from_user.id, stage=rate_id) 283 except exc.DatabaseError: 284 await callback.answer(text=text.DB_ERROR, show_alert=True) 285 else: 286 await callback.answer( 287 f"Изменение тарифа на {text.rates[rate_id]} успешно!", show_alert=True 288 ) 289 await post_user_data(callback) 290 finally: 291 await bot.delete_message(callback.from_user.id, callback.message.message_id)
Изменяет тариф пользователя.
Arguments:
- callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
- bot (Bot): Экземпляр бота для выполнения действий.
Обрабатывает изменение тарифа пользователя, включая комиссию и условия.
294@router.callback_query(F.data == "top_up_balance") 295@bot_except 296async def input_balance(callback: CallbackQuery, state: FSMContext): 297 """Запрашивает у пользователя сумму для пополнения баланса. 298 299 Args: 300 callback (CallbackQuery): Событие обратного вызова, инициировавшее команду. 301 state (FSMContext): Контекст состояния для управления состоянием пользователя. 302 303 Отправляет сообщение с предложением выбрать сумму или ввести вручную. 304 """ 305 await callback.message.answer( 306 "Выберите сумму или введите вручную. Для отмены используйте /cancel." 307 f"<b>\n(Актуальная комиссия провайдера {settings.transfer_fee*100-100} %</b>", 308 reply_markup=pay_keyboard, 309 ) 310 await state.set_state(Service.balance)
Запрашивает у пользователя сумму для пополнения баланса.
Arguments:
- callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
- state (FSMContext): Контекст состояния для управления состоянием пользователя.
Отправляет сообщение с предложением выбрать сумму или ввести вручную.
313@router.message(Service.balance, Command("cancel")) 314@bot_except 315async def cancel_input_balance(message: Message, state: FSMContext): 316 """Отменяет процесс ввода суммы для пополнения баланса. 317 318 Args: 319 message (Message): Сообщение, инициировавшее команду. 320 state (FSMContext): Контекст состояния для управления состоянием пользователя. 321 322 Отправляет сообщение об отмене операции. 323 """ 324 await state.clear() 325 await state.set_state() 326 await message.answer("Отменено")
Отменяет процесс ввода суммы для пополнения баланса.
Arguments:
- message (Message): Сообщение, инициировавшее команду.
- state (FSMContext): Контекст состояния для управления состоянием пользователя.
Отправляет сообщение об отмене операции.
329@router.callback_query(F.data.startswith("pay_sub_")) 330@router.message(Service.balance) 331@async_speed_metric 332@bot_except 333async def pay(trigger: Union[Message, CallbackQuery], bot: Bot, state: FSMContext): 334 """Обрабатывает процесс оплаты. 335 336 Args: 337 trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду. 338 bot (Bot): Экземпляр бота для выполнения действий. 339 state (FSMContext): Контекст состояния для управления состоянием пользователя. 340 341 Создает платеж и отправляет ссылку для пополнения баланса. Обрабатывает возможные ошибки. 342 """ 343 try: 344 client = Client(settings.YOO_TOKEN.get_secret_value()) 345 transaction_label = uuid4() 346 SUM = ( 347 float( 348 getattr( 349 trigger, "text", getattr(trigger, "data", "_None").split("_")[-1] 350 ) 351 ) 352 * settings.transfer_fee 353 ) 354 355 quickpay = Quickpay( 356 receiver=client.account_info().account, 357 quickpay_form="shop", 358 targets=f"Sponsor this project ({SUM} rub)", 359 paymentType="SB", 360 sum=SUM, 361 label=transaction_label, 362 ) 363 364 await utils.insert_transaction( 365 dict( 366 user_id=trigger.from_user.id, 367 date=datetime.now(timezone.utc), 368 amount=SUM, 369 label=transaction_label, 370 transaction_reference=quickpay.redirected_url, 371 ) 372 ) 373 374 await getattr(trigger, "message", trigger).answer( 375 "Многоразовая ссылка на пополнение счета." 376 "\n<b>(Действительна в течение 12 часов)</b>", 377 reply_markup=kb.get_pay_url(SUM, quickpay.redirected_url), 378 ) 379 await getattr(trigger, "message", trigger).answer( 380 "В случае если у вас по каким-либо причинам не прошла оплата, обратитесь в техподдержку", 381 reply_markup=kb.static_support_button, 382 ) 383 384 except KeyError: 385 await getattr(trigger, "message", trigger).answer( 386 "Истек срок давности сообщения" 387 ) 388 except exc.DatabaseError: 389 await getattr(trigger, "message", trigger).answer( 390 text=text.DB_ERROR, show_alert=True 391 ) 392 except YooMoneyError: 393 await getattr(trigger, "message", trigger).answer( 394 text=text.YOO_ERROR, show_alert=True 395 ) 396 except ValueError: 397 await getattr(trigger, "message", trigger).answer( 398 text="Сумма должна быть числом", show_alert=True 399 ) 400 else: 401 await state.clear() 402 await state.set_state() 403 finally: 404 await bot.delete_message( 405 trigger.from_user.id, getattr(trigger, "message", trigger).message_id 406 )
Обрабатывает процесс оплаты.
Arguments:
- trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду.
- bot (Bot): Экземпляр бота для выполнения действий.
- state (FSMContext): Контекст состояния для управления состоянием пользователя.
Создает платеж и отправляет ссылку для пополнения баланса. Обрабатывает возможные ошибки.