feat: AI routing DB-driven, payment gateway full integration, WeChat mini-program CI/CD
- AI routing rules now stored in system_configs DB table instead of hardcoded config - Multi-model support via name|model composite key for same-provider routing - UnifiedPayService with HMAC-SHA256 gateway integration (alipay/wechat) - Admin payment panel: list, stats, search, filter, refund - WeChat mini-program CI/CD via miniprogram-ci (v1.0.9) - Translation quota extended to LLM provider tier - SearchService with DB-driven provider config (bing/google_cse/searxng) - Footer cleanup across admin/workspace/uni-app - Private key excluded from git tracking
This commit is contained in:
@@ -64,6 +64,7 @@ class UnifiedPayService(PaymentGateway):
|
||||
payment_method = "wechat"
|
||||
elif payment_method == "pc":
|
||||
payment_method = "alipay"
|
||||
remark = kwargs.get("remark", "")
|
||||
body = {
|
||||
"merchant_order_id": order_no,
|
||||
"amount": amount / 100,
|
||||
@@ -71,6 +72,8 @@ class UnifiedPayService(PaymentGateway):
|
||||
"subject": description or "TradeMate 会员充值",
|
||||
"notify_url": self.webhook_url,
|
||||
}
|
||||
if remark:
|
||||
body["remark"] = remark
|
||||
result = await self._request("POST", "/v1/pay/orders", body)
|
||||
out = {
|
||||
"gateway_order_id": result.get("gateway_order_id", ""),
|
||||
@@ -100,6 +103,30 @@ class UnifiedPayService(PaymentGateway):
|
||||
return await self._request("GET", f"/v1/pay/refunds/{order_no}")
|
||||
|
||||
def verify_callback(self, headers: dict, body: str) -> bool:
|
||||
auth = headers.get("authorization", headers.get("Authorization", ""))
|
||||
if not auth.startswith("PAY "):
|
||||
logger.warning("Webhook missing PAY Authorization header")
|
||||
return False
|
||||
parts = auth[4:].strip().split(":")
|
||||
if len(parts) != 3:
|
||||
logger.warning("Webhook invalid Authorization format")
|
||||
return False
|
||||
api_key, timestamp, signature = parts
|
||||
if api_key != self.api_key:
|
||||
logger.warning("Webhook API key mismatch")
|
||||
return False
|
||||
now = int(time.time())
|
||||
if abs(now - int(timestamp)) > 300:
|
||||
logger.warning("Webhook timestamp expired")
|
||||
return False
|
||||
body_sha256 = hashlib.sha256(body.encode()).hexdigest()
|
||||
sign_str = f"POST\n/api/v1/payment/webhook\n{timestamp}\n{body_sha256}"
|
||||
expected = hmac.new(
|
||||
self.api_secret.encode(), sign_str.encode(), hashlib.sha256
|
||||
).hexdigest()
|
||||
if not hmac.compare_digest(expected, signature):
|
||||
logger.warning("Webhook signature mismatch")
|
||||
return False
|
||||
return True
|
||||
|
||||
def parse_callback(self, body: str, headers: dict) -> Dict[str, Any]:
|
||||
@@ -115,3 +142,6 @@ class UnifiedPayService(PaymentGateway):
|
||||
"success": event == "recharge.completed",
|
||||
"raw": payload,
|
||||
}
|
||||
|
||||
async def close_order(self, order_no: str) -> Dict[str, Any]:
|
||||
return await self._request("POST", f"/v1/pay/orders/{order_no}/close")
|
||||
|
||||
Reference in New Issue
Block a user