修复多商户轮换死锁,并加固回调解密与证书校验。
余额不足等明确错误在查单宽限 PENDING 时会卡死不换号;现确认无单后切换,停用商户仍可解回调、请求带 notify_url。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -45,11 +45,29 @@ def row_to_wx_cfg(row) -> Dict:
|
||||
}
|
||||
|
||||
|
||||
def wx_cfg_is_complete(cfg: Optional[Dict]) -> bool:
|
||||
def wx_cfg_is_complete(cfg: Optional[Dict], *, require_key_file: bool = True) -> bool:
|
||||
if not cfg:
|
||||
return False
|
||||
required = ('MCHID', 'APPID', 'API_V3_KEY', 'PRIVATE_KEY_PATH', 'CERT_SERIAL_NO', 'TRANSFER_SCENE_ID')
|
||||
return all(str(cfg.get(k) or '').strip() for k in required)
|
||||
if not all(str(cfg.get(k) or '').strip() for k in required):
|
||||
return False
|
||||
if require_key_file:
|
||||
pk = str(cfg.get('PRIVATE_KEY_PATH') or '').strip()
|
||||
if not os.path.isfile(pk):
|
||||
logger.warning('商户私钥文件不存在已跳过 mch_id=%s path=%s', cfg.get('MCHID'), pk)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _rows_to_complete_cfgs(rows) -> List[Dict]:
|
||||
result = []
|
||||
for row in rows:
|
||||
cfg = row_to_wx_cfg(row)
|
||||
if wx_cfg_is_complete(cfg):
|
||||
result.append(cfg)
|
||||
else:
|
||||
logger.warning('商户配置不完整已跳过 mch_id=%s id=%s', row.mch_id, row.id)
|
||||
return result
|
||||
|
||||
|
||||
def list_enabled_wx_cfgs() -> List[Dict]:
|
||||
@@ -59,13 +77,7 @@ def list_enabled_wx_cfgs() -> List[Dict]:
|
||||
rows = list(
|
||||
WechatPayMchConfig.objects.filter(enabled=True).order_by('priority', 'id')
|
||||
)
|
||||
result = []
|
||||
for row in rows:
|
||||
cfg = row_to_wx_cfg(row)
|
||||
if wx_cfg_is_complete(cfg):
|
||||
result.append(cfg)
|
||||
else:
|
||||
logger.warning('商户配置不完整已跳过 mch_id=%s id=%s', row.mch_id, row.id)
|
||||
result = _rows_to_complete_cfgs(rows)
|
||||
if result:
|
||||
return result
|
||||
|
||||
@@ -76,7 +88,29 @@ def list_enabled_wx_cfgs() -> List[Dict]:
|
||||
return []
|
||||
|
||||
|
||||
def list_wx_cfgs_for_callback() -> List[Dict]:
|
||||
"""
|
||||
回调验签/解密:包含已停用商户。
|
||||
解密只需 API_V3_KEY;验签缺平台公钥时会自动跳过该户。
|
||||
"""
|
||||
from peizhi.models import WechatPayMchConfig
|
||||
|
||||
rows = list(WechatPayMchConfig.objects.all().order_by('priority', 'id'))
|
||||
result = []
|
||||
for row in rows:
|
||||
cfg = row_to_wx_cfg(row)
|
||||
if str(cfg.get('MCHID') or '').strip() and str(cfg.get('API_V3_KEY') or '').strip():
|
||||
result.append(cfg)
|
||||
if result:
|
||||
return result
|
||||
fb = _settings_fallback_cfg()
|
||||
if str(fb.get('MCHID') or '').strip() and str(fb.get('API_V3_KEY') or '').strip():
|
||||
return [fb]
|
||||
return []
|
||||
|
||||
|
||||
def get_wx_cfg_by_mch_id(mch_id: str) -> Optional[Dict]:
|
||||
"""按 mch_id 取配置(含停用);查单需可签名,故要求私钥文件存在。"""
|
||||
mch_id = (mch_id or '').strip()
|
||||
if not mch_id:
|
||||
return None
|
||||
|
||||
@@ -102,8 +102,8 @@ def verify_wechat_sign(headers, body, wx_cfg=None):
|
||||
if wx_cfg:
|
||||
cfgs = [wx_cfg]
|
||||
else:
|
||||
from utils.wechat_mch_service import list_enabled_wx_cfgs
|
||||
cfgs = list_enabled_wx_cfgs()
|
||||
from utils.wechat_mch_service import list_wx_cfgs_for_callback
|
||||
cfgs = list_wx_cfgs_for_callback()
|
||||
|
||||
for cfg in cfgs:
|
||||
cert_path = _resolve_platform_pub_path(cfg, serial=serial)
|
||||
@@ -139,10 +139,10 @@ def decrypt_callback_with_merchants(associated_data, nonce, ciphertext):
|
||||
仅对启用商户密钥做 AES 尝试(通常很少),成功后返回 (plaintext, wx_cfg)。
|
||||
业务侧再用 out_bill_no 反查记录上的 mch_id,对齐一致性。
|
||||
"""
|
||||
from utils.wechat_mch_service import list_enabled_wx_cfgs
|
||||
from utils.wechat_mch_service import list_wx_cfgs_for_callback
|
||||
|
||||
last_err = None
|
||||
for cfg in list_enabled_wx_cfgs():
|
||||
for cfg in list_wx_cfgs_for_callback():
|
||||
try:
|
||||
text = decrypt_callback_ciphertext(associated_data, nonce, ciphertext, wx_cfg=cfg)
|
||||
return text, cfg
|
||||
|
||||
Reference in New Issue
Block a user