""" 支付渠道配置 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" }, )