diff --git a/app/handlers/stars_payments.py b/app/handlers/stars_payments.py index a55d4efe..030abc11 100644 --- a/app/handlers/stars_payments.py +++ b/app/handlers/stars_payments.py @@ -10,6 +10,7 @@ from app.database.crud.user import get_user_by_telegram_id from app.external.telegram_stars import TelegramStarsService from app.localization.loader import DEFAULT_LANGUAGE from app.localization.texts import get_texts +from app.middlewares.global_error import schedule_error_notification from app.services.payment_service import PaymentService @@ -113,6 +114,10 @@ async def _handle_wheel_spin_payment( except Exception as e: logger.error(f'Ошибка обработки wheel spin payment: {e}', exc_info=True) + if message.bot: + schedule_error_notification( + message.bot, e, f'Wheel spin payment error: user={user.id if user else "unknown"}' + ) await message.answer( '❌ Произошла ошибка при обработке спина. Обратитесь в поддержку.', ) @@ -139,6 +144,12 @@ async def _handle_trial_payment( parts = payload.split('_') if len(parts) < 2: logger.error(f'Невалидный trial payload: {payload}') + if message.bot: + schedule_error_notification( + message.bot, + ValueError(f'Invalid trial payload: {payload}'), + f'Trial payment: invalid payload format, user={user.id if user else "unknown"}', + ) await message.answer( '❌ Ошибка: неверный формат платежа. Обратитесь в поддержку.', ) @@ -148,6 +159,12 @@ async def _handle_trial_payment( subscription_id = int(parts[1]) except ValueError: logger.error(f'Невалидный subscription_id в trial payload: {payload}') + if message.bot: + schedule_error_notification( + message.bot, + ValueError(f'Invalid subscription_id in payload: {payload}'), + f'Trial payment: invalid subscription_id, user={user.id if user else "unknown"}', + ) await message.answer( '❌ Ошибка: неверный ID подписки. Обратитесь в поддержку.', ) @@ -178,6 +195,12 @@ async def _handle_trial_payment( if not subscription: logger.error(f'Не удалось активировать триальную подписку {subscription_id} для пользователя {user.id}') + if message.bot: + schedule_error_notification( + message.bot, + RuntimeError(f'Failed to activate trial subscription {subscription_id}'), + f'Trial activation failed: subscription_id={subscription_id}, user={user.id}', + ) # Возвращаем деньги на баланс from app.database.crud.user import add_user_balance @@ -200,6 +223,12 @@ async def _handle_trial_payment( except Exception as rw_error: logger.error(f'Ошибка создания пользователя RemnaWave для триала: {rw_error}') # Не откатываем подписку, просто логируем - RemnaWave может быть временно недоступен + if message.bot: + schedule_error_notification( + message.bot, + rw_error, + f'RemnaWave trial user creation failed: subscription={subscription_id}, user={user.id}', + ) await db.commit() await db.refresh(user) @@ -234,6 +263,10 @@ async def _handle_trial_payment( except Exception as e: logger.error(f'Ошибка обработки trial payment: {e}', exc_info=True) + if message.bot: + schedule_error_notification( + message.bot, e, f'Trial payment error: user={user.id if user else "unknown"}' + ) await message.answer( '❌ Произошла ошибка при активации пробной подписки. Обратитесь в поддержку.', ) @@ -279,6 +312,10 @@ async def handle_pre_checkout_query(query: types.PreCheckoutQuery): texts = get_texts(user.language or DEFAULT_LANGUAGE) except Exception as db_error: logger.error(f'Ошибка подключения к БД в pre_checkout_query: {db_error}') + if query.bot: + schedule_error_notification( + query.bot, db_error, f'Pre-checkout DB error: user={query.from_user.id}' + ) await query.answer( ok=False, error_message=texts.t( @@ -293,6 +330,10 @@ async def handle_pre_checkout_query(query: types.PreCheckoutQuery): except Exception as e: logger.error(f'Ошибка в pre_checkout_query: {e}', exc_info=True) + if query.bot: + schedule_error_notification( + query.bot, e, f'Pre-checkout query error: user={query.from_user.id}' + ) await query.answer( ok=False, error_message=texts.t( @@ -321,6 +362,12 @@ async def handle_successful_payment(message: types.Message, db: AsyncSession, st if not user: logger.error(f'Пользователь {user_id} не найден при обработке Stars платежа') + if message.bot: + schedule_error_notification( + message.bot, + RuntimeError(f'User {user_id} not found for Stars payment'), + f'Stars payment: user not found, telegram_id={user_id}', + ) await message.answer( texts.t( 'STARS_PAYMENT_USER_NOT_FOUND', @@ -428,6 +475,12 @@ async def handle_successful_payment(message: types.Message, db: AsyncSession, st ) else: logger.error(f'Ошибка обработки Stars платежа для пользователя {user.id}') + if message.bot: + schedule_error_notification( + message.bot, + RuntimeError(f'Stars payment processing failed for user {user.id}'), + f'Stars payment enrollment error: user={user.id}, stars={payment.total_amount}', + ) await message.answer( texts.t( 'STARS_PAYMENT_ENROLLMENT_ERROR', @@ -438,6 +491,8 @@ async def handle_successful_payment(message: types.Message, db: AsyncSession, st except Exception as e: logger.error(f'Ошибка в successful_payment: {e}', exc_info=True) + if message.bot: + schedule_error_notification(message.bot, e, f'Stars successful_payment error: user={user_id}') await message.answer( texts.t( 'STARS_PAYMENT_PROCESSING_ERROR', diff --git a/app/handlers/webhooks.py b/app/handlers/webhooks.py index 580e59da..1f428c2b 100644 --- a/app/handlers/webhooks.py +++ b/app/handlers/webhooks.py @@ -1,6 +1,6 @@ import logging -from aiogram import types +from aiogram import Bot, types from aiohttp import web from app.config import settings @@ -9,10 +9,20 @@ from app.database.crud.user import add_user_balance, get_user_by_id from app.database.database import AsyncSessionLocal from app.database.models import PaymentMethod, TransactionType from app.external.tribute import TributeService +from app.middlewares.global_error import schedule_error_notification logger = logging.getLogger(__name__) +# Глобальная ссылка на бота для отправки уведомлений +_bot_instance: Bot | None = None + + +def set_webhook_bot(bot: Bot) -> None: + """Устанавливает экземпляр бота для отправки уведомлений об ошибках в webhook.""" + global _bot_instance + _bot_instance = bot + async def tribute_webhook(request): try: @@ -30,6 +40,12 @@ async def tribute_webhook(request): if not processed_data: logger.error('Ошибка обработки Tribute webhook') + if _bot_instance: + schedule_error_notification( + _bot_instance, + ValueError('Invalid webhook data'), + 'Tribute webhook: processed_data is None', + ) return web.Response(status=400, text='Invalid webhook data') async with AsyncSessionLocal() as db: @@ -70,11 +86,15 @@ async def tribute_webhook(request): except Exception as e: logger.error(f'Ошибка обработки Tribute webhook: {e}') + if _bot_instance: + schedule_error_notification(_bot_instance, e, 'Tribute webhook DB error') await db.rollback() return web.Response(status=500, text='Internal error') except Exception as e: logger.error(f'Ошибка в Tribute webhook: {e}') + if _bot_instance: + schedule_error_notification(_bot_instance, e, 'Tribute webhook general error') return web.Response(status=500, text='Internal error') @@ -126,10 +146,14 @@ async def handle_successful_payment(message: types.Message): except Exception as e: logger.error(f'Ошибка обработки Stars платежа: {e}') + if message.bot: + schedule_error_notification(message.bot, e, 'Stars payment DB error') await db.rollback() except Exception as e: logger.error(f'Ошибка в обработчике Stars платежа: {e}') + if message.bot: + schedule_error_notification(message.bot, e, 'Stars payment general error') async def handle_pre_checkout_query(pre_checkout_query: types.PreCheckoutQuery): @@ -139,4 +163,6 @@ async def handle_pre_checkout_query(pre_checkout_query: types.PreCheckoutQuery): except Exception as e: logger.error(f'Ошибка в pre-checkout query: {e}') + if pre_checkout_query.bot: + schedule_error_notification(pre_checkout_query.bot, e, 'Pre-checkout query error') await pre_checkout_query.answer(ok=False, error_message='Ошибка обработки платежа')