From 43de4cc08be6d4daf814ff81e98dcddb325c4b3d Mon Sep 17 00:00:00 2001 From: machka-pasla Date: Fri, 5 Sep 2025 15:46:50 +0300 Subject: [PATCH] Add cryptocurrency and stars payment handlers to subscription module - Implemented new callback handlers for cryptocurrency and stars payments, enhancing payment options for users. - Integrated error handling and user feedback mechanisms for payment processes, improving overall user experience. - Updated the pre-checkout and successful payment handling to accommodate new payment methods, ensuring seamless transaction processing. - Organized imports for better modularity and clarity in the payments module. --- bot/handlers/user/subscription/payments.py | 177 +++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/bot/handlers/user/subscription/payments.py b/bot/handlers/user/subscription/payments.py index 482ce22..3222a35 100644 --- a/bot/handlers/user/subscription/payments.py +++ b/bot/handlers/user/subscription/payments.py @@ -6,6 +6,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from config.settings import Settings from bot.keyboards.inline.user_keyboards import get_payment_method_keyboard, get_payment_url_keyboard from bot.services.yookassa_service import YooKassaService +from bot.services.crypto_pay_service import CryptoPayService +from bot.services.stars_service import StarsService from bot.middlewares.i18n import JsonI18n from db.dal import payment_dal, user_billing_dal @@ -259,3 +261,178 @@ async def pay_yk_callback_handler(callback: types.CallbackQuery, settings: Setti pass +@router.callback_query(F.data.startswith("pay_crypto:")) +async def pay_crypto_callback_handler( + callback: types.CallbackQuery, + settings: Settings, + i18n_data: dict, + session: AsyncSession, + cryptopay_service: CryptoPayService, +): + current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) + i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") + get_text = (lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) if i18n else key) + + if not i18n or not callback.message: + try: + await callback.answer(get_text("error_occurred_try_again"), show_alert=True) + except Exception: + pass + return + + if not cryptopay_service or not getattr(cryptopay_service, "configured", False): + try: + await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True) + except Exception: + pass + return + + try: + _, data_payload = callback.data.split(":", 1) + months_str, price_str = data_payload.split(":") + months = int(months_str) + price_amount = float(price_str) + except (ValueError, IndexError): + try: + await callback.answer(get_text("error_try_again"), show_alert=True) + except Exception: + pass + return + + user_id = callback.from_user.id + payment_description = get_text("payment_description_subscription", months=months) + + invoice_url = await cryptopay_service.create_invoice( + session=session, + user_id=user_id, + months=months, + amount=price_amount, + description=payment_description, + ) + + if invoice_url: + try: + await callback.message.edit_text( + get_text(key="payment_link_message", months=months), + reply_markup=get_payment_url_keyboard(invoice_url, current_lang, i18n), + disable_web_page_preview=False, + ) + except Exception: + try: + await callback.message.answer( + get_text(key="payment_link_message", months=months), + reply_markup=get_payment_url_keyboard(invoice_url, current_lang, i18n), + disable_web_page_preview=False, + ) + except Exception: + pass + try: + await callback.answer() + except Exception: + pass + return + + try: + await callback.answer(get_text("error_payment_gateway"), show_alert=True) + except Exception: + pass + + +@router.callback_query(F.data.startswith("pay_stars:")) +async def pay_stars_callback_handler( + callback: types.CallbackQuery, + settings: Settings, + i18n_data: dict, + session: AsyncSession, + stars_service: StarsService, +): + current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) + i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") + get_text = (lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) if i18n else key) + + if not i18n or not callback.message: + try: + await callback.answer(get_text("error_occurred_try_again"), show_alert=True) + except Exception: + pass + return + + if not settings.STARS_ENABLED: + try: + await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True) + except Exception: + pass + return + + try: + _, data_payload = callback.data.split(":", 1) + months_str, stars_price_str = data_payload.split(":") + months = int(months_str) + stars_price = int(stars_price_str) + except (ValueError, IndexError): + try: + await callback.answer(get_text("error_try_again"), show_alert=True) + except Exception: + pass + return + + user_id = callback.from_user.id + payment_description = get_text("payment_description_subscription", months=months) + + payment_db_id = await stars_service.create_invoice( + session=session, + user_id=user_id, + months=months, + stars_price=stars_price, + description=payment_description, + ) + + if payment_db_id: + try: + await callback.answer() + except Exception: + pass + return + + try: + await callback.answer(get_text("error_payment_gateway"), show_alert=True) + except Exception: + pass + + +@router.pre_checkout_query() +async def handle_pre_checkout_query(query: types.PreCheckoutQuery): + try: + await query.answer(ok=True) + except Exception: + # Nothing else to do here; Telegram will show an error if not answered + pass + + +@router.message(F.successful_payment) +async def handle_successful_stars_payment( + message: types.Message, + settings: Settings, + i18n_data: dict, + session: AsyncSession, + stars_service: StarsService, +): + payload = (message.successful_payment.invoice_payload + if message and message.successful_payment else "") + try: + payment_db_id_str, months_str = (payload or "").split(":", 1) + payment_db_id = int(payment_db_id_str) + months = int(months_str) + except Exception: + return + + stars_amount = int(message.successful_payment.total_amount) if message.successful_payment else 0 + await stars_service.process_successful_payment( + session=session, + message=message, + payment_db_id=payment_db_id, + months=months, + stars_amount=stars_amount, + i18n_data=i18n_data, + ) +