- 前端 React+TypeScript (源码+构建产物) - 后端 Python FastAPI - 数据库迁移 001-015 - Docker部署配置 - 微信ClawBot API频道绑定 - 项目文档 README.md
227 lines
7.1 KiB
Python
227 lines
7.1 KiB
Python
"""
|
|
支付渠道配置 API(管理员)
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from fastapi.responses import StreamingResponse
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func
|
|
from pydantic import BaseModel
|
|
from typing import Optional
|
|
from ...database import get_db, SessionLocal
|
|
from ...models import PaymentConfig, Order
|
|
from ...auth import get_current_admin
|
|
from ...middleware.log_service import LogService
|
|
import uuid
|
|
import csv
|
|
import io
|
|
from datetime import datetime
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class PaymentConfigUpdate(BaseModel):
|
|
app_id: Optional[str] = None
|
|
merchant_id: Optional[str] = None
|
|
api_key: Optional[str] = None
|
|
api_v3_key: Optional[str] = None
|
|
cert_path: Optional[str] = None
|
|
cert_serial_no: Optional[str] = None
|
|
alipay_public_key: Optional[str] = None
|
|
app_private_key: Optional[str] = None
|
|
gateway_url: Optional[str] = None
|
|
notify_url: Optional[str] = None
|
|
return_url: Optional[str] = None
|
|
status: Optional[str] = None
|
|
|
|
|
|
# ============================================================
|
|
# 支付配置管理
|
|
# ============================================================
|
|
|
|
@router.get("/configs")
|
|
async def get_payment_configs(
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""获取支付配置列表"""
|
|
configs = db.query(PaymentConfig).order_by(
|
|
PaymentConfig.created_at.desc()
|
|
).all()
|
|
return [
|
|
{
|
|
"id": c.id,
|
|
"type": c.type,
|
|
"app_id": c.app_id,
|
|
"merchant_id": c.merchant_id,
|
|
"api_key": c.api_key,
|
|
"api_v3_key": c.api_v3_key,
|
|
"cert_path": c.cert_path,
|
|
"cert_serial_no": c.cert_serial_no,
|
|
"alipay_public_key": c.alipay_public_key,
|
|
"app_private_key": c.app_private_key,
|
|
"gateway_url": c.gateway_url,
|
|
"notify_url": c.notify_url,
|
|
"return_url": c.return_url,
|
|
"status": c.status,
|
|
"created_at": c.created_at.isoformat() if c.created_at else None,
|
|
"updated_at": c.updated_at.isoformat() if c.updated_at else None,
|
|
}
|
|
for c in configs
|
|
]
|
|
|
|
|
|
@router.put("/configs/{id}")
|
|
async def update_payment_config(
|
|
id: str,
|
|
data: PaymentConfigUpdate,
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""更新支付配置"""
|
|
config = db.query(PaymentConfig).filter(PaymentConfig.id == id).first()
|
|
if not config:
|
|
raise HTTPException(status_code=404, detail="支付配置不存在")
|
|
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for key, value in update_data.items():
|
|
if hasattr(config, key):
|
|
setattr(config, key, value)
|
|
config.updated_at = datetime.now()
|
|
db.commit()
|
|
|
|
LogService.log_audit(
|
|
admin_id=admin_user.id,
|
|
admin_name=admin_user.username,
|
|
action="update_payment_config",
|
|
target_type="payment_config",
|
|
target_id=id,
|
|
detail=f"更新支付配置 [{config.type}]",
|
|
)
|
|
|
|
return {"code": 0, "message": "支付配置已更新"}
|
|
|
|
|
|
# ============================================================
|
|
# 订单管理
|
|
# ============================================================
|
|
|
|
@router.get("/orders")
|
|
async def get_orders(
|
|
status: str = Query(None, description="订单状态筛选: pending/paid/refunded/cancelled"),
|
|
order_type: str = Query(None, description="订单类型筛选: membership/service/app"),
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""获取所有订单列表"""
|
|
query = db.query(Order)
|
|
|
|
if status:
|
|
query = query.filter(Order.status == status)
|
|
if order_type:
|
|
query = query.filter(Order.order_type == order_type)
|
|
|
|
total = query.count()
|
|
orders = query.order_by(Order.created_at.desc()).offset(
|
|
(page - 1) * page_size
|
|
).limit(page_size).all()
|
|
|
|
return {
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"items": [
|
|
{
|
|
"id": o.id,
|
|
"user_id": o.user_id,
|
|
"order_type": o.order_type,
|
|
"item_id": o.item_id,
|
|
"item_name": o.item_name,
|
|
"amount": float(o.amount) if o.amount else 0,
|
|
"coupon_id": o.coupon_id,
|
|
"discount_amount": float(o.discount_amount) if o.discount_amount else 0,
|
|
"payment_method": o.payment_method,
|
|
"payment_id": o.payment_id,
|
|
"status": o.status,
|
|
"created_at": o.created_at.isoformat() if o.created_at else None,
|
|
"updated_at": o.updated_at.isoformat() if o.updated_at else None,
|
|
}
|
|
for o in orders
|
|
],
|
|
}
|
|
|
|
|
|
@router.put("/orders/{id}/refund")
|
|
async def refund_order(
|
|
id: str,
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""退款操作"""
|
|
order = db.query(Order).filter(Order.id == id).first()
|
|
if not order:
|
|
raise HTTPException(status_code=404, detail="订单不存在")
|
|
if order.status != "paid":
|
|
raise HTTPException(status_code=400, detail="只能对已支付的订单进行退款")
|
|
if order.status == "refunded":
|
|
raise HTTPException(status_code=400, detail="订单已退款")
|
|
|
|
order.status = "refunded"
|
|
order.updated_at = datetime.now()
|
|
db.commit()
|
|
|
|
LogService.log_audit(
|
|
admin_id=admin_user.id,
|
|
admin_name=admin_user.username,
|
|
action="refund_order",
|
|
target_type="order",
|
|
target_id=id,
|
|
detail=f"退款订单 {order.id}, 金额 {float(order.amount)}",
|
|
)
|
|
|
|
return {"message": "退款成功"}
|
|
|
|
|
|
@router.get("/orders/export")
|
|
async def export_orders(
|
|
status: str = Query(None, description="订单状态筛选"),
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""导出订单(CSV格式)"""
|
|
query = db.query(Order)
|
|
if status:
|
|
query = query.filter(Order.status == status)
|
|
|
|
orders = query.order_by(Order.created_at.desc()).all()
|
|
|
|
output = io.StringIO()
|
|
writer = csv.writer(output)
|
|
writer.writerow([
|
|
"订单ID", "用户ID", "订单类型", "商品ID", "商品名称",
|
|
"金额", "优惠券ID", "折扣金额", "支付方式", "支付ID",
|
|
"状态", "创建时间", "更新时间"
|
|
])
|
|
|
|
for o in orders:
|
|
writer.writerow([
|
|
o.id, o.user_id, o.order_type, o.item_id, o.item_name,
|
|
float(o.amount) if o.amount else 0,
|
|
o.coupon_id,
|
|
float(o.discount_amount) if o.discount_amount else 0,
|
|
o.payment_method, o.payment_id,
|
|
o.status,
|
|
o.created_at.isoformat() if o.created_at else "",
|
|
o.updated_at.isoformat() if o.updated_at else "",
|
|
])
|
|
|
|
output.seek(0)
|
|
return StreamingResponse(
|
|
iter([output.getvalue()]),
|
|
media_type="text/csv",
|
|
headers={
|
|
"Content-Disposition": f"attachment; filename=orders_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
|
|
},
|
|
)
|