Files
pay-service/src/presentation/routing/order.py
2026-05-09 14:57:15 +03:00

121 lines
5.5 KiB
Python

from urllib.parse import parse_qs
import orjson
from fastapi import APIRouter, Depends, Request
from fastapi.responses import ORJSONResponse
from src.application.commands import CreateOrderCommand
from src.application.commands import CreatePaymentCloudkassirCommand
from src.application.contracts import ILogger
from src.application.domain.dto import AuthContext
from src.application.domain.enums import OrderStatus
from src.application.domain.exceptions import ConflictException
from src.presentation.decorators import require_access_token, csrf_protect
from src.presentation.dependencies.commands import get_create_order_command, get_create_payment_cloudkassir_command
from src.presentation.dependencies.logger import get_logger
from src.presentation.schemas.order import CreateOrder,CreateOrderResponse,ErrorResponse,OrderPaymentResponse
from src.presentation.schemas.itpay_payment_models import ItpayPaymentData
order_router = APIRouter(prefix='/order', tags=['orders'])
@order_router.post(
'/create',
response_model=CreateOrderResponse,
status_code=201,
responses={
400: {'model': ErrorResponse, 'description': 'Bad Request'},
401: {'model': ErrorResponse, 'description': 'Unauthorized'},
403: {'model': ErrorResponse, 'description': 'Forbidden'},
404: {'model': ErrorResponse, 'description': 'Not Found'},
409: {'model': ErrorResponse, 'description': 'Conflict'},
500: {'model': ErrorResponse, 'description': 'Internal Server Error'},
502: {'model': ErrorResponse, 'description': 'Bad Gateway'},
503: {'model': ErrorResponse, 'description': 'Service Unavailable'},
},
)
#@csrf_protect()
async def create_order(
payment_data: CreateOrder,
#auth: AuthContext = Depends(require_access_token),
command: CreateOrderCommand = Depends(get_create_order_command),
logger: ILogger = Depends(get_logger),
) -> CreateOrderResponse:
#o = await command(payment_data, auth.user_id)
o = await command(payment_data, '01KPKAFN6J1NJBY15DX8JE2QYB')
itpay_error = o.status in (
OrderStatus.CANCELLED,
OrderStatus.REJECTED,
OrderStatus.ERROR,
)
log_ids = {
'event': 'order_create_itpay_failed' if itpay_error else 'order_created',
'order_id': o.id,
'user_id': o.user_id,
'client_payment_id': o.client_payment_id,
'itpay_id': o.itpay_id,
'order_status': o.status.value if o.status is not None else None,
}
logger.info(log_ids)
if itpay_error:
raise ConflictException(message='Payment provider rejected order')
content = CreateOrderResponse(
status_code=201,
order=OrderPaymentResponse(
id=o.id,
created_at=o.created_at.isoformat() if o.created_at is not None else None,
updated_at=o.updated_at.isoformat() if o.updated_at is not None else None,
user_id=o.user_id,
usdt_amount=str(o.usdt_amount) if o.usdt_amount is not None else None,
usdt_exchange_rate=str(o.usdt_exchange_rate) if o.usdt_exchange_rate is not None else None,
gas_fee=str(o.gas_fee) if o.gas_fee is not None else None,
total_price=str(o.total_price) if o.total_price is not None else None,
service_fee=str(o.service_fee) if o.service_fee is not None else None,
status=o.status,
client_payment_id=o.client_payment_id,
itpay_payment_qr_url_desktop=o.itpay_payment_qr_url_desktop,
itpay_payment_qr_url_android=o.itpay_payment_qr_url_android,
itpay_payment_qr_url_ios=o.itpay_payment_qr_url_ios,
itpay_payment_qr_image_desktop=o.itpay_payment_qr_image_desktop,
itpay_payment_qr_image_android=o.itpay_payment_qr_image_android,
itpay_payment_qr_image_ios=o.itpay_payment_qr_image_ios,
itpay_id=o.itpay_id,
itpay_qr_id=o.itpay_qr_id,
itpay_amount=str(o.itpay_amount) if o.itpay_amount is not None else None,
itpay_created_at=o.itpay_created_at.isoformat() if o.itpay_created_at is not None else None,
),
)
return content
@order_router.post('/webhook/itpay')
async def itpay_webhook(
request: Request,
payment_command: CreatePaymentCloudkassirCommand = Depends(get_create_payment_cloudkassir_command),
logger: ILogger = Depends(get_logger),
) -> ORJSONResponse:
raw = await request.body()
ct = (request.headers.get('content-type') or '').lower()
if 'application/json' in ct:
payload = orjson.loads(raw)
elif 'application/x-www-form-urlencoded' in ct:
decoded = raw.decode('utf-8', errors='replace')
qs = parse_qs(decoded, keep_blank_values=True)
payload = {k: (vals[0] if len(vals) == 1 else vals) for k, vals in qs.items()}
else:
payload = orjson.loads(raw)
data = payload.get('data') if isinstance(payload.get('data'), dict) else {}
status = str(data.get('status') or '').strip().lower()
log_payload = {
'event': 'itpay_webhook_received',
'webhook_id': payload.get('id'),
'webhook_type': payload.get('type'),
'payment_id': data.get('id'),
'client_payment_id': data.get('client_payment_id'),
'payment_status': status,
'itpay_metadata': data.get('metadata'),
}
logger.info(log_payload)
if status == 'completed':
payment = ItpayPaymentData.model_validate(data)
await payment_command(payment)
return ORJSONResponse(content={'status': 0})