Files
X25020501_card_book/core/routers/pay/routes.py
2025-05-13 15:49:00 +08:00

157 lines
5.2 KiB
Python

# -*- coding = utf-8 -*-
# @Time : 2025/2/24 上午9:03
# @File : routes.py
# @Software : PyCharm
# @Author : xingc
# @Desc :
import logging
from xml.parsers.expat import ExpatError
import xmltodict
from asyncer import asyncify
from fastapi import APIRouter, Request, HTTPException, status, Depends, Response
from sqlalchemy.ext.asyncio import AsyncSession
from core.base import schema as base_schema
from core.base.scheduler import add_task
from core.db.crud import CRUD
from core.db.engine import get_async_session
from core.db.models import Order, Product
from core.routers.auth.schema import LoginUser
from core.routers.auth.services import get_current_user_form_http
from core.utils.helper import generate_keyname, generate_order_number
from core.wechat.api import wechat_pay_api
from . import schema
from .actions import actions_after_payment
logger = logging.getLogger(__name__)
pay_router = APIRouter(prefix='/pay', tags=['支付模块'])
@pay_router.get(
'/order/check/{order_id}',
summary='获取订单状态',
response_model=base_schema.BaseResponse[schema.CheckOrderOut])
async def check_order(
order_id: str,
login_user: LoginUser = Depends(get_current_user_form_http),
db_session: AsyncSession = Depends(get_async_session)
):
order = await CRUD(db_session, Order).get(filters={
'order_id': order_id, 'user_id': login_user.user_id
})
if order is None:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail='订单不存在')
return {'data': order}
@pay_router.post(
'/order/create',
summary='订单创建',
response_model=base_schema.BaseResponse[schema.CreateOrderOut]
)
async def create_order(
request: Request,
payload: schema.CreateOrderIn,
login_user: LoginUser = Depends(get_current_user_form_http),
db_session: AsyncSession = Depends(get_async_session),
):
db_crud = CRUD(db_session, Order)
product = await db_crud.get(model=Product, filters={'id': payload.product_id})
if product is None:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail='产品不存在')
if product.price != payload.total_fee:
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail='产品费用不合法')
client_ip = (
request.headers.get('X-Real-IP') or
request.headers.get('x-real-ip') or
request.client.host
)
out_trade_no = generate_order_number()
prepay_id = await wechat_pay_api.create_unified_order(
**payload.model_dump(exclude_none=True, exclude={'product_id'}),
out_trade_no=out_trade_no,
create_ip=client_ip
)
payment_params = wechat_pay_api.generate_payment_params(prepay_id)
order: Order = await db_crud.create(
data={
'order_id': out_trade_no,
'user_id': login_user.user_id,
'product_id': payload.product_id,
'total_fee': payload.total_fee,
'status': base_schema.OrderStatusType.WAIT_PAYMENT
},
filters={
'order_id': out_trade_no,
}
)
return {'data': {'payment_params': payment_params, 'order_id': order.order_id}}
@pay_router.post('/wechat/notify', summary='处理支付结果通知')
async def wechat_notify(
request: Request,
db_session: AsyncSession = Depends(get_async_session)
):
def generate_response():
xml_response = """
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
"""
return Response(content=xml_response.strip(), media_type="application/xml")
body = await request.body()
logger.debug(body.decode())
try:
result = xmltodict.parse(body.decode())
except ExpatError as e:
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail='无法处理的实体') from e
# 验证签名
params = result.get('xml', {})
sign = params.pop('sign', None)
if sign is None:
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail='无法处理的实体')
local_sign = wechat_pay_api.generate_sign(params)
if sign != local_sign:
return generate_response()
# 处理支付结果
db_crud = CRUD(db_session, Order)
order = await db_crud.get(filters={'order_id': params['out_trade_no']})
if order and order.status.value not in ['ok', 'fail']:
record = {
'order_id': params['out_trade_no'],
'transaction_id': params['transaction_id']
}
if params['return_code'] == 'SUCCESS' and params['result_code'] == 'SUCCESS':
record.update({
'notify_total_fee': params['total_fee'],
'status': base_schema.OrderStatusType.PAID
})
await asyncify(add_task)(
actions_after_payment,
notify_params=params,
task_id=record['order_id'],
)
else:
record.update({
'status': base_schema.OrderStatusType.FAIL,
})
await db_crud.update(
data=record,
filters={
'order_id': record['order_id'], 'status': base_schema.OrderStatusType.WAIT_PAYMENT
}
)
return generate_response()