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 CreatePaymentCommand 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_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']) itpay_router = APIRouter(prefix='/itpay', tags=['itpay']) @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 @itpay_router.post('/webhook') async def itpay_webhook( request: Request, payment_command: CreatePaymentCommand = Depends(get_create_payment_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})