diff --git a/users/tixian_shenhe_services.py b/users/tixian_shenhe_services.py index 7952a80..91a0f99 100644 --- a/users/tixian_shenhe_services.py +++ b/users/tixian_shenhe_services.py @@ -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: diff --git a/users/tixian_shenhe_views.py b/users/tixian_shenhe_views.py index 7e126ed..aa900c9 100644 --- a/users/tixian_shenhe_views.py +++ b/users/tixian_shenhe_views.py @@ -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: diff --git a/users/views/tixian.py b/users/views/tixian.py index 6b5a892..25fb498 100644 --- a/users/views/tixian.py +++ b/users/views/tixian.py @@ -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, )