fix: 简化提现收款对账,避免永远卡在收款处理中
- 同一审核单只保留最新一条进行中打款,多次点击不再堆叠阻塞 - 有 package 时查单失败也直接调起微信确认 - 无 package 超过20秒即作废允许重试;转账/网络失败返回明确错误而非 code12 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user