1
This commit is contained in:
@@ -57,18 +57,82 @@ class DailyFileHandler(logging.FileHandler):
|
|||||||
self._file_handler = logging.FileHandler(
|
self._file_handler = logging.FileHandler(
|
||||||
self.baseFilename, mode="a", encoding=self.encoding
|
self.baseFilename, mode="a", encoding=self.encoding
|
||||||
)
|
)
|
||||||
self._file_handler.setFormatter(self.formatter)
|
if self._file_handler:
|
||||||
self._current_date = date_str
|
self._file_handler.emit(record)
|
||||||
self.stream = self._file_handler.stream
|
else:
|
||||||
super().emit(record)
|
super().emit(record)
|
||||||
|
|
||||||
|
|
||||||
_handler = DailyFileHandler(_log_dir)
|
# Add daily file handler
|
||||||
_handler.setFormatter(logging.Formatter(
|
_log_handler = DailyFileHandler(_log_dir)
|
||||||
"[%(asctime)s] %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
|
_log_formatter = logging.Formatter(
|
||||||
))
|
"[%(asctime)s] %(levelname)s %(message)s",
|
||||||
if not logger.handlers:
|
datefmt="%Y-%m-%d %H:%M:%S"
|
||||||
logger.addHandler(_handler)
|
)
|
||||||
|
_log_handler.setFormatter(_log_formatter)
|
||||||
|
logger.addHandler(_log_handler)
|
||||||
|
|
||||||
|
# Monkey patch to fix alipay-sdk-python bytes/str issue
|
||||||
|
def _patch_alipay_sdk():
|
||||||
|
try:
|
||||||
|
from alipay.aop.api.util import WebUtils
|
||||||
|
|
||||||
|
# 直接用我们自己的安全实现替换 do_post
|
||||||
|
def safe_do_post(url, query_string, headers, params, charset, timeout):
|
||||||
|
import http.client
|
||||||
|
import socket
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
parse_result = urlparse(url)
|
||||||
|
if parse_result.scheme == 'https':
|
||||||
|
conn = http.client.HTTPSConnection(
|
||||||
|
parse_result.hostname,
|
||||||
|
parse_result.port or 443,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
conn = http.client.HTTPConnection(
|
||||||
|
parse_result.hostname,
|
||||||
|
parse_result.port or 80,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
body = query_string.encode(charset) if params is None else params
|
||||||
|
conn.request(
|
||||||
|
'POST',
|
||||||
|
parse_result.path + ('?' + parse_result.query if parse_result.query else ''),
|
||||||
|
body,
|
||||||
|
headers
|
||||||
|
)
|
||||||
|
response = conn.getresponse()
|
||||||
|
if response.status == 200:
|
||||||
|
response_body = response.read()
|
||||||
|
# 确保返回的是 str,而不是 bytes
|
||||||
|
if isinstance(response_body, bytes):
|
||||||
|
response_body = response_body.decode(charset)
|
||||||
|
return response_body
|
||||||
|
else:
|
||||||
|
response_body = response.read()
|
||||||
|
if isinstance(response_body, bytes):
|
||||||
|
response_body = response_body.decode(charset)
|
||||||
|
raise Exception(f"invalid http status {response.status}, detail body: {response_body}")
|
||||||
|
except socket.timeout:
|
||||||
|
raise Exception("timeout")
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Apply patch - 直接替换,避免原函数的问题
|
||||||
|
WebUtils.do_post = safe_do_post
|
||||||
|
logger.info("Successfully replaced alipay-sdk-python WebUtils.do_post with safe implementation")
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to patch alipay-sdk-python: {e}")
|
||||||
|
|
||||||
|
# Apply the patch when module is loaded
|
||||||
|
_patch_alipay_sdk()
|
||||||
|
|
||||||
# Orders pending payment for longer than this are auto-cancelled
|
# Orders pending payment for longer than this are auto-cancelled
|
||||||
ORDER_EXPIRE_MINUTES = 5
|
ORDER_EXPIRE_MINUTES = 5
|
||||||
@@ -140,51 +204,6 @@ def _is_mock_mode(db_configs: dict[str, str]) -> bool:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
_alipay_client = None
|
_alipay_client = None
|
||||||
_alipay_client_app_id = None
|
_alipay_client_app_id = None
|
||||||
_alipay_web_utils_patched = False
|
|
||||||
|
|
||||||
|
|
||||||
def _patch_alipay_web_utils():
|
|
||||||
"""Monkey-patch alipay SDK WebUtils.do_post to fix Python 3 bytes/str TypeError.
|
|
||||||
|
|
||||||
The SDK's do_post raises::
|
|
||||||
|
|
||||||
TypeError: can only concatenate str (not "bytes") to str
|
|
||||||
|
|
||||||
when the HTTP response is non-2xx, because ``response.read()`` returns
|
|
||||||
bytes but is used directly in a str concatenation inside the SDK.
|
|
||||||
"""
|
|
||||||
global _alipay_web_utils_patched
|
|
||||||
if _alipay_web_utils_patched:
|
|
||||||
return
|
|
||||||
|
|
||||||
import alipay.aop.api.util.WebUtils as _web_utils
|
|
||||||
|
|
||||||
_original_do_post = _web_utils.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 TypeError as e:
|
|
||||||
err_str = str(e)
|
|
||||||
if "bytes" not in err_str and "str" not in err_str:
|
|
||||||
raise
|
|
||||||
|
|
||||||
# SDK bug: response.read() returned bytes but was used in str concat.
|
|
||||||
# The original HTTP status is lost due to the TypeError; we raise a
|
|
||||||
# descriptive RuntimeError so the caller can handle it gracefully.
|
|
||||||
try:
|
|
||||||
from alipay.aop.api.util.WebUtils import THREAD_LOCAL
|
|
||||||
uuid = THREAD_LOCAL.uuid
|
|
||||||
except Exception:
|
|
||||||
uuid = "???"
|
|
||||||
raise RuntimeError(
|
|
||||||
f"[{uuid}] Alipay HTTP request failed (non-2xx response). "
|
|
||||||
f"The SDK raised a bytes/str TypeError. "
|
|
||||||
f"URL: {url}"
|
|
||||||
) from e
|
|
||||||
|
|
||||||
_web_utils.do_post = _patched_do_post
|
|
||||||
_alipay_web_utils_patched = True
|
|
||||||
|
|
||||||
|
|
||||||
def _get_alipay_client(app_id: str, private_key: str, public_key: str, gateway: str = ""):
|
def _get_alipay_client(app_id: str, private_key: str, public_key: str, gateway: str = ""):
|
||||||
@@ -204,9 +223,6 @@ def _get_alipay_client(app_id: str, private_key: str, public_key: str, gateway:
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Fix SDK's Python 3 bytes/str bug in WebUtils.do_post (once per process)
|
|
||||||
_patch_alipay_web_utils()
|
|
||||||
|
|
||||||
config = AlipayClientConfig()
|
config = AlipayClientConfig()
|
||||||
config.server_url = gateway or "https://openapi.alipay.com/gateway.do"
|
config.server_url = gateway or "https://openapi.alipay.com/gateway.do"
|
||||||
config.app_id = app_id
|
config.app_id = app_id
|
||||||
@@ -422,16 +438,13 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except RuntimeError as e:
|
except Exception as e:
|
||||||
# The patched WebUtils raises RuntimeError on non-2xx HTTP responses
|
# 处理 SDK 内部的 bytes/str 错误
|
||||||
# (the original SDK would have raised a confusing TypeError). This is
|
if "TypeError" in str(e) and ("bytes" in str(e) or "str" in str(e)):
|
||||||
# expected — the Alipay gateway rejected the request for some reason.
|
logger.error(
|
||||||
logger.warning(
|
f"Alipay SDK TypeError (bytes/str issue): order_no={order.order_no}, "
|
||||||
f"Alipay precreate HTTP error: order_no={order.order_no}, "
|
f"error={str(e)}"
|
||||||
f"detail={str(e)}"
|
)
|
||||||
)
|
|
||||||
return None
|
|
||||||
except Exception:
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user