mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-01 07:42:30 +00:00
- Add ContextVarsMiddleware for automatic user_id/chat_id/username binding via structlog contextvars (aiogram) and http_method/http_path (FastAPI) - Use bound_contextvars() context manager instead of clear_contextvars() to safely restore previous state instead of wiping all context - Register ContextVarsMiddleware as outermost middleware (before GlobalError) so all error logs include user context - Replace structlog.get_logger() with structlog.get_logger(__name__) across 270 calls in 265 files for meaningful logger names - Switch wrapper_class from BoundLogger to make_filtering_bound_logger() for pre-processor level filtering (performance optimization) - Migrate 1411 %-style positional arg logger calls to structlog kwargs style across 161 files via AST script - Migrate log_rotation_service.py from stdlib logging to structlog - Add payment module prefixes to TelegramNotifierProcessor.IGNORED_LOGGER_PREFIXES and ExcludePaymentFilter.PAYMENT_MODULES to prevent payment data leaking to Telegram notifications and general log files - Fix LoggingMiddleware: add from_user null-safety for channel posts, switch time.time() to time.monotonic() for duration measurement - Remove duplicate logger assignments in purchase.py, config.py, inline.py, and admin/payments.py
119 lines
4.3 KiB
Python
119 lines
4.3 KiB
Python
import asyncio
|
|
import json
|
|
from typing import Any
|
|
|
|
import structlog
|
|
from aiohttp import web
|
|
|
|
from app.config import settings
|
|
from app.database.database import AsyncSessionLocal
|
|
from app.external.heleket import HeleketService
|
|
from app.services.payment_service import PaymentService
|
|
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
class HeleketWebhookHandler:
|
|
def __init__(self, payment_service: PaymentService) -> None:
|
|
self.payment_service = payment_service
|
|
self.service = HeleketService()
|
|
|
|
async def handle(self, request: web.Request) -> web.Response:
|
|
if not settings.is_heleket_enabled():
|
|
logger.warning('Получен Heleket webhook, но сервис отключен')
|
|
return web.json_response({'status': 'error', 'reason': 'disabled'}, status=503)
|
|
|
|
try:
|
|
payload: dict[str, Any] = await request.json()
|
|
except json.JSONDecodeError:
|
|
logger.error('Некорректный JSON Heleket webhook')
|
|
return web.json_response({'status': 'error', 'reason': 'invalid_json'}, status=400)
|
|
|
|
if not self.service.verify_webhook_signature(payload):
|
|
return web.json_response({'status': 'error', 'reason': 'invalid_signature'}, status=401)
|
|
|
|
processed: bool | None = None
|
|
async with AsyncSessionLocal() as db:
|
|
try:
|
|
processed = await self.payment_service.process_heleket_webhook(db, payload)
|
|
await db.commit()
|
|
except Exception as e:
|
|
logger.error('Ошибка обработки Heleket webhook', error=e)
|
|
await db.rollback()
|
|
return web.json_response({'status': 'error', 'reason': 'internal_error'}, status=500)
|
|
|
|
if processed:
|
|
return web.json_response({'status': 'ok'}, status=200)
|
|
|
|
return web.json_response({'status': 'error', 'reason': 'not_processed'}, status=400)
|
|
|
|
async def health_check(self, _: web.Request) -> web.Response:
|
|
return web.json_response(
|
|
{
|
|
'status': 'ok',
|
|
'service': 'heleket_webhook',
|
|
'enabled': settings.is_heleket_enabled(),
|
|
'path': settings.HELEKET_WEBHOOK_PATH,
|
|
}
|
|
)
|
|
|
|
async def options_handler(self, _: web.Request) -> web.Response:
|
|
return web.Response(
|
|
status=200,
|
|
headers={
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
},
|
|
)
|
|
|
|
|
|
def create_heleket_app(payment_service: PaymentService) -> web.Application:
|
|
handler = HeleketWebhookHandler(payment_service)
|
|
app = web.Application()
|
|
app.router.add_post(settings.HELEKET_WEBHOOK_PATH, handler.handle)
|
|
app.router.add_get('/heleket/health', handler.health_check)
|
|
app.router.add_get('/health', handler.health_check)
|
|
app.router.add_options(settings.HELEKET_WEBHOOK_PATH, handler.options_handler)
|
|
return app
|
|
|
|
|
|
async def start_heleket_webhook_server(payment_service: PaymentService) -> None:
|
|
if not settings.is_heleket_enabled():
|
|
logger.info('Heleket отключен, webhook сервер не запускается')
|
|
return
|
|
|
|
app = create_heleket_app(payment_service)
|
|
runner = web.AppRunner(app)
|
|
await runner.setup()
|
|
|
|
site = web.TCPSite(
|
|
runner,
|
|
host=settings.HELEKET_WEBHOOK_HOST,
|
|
port=settings.HELEKET_WEBHOOK_PORT,
|
|
)
|
|
|
|
try:
|
|
await site.start()
|
|
logger.info(
|
|
'Heleket webhook сервер запущен на',
|
|
HELEKET_WEBHOOK_HOST=settings.HELEKET_WEBHOOK_HOST,
|
|
HELEKET_WEBHOOK_PORT=settings.HELEKET_WEBHOOK_PORT,
|
|
)
|
|
logger.info(
|
|
'Heleket webhook URL: http://',
|
|
HELEKET_WEBHOOK_HOST=settings.HELEKET_WEBHOOK_HOST,
|
|
HELEKET_WEBHOOK_PORT=settings.HELEKET_WEBHOOK_PORT,
|
|
HELEKET_WEBHOOK_PATH=settings.HELEKET_WEBHOOK_PATH,
|
|
)
|
|
|
|
while True:
|
|
await asyncio.sleep(1)
|
|
except asyncio.CancelledError:
|
|
logger.info('Heleket webhook сервер остановлен по запросу')
|
|
finally:
|
|
await site.stop()
|
|
await runner.cleanup()
|
|
logger.info('Heleket webhook сервер корректно остановлен')
|