修复IP白名单失败不换商户:跳过查单并放行卡住的旧单。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-14 20:31:01 +08:00
parent 2044df3849
commit 2f3889855c
2 changed files with 69 additions and 3 deletions

View File

@@ -1394,6 +1394,18 @@ def reconcile_shenhe_wechat_bills(shenhe_danhao):
wx_data = query_wechat_transfer_bill(rec.tixian_id)
if wx_data is None:
# IP 未放行时创建/查单都会失败。若从未拿到 package说明没进待确认
# 标失败放行换商户;否则会永远卡在「处理中」并反复打第一个商户。
has_pkg = bool(_parse_package_info(rec.wechat_package))
if rec.zhuangtai == 0 and not has_pkg:
rec.zhuangtai = 2
rec.fail_reason = '查单不可用且未待确认常见于IP白名单已换其他商户重试'
rec.save(update_fields=['zhuangtai', 'fail_reason', 'update_time'])
logger.warning(
'对账查单不可用已放行换号 shenhe=%s bill=%s mch=%s',
shenhe_danhao, rec.tixian_id, rec.mch_id,
)
continue
logger.error(
'对账查单不可用 shenhe_danhao=%s bill=%s',
shenhe_danhao, rec.tixian_id,

View File

@@ -90,11 +90,25 @@ def _is_wx_unsafe_to_switch(err_code='', err_msg='', status_code=None):
return any(k in blob for k in keys)
def _is_wx_ip_denied(err_code='', err_msg=''):
"""商户未配置服务器出口 IP 白名单(创建/查单都会拦)。"""
blob = f'{err_code} {err_msg}'.upper().replace('\t', '')
if '268491067' in blob:
return True
if 'IP' in blob and '不允许' in blob:
return True
return any(k in blob for k in (
'IP地址不允许', 'IP 地址不允许', '不允许调用该接口', '不允许调用接口',
'NOT_ALLOW_IP', 'IP_DENIED',
))
def _is_wx_clear_switchable_fail(err_code='', err_msg='', status_code=None):
"""
明确未建单的业务失败:可静默换下一商户(日额度/余额不足/无权限等)。
查单失败(None)时也允许换——避免首户卡死后永不试下一户。
明确未建单的业务失败:可静默换下一商户(日额度/余额不足/IP白名单/无权限等)。
"""
if _is_wx_ip_denied(err_code, err_msg):
return True
if _is_wx_unsafe_to_switch(err_code, err_msg, status_code):
return False
if status_code in (400, 401, 403):
@@ -267,6 +281,12 @@ def process_audit_collect(request):
if not mch_cfgs or not wx_cfg_is_complete(mch_cfgs[0]):
logger.error('微信打款配置不完整 enabled_count=%s', len(mch_cfgs))
return Response({'code': 11, 'msg': '系统配置异常,请联系客服'})
if len(mch_cfgs) < 2:
logger.warning(
'启用且配置完整的转账商户仅 %s 个,无法轮换 mch_ids=%s '
'(后台多个启用但私钥路径无效会被跳过)',
len(mch_cfgs), [c.get('MCHID') for c in mch_cfgs],
)
ctx, err = resolve_collect_context(
yonghuid,
@@ -479,13 +499,47 @@ def process_audit_collect(request):
if resp.status_code == 200 and (not err_code or err_msg == '微信接口调用失败'):
err_msg = f'微信状态:{state200 or "未知"}'
logger.error(
'微信发起转账未进入待确认: bill=%s mch=%s code=%s msg=%s HTTP%s state=%s',
'微信发起转账未进入待确认: bill=%s mch=%s code=%s msg=%s HTTP%s state=%s body=%s',
tixian_id, mch_id, err_code, err_msg, resp.status_code, state200,
json.dumps(wx_result, ensure_ascii=False)[:500],
)
has_next = mch_idx < len(mch_cfgs) - 1
unsafe = _is_wx_unsafe_to_switch(err_code, err_msg, resp.status_code)
clear_fail = _is_wx_clear_switchable_fail(err_code, err_msg, resp.status_code)
ip_denied = _is_wx_ip_denied(err_code, err_msg)
# IP 白名单/明确 4xx创建失败时查单也会被同一 IP 拦住,查了只会误卡「处理中」
# 直接标失败并静默换下一户,绝不回前端中间错误
if ip_denied or (clear_fail and resp.status_code in (400, 401, 403)):
user_msg = err_msg or '微信收款发起失败,请稍后重试'
last_user_msg = user_msg
last_was_balance = _is_wx_balance_insufficient(err_code, err_msg)
_mark_auto_record_failed(tixian_id, f'[{mch_id}] {user_msg}')
if has_next:
logger.warning(
'商户明确失败(含IP白名单),跳过查单静默换下一户 shenhe=%s '
'from=%s code=%s msg=%s http=%s idx=%s/%s',
shenhe_danhao, mch_id, err_code, err_msg, resp.status_code,
mch_idx + 1, len(mch_cfgs),
)
continue
if quota_reserved:
release_collect_quota_for_record(
TixianAutoRecord.objects.get(tixian_id=tixian_id),
)
quota_reserved = False
if ip_denied:
return Response({
'code': 99,
'msg': '微信商户未配置服务器IP白名单或配置未生效请联系管理员检查各商户号',
})
if last_was_balance:
return Response({
'code': 99,
'msg': '运营账户资金不足,需等管理员充值后才能提现',
})
return Response({'code': 99, 'msg': user_msg})
wx_q = query_wechat_transfer_bill(tixian_id, wx_cfg=wx_cfg)