mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-02 08:11:32 +00:00
244 lines
9.7 KiB
Python
244 lines
9.7 KiB
Python
import logging
|
||
import asyncio
|
||
from typing import Optional, List
|
||
from aiohttp import web
|
||
from aiogram import Bot
|
||
|
||
from app.config import settings
|
||
from app.services.tribute_service import TributeService
|
||
from app.services.payment_service import PaymentService
|
||
from app.external.yookassa_webhook import YooKassaWebhookHandler
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class WebhookServer:
|
||
|
||
def __init__(self, bot: Bot, payment_service: PaymentService):
|
||
self.bot = bot
|
||
self.payment_service = payment_service
|
||
self.tribute_service = TributeService()
|
||
self.yookassa_handler = YooKassaWebhookHandler(payment_service)
|
||
|
||
self.tribute_app = None
|
||
self.tribute_runner = None
|
||
self.tribute_site = None
|
||
|
||
self.yookassa_app = None
|
||
self.yookassa_runner = None
|
||
self.yookassa_site = None
|
||
|
||
async def create_tribute_app(self) -> web.Application:
|
||
self.tribute_app = web.Application()
|
||
|
||
self.tribute_app.middlewares.append(self._logging_middleware)
|
||
|
||
self.tribute_app.router.add_post(settings.TRIBUTE_WEBHOOK_PATH, self._tribute_webhook_handler)
|
||
self.tribute_app.router.add_get('/health', self._tribute_health_check)
|
||
|
||
logger.info(f"Tribute webhook настроен:")
|
||
logger.info(f" - Webhook path: {settings.TRIBUTE_WEBHOOK_PATH}")
|
||
logger.info(f" - Port: {settings.TRIBUTE_WEBHOOK_PORT}")
|
||
|
||
return self.tribute_app
|
||
|
||
async def create_yookassa_app(self) -> web.Application:
|
||
self.yookassa_app = web.Application()
|
||
|
||
self.yookassa_app.middlewares.append(self._logging_middleware)
|
||
|
||
self.yookassa_app.router.add_post(settings.YOOKASSA_WEBHOOK_PATH, self.yookassa_handler.handle_webhook)
|
||
self.yookassa_app.router.add_get('/health', self._yookassa_health_check)
|
||
|
||
logger.info(f"YooKassa webhook настроен:")
|
||
logger.info(f" - Webhook path: {settings.YOOKASSA_WEBHOOK_PATH}")
|
||
logger.info(f" - Port: {settings.YOOKASSA_WEBHOOK_PORT}")
|
||
|
||
return self.yookassa_app
|
||
|
||
async def start_tribute_server(self):
|
||
if not settings.TRIBUTE_ENABLED:
|
||
logger.info("Tribute отключен, сервер не запускается")
|
||
return
|
||
|
||
try:
|
||
await self.create_tribute_app()
|
||
|
||
self.tribute_runner = web.AppRunner(self.tribute_app)
|
||
await self.tribute_runner.setup()
|
||
|
||
self.tribute_site = web.TCPSite(
|
||
self.tribute_runner,
|
||
host='0.0.0.0',
|
||
port=settings.TRIBUTE_WEBHOOK_PORT
|
||
)
|
||
|
||
await self.tribute_site.start()
|
||
|
||
logger.info(f"✅ Tribute webhook сервер запущен на 0.0.0.0:{settings.TRIBUTE_WEBHOOK_PORT}")
|
||
logger.info(f"🎯 Tribute webhook URL: http://your-server:{settings.TRIBUTE_WEBHOOK_PORT}{settings.TRIBUTE_WEBHOOK_PATH}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка запуска Tribute webhook сервера: {e}")
|
||
raise
|
||
|
||
async def start_yookassa_server(self):
|
||
if not settings.is_yookassa_enabled():
|
||
logger.info("YooKassa отключен, сервер не запускается")
|
||
return
|
||
|
||
try:
|
||
await self.create_yookassa_app()
|
||
|
||
self.yookassa_runner = web.AppRunner(self.yookassa_app)
|
||
await self.yookassa_runner.setup()
|
||
|
||
self.yookassa_site = web.TCPSite(
|
||
self.yookassa_runner,
|
||
host='0.0.0.0',
|
||
port=settings.YOOKASSA_WEBHOOK_PORT
|
||
)
|
||
|
||
await self.yookassa_site.start()
|
||
|
||
logger.info(f"✅ YooKassa webhook сервер запущен на 0.0.0.0:{settings.YOOKASSA_WEBHOOK_PORT}")
|
||
logger.info(f"🎯 YooKassa webhook URL: http://your-server:{settings.YOOKASSA_WEBHOOK_PORT}{settings.YOOKASSA_WEBHOOK_PATH}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка запуска YooKassa webhook сервера: {e}")
|
||
raise
|
||
|
||
async def start(self):
|
||
tasks = []
|
||
|
||
if settings.TRIBUTE_ENABLED:
|
||
tasks.append(self.start_tribute_server())
|
||
|
||
if settings.is_yookassa_enabled():
|
||
tasks.append(self.start_yookassa_server())
|
||
|
||
if not tasks:
|
||
logger.warning("Ни один платежный провайдер не включен!")
|
||
return
|
||
|
||
await asyncio.gather(*tasks)
|
||
|
||
logger.info("🚀 Все webhook серверы запущены!")
|
||
|
||
async def stop(self):
|
||
tasks = []
|
||
|
||
if self.tribute_site:
|
||
tasks.append(self.tribute_site.stop())
|
||
if self.tribute_runner:
|
||
tasks.append(self.tribute_runner.cleanup())
|
||
|
||
if self.yookassa_site:
|
||
tasks.append(self.yookassa_site.stop())
|
||
if self.yookassa_runner:
|
||
tasks.append(self.yookassa_runner.cleanup())
|
||
|
||
if tasks:
|
||
try:
|
||
await asyncio.gather(*tasks, return_exceptions=True)
|
||
logger.info("🛑 Все webhook серверы остановлены")
|
||
except Exception as e:
|
||
logger.error(f"Ошибка остановки webhook серверов: {e}")
|
||
|
||
async def _tribute_webhook_handler(self, request: web.Request) -> web.Response:
|
||
try:
|
||
raw_body = await request.read()
|
||
|
||
if not raw_body:
|
||
logger.warning("Получен пустой Tribute webhook")
|
||
return web.json_response(
|
||
{"status": "error", "reason": "empty_body"},
|
||
status=400
|
||
)
|
||
|
||
try:
|
||
payload = raw_body.decode('utf-8')
|
||
import json
|
||
webhook_data = json.loads(payload)
|
||
except (UnicodeDecodeError, json.JSONDecodeError) as e:
|
||
logger.error(f"Ошибка декодирования Tribute webhook: {e}")
|
||
return web.json_response(
|
||
{"status": "error", "reason": "invalid_payload"},
|
||
status=400
|
||
)
|
||
|
||
signature = request.headers.get('X-Tribute-Signature')
|
||
if signature and not self.tribute_service.verify_webhook_signature(payload, signature):
|
||
logger.error("Неверная подпись Tribute webhook")
|
||
return web.json_response(
|
||
{"status": "error", "reason": "invalid_signature"},
|
||
status=400
|
||
)
|
||
|
||
result = await self.tribute_service.process_webhook(webhook_data)
|
||
|
||
if result:
|
||
from app.database.database import get_db
|
||
async with get_db() as db:
|
||
success = await self.payment_service.process_tribute_payment(
|
||
db=db,
|
||
user_id=result['user_id'],
|
||
amount_kopeks=result['amount_kopeks'],
|
||
payment_id=result['payment_id']
|
||
)
|
||
|
||
if success:
|
||
logger.info(f"✅ Успешно обработан Tribute платеж: {result['payment_id']}")
|
||
return web.json_response({"status": "ok"}, status=200)
|
||
else:
|
||
logger.error(f"❌ Ошибка обработки Tribute платежа: {result['payment_id']}")
|
||
return web.json_response(
|
||
{"status": "error", "reason": "processing_failed"},
|
||
status=500
|
||
)
|
||
else:
|
||
logger.error("Не удалось обработать Tribute webhook данные")
|
||
return web.json_response(
|
||
{"status": "error", "reason": "invalid_data"},
|
||
status=400
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Критическая ошибка обработки Tribute webhook: {e}", exc_info=True)
|
||
return web.json_response(
|
||
{"status": "error", "reason": "internal_error"},
|
||
status=500
|
||
)
|
||
|
||
async def _tribute_health_check(self, request: web.Request) -> web.Response:
|
||
return web.json_response({
|
||
"status": "ok",
|
||
"service": "tribute-webhooks",
|
||
"tribute_enabled": settings.TRIBUTE_ENABLED,
|
||
"port": settings.TRIBUTE_WEBHOOK_PORT
|
||
})
|
||
|
||
async def _yookassa_health_check(self, request: web.Request) -> web.Response:
|
||
return web.json_response({
|
||
"status": "ok",
|
||
"service": "yookassa-webhooks",
|
||
"yookassa_enabled": settings.is_yookassa_enabled(),
|
||
"port": settings.YOOKASSA_WEBHOOK_PORT
|
||
})
|
||
|
||
async def _logging_middleware(self, request: web.Request, handler):
|
||
start_time = request.loop.time()
|
||
|
||
try:
|
||
response = await handler(request)
|
||
process_time = request.loop.time() - start_time
|
||
|
||
logger.info(f"{request.method} {request.path_qs} -> {response.status} ({process_time:.3f}s)")
|
||
|
||
return response
|
||
|
||
except Exception as e:
|
||
process_time = request.loop.time() - start_time
|
||
logger.error(f"{request.method} {request.path_qs} -> ERROR ({process_time:.3f}s): {e}")
|
||
raise
|