Files
pay-service/src/presentation/routing/order.py
2026-04-22 12:36:20 +03:00

115 lines
4.4 KiB
Python

import json
from decimal import Decimal
from urllib.parse import parse_qs
import aiohttp
from fastapi import APIRouter, Depends, Request
from fastapi.responses import ORJSONResponse
from ulid import ULID
from src.application.contracts import ILogger
from src.application.domain.dto import AuthContext
from src.application.domain.exceptions import ApplicationException
from src.presentation.decorators import csrf_protect, require_access_token
from src.presentation.dependencies.logger import get_logger
from src.presentation.schemas.order import CreateOrder
from src.infrastructure.config import settings
order_router = APIRouter(prefix='/order', tags=['orders'])
ITPAY_API_BASE = 'https://api.gw.itpay.ru'
HARDCODED_USDT_TO_RUB = Decimal('100')
HARDCODED_GAS_RUB = Decimal('15')
HARDCODED_OUR_COMMISSION_RUB = Decimal('25')
def _amount_rub_for_itpay(amount_usdt: Decimal) -> Decimal:
return (amount_usdt * HARDCODED_USDT_TO_RUB + HARDCODED_GAS_RUB + HARDCODED_OUR_COMMISSION_RUB).quantize(Decimal('0.01'))
@order_router.post('/create')
#@csrf_protect()
async def create_order(
request: Request,
body: CreateOrder,
#auth: AuthContext = Depends(require_access_token),
logger: ILogger = Depends(get_logger),
) -> ORJSONResponse:
amount_rub = _amount_rub_for_itpay(body.amount_usdt)
amount_str = str(amount_rub)
client_payment_id = str(ULID())
payload = {
'amount': amount_str,
'client_payment_id': client_payment_id,
'description': f'USDT {body.amount_usdt}',
'metadata': {
'user_id': '01KPSYW27JZ26HBDR3QS5J6VMS',
'amount_usdt': str(body.amount_usdt),
'rate': str(HARDCODED_USDT_TO_RUB),
'gas_rub': str(HARDCODED_GAS_RUB),
'commission_rub': str(HARDCODED_OUR_COMMISSION_RUB),
},
}
url = f'{ITPAY_API_BASE}/v1/payments'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
try:
timeout = aiohttp.ClientTimeout(total=30)
async with aiohttp.ClientSession(timeout=timeout) as session:
auth = aiohttp.BasicAuth(settings.ITPAY_PUBLIC_ID, settings.ITPAY_API_SECRET)
async with session.post(url, json=payload, headers=headers, auth=auth) as resp:
response_text = await resp.text()
try:
response_json = json.loads(response_text)
except json.JSONDecodeError:
response_json = {'raw': response_text}
if resp.status >= 400:
logger.warning(f'itpay payments POST {resp.status} {response_text}')
raise ApplicationException(status_code=502, message='Payment provider error')
except ApplicationException:
raise
except aiohttp.ClientError as e:
logger.error(str(e))
raise ApplicationException(status_code=502, message='Payment provider unreachable')
return ORJSONResponse(
content={
'itpay': response_json,
'client_payment_id': client_payment_id,
'amount_usdt': str(body.amount_usdt),
'amount_rub': amount_str,
'hardcoded': {
'usdt_to_rub': str(HARDCODED_USDT_TO_RUB),
'gas_rub': str(HARDCODED_GAS_RUB),
'commission_rub': str(HARDCODED_OUR_COMMISSION_RUB),
},
}
)
@order_router.post('/webhook/itpay')
async def itpay_webhook(request: Request, logger: ILogger = Depends(get_logger)) -> ORJSONResponse:
raw = await request.body()
ct = (request.headers.get('content-type') or '').lower()
if 'application/json' in ct:
try:
parsed = json.loads(raw.decode('utf-8'))
except (json.JSONDecodeError, UnicodeDecodeError):
parsed = raw.decode('utf-8', errors='replace')
elif 'application/x-www-form-urlencoded' in ct:
decoded = raw.decode('utf-8', errors='replace')
qs = parse_qs(decoded, keep_blank_values=True)
parsed = {k: (vals[0] if len(vals) == 1 else vals) for k, vals in qs.items()}
else:
parsed = raw.decode('utf-8', errors='replace')
log_payload = {
'method': request.method,
'url': str(request.url),
'headers': {k: v for k, v in request.headers.items()},
'body': parsed,
}
logger.info(json.dumps(log_payload, ensure_ascii=False, default=str))
return ORJSONResponse(content={'status': 0})