This commit is contained in:
eric
2026-03-10 07:46:31 -05:00
parent 192097be0f
commit 82edb2dd7f
2 changed files with 88 additions and 15 deletions

View File

@@ -108,7 +108,7 @@ async def root():
"status": "ok",
"service": "payjsapi",
"routes": {
"nomadvip": "/nomadvip/payh5, /nomadvip/payh5/redirect, /nomadvip/zpay_order_status, /nomadvip/zpay_notify",
"nomadvip": "/nomadvip/payh5, /nomadvip/payh5/redirect, /nomadvip/zpay_order_status, /nomadvip/zpay_notify, /nomadvip/xorpay_notify",
"digital": "/digital/payh5, /digital/payh5/redirect, /digital/zpay_order_status, /digital/zpay_notify, /digital/xorpay_notify",
"cnomadcna": "/cnomadcna/payh5, ...",
"nomadlms": "/nomadlms/payh5, ...",

View File

@@ -1,4 +1,4 @@
"""nomadvip 项目支付接口:/nomadvip/*固定 ZPAY"""
"""nomadvip 项目支付接口:/nomadvip/*支持 xorpay/zpayPAYMENT_PROVIDER"""
import logging
import random
import string
@@ -7,18 +7,23 @@ import time
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import RedirectResponse, JSONResponse
from ..config import BASE_URL, ZPAY_PID, ZPAY_KEY
from ..config import BASE_URL
from ..payment import get_payment_provider
from ..payment.zpay import ZPayProvider
from ..services import handle_payment_success, processed_orders
router = APIRouter(prefix="/nomadvip", tags=["nomadvip"])
_nomadvip_zpay = ZPayProvider(pid=ZPAY_PID, key=ZPAY_KEY)
def _get_notify_path(provider_name: str) -> str:
return f"{BASE_URL.rstrip('/')}/nomadvip/xorpay_notify" if provider_name == "xorpay" else f"{BASE_URL.rstrip('/')}/nomadvip/zpay_notify"
@router.post("/payh5")
async def nomadvip_payh5(item: dict):
"""nomadvip 专用:固定 ZPAY表单提交支付"""
"""nomadvip 专用:创建支付订单xorpay 直接返回收银台参数zpay 返回表单参数"""
print("nomadvip/payh5 item:", item)
provider = get_payment_provider()
total_fee = int(item.get("total_fee", 8800))
total_fee_yuan = f"{total_fee / 100:.2f}"
user_id = item.get("user_id", "unknown")
@@ -33,9 +38,9 @@ async def nomadvip_payh5(item: dict):
"vip": "VIP会员充值",
}
name = type_map.get(pay_type, "VIP会员充值")
notify_url = f"{BASE_URL.rstrip('/')}/nomadvip/zpay_notify"
notify_url = _get_notify_path(provider.name)
channel = item.get("channel", "alipay")
result = _nomadvip_zpay.create_order(
result = provider.create_order(
order_id=order_id,
name=name,
total_fee_yuan=total_fee_yuan,
@@ -44,20 +49,30 @@ async def nomadvip_payh5(item: dict):
return_url=item.get("return_url"),
client_ip=item.get("client_ip"),
)
if provider.name == "xorpay":
return {
"status": "ok",
"xorpay_params": result.get("params", {}),
"xorpay_url": result.get("pay_url", ""),
"provider": provider.name,
}
params = result.get("params", {})
params_str = {k: str(v) for k, v in params.items() if v is not None and str(v).strip() != ""}
return {
"status": "ok",
"params": params_str,
"pay_url": result.get("pay_url", ""),
"provider": "zpay",
"provider": provider.name,
"submit_method": "POST",
}
@router.post("/payh5/redirect")
async def nomadvip_payh5_redirect(item: dict, request: Request):
"""nomadvip 专用ZPAY mapi 接口"""
"""nomadvip 专用ZPAY mapi 接口xorpay 无需 redirect直接 payh5 即可)"""
provider = get_payment_provider()
if provider.name != "zpay":
raise HTTPException(status_code=400, detail="payh5/redirect 仅支持 zpayxorpay 请直接使用 payh5")
logging.info(f"nomadvip payh5/redirect 请求: item={item}")
try:
total_fee = float(item.get("total_fee", 8800))
@@ -80,7 +95,9 @@ async def nomadvip_payh5_redirect(item: dict, request: Request):
type_map = {"book": "购买书籍", "meetup": "数字游民社区报名", "video": "视频解锁", "vip": "VIP会员充值"}
name = type_map.get(pay_type, "VIP会员充值")
notify_url = f"{BASE_URL.rstrip('/')}/nomadvip/zpay_notify"
result = _nomadvip_zpay.create_order_api(
if not isinstance(provider, ZPayProvider):
raise HTTPException(status_code=400, detail="payh5/redirect 仅支持 zpay")
result = provider.create_order_api(
order_id=order_id,
name=name,
total_fee_yuan=total_fee_yuan,
@@ -115,17 +132,73 @@ async def nomadvip_payh5_redirect(item: dict, request: Request):
@router.get("/zpay_order_status")
async def nomadvip_zpay_order_status(out_trade_no: str):
"""nomadvip 专用:查询 ZPAY 订单支付状态"""
result = _nomadvip_zpay.query_order_status(out_trade_no, timeout=15)
"""nomadvip 专用:查询 ZPAY 订单支付状态xorpay 无此接口)"""
provider = get_payment_provider()
if not isinstance(provider, ZPayProvider):
raise HTTPException(status_code=400, detail="zpay_order_status 仅支持 zpay")
result = provider.query_order_status(out_trade_no, timeout=15)
if result.get("error"):
logging.warning(f"nomadvip ZPAY 订单查询失败: {result.get('error')}")
return result
@router.get("/order_status")
async def nomadvip_order_status(order_id: str):
"""nomadvip 通用查询订单支付状态xorpay 查 PocketBasezpay 查 ZPAY"""
provider = get_payment_provider()
if isinstance(provider, ZPayProvider):
result = provider.query_order_status(order_id, timeout=15)
if result.get("error"):
logging.warning(f"nomadvip ZPAY 订单查询失败: {result.get('error')}")
return result
# xorpay查 PocketBase
try:
from ..services.pb import get_pb_client
pb = get_pb_client()
records = pb.collection("payments").get_list(1, 1, {"filter": f'order_id = "{order_id}"'})
return {"paid": len(records.items) > 0}
except Exception as e:
logging.warning(f"nomadvip PocketBase 查询失败: {e}")
return {"paid": False, "error": str(e)}
@router.post("/xorpay_notify")
async def nomadvip_xorpay_notify(request: Request):
"""nomadvip 专用XorPay 异步通知"""
form_data = await request.form()
data = {k: v for k, v in form_data.items()}
logging.info(f"nomadvip XorPay 异步通知: {data}")
order_id = data.get("order_id", "")
if order_id in processed_orders:
logging.info(f"nomadvip 订单已处理,跳过: {order_id}")
return "ok"
provider = get_payment_provider()
if provider.name != "xorpay":
raise HTTPException(status_code=400, detail="xorpay_notify only for xorpay")
if not provider.verify_notify(data):
logging.error(f"nomadvip XorPay 验签失败: {data}")
raise HTTPException(status_code=400, detail="sign error")
parsed = provider.parse_notify(data)
if parsed.get("trade_status") != "success":
return "ok"
pay_price = parsed.get("pay_price", data.get("pay_price", ""))
try:
handle_payment_success(order_id, pay_price, "nomadvip-xorpay", use_memos=True)
logging.info(f"nomadvip 业务处理完成: order_id={order_id}")
except Exception as e:
logging.error(f"nomadvip PocketBase/Memos 处理异常: {e}", exc_info=True)
raise
return provider.success_response()
@router.get("/zpay_notify")
@router.post("/zpay_notify")
async def nomadvip_zpay_notify(request: Request):
"""nomadvip 专用ZPAY 异步通知"""
provider = get_payment_provider()
if provider.name != "zpay":
raise HTTPException(status_code=400, detail="zpay_notify only for zpay")
if request.method == "GET":
data = dict(request.query_params)
else:
@@ -136,10 +209,10 @@ async def nomadvip_zpay_notify(request: Request):
if order_id in processed_orders:
logging.info(f"nomadvip 订单已处理,跳过: {order_id}")
return "success"
if not _nomadvip_zpay.verify_notify(data):
if not provider.verify_notify(data):
logging.error(f"nomadvip ZPAY 验签失败: {data}")
raise HTTPException(status_code=400, detail="sign error")
parsed = _nomadvip_zpay.parse_notify(data)
parsed = provider.parse_notify(data)
trade_status = parsed.get("trade_status") or data.get("trade_status", "")
if trade_status != "success" and trade_status != "TRADE_SUCCESS":
logging.info(f"nomadvip 非成功状态,跳过: trade_status={trade_status}")
@@ -151,4 +224,4 @@ async def nomadvip_zpay_notify(request: Request):
except Exception as e:
logging.error(f"nomadvip PocketBase/Memos 处理异常: {e}", exc_info=True)
raise
return _nomadvip_zpay.success_response()
return provider.success_response()