122 lines
5.5 KiB
Python
122 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 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})
|