Edit on GitHub

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        )
logger = <RootLogger root (DEBUG)>
router = <Router '0x7f23dac71f70'>
pay_keyboard = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text='100', url=None, callback_data='pay_sub_100', web_app=None, login_url=None, switch_inline_query=None, switch_inline_query_current_chat=None, switch_inline_query_chosen_chat=None, copy_text=None, callback_game=None, pay=None), InlineKeyboardButton(text='250', url=None, callback_data='pay_sub_250', web_app=None, login_url=None, switch_inline_query=None, switch_inline_query_current_chat=None, switch_inline_query_chosen_chat=None, copy_text=None, callback_game=None, pay=None), InlineKeyboardButton(text='500', url=None, callback_data='pay_sub_500', web_app=None, login_url=None, switch_inline_query=None, switch_inline_query_current_chat=None, switch_inline_query_chosen_chat=None, copy_text=None, callback_game=None, pay=None), InlineKeyboardButton(text='1000', url=None, callback_data='pay_sub_1000', web_app=None, login_url=None, switch_inline_query=None, switch_inline_query_current_chat=None, switch_inline_query_chosen_chat=None, copy_text=None, callback_game=None, pay=None)]])
@router.message(Command('sub'))
@router.callback_query(F.data == 'user_payment')
@router.message(F.text == 'Подписка')
@async_speed_metric
@bot_except
async def subscribe_manager( trigger: Union[aiogram.types.message.Message, aiogram.types.callback_query.CallbackQuery], state: aiogram.fsm.context.FSMContext):
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): Контекст состояния для управления состоянием пользователя.

Если подписка активна, отправляет информацию о текущем статусе подписки, тарифе и балансе пользователя.

@router.message(Command('history'))
@router.callback_query(F.data == 'transact_history')
@bot_except
async def get_user_transact_choose( trigger: Union[aiogram.types.message.Message, aiogram.types.callback_query.CallbackQuery]):
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]): Сообщение или событие обратного вызова, инициировавшее команду.

Отправляет сообщение с предложением выбрать тип операции для отображения истории транзакций.

@router.callback_query(F.data.startswith('transact_history_'))
@async_speed_metric
@bot_except
async def get_user_transact(callback: aiogram.types.callback_query.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.

@router.callback_query(F.data == 'change_rate')
@async_speed_metric
@bot_except
async def post_rate_list(callback: aiogram.types.callback_query.CallbackQuery):
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): Событие обратного вызова, инициировавшее команду.

Отправляет сообщение с предложением выбрать тариф.

@router.callback_query(F.data.startswith('rate_info_'))
@async_speed_metric
@bot_except
async def choose_rate(callback: aiogram.types.callback_query.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): Событие обратного вызова, инициировавшее команду.

Отправляет сообщение с описанием выбранного тарифа и информацией о комиссии.

@router.callback_query(F.data.startswith('accept_rate_'))
@async_speed_metric
@bot_except
async def change_rate( callback: aiogram.types.callback_query.CallbackQuery, bot: aiogram.client.bot.Bot):
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): Экземпляр бота для выполнения действий.

Обрабатывает изменение тарифа пользователя, включая комиссию и условия.

@router.callback_query(F.data == 'top_up_balance')
@bot_except
async def input_balance( callback: aiogram.types.callback_query.CallbackQuery, state: aiogram.fsm.context.FSMContext):
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): Контекст состояния для управления состоянием пользователя.

Отправляет сообщение с предложением выбрать сумму или ввести вручную.

@router.message(Service.balance, Command('cancel'))
@bot_except
async def cancel_input_balance( message: aiogram.types.message.Message, state: aiogram.fsm.context.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): Контекст состояния для управления состоянием пользователя.

Отправляет сообщение об отмене операции.

@router.callback_query(F.data.startswith('pay_sub_'))
@router.message(Service.balance)
@async_speed_metric
@bot_except
async def pay( trigger: Union[aiogram.types.message.Message, aiogram.types.callback_query.CallbackQuery], bot: aiogram.client.bot.Bot, state: aiogram.fsm.context.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): Контекст состояния для управления состоянием пользователя.

Создает платеж и отправляет ссылку для пополнения баланса. Обрабатывает возможные ошибки.