chore: initialize tuhui repository

This commit is contained in:
Codex
2026-03-08 19:28:32 +08:00
commit ee10c46aae
189 changed files with 17754 additions and 0 deletions

267
backend/app/api/payment.py Normal file
View File

@@ -0,0 +1,267 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""支付相关接口"""
from fastapi import APIRouter, Depends, HTTPException, Request, BackgroundTasks
from sqlalchemy.orm import Session
from sqlalchemy import and_
from datetime import datetime
import json
from app.core.database import get_db
from app.core.config import settings
from app.models.order import Order, OrderStatus
from app.models.work import Work
from app.models.download import DownloadRecord
from app.ysm_sdk import create_payment, query_order, PaymentNotify
from app.api.orders import get_current_user
from app.models.user import User
router = APIRouter(prefix="/payment", tags=["支付"])
@router.post("/create/{order_id}")
async def create_payment_url(
order_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
创建支付链接(真实支付)
Args:
order_id: 订单ID
Returns:
{
"pay_url": "支付链接",
"order_number": "订单号",
"amount": 金额(分)
}
"""
# 查询订单
order = db.query(Order).filter(
and_(
Order.id == order_id,
Order.user_id == current_user.id
)
).first()
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
# 检查订单状态
if order.status == OrderStatus.PAID:
raise HTTPException(status_code=400, detail="订单已支付")
if order.status == OrderStatus.CANCELLED:
raise HTTPException(status_code=400, detail="订单已取消")
# 查询作品信息
work = db.query(Work).filter(Work.id == order.work_id).first()
if not work:
raise HTTPException(status_code=404, detail="作品不存在")
try:
# 创建支付
pay_url = await create_payment(
appid=settings.YSM_APPID,
appsecret=settings.YSM_APPSECRET,
order_id=order.order_no, # 使用 order_no 字段
description=f"{work.title} - 爱设计作品购买",
amount=int(order.amount * 100), # 元转分
notify_url=settings.YSM_NOTIFY_URL,
nopay_url=settings.YSM_NOPAY_URL,
callback_url=settings.YSM_CALLBACK_URL,
pay_type=1 # 默认微信内支付
)
return {
"pay_url": pay_url,
"order_number": order.order_no, # 使用 order_no 字段
"amount": int(order.amount * 100),
"description": f"{work.title} - 爱设计作品购买"
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/notify")
async def payment_notify(
request: Request,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db)
):
"""
支付回调接口(易收米异步通知)
当用户支付成功后,支付平台会异步请求此接口
"""
# 创建通知处理器
notify_handler = PaymentNotify(
appid=settings.YSM_APPID,
appsecret=settings.YSM_APPSECRET
)
# 获取请求数据
try:
request_data = await request.body()
data = json.loads(request_data.decode('utf-8'))
except Exception as e:
return {"error": "数据格式错误"}
# 验证签名
success, message = await notify_handler.process(data)
if not success:
return {"error": message}
# 获取商户订单号
mch_orderid = data.get('mch_orderid')
# 查询订单
order = db.query(Order).filter(Order.order_no == mch_orderid).first()
if not order:
return {"error": "订单不存在"}
# 防止重复处理
if order.status == OrderStatus.PAID:
return "success"
# 更新订单状态
if data.get('state') == 'SUCCESS':
try:
# 更新订单
order.status = OrderStatus.PAID
order.payment_method = "ysm_wechat" # 易收米微信支付
order.paid_at = datetime.now()
# 创建下载记录
download_record = DownloadRecord(
user_id=order.user_id,
work_id=order.work_id,
order_id=order.id
)
db.add(download_record)
# 更新作品下载次数
work = db.query(Work).filter(Work.id == order.work_id).first()
if work:
work.downloads += 1
db.commit()
return "success"
except Exception as e:
db.rollback()
print(f"处理支付回调失败: {str(e)}")
return {"error": "处理失败"}
return {"error": "支付未完成"}
@router.get("/query/{order_number}")
async def query_order_status(
order_number: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
查询订单支付状态
Args:
order_number: 订单号
Returns:
订单状态信息
"""
# 查询本地订单
order = db.query(Order).filter(
and_(
Order.order_no == order_number,
Order.user_id == current_user.id
)
).first()
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
try:
# 查询易收米订单状态
result = await query_order(
appid=settings.YSM_APPID,
mch_orderid=order_number
)
if result:
# 如果远程订单已支付,但本地订单未更新,则更新本地订单
if result.get('state') == 'SUCCESS' and order.status != OrderStatus.PAID:
order.status = OrderStatus.PAID
order.payment_method = "ysm_wechat"
order.paid_at = datetime.now()
# 创建下载记录
download_record = DownloadRecord(
user_id=order.user_id,
work_id=order.work_id,
order_id=order.id
)
db.add(download_record)
# 更新作品下载次数
work = db.query(Work).filter(Work.id == order.work_id).first()
if work:
work.downloads += 1
db.commit()
return {
"order_number": order_number,
"local_status": order.status,
"remote_status": result.get('state'),
"amount": order.amount,
"paid_at": order.paid_at.isoformat() if order.paid_at else None,
"remote_info": result
}
else:
raise HTTPException(status_code=500, detail="查询订单失败")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/cancel/{order_id}")
async def cancel_order(
order_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
取消订单
Args:
order_id: 订单ID
"""
# 查询订单
order = db.query(Order).filter(
and_(
Order.id == order_id,
Order.user_id == current_user.id
)
).first()
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
# 只能取消待支付的订单
if order.status != OrderStatus.PENDING:
raise HTTPException(status_code=400, detail="订单状态不允许取消")
order.status = OrderStatus.CANCELLED
db.commit()
return {"message": "订单已取消"}