This commit is contained in:
2026-06-11 09:25:55 +08:00
parent 467cffb97d
commit 524efec4e7
+192 -1
View File
@@ -12,6 +12,103 @@ except AttributeError:
else: else:
ssl._create_default_https_context = _create_unverified_https_context ssl._create_default_https_context = _create_unverified_https_context
# 猴子补丁修复 alipay-sdk-python 的 ResponseException 错误
# 修复 WebUtils.py 中 bytes 和 str 拼接的问题
def fix_alipay_web_utils():
try:
import sys
from alipay.aop.api.util import WebUtils
# 保存原始的 do_post 函数
original_do_post = WebUtils.do_post
def patched_do_post(url, query_string, headers, params, charset, timeout):
try:
return original_do_post(url, query_string, headers, params, charset, timeout)
except Exception as e:
# 如果是 ResponseException,尝试修复错误信息
error_msg = str(e)
if "invalid http status" in error_msg or "ResponseException" in error_msg:
# 重新实现一个更安全的版本
import urllib.request
import urllib.error
import urllib.parse
import ssl
import json
import socket
from alipay.aop.api.exception.ResponseException import ResponseException
logger.warning("Alipay SDK ResponseException detected, using fallback request")
# 构造请求
if query_string:
full_url = url + "?" + query_string
else:
full_url = url
# 准备 headers
req_headers = {}
if headers:
for key, value in headers.items():
req_headers[key] = value
req_headers["Content-type"] = "application/x-www-form-urlencoded;charset=" + charset
req_headers["Connection"] = "Keep-Alive"
req_headers["Cache-Control"] = "no-cache"
req_headers["User-Agent"] = "Mozilla/4.0 (compatible; MSIE 6.0; Windows XP; SV1)"
# 准备 post data
post_data = ""
if params:
post_params = []
for key, value in params.items():
post_params.append(
"%s=%s" % (key, urllib.parse.quote(str(value), encoding=charset))
)
post_data = "&".join(post_params)
logger.debug(f"Request URL: {full_url}")
logger.debug(f"Request data: {post_data[:200]}...")
# 发送请求
try:
req = urllib.request.Request(full_url, data=post_data.encode(charset) if post_data else None, headers=req_headers)
# 创建不验证证书的 context
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as response:
response_body = response.read().decode(charset)
logger.debug(f"Response: {response_body[:500]}...")
return response_body
except urllib.error.HTTPError as e:
try:
error_body = e.read().decode(charset)
except:
error_body = str(e)
logger.error(f"Alipay HTTP error: {e.code}, body: {error_body}")
raise ResponseException(str(e.code) + "," + error_body)
except Exception as e2:
logger.exception(f"Alipay request failed")
raise
else:
raise
# 应用补丁
WebUtils.do_post = patched_do_post
logger.info("Alipay WebUtils monkey patch applied successfully")
except ImportError:
# alipay 模块还没安装,跳过
pass
except Exception:
logger.exception("Failed to apply Alipay WebUtils monkey patch")
# 应用补丁
fix_alipay_web_utils()
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@@ -345,17 +442,22 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str
request = AlipayTradePrecreateRequest(biz_model=model) request = AlipayTradePrecreateRequest(biz_model=model)
# 执行API调用 # 执行API调用
logger.info(f"Calling Alipay trade.precreate for order: {order.order_no}")
response_content = client.execute(request) response_content = client.execute(request)
if not response_content: if not response_content:
logger.error(f"Alipay precreate failed: empty response, order_no={order.order_no}") logger.error(f"Alipay precreate failed: empty response, order_no={order.order_no}")
return None return None
logger.info(f"Alipay response received: {response_content[:500]}...")
# 解析响应结果 # 解析响应结果
response = AlipayTradePrecreateResponse() response = AlipayTradePrecreateResponse()
response.parse_response_content(response_content) response.parse_response_content(response_content)
if response.is_success(): if response.is_success():
qr_url = response.qr_code qr_url = response.qr_code
logger.info(f"Alipay precreate success, qr_url: {qr_url}")
return qr_url return qr_url
else: else:
logger.error( logger.error(
@@ -365,11 +467,100 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str
) )
return None return None
except Exception: except Exception as e:
# 特殊处理 bytes 和 str 拼接的错误
error_str = str(e)
if "can only concatenate str (not \"bytes\") to str" in error_str:
logger.error(f"Alipay SDK bytes/str concat error, order_no={order.order_no}")
# 尝试直接用 urllib 发送请求作为备选方案
return _fallback_alipay_request(order, db_configs)
else:
logger.exception(f"Alipay precreate exception: order_no={order.order_no}") logger.exception(f"Alipay precreate exception: order_no={order.order_no}")
return None return None
def _fallback_alipay_request(order: PaymentOrder, db_configs: dict[str, str]) -> str | None:
"""
备选方案:直接使用 urllib 发送支付宝请求,绕过 SDK 的 WebUtils
"""
try:
import urllib.request
import urllib.error
import urllib.parse
import ssl
from alipay.aop.api.util.Signature import sign_with_rsa
from alipay.aop.api.response.AlipayTradePrecreateResponse import (
AlipayTradePrecreateResponse,
)
logger.info("Using fallback Alipay request method")
app_id = db_configs.get("payment_alipay_app_id", "")
private_key = db_configs.get("payment_alipay_private_key", "")
gateway = db_configs.get("payment_alipay_gateway", "https://openapi.alipay.com/gateway.do")
charset = "utf-8"
# 构造请求参数
biz_content = {
"out_trade_no": order.order_no,
"total_amount": f"{order.amount:.2f}",
"subject": f"充值订单 {order.order_no}",
"product_code": "QR_CODE_OFFLINE"
}
params = {
"app_id": app_id,
"method": "alipay.trade.precreate",
"format": "json",
"charset": charset,
"sign_type": "RSA2",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"biz_content": str(biz_content).replace("'", "\"")
}
# 排序并签名
sorted_keys = sorted(params.keys())
sign_content = "&".join([f"{k}={params[k]}" for k in sorted_keys])
sign = sign_with_rsa(private_key.encode(charset), sign_content.encode(charset))
params["sign"] = sign
# 构造请求
query_string = urllib.parse.urlencode(params)
full_url = gateway
# 创建不验证证书的 context
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
# 发送请求
req = urllib.request.Request(full_url, data=query_string.encode(charset))
req.add_header("Content-type", f"application/x-www-form-urlencoded;charset={charset}")
with urllib.request.urlopen(req, timeout=30, context=ctx) as response:
response_body = response.read().decode(charset)
logger.info(f"Fallback Alipay response: {response_body[:500]}...")
# 解析响应
resp = AlipayTradePrecreateResponse()
resp.parse_response_content(response_body)
if resp.is_success():
return resp.qr_code
else:
logger.error(
f"Fallback Alipay precreate failed: code={resp.code}, "
f"msg={resp.msg}, sub_code={resp.sub_code}, "
f"sub_msg={resp.sub_msg}"
)
return None
except Exception:
logger.exception("Fallback Alipay request also failed")
return None
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Alipay callback verification # Alipay callback verification
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------