diff --git a/app/webserver/payments.py b/app/webserver/payments.py index 42f9f315..49bdea4a 100644 --- a/app/webserver/payments.py +++ b/app/webserver/payments.py @@ -18,6 +18,7 @@ from app.external.heleket_webhook import HeleketWebhookHandler from app.external.pal24_client import Pal24APIError from app.external.tribute import TributeService as TributeAPI from app.external.wata_webhook import WataWebhookHandler +from app.middlewares.global_error import schedule_error_notification from app.services.pal24_service import Pal24Service from app.services.payment_service import PaymentService from app.services.tribute_service import TributeService @@ -217,14 +218,24 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute status_code=status.HTTP_400_BAD_REQUEST, ) - result = await tribute_service.process_webhook(payload) - if result: - return JSONResponse({'status': 'ok', 'result': result}) + try: + result = await tribute_service.process_webhook(payload) + if result: + return JSONResponse({'status': 'ok', 'result': result}) - return JSONResponse( - {'status': 'error', 'reason': 'processing_failed'}, - status_code=status.HTTP_400_BAD_REQUEST, - ) + error = ValueError('Tribute webhook processing returned empty result') + schedule_error_notification(bot, error, 'Tribute webhook processing failed') + return JSONResponse( + {'status': 'error', 'reason': 'processing_failed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) + except Exception as e: + logger.exception('Tribute webhook processing error: %s', e) + schedule_error_notification(bot, e, 'Tribute webhook processing exception') + return JSONResponse( + {'status': 'error', 'reason': 'processing_failed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) routes_registered = True @@ -256,18 +267,28 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute status_code=status.HTTP_400_BAD_REQUEST, ) - success = await _process_payment_service_callback( - payment_service, - payload, - 'process_mulenpay_callback', - ) - if success: - return JSONResponse({'status': 'ok'}) + try: + success = await _process_payment_service_callback( + payment_service, + payload, + 'process_mulenpay_callback', + ) + if success: + return JSONResponse({'status': 'ok'}) - return JSONResponse( - {'status': 'error', 'reason': 'processing_failed'}, - status_code=status.HTTP_400_BAD_REQUEST, - ) + error = ValueError('MulenPay callback processing returned False') + schedule_error_notification(bot, error, f'MulenPay webhook processing failed: {payload}') + return JSONResponse( + {'status': 'error', 'reason': 'processing_failed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) + except Exception as e: + logger.exception('MulenPay webhook processing error: %s', e) + schedule_error_notification(bot, e, 'MulenPay webhook processing exception') + return JSONResponse( + {'status': 'error', 'reason': 'processing_failed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) routes_registered = True @@ -311,18 +332,28 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute status_code=status.HTTP_401_UNAUTHORIZED, ) - success = await _process_payment_service_callback( - payment_service, - payload, - 'process_cryptobot_webhook', - ) - if success: - return JSONResponse({'status': 'ok'}) + try: + success = await _process_payment_service_callback( + payment_service, + payload, + 'process_cryptobot_webhook', + ) + if success: + return JSONResponse({'status': 'ok'}) - return JSONResponse( - {'status': 'error', 'reason': 'processing_failed'}, - status_code=status.HTTP_400_BAD_REQUEST, - ) + error = ValueError('CryptoBot webhook processing returned False') + schedule_error_notification(bot, error, f'CryptoBot webhook processing failed: invoice_id={payload.get("payload", {}).get("invoice_id")}') + return JSONResponse( + {'status': 'error', 'reason': 'processing_failed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) + except Exception as e: + logger.exception('CryptoBot webhook processing error: %s', e) + schedule_error_notification(bot, e, 'CryptoBot webhook processing exception') + return JSONResponse( + {'status': 'error', 'reason': 'processing_failed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) routes_registered = True @@ -416,18 +447,29 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute }: return JSONResponse({'status': 'ok', 'ignored': event_type}) - success = await _process_payment_service_callback( - payment_service, - webhook_data, - 'process_yookassa_webhook', - ) - if success: - return JSONResponse({'status': 'ok'}) + try: + success = await _process_payment_service_callback( + payment_service, + webhook_data, + 'process_yookassa_webhook', + ) + if success: + return JSONResponse({'status': 'ok'}) - return JSONResponse( - {'status': 'error', 'reason': 'processing_failed'}, - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) + error = ValueError('YooKassa webhook processing returned False') + payment_id = webhook_data.get('object', {}).get('id', 'unknown') + schedule_error_notification(bot, error, f'YooKassa webhook processing failed: payment_id={payment_id}') + return JSONResponse( + {'status': 'error', 'reason': 'processing_failed'}, + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + except Exception as e: + logger.exception('YooKassa webhook processing error: %s', e) + schedule_error_notification(bot, e, 'YooKassa webhook processing exception') + return JSONResponse( + {'status': 'error', 'reason': 'processing_failed'}, + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) routes_registered = True @@ -478,18 +520,29 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute status_code=status.HTTP_400_BAD_REQUEST, ) - success = await _process_payment_service_callback( - payment_service, - payload, - 'process_wata_webhook', - ) - if success: - return JSONResponse({'status': 'ok'}) + try: + success = await _process_payment_service_callback( + payment_service, + payload, + 'process_wata_webhook', + ) + if success: + return JSONResponse({'status': 'ok'}) - return JSONResponse( - {'status': 'error', 'reason': 'not_processed'}, - status_code=status.HTTP_400_BAD_REQUEST, - ) + error = ValueError('Wata webhook processing returned False') + order_id = payload.get('order_id', 'unknown') + schedule_error_notification(bot, error, f'Wata webhook processing failed: order_id={order_id}') + return JSONResponse( + {'status': 'error', 'reason': 'not_processed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) + except Exception as e: + logger.exception('Wata webhook processing error: %s', e) + schedule_error_notification(bot, e, 'Wata webhook processing exception') + return JSONResponse( + {'status': 'error', 'reason': 'not_processed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) routes_registered = True @@ -533,18 +586,29 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute status_code=status.HTTP_401_UNAUTHORIZED, ) - success = await _process_payment_service_callback( - payment_service, - payload, - 'process_heleket_webhook', - ) - if success: - return JSONResponse({'status': 'ok'}) + try: + success = await _process_payment_service_callback( + payment_service, + payload, + 'process_heleket_webhook', + ) + if success: + return JSONResponse({'status': 'ok'}) - return JSONResponse( - {'status': 'error', 'reason': 'not_processed'}, - status_code=status.HTTP_400_BAD_REQUEST, - ) + error = ValueError('Heleket webhook processing returned False') + uuid_val = payload.get('uuid', 'unknown') + schedule_error_notification(bot, error, f'Heleket webhook processing failed: uuid={uuid_val}') + return JSONResponse( + {'status': 'error', 'reason': 'not_processed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) + except Exception as e: + logger.exception('Heleket webhook processing error: %s', e) + schedule_error_notification(bot, e, 'Heleket webhook processing exception') + return JSONResponse( + {'status': 'error', 'reason': 'not_processed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) routes_registered = True @@ -595,18 +659,29 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute status_code=status.HTTP_400_BAD_REQUEST, ) - success = await _process_payment_service_callback( - payment_service, - parsed_payload, - 'process_pal24_callback', - ) - if success: - return JSONResponse({'status': 'ok'}) + try: + success = await _process_payment_service_callback( + payment_service, + parsed_payload, + 'process_pal24_callback', + ) + if success: + return JSONResponse({'status': 'ok'}) - return JSONResponse( - {'status': 'error', 'reason': 'not_processed'}, - status_code=status.HTTP_400_BAD_REQUEST, - ) + error = ValueError('Pal24 callback processing returned False') + bill_id = parsed_payload.get('bill_id', 'unknown') + schedule_error_notification(bot, error, f'Pal24 webhook processing failed: bill_id={bill_id}') + return JSONResponse( + {'status': 'error', 'reason': 'not_processed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) + except Exception as e: + logger.exception('Pal24 webhook processing error: %s', e) + schedule_error_notification(bot, e, 'Pal24 webhook processing exception') + return JSONResponse( + {'status': 'error', 'reason': 'not_processed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) routes_registered = True @@ -640,18 +715,29 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute status_code=status.HTTP_400_BAD_REQUEST, ) - success = await _process_payment_service_callback( - payment_service, - payload, - 'process_platega_webhook', - ) - if success: - return JSONResponse({'status': 'ok'}) + try: + success = await _process_payment_service_callback( + payment_service, + payload, + 'process_platega_webhook', + ) + if success: + return JSONResponse({'status': 'ok'}) - return JSONResponse( - {'status': 'error', 'reason': 'not_processed'}, - status_code=status.HTTP_400_BAD_REQUEST, - ) + error = ValueError('Platega webhook processing returned False') + transaction_id = payload.get('transactionId', 'unknown') + schedule_error_notification(bot, error, f'Platega webhook processing failed: transactionId={transaction_id}') + return JSONResponse( + {'status': 'error', 'reason': 'not_processed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) + except Exception as e: + logger.exception('Platega webhook processing error: %s', e) + schedule_error_notification(bot, e, 'Platega webhook processing exception') + return JSONResponse( + {'status': 'error', 'reason': 'not_processed'}, + status_code=status.HTTP_400_BAD_REQUEST, + ) routes_registered = True @@ -716,6 +802,7 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute return JSONResponse({'code': 0}) except Exception as e: logger.exception('CloudPayments check webhook error: %s', e) + schedule_error_notification(bot, e, 'CloudPayments check webhook error') # В случае ошибки всё равно разрешаем платёж return JSONResponse({'code': 0}) @@ -740,6 +827,7 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute webhook_data = cloudpayments_service.parse_webhook_data(dict(form_data)) except Exception as error: logger.error('CloudPayments pay webhook parse error: %s', error) + schedule_error_notification(bot, error, 'CloudPayments pay webhook parse error') return JSONResponse({'code': 0}) # Возвращаем 0, чтобы не было повторов # Обрабатываем платёж @@ -772,6 +860,7 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute webhook_data = cloudpayments_service.parse_webhook_data(dict(form_data)) except Exception as error: logger.error('CloudPayments fail webhook parse error: %s', error) + schedule_error_notification(bot, error, 'CloudPayments fail webhook parse error') return JSONResponse({'code': 0}) # Обрабатываем неуспешный платёж @@ -813,6 +902,7 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute logger.info('CloudPayments webhook parsed data: %s', webhook_data) except Exception as error: logger.error('CloudPayments webhook parse error: %s', error) + schedule_error_notification(bot, error, 'CloudPayments webhook parse error') # Может быть это Check уведомление - просто разрешаем return JSONResponse({'code': 0}) @@ -859,6 +949,7 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute return JSONResponse({'code': 0}) except Exception as e: logger.exception('CloudPayments universal webhook error: %s', e) + schedule_error_notification(bot, e, 'CloudPayments universal webhook error') return JSONResponse({'code': 0}) routes_registered = True @@ -902,8 +993,9 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute # Получаем данные формы try: form_data = await request.form() - except Exception: + except Exception as form_error: logger.error('Freekassa webhook: не удалось прочитать данные формы') + schedule_error_notification(bot, form_error, 'Freekassa webhook: form read error') return Response('Error reading form data', status_code=status.HTTP_400_BAD_REQUEST) # Извлекаем параметры @@ -945,17 +1037,22 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute cur_id=cur_id_int, client_ip=client_ip, ) + if success: + return Response('YES', status_code=status.HTTP_200_OK) + + error = ValueError('Freekassa webhook processing returned False') + schedule_error_notification(bot, error, f'Freekassa webhook processing failed: order_id={order_id}, intid={intid}') + return Response('Error', status_code=status.HTTP_400_BAD_REQUEST) + except Exception as e: + logger.exception('Freekassa webhook processing error: %s', e) + schedule_error_notification(bot, e, f'Freekassa webhook processing exception: order_id={order_id}') + return Response('Error', status_code=status.HTTP_400_BAD_REQUEST) finally: try: await db_generator.__anext__() except StopAsyncIteration: pass - if success: - return Response('YES', status_code=status.HTTP_200_OK) - - return Response('Error', status_code=status.HTTP_400_BAD_REQUEST) - routes_registered = True # KassaAI webhook @@ -976,8 +1073,9 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute # Получаем данные формы try: form_data = await request.form() - except Exception: + except Exception as form_error: logger.error('KassaAI webhook: не удалось прочитать данные формы') + schedule_error_notification(bot, form_error, 'KassaAI webhook: form read error') return Response('Error reading form data', status_code=status.HTTP_400_BAD_REQUEST) # Извлекаем параметры (те же что и у Freekassa) @@ -998,6 +1096,7 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute cur_id_int = int(cur_id) if cur_id else None except (ValueError, TypeError) as e: logger.error('KassaAI webhook: некорректные параметры - %s', e) + schedule_error_notification(bot, e, 'KassaAI webhook: invalid parameters') return Response('Invalid parameters', status_code=status.HTTP_400_BAD_REQUEST) # Обрабатываем webhook @@ -1017,17 +1116,22 @@ def create_payment_router(bot: Bot, payment_service: PaymentService) -> APIRoute intid=intid, cur_id=cur_id_int, ) + if success: + return Response('YES', status_code=status.HTTP_200_OK) + + error = ValueError('KassaAI webhook processing returned False') + schedule_error_notification(bot, error, f'KassaAI webhook processing failed: order_id={order_id}, intid={intid}') + return Response('Error', status_code=status.HTTP_400_BAD_REQUEST) + except Exception as e: + logger.exception('KassaAI webhook processing error: %s', e) + schedule_error_notification(bot, e, f'KassaAI webhook processing exception: order_id={order_id}') + return Response('Error', status_code=status.HTTP_400_BAD_REQUEST) finally: try: await db_generator.__anext__() except StopAsyncIteration: pass - if success: - return Response('YES', status_code=status.HTTP_200_OK) - - return Response('Error', status_code=status.HTTP_400_BAD_REQUEST) - routes_registered = True if routes_registered: diff --git a/app/webserver/telegram.py b/app/webserver/telegram.py index 6b0f4795..1b2e6c59 100644 --- a/app/webserver/telegram.py +++ b/app/webserver/telegram.py @@ -10,6 +10,7 @@ from fastapi import APIRouter, HTTPException, Request, status from fastapi.responses import JSONResponse from app.config import settings +from app.middlewares.global_error import schedule_error_notification logger = logging.getLogger(__name__) @@ -167,6 +168,10 @@ class TelegramWebhookProcessor: raise except Exception as error: # pragma: no cover - логируем сбой обработчика logger.exception('Ошибка обработки Telegram update в worker %s: %s', worker_id, error) + # Отправляем уведомление в админский чат + schedule_error_notification( + self._bot, error, f'Telegram webhook worker {worker_id}' + ) finally: self._queue.task_done() finally: @@ -188,6 +193,7 @@ async def _dispatch_update( raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='webhook_queue_full') from error except TelegramWebhookProcessorNotRunningError as error: logger.error('Telegram webhook processor неактивен: %s', error) + schedule_error_notification(bot, error, 'Telegram webhook processor unavailable') raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='webhook_processor_unavailable' ) from error @@ -222,12 +228,14 @@ def create_telegram_router( payload: Any = await request.json() except Exception as error: # pragma: no cover - defensive logging logger.error('Ошибка чтения Telegram webhook: %s', error) + schedule_error_notification(bot, error, 'Telegram webhook JSON parse error') raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='invalid_payload') from error try: update = Update.model_validate(payload) except Exception as error: # pragma: no cover - defensive logging logger.error('Ошибка валидации Telegram update: %s', error) + schedule_error_notification(bot, error, 'Telegram update validation error') raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='invalid_update') from error await _dispatch_update(update, dispatcher=dispatcher, bot=bot, processor=processor)