fix: 简化提现收款对账,避免永远卡在收款处理中

- 同一审核单只保留最新一条进行中打款,多次点击不再堆叠阻塞
- 有 package 时查单失败也直接调起微信确认
- 无 package 超过20秒即作废允许重试;转账/网络失败返回明确错误而非 code12

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-10 21:59:28 +08:00
parent 921941a36d
commit 8bc0afb7f4
3 changed files with 73 additions and 72 deletions

View File

@@ -105,8 +105,8 @@ RECONCILE_AUDIT_CLOSED = 'audit_closed'
# 单笔实际到账上限(仅收款时校验)
MAX_SINGLE_COLLECT_AMOUNT = decimal.Decimal('200')
# 查单失败且本地 zhuangtai=0 超过该分钟数,才允许新建打款记录
PENDING_QUERY_GRACE_MINUTES = 2
# 查单失败且本地 zhuangtai=0 超过该数,才短暂提示「发起中」
PENDING_QUERY_GRACE_SECONDS = 20
def _wait_confirm_payload(auto_record, package):
@@ -1192,12 +1192,12 @@ def _sync_record_from_wx_query(auto_record, wx_data):
)
return RECONCILE_ALLOW_NEW, {}
age = timezone.now() - auto_record.CreateTime
if auto_record.zhuangtai == 0 and age < timedelta(minutes=PENDING_QUERY_GRACE_MINUTES):
if auto_record.zhuangtai == 0 and age < timedelta(seconds=PENDING_QUERY_GRACE_SECONDS):
local = _maybe_wait_confirm_from_local(auto_record)
if local:
return local
return RECONCILE_PENDING, {
'msg': '收款处理中,请稍后再试',
'msg': '收款发起中,请稍后再试',
'tixian_id': auto_record.tixian_id,
}
if auto_record.zhuangtai == 0:
@@ -1276,11 +1276,24 @@ def get_auto_record_for_collect(shenhe_danhao):
)
def _collapse_stale_pending_records(shenhe_danhao):
"""同一审核单只保留最新一条进行中打款,其余作废(防多次点击堆叠导致永远处理中)。"""
pending = list(
TixianAutoRecord.query.filter(shenhe_danhao=shenhe_danhao, zhuangtai=0)
.order_by('-CreateTime')
)
if len(pending) <= 1:
return pending[0] if pending else None
keep = pending[0]
for stale in pending[1:]:
_mark_transfer_attempt_failed(stale, '重复发起已作废', close_audit=False)
return keep
def reconcile_shenhe_wechat_bills(shenhe_danhao):
"""
P0 防双笔:同一 shenhe_danhao 下所有打款记录逐条问微信
任一 SUCCESS / 进行中 → 禁止新建;全部终态失败才 ALLOW_NEW
返回 (action, payload)
收款前对账:防双笔,但不因查单失败无限「处理中」。
原则:有 package 就调起微信确认;无 package 的旧单作废;仅极短窗口内提示发起中。
"""
records = list(
TixianAutoRecord.query.filter(shenhe_danhao=shenhe_danhao).order_by('-CreateTime')
@@ -1295,64 +1308,54 @@ def reconcile_shenhe_wechat_bills(shenhe_danhao):
pass
return RECONCILE_COMPLETED, {'msg': '该提现已完成,请勿重复操作'}
if not records:
pending = _collapse_stale_pending_records(shenhe_danhao)
if not pending:
return RECONCILE_ALLOW_NEW, {}
pending_hit = None
for rec in records:
if rec.zhuangtai == 2:
continue
if rec.zhuangtai == 1:
return RECONCILE_COMPLETED, {'msg': '该提现已完成,请勿重复操作'}
local_wait = _maybe_wait_confirm_from_local(pending)
if local_wait:
return local_wait
local_wait = _maybe_wait_confirm_from_local(rec)
if local_wait:
return local_wait
wx_data = query_wechat_transfer_bill(rec.tixian_id, club_id_for_tixian_yonghuid(rec.yonghuid))
if wx_data is None:
logger.warning(
'对账查单不可用 shenhe_danhao=%s bill=%s,尝试本地 package 或短 grace',
shenhe_danhao, rec.tixian_id,
age = timezone.now() - pending.CreateTime
if not _parse_package_info(pending.wechat_package):
if age >= timedelta(seconds=PENDING_QUERY_GRACE_SECONDS):
_mark_transfer_attempt_failed(
pending, '上次发起未成功,请重新收款', close_audit=False,
)
local_wait = _maybe_wait_confirm_from_local(rec)
if local_wait:
return local_wait
if rec.zhuangtai == 0 and not _parse_package_info(rec.wechat_package):
_mark_transfer_attempt_failed(
rec, '微信查单暂不可用,请重新发起收款', close_audit=False,
)
continue
if rec.zhuangtai == 0:
age = timezone.now() - rec.CreateTime
if age >= timedelta(minutes=PENDING_QUERY_GRACE_MINUTES):
_mark_transfer_attempt_failed(
rec, '微信查单暂不可用,请重新发起收款', close_audit=False,
)
continue
if pending_hit is None:
pending_hit = (RECONCILE_PENDING, {
'msg': '收款处理中,请稍后再试',
'tixian_id': rec.tixian_id,
})
continue
return RECONCILE_ALLOW_NEW, {}
return RECONCILE_PENDING, {
'msg': '收款发起中,请稍后再试',
'tixian_id': pending.tixian_id,
}
action, payload = _sync_record_from_wx_query(rec, wx_data)
if action == RECONCILE_COMPLETED:
return action, payload
if action == RECONCILE_WAIT_CONFIRM:
return action, payload
if action == RECONCILE_ALLOW_NEW:
continue
if action == RECONCILE_AUDIT_CLOSED:
return action, payload
if action == RECONCILE_PENDING:
if pending_hit is None:
pending_hit = (action, payload)
continue
wx_data = query_wechat_transfer_bill(
pending.tixian_id, club_id_for_tixian_yonghuid(pending.yonghuid),
)
if wx_data is None:
logger.warning(
'查单不可用 bill=%s,使用本地 package 继续收款', pending.tixian_id,
)
return RECONCILE_WAIT_CONFIRM, _wait_confirm_payload(
pending, _parse_package_info(pending.wechat_package),
)
if pending_hit:
return pending_hit
action, payload = _sync_record_from_wx_query(pending, wx_data)
if action == RECONCILE_COMPLETED:
return action, payload
if action == RECONCILE_WAIT_CONFIRM:
return action, payload
if action == RECONCILE_ALLOW_NEW:
return action, payload
if action == RECONCILE_AUDIT_CLOSED:
return action, payload
if action == RECONCILE_PENDING:
pkg = _parse_package_info(pending.wechat_package)
if pkg:
return RECONCILE_WAIT_CONFIRM, _wait_confirm_payload(pending, pkg)
if age >= timedelta(seconds=PENDING_QUERY_GRACE_SECONDS):
_mark_transfer_attempt_failed(pending, '收款超时,请重新发起', close_audit=False)
return RECONCILE_ALLOW_NEW, {}
return action, payload
return RECONCILE_ALLOW_NEW, {}
@@ -1439,15 +1442,8 @@ def handle_post_transfer_failure(tixian_id, shenhe_danhao, err_code='', err_msg=
local = _maybe_wait_confirm_from_local(ar)
if local:
return local
logger.warning(
'发起转账失败且查单不可用 bill=%s code=%s msg=%s,短 grace 后允许重试',
tixian_id, err_code, err_msg,
)
age = timezone.now() - ar.CreateTime
if age >= timedelta(minutes=PENDING_QUERY_GRACE_MINUTES):
_mark_transfer_attempt_failed(ar, err_msg or '微信发起转账失败', close_audit=False)
return RECONCILE_ALLOW_NEW, {}
return RECONCILE_PENDING, '收款处理中,请稍后再试'
_mark_transfer_attempt_failed(ar, err_msg or '微信发起转账失败', close_audit=False)
return RECONCILE_ALLOW_NEW, {'wx_err': err_msg or '微信发起转账失败'}
action, payload = _sync_record_from_wx_query(ar, wx_data)
if action == RECONCILE_ALLOW_NEW:

View File

@@ -394,12 +394,17 @@ def process_audit_collect(request):
except requests.RequestException as e:
logger.error(
'微信打款网络异常(保持待查单): bill=%s err=%s',
'微信打款网络异常: bill=%s err=%s',
tixian_id, e, exc_info=True,
)
_mark_auto_record_failed(tixian_id, '网络异常')
if quota_reserved:
release_collect_quota_for_record(
TixianAutoRecord.query.get(tixian_id=tixian_id),
)
return Response({
'code': 12,
'msg': '收款请求已提交,请稍后再试',
'code': 99,
'msg': '网络异常,请稍后再次点击收款',
})
finally:

View File

@@ -1330,7 +1330,7 @@ class TixianQueRenAutoView(APIView):
if wx_data.get('_not_found'):
age = timezone.now() - auto_record.CreateTime
if auto_record.zhuangtai == 0 and age >= timedelta(minutes=2):
if auto_record.zhuangtai == 0 and age >= timedelta(seconds=20):
_mark_transfer_attempt_failed(
auto_record, '微信未找到该打款单', close_audit=False,
)