Files
gitlab-instance-0a899031_pa…/app/routers/nomadvip.py
2026-03-10 05:25:08 -05:00

155 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""nomadvip 项目支付接口:/nomadvip/*,固定 ZPAY"""
import logging
import random
import string
import time
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import RedirectResponse, JSONResponse
from ..config import BASE_URL, ZPAY_PID, ZPAY_KEY
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)
@router.post("/payh5")
async def nomadvip_payh5(item: dict):
"""nomadvip 专用:固定 ZPAY表单提交支付"""
print("nomadvip/payh5 item:", item)
total_fee = int(item.get("total_fee", 8800))
total_fee_yuan = f"{total_fee / 100:.2f}"
user_id = item.get("user_id", "unknown")
pay_type = (item.get("type") or "vip").lower()
random_suffix = "".join(random.choices(string.hexdigits.lower(), k=8))
timestamp = int(time.time())
order_id = f"{user_id}_{pay_type}_order_{timestamp}_{random_suffix}"
type_map = {
"book": "购买书籍",
"meetup": "数字游民社区报名",
"video": "视频解锁",
"vip": "VIP会员充值",
}
name = type_map.get(pay_type, "VIP会员充值")
notify_url = f"{BASE_URL.rstrip('/')}/nomadvip/zpay_notify"
channel = item.get("channel", "alipay")
result = _nomadvip_zpay.create_order(
order_id=order_id,
name=name,
total_fee_yuan=total_fee_yuan,
pay_type=channel,
notify_url=notify_url,
return_url=item.get("return_url"),
client_ip=item.get("client_ip"),
)
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",
"submit_method": "POST",
}
@router.post("/payh5/redirect")
async def nomadvip_payh5_redirect(item: dict, request: Request):
"""nomadvip 专用ZPAY mapi 接口"""
logging.info(f"nomadvip payh5/redirect 请求: item={item}")
try:
total_fee = float(item.get("total_fee", 8800))
except (TypeError, ValueError):
logging.warning(f"nomadvip payh5/redirect 无效 total_fee: {item.get('total_fee')}")
total_fee = 8800.0
total_fee_yuan = f"{total_fee / 100:.2f}"
user_id = item.get("user_id", "unknown")
pay_type = (item.get("type") or "vip").lower()
channel = item.get("channel", "alipay")
return_url = item.get("return_url")
device = item.get("device", "pc")
client_ip = item.get("client_ip") or (request.client.host if request.client else "127.0.0.1")
forwarded = request.headers.get("x-forwarded-for")
if forwarded:
client_ip = forwarded.split(",")[0].strip()
random_suffix = "".join(random.choices(string.hexdigits.lower(), k=8))
timestamp = int(time.time())
order_id = f"{user_id}_{pay_type}_order_{timestamp}_{random_suffix}"
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(
order_id=order_id,
name=name,
total_fee_yuan=total_fee_yuan,
pay_type=channel,
notify_url=notify_url,
return_url=return_url,
client_ip=client_ip,
device=device,
)
if result.get("status") != "ok":
err_msg = result.get("msg", "ZPAY mapi 创建订单失败")
logging.error(f"nomadvip payh5/redirect ZPAY 创建订单失败: {err_msg}, result={result}")
raise HTTPException(status_code=500, detail=err_msg)
payurl = result.get("payurl")
payurl2 = result.get("payurl2")
img = result.get("img")
if device == "pc" and img:
return JSONResponse(status_code=200, content={
"status": "ok", "qrcode_mode": True, "order_id": order_id,
"img": img, "payurl": payurl, "return_url": return_url, "channel": channel,
})
if device == "wechat" and channel.lower() in ("wxpay", "wx", "wechat"):
return JSONResponse(status_code=200, content={"use_form": True, "order_id": order_id})
headers = {"X-Pay-Order-Id": order_id}
if device == "h5" and channel.lower() in ("wxpay", "wx", "wechat") and payurl2:
return RedirectResponse(url=payurl2, status_code=302, headers=headers)
if payurl:
return RedirectResponse(url=payurl, status_code=302, headers=headers)
logging.error(f"nomadvip payh5/redirect ZPAY 未返回支付链接: device={device}, channel={channel}, result keys={list(result.keys())}")
raise HTTPException(status_code=500, detail="ZPAY 未返回支付链接")
@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)
if result.get("error"):
logging.warning(f"nomadvip ZPAY 订单查询失败: {result.get('error')}")
return result
@router.get("/zpay_notify")
@router.post("/zpay_notify")
async def nomadvip_zpay_notify(request: Request):
"""nomadvip 专用ZPAY 异步通知"""
if request.method == "GET":
data = dict(request.query_params)
else:
form_data = await request.form()
data = {k: v for k, v in form_data.items()}
logging.info(f"nomadvip ZPAY 异步通知: {data}")
order_id = data.get("out_trade_no", "")
if order_id in processed_orders:
logging.info(f"nomadvip 订单已处理,跳过: {order_id}")
return "success"
if not _nomadvip_zpay.verify_notify(data):
logging.error(f"nomadvip ZPAY 验签失败: {data}")
raise HTTPException(status_code=400, detail="sign error")
parsed = _nomadvip_zpay.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}")
return "success"
pay_price = parsed.get("pay_price") or data.get("money", "")
try:
handle_payment_success(order_id, pay_price, "nomadvip-zpay", 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 _nomadvip_zpay.success_response()