Files
remnawave-bedolaga-telegram…/app/external/heleket_webhook.py
2026-01-17 01:18:02 +03:00

118 lines
4.2 KiB
Python

import asyncio
import json
import logging
from typing import Any, Dict, Optional
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 = logging.getLogger(__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: Optional[bool] = 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(f"Ошибка обработки Heleket webhook: {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 сервер запущен на %s:%s",
settings.HELEKET_WEBHOOK_HOST,
settings.HELEKET_WEBHOOK_PORT,
)
logger.info(
"Heleket webhook URL: http://%s:%s%s",
settings.HELEKET_WEBHOOK_HOST,
settings.HELEKET_WEBHOOK_PORT,
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 сервер корректно остановлен")