's'
This commit is contained in:
@@ -18,7 +18,8 @@ from ..payment import (
|
||||
resolve_channel,
|
||||
)
|
||||
from ..payment.zpay import ZPayProvider
|
||||
from ..services import handle_payment_success, processed_orders
|
||||
from ..payment.xorpay import XorPayProvider
|
||||
from ..services import handle_payment_success, try_add_processed_order, remove_processed_order
|
||||
|
||||
|
||||
def _safe_log(level: int, message: str, *args, **kwargs) -> None:
|
||||
@@ -28,6 +29,45 @@ def _safe_log(level: int, message: str, *args, **kwargs) -> None:
|
||||
pass
|
||||
|
||||
|
||||
async def _poll_xorpay_until_paid(
|
||||
order_id: str,
|
||||
project_name: str,
|
||||
site_id: str,
|
||||
max_attempts: int = 45,
|
||||
interval_sec: float = 2.0,
|
||||
) -> None:
|
||||
"""创建 XorPay 订单后后台轮询,支付成功即更新 DB,不依赖 notify 回调"""
|
||||
provider = get_payment_provider_by_name("xorpay")
|
||||
if not isinstance(provider, XorPayProvider):
|
||||
return
|
||||
for attempt in range(max_attempts):
|
||||
if attempt > 0:
|
||||
await asyncio.sleep(interval_sec)
|
||||
try:
|
||||
result = await asyncio.to_thread(provider.query_order_status, order_id, 6)
|
||||
if not result.get("paid"):
|
||||
continue
|
||||
pay_price = str(result.get("pay_price", "") or result.get("price", "") or "1")
|
||||
if not try_add_processed_order(order_id):
|
||||
return
|
||||
try:
|
||||
await asyncio.to_thread(
|
||||
handle_payment_success,
|
||||
order_id,
|
||||
pay_price,
|
||||
f"{project_name}-xorpay-poll",
|
||||
use_memos=False,
|
||||
site_id=site_id,
|
||||
)
|
||||
_safe_log(logging.INFO, "%s XorPay 轮询检测到支付 order_id=%s", project_name, order_id)
|
||||
except Exception as e:
|
||||
remove_processed_order(order_id)
|
||||
_safe_log(logging.ERROR, "%s XorPay 轮询更新失败 order_id=%s: %s", project_name, order_id, e)
|
||||
return
|
||||
except Exception as e:
|
||||
_safe_log(logging.WARNING, "%s XorPay 轮询异常 order_id=%s attempt=%s: %s", project_name, order_id, attempt + 1, e)
|
||||
|
||||
|
||||
def create_digital_router(prefix: str, project_name: str, site_id: str | None = None) -> APIRouter:
|
||||
"""
|
||||
创建 digital/cnomadcna/nomadlms 的支付路由
|
||||
@@ -77,6 +117,7 @@ def create_digital_router(prefix: str, project_name: str, site_id: str | None =
|
||||
type_map = {
|
||||
"book": "购买书籍",
|
||||
"meetup": "数字游民社区报名",
|
||||
"salon": "沙龙报名茶歇费",
|
||||
"video": "视频解锁",
|
||||
"vip": "VIP会员充值",
|
||||
}
|
||||
@@ -101,6 +142,8 @@ def create_digital_router(prefix: str, project_name: str, site_id: str | None =
|
||||
notify_url,
|
||||
return_url or "",
|
||||
)
|
||||
if provider.name == "xorpay" and site_id and pay_type in ("salon", "meetup"):
|
||||
asyncio.create_task(_poll_xorpay_until_paid(order_id, project_name, site_id))
|
||||
if provider.name == "xorpay":
|
||||
return {
|
||||
"status": "ok",
|
||||
@@ -261,27 +304,39 @@ def create_digital_router(prefix: str, project_name: str, site_id: str | None =
|
||||
|
||||
@router.post("/xorpay_notify")
|
||||
async def xorpay_notify(request: Request):
|
||||
"""XorPay 异步通知"""
|
||||
"""XorPay 异步通知:先返回 200 避免超时重试,后台异步更新 solanRed/site_vip"""
|
||||
form_data = await request.form()
|
||||
data = {k: v for k, v in form_data.items()}
|
||||
_safe_log(logging.INFO, "%s XorPay 异步通知: %s", project_name, data)
|
||||
order_id = data.get("order_id", "")
|
||||
if order_id in processed_orders:
|
||||
return "ok"
|
||||
provider = get_payment_provider_by_name("xorpay")
|
||||
if not try_add_processed_order(order_id):
|
||||
return provider.success_response()
|
||||
if not provider.verify_notify(data):
|
||||
_safe_log(logging.ERROR, "%s XorPay 验签失败: %s", project_name, data)
|
||||
raise HTTPException(status_code=400, detail="sign error")
|
||||
parsed = provider.parse_notify(data)
|
||||
if parsed.get("trade_status") != "success":
|
||||
return "ok"
|
||||
handle_payment_success(
|
||||
order_id,
|
||||
parsed.get("pay_price", data.get("pay_price", "")),
|
||||
f"{project_name}-xorpay",
|
||||
use_memos=False,
|
||||
site_id=site_id,
|
||||
)
|
||||
return provider.success_response()
|
||||
|
||||
# 先返回 200,后台异步更新 DB,用户再次打开首页时 by-wechat 可直接查到 solanRed
|
||||
pay_price = parsed.get("pay_price", data.get("pay_price", ""))
|
||||
provider_name = f"{project_name}-xorpay"
|
||||
|
||||
def _do_update():
|
||||
try:
|
||||
handle_payment_success(
|
||||
order_id,
|
||||
pay_price,
|
||||
provider_name,
|
||||
use_memos=False,
|
||||
site_id=site_id,
|
||||
)
|
||||
except Exception as e:
|
||||
_safe_log(logging.ERROR, "%s XorPay 后台更新失败 order_id=%s: %s", project_name, order_id, e)
|
||||
remove_processed_order(order_id)
|
||||
|
||||
asyncio.create_task(asyncio.to_thread(_do_update))
|
||||
return provider.success_response()
|
||||
|
||||
@router.get("/zpay_notify")
|
||||
@@ -295,7 +350,7 @@ def create_digital_router(prefix: str, project_name: str, site_id: str | None =
|
||||
data = {k: v for k, v in form_data.items()}
|
||||
_safe_log(logging.INFO, "%s ZPAY 异步通知: %s", project_name, data)
|
||||
order_id = data.get("out_trade_no", "")
|
||||
if order_id in processed_orders:
|
||||
if not try_add_processed_order(order_id):
|
||||
return "success"
|
||||
provider = get_payment_provider_by_name("zpay")
|
||||
if not provider.verify_notify(data):
|
||||
@@ -304,13 +359,18 @@ def create_digital_router(prefix: str, project_name: str, site_id: str | None =
|
||||
parsed = provider.parse_notify(data)
|
||||
if parsed.get("trade_status") != "success":
|
||||
return "success"
|
||||
handle_payment_success(
|
||||
order_id,
|
||||
parsed.get("pay_price", data.get("money", "")),
|
||||
f"{project_name}-zpay",
|
||||
use_memos=False,
|
||||
site_id=site_id,
|
||||
)
|
||||
try:
|
||||
await asyncio.to_thread(
|
||||
handle_payment_success,
|
||||
order_id,
|
||||
parsed.get("pay_price", data.get("money", "")),
|
||||
f"{project_name}-zpay",
|
||||
use_memos=False,
|
||||
site_id=site_id,
|
||||
)
|
||||
except Exception:
|
||||
remove_processed_order(order_id)
|
||||
raise
|
||||
return provider.success_response()
|
||||
|
||||
return router
|
||||
|
||||
Reference in New Issue
Block a user