From 840091cb051a3ba86637ed1d15f7169c3bbac07e Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 21 Nov 2025 04:02:28 +0300 Subject: [PATCH] Revert "Clean up Telegram Stars payment flow" --- app/handlers/balance/stars.py | 66 ++++++++++------------------------ app/handlers/stars_payments.py | 46 ++++-------------------- app/services/payment/stars.py | 30 ++++++++++++++++ 3 files changed, 56 insertions(+), 86 deletions(-) diff --git a/app/handlers/balance/stars.py b/app/handlers/balance/stars.py index a8cac0f8..0dcf0031 100644 --- a/app/handlers/balance/stars.py +++ b/app/handlers/balance/stars.py @@ -1,10 +1,11 @@ import logging from aiogram import types from aiogram.fsm.context import FSMContext +from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database.models import User -from app.keyboards.inline import get_back_keyboard +from app.keyboards.inline import get_back_keyboard, get_payment_methods_keyboard from app.localization.texts import get_texts from app.services.payment_service import PaymentService from app.states import BalanceStates @@ -21,11 +22,11 @@ async def start_stars_payment( state: FSMContext ): texts = get_texts(db_user.language) - + if not settings.TELEGRAM_STARS_ENABLED: await callback.answer("❌ Пополнение через Stars временно недоступно", show_alert=True) return - + # Формируем текст сообщения в зависимости от настройки if settings.YOOKASSA_QUICK_AMOUNT_SELECTION_ENABLED and not settings.DISABLE_TOPUP_BUTTONS: message_text = ( @@ -34,10 +35,10 @@ async def start_stars_payment( ) else: message_text = texts.TOP_UP_AMOUNT - + # Создаем клавиатуру keyboard = get_back_keyboard(db_user.language) - + # Если включен быстрый выбор суммы и не отключены кнопки, добавляем кнопки if settings.YOOKASSA_QUICK_AMOUNT_SELECTION_ENABLED and not settings.DISABLE_TOPUP_BUTTONS: from .main import get_quick_amount_buttons @@ -45,17 +46,12 @@ async def start_stars_payment( if quick_amount_buttons: # Вставляем кнопки быстрого выбора перед кнопкой "Назад" keyboard.inline_keyboard = quick_amount_buttons + keyboard.inline_keyboard - + await callback.message.edit_text( message_text, reply_markup=keyboard ) - - await state.update_data( - stars_prompt_message_id=callback.message.message_id, - stars_prompt_chat_id=callback.message.chat.id, - ) - + await state.set_state(BalanceStates.waiting_for_amount) await state.update_data(payment_method="stars") await callback.answer() @@ -69,48 +65,29 @@ async def process_stars_payment_amount( state: FSMContext ): texts = get_texts(db_user.language) - + if not settings.TELEGRAM_STARS_ENABLED: await message.answer("⚠️ Оплата Stars временно недоступна") return - + try: amount_rubles = amount_kopeks / 100 stars_amount = TelegramStarsService.calculate_stars_from_rubles(amount_rubles) - stars_rate = settings.get_stars_rate() - + stars_rate = settings.get_stars_rate() + payment_service = PaymentService(message.bot) invoice_link = await payment_service.create_stars_invoice( amount_kopeks=amount_kopeks, description=f"Пополнение баланса на {texts.format_price(amount_kopeks)}", payload=f"balance_{db_user.id}_{amount_kopeks}" ) - + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="⭐ Оплатить", url=invoice_link)], [types.InlineKeyboardButton(text=texts.BACK, callback_data="balance_topup")] ]) - - state_data = await state.get_data() - - prompt_message_id = state_data.get("stars_prompt_message_id") - prompt_chat_id = state_data.get("stars_prompt_chat_id", message.chat.id) - - try: - await message.delete() - except Exception as delete_error: # pragma: no cover - зависит от прав бота - logger.warning("Не удалось удалить сообщение с суммой Stars: %s", delete_error) - - if prompt_message_id: - try: - await message.bot.delete_message(prompt_chat_id, prompt_message_id) - except Exception as delete_error: # pragma: no cover - диагностический лог - logger.warning( - "Не удалось удалить сообщение с запросом суммы Stars: %s", - delete_error, - ) - - invoice_message = await message.answer( + + await message.answer( f"⭐ Оплата через Telegram Stars\n\n" f"💰 Сумма: {texts.format_price(amount_kopeks)}\n" f"⭐ К оплате: {stars_amount} звезд\n" @@ -119,14 +96,9 @@ async def process_stars_payment_amount( reply_markup=keyboard, parse_mode="HTML" ) - - await state.update_data( - stars_invoice_message_id=invoice_message.message_id, - stars_invoice_chat_id=invoice_message.chat.id, - ) - - await state.set_state(None) - + + await state.clear() + except Exception as e: logger.error(f"Ошибка создания Stars invoice: {e}") - await message.answer("⚠️ Ошибка создания платежа") + await message.answer("⚠️ Ошибка создания платежа") \ No newline at end of file diff --git a/app/handlers/stars_payments.py b/app/handlers/stars_payments.py index 48356450..4b49f51f 100644 --- a/app/handlers/stars_payments.py +++ b/app/handlers/stars_payments.py @@ -1,10 +1,10 @@ import logging from decimal import Decimal, ROUND_HALF_UP from aiogram import Dispatcher, types, F -from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings +from app.database.models import User from app.services.payment_service import PaymentService from app.external.telegram_stars import TelegramStarsService from app.database.crud.user import get_user_by_telegram_id @@ -18,9 +18,7 @@ async def handle_pre_checkout_query(query: types.PreCheckoutQuery): texts = get_texts(DEFAULT_LANGUAGE) try: - logger.info( - f"📋 Pre-checkout query от {query.from_user.id}: {query.total_amount} XTR, payload: {query.invoice_payload}" - ) + logger.info(f"📋 Pre-checkout query от {query.from_user.id}: {query.total_amount} XTR, payload: {query.invoice_payload}") allowed_prefixes = ("balance_", "admin_stars_test_", "simple_sub_") @@ -37,7 +35,6 @@ async def handle_pre_checkout_query(query: types.PreCheckoutQuery): try: from app.database.database import get_db - async for db in get_db(): user = await get_user_by_telegram_id(db, query.from_user.id) if not user: @@ -80,7 +77,6 @@ async def handle_pre_checkout_query(query: types.PreCheckoutQuery): async def handle_successful_payment( message: types.Message, db: AsyncSession, - state: FSMContext, **kwargs ): texts = get_texts(DEFAULT_LANGUAGE) @@ -110,27 +106,6 @@ async def handle_successful_payment( return payment_service = PaymentService(message.bot) - - state_data = await state.get_data() - prompt_message_id = state_data.get("stars_prompt_message_id") - prompt_chat_id = state_data.get("stars_prompt_chat_id", message.chat.id) - invoice_message_id = state_data.get("stars_invoice_message_id") - invoice_chat_id = state_data.get("stars_invoice_chat_id", message.chat.id) - - for chat_id, message_id, label in [ - (prompt_chat_id, prompt_message_id, "запрос суммы"), - (invoice_chat_id, invoice_message_id, "инвойс Stars"), - ]: - if message_id: - try: - await message.bot.delete_message(chat_id, message_id) - except Exception as delete_error: # pragma: no cover - зависит от прав бота - logger.warning( - "Не удалось удалить сообщение %s после оплаты Stars: %s", - label, - delete_error, - ) - success = await payment_service.process_stars_payment( db=db, user_id=user.id, @@ -138,14 +113,7 @@ async def handle_successful_payment( payload=payment.invoice_payload, telegram_payment_charge_id=payment.telegram_payment_charge_id ) - - await state.update_data( - stars_prompt_message_id=None, - stars_prompt_chat_id=None, - stars_invoice_message_id=None, - stars_invoice_chat_id=None, - ) - + if success: rubles_amount = TelegramStarsService.calculate_rubles_from_stars(payment.total_amount) amount_kopeks = int((rubles_amount * Decimal(100)).to_integral_value(rounding=ROUND_HALF_UP)) @@ -204,15 +172,15 @@ async def handle_successful_payment( def register_stars_handlers(dp: Dispatcher): - + dp.pre_checkout_query.register( handle_pre_checkout_query, - F.currency == "XTR" + F.currency == "XTR" ) - + dp.message.register( handle_successful_payment, F.successful_payment ) - + logger.info("🌟 Зарегистрированы обработчики Telegram Stars платежей") diff --git a/app/services/payment/stars.py b/app/services/payment/stars.py index 3f1022d7..b7fd5942 100644 --- a/app/services/payment/stars.py +++ b/app/services/payment/stars.py @@ -515,6 +515,36 @@ class TelegramStarsMixin: exc_info=True, ) + if getattr(self, "bot", None): + try: + keyboard = await self.build_topup_success_keyboard(user) + + charge_id_short = (telegram_payment_charge_id or getattr(transaction, "external_id", ""))[:8] + + await self.bot.send_message( + user.telegram_id, + ( + "✅ Пополнение успешно!\n\n" + f"⭐ Звезд: {stars_amount}\n" + f"💰 Сумма: {settings.format_price(amount_kopeks)}\n" + "🦊 Способ: Telegram Stars\n" + f"🆔 Транзакция: {charge_id_short}...\n\n" + "Баланс пополнен автоматически!" + ), + parse_mode="HTML", + reply_markup=keyboard, + ) + logger.info( + "✅ Отправлено уведомление пользователю %s о пополнении на %s", + user.telegram_id, + settings.format_price(amount_kopeks), + ) + except Exception as error: # pragma: no cover - диагностический лог + logger.error( + "Ошибка отправки уведомления о пополнении Stars: %s", + error, + ) + # Проверяем наличие сохраненной корзины для возврата к оформлению подписки try: from aiogram import types