refactor: replace direct WeChat/Alipay with unified pay-api gateway

Switch from direct WeChat Pay / Alipay integrations to the unified
宇之然 pay-api gateway (HMAC-SHA256 auth). Removes wechat_pay.py,
keeps PaymentGateway abstraction, adds UnifiedPayService. Simplifies
payment.py create_order to {plan, pay_type} params. Single webhook
endpoint replaces separate WeChat/Alipay notify handlers.
This commit is contained in:
TradeMate Dev
2026-05-29 18:36:50 +08:00
parent 5d2bced39f
commit 3e39cf0170
34 changed files with 973 additions and 424 deletions
+55 -33
View File
@@ -1,10 +1,9 @@
from fastapi import APIRouter, Depends, HTTPException, Request, Header
from fastapi import APIRouter, Depends, HTTPException, Request, Query
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
from typing import Optional
from app.database import get_db
from app.services.payment import PaymentService
from app.services.wechat_pay import WeChatPayService
from app.api.v1.deps import get_current_user_id
from app.core.csrf import require_csrf_token
@@ -13,12 +12,12 @@ router = APIRouter()
class CreateOrderRequest(BaseModel):
plan: str
pay_type: str = "jsapi"
pay_type: str = "alipay"
class PaymentCallbackRequest(BaseModel):
payment_id: str
success: bool
class RefundRequest(BaseModel):
order_no: str
reason: str = ""
@router.get("/plans")
@@ -50,42 +49,65 @@ async def create_order(
raise HTTPException(status_code=400, detail=str(e))
@router.post("/callback")
async def payment_callback(
data: PaymentCallbackRequest,
@router.get("/query/{order_no}")
async def query_payment(
order_no: str,
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
svc = PaymentService(db)
try:
return await svc.query_payment(user_id, order_no)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.get("/transactions")
async def list_transactions(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
svc = PaymentService(db)
return await svc.list_transactions(user_id, page, size)
@router.post("/refund")
async def refund(
data: RefundRequest,
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
_csrf: str = Depends(require_csrf_token),
):
svc = PaymentService(db)
success = await svc.handle_payment_callback(data.payment_id, data.success)
if not success:
raise HTTPException(status_code=404, detail="Order not found")
return {"status": "ok"}
try:
return await svc.refund(user_id, data.order_no, data.reason)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/notify")
async def wechat_pay_notify(request: Request, db: AsyncSession = Depends(get_db)):
@router.post("/webhook")
async def unified_webhook(request: Request, db: AsyncSession = Depends(get_db)):
body = await request.body()
body_str = body.decode("utf-8")
headers = dict(request.headers)
wxpay = WeChatPayService()
if not wxpay.verify_callback(headers, body_str):
raise HTTPException(status_code=401, detail="签名验证失败")
import json
data = json.loads(body_str)
resource = data.get("resource", {})
ciphertext = resource.get("ciphertext", "")
nonce = resource.get("nonce", "")
associated_data = resource.get("associated_data", "")
try:
data = json.loads(body_str)
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="无效的 JSON")
plaintext = wxpay.decrypt_callback(ciphertext, nonce, associated_data)
pay_data = json.loads(plaintext)
out_trade_no = pay_data.get("out_trade_no", "")
trade_state = pay_data.get("trade_state", "")
event = data.get("event", "")
pay_data = data.get("data", {})
merchant_order_id = pay_data.get("merchant_order_id", "")
order_id = pay_data.get("order_id", "")
transaction_id = pay_data.get("transaction_id", "")
amount = pay_data.get("amount", 0)
success = event == "recharge.completed"
success = trade_state == "SUCCESS"
svc = PaymentService(db)
await svc.handle_payment_callback(out_trade_no, success)
return {"code": "SUCCESS", "message": "OK"}
await svc.handle_callback(
merchant_order_id, order_id, transaction_id,
success, amount, body_str,
)
return {"code": 0, "message": "OK"}