feat(wallet): 解绑微信时待审核提现「立即退回现金」
原实现只清 openid,reviewing 单的退款推迟到管理员审核打款(execute_withdraw_transfer 回读空 openid)才触发——用户解绑后后台仍挂单、现金不即时退回。改为 force 解绑时直接对 所有 reviewing 单走 _refund_withdraw 立即退回现金 + 置终态(新增 refund_reviewing_ withdraws_on_unbind),让提示文案名副其实、后台不再挂死单。_refund_withdraw 加可选 remark 自定义流水文案。同步 docs/api。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -155,8 +155,8 @@ def bind_wechat(req: BindWechatRequest, user: CurrentUser, db: DbSession) -> Bin
|
||||
def unbind_wechat(
|
||||
user: CurrentUser, db: DbSession, req: UnbindWechatRequest | None = None
|
||||
) -> UnbindWechatResultOut:
|
||||
# 有「待审核(reviewing)」提现单时,首次解绑拦下要求确认:这类单还没打款,解绑后审核时
|
||||
# 回读空 openid → 自动退回现金余额(钱不丢),但用户需知情。确认后客户端带 force=true 再调即放行。
|
||||
# 有「待审核(reviewing)」提现单时,首次解绑拦下要求确认:这类单还没打款,确认解绑(force)
|
||||
# 时会被立即退回现金余额(refund_reviewing_withdraws_on_unbind),后台不再挂单,用户需先知情。
|
||||
# (pending 单转账已在途、解绑影响不到,不拦;见 has_reviewing_withdraw。)
|
||||
force = req.force if req is not None else False
|
||||
if not force and crud_wallet.has_reviewing_withdraw(db, user.id):
|
||||
@@ -166,8 +166,10 @@ def unbind_wechat(
|
||||
needs_confirm=True,
|
||||
message="你有提现正在审核中,解绑微信后这笔会自动退回到现金余额。确定解绑吗?",
|
||||
)
|
||||
# 先把待审核提现立即退回现金(无则 no-op),再清 openid —— 让"解绑即退"名副其实
|
||||
refunded = crud_wallet.refund_reviewing_withdraws_on_unbind(db, user.id)
|
||||
crud_wallet.unbind_wechat_openid(db, user.id)
|
||||
logger.info("unbind wechat ok user_id=%d force=%s", user.id, force)
|
||||
logger.info("unbind wechat ok user_id=%d force=%s refunded_withdraws=%d", user.id, force, refunded)
|
||||
return UnbindWechatResultOut(bound=False)
|
||||
|
||||
|
||||
|
||||
@@ -337,8 +337,8 @@ def bind_wechat_openid(db: Session, user_id: int, code: str) -> dict:
|
||||
def has_reviewing_withdraw(db: Session, user_id: int) -> bool:
|
||||
"""用户是否有「待审核(reviewing)」的提现单。
|
||||
|
||||
只看 reviewing:这类单还没打款,解绑微信后审核打款时回读空 openid → 自动退回现金余额
|
||||
(见 execute_withdraw_transfer),解绑前据此提示用户知情确认。
|
||||
只看 reviewing:这类单还没打款,用户确认解绑时会被立即退回现金
|
||||
(refund_reviewing_withdraws_on_unbind),解绑前据此提示用户知情确认。
|
||||
pending 单转账已在途(execute 已过 openid 闸),解绑影响不到、照常打款,故不计入。
|
||||
"""
|
||||
return (
|
||||
@@ -366,6 +366,33 @@ def unbind_wechat_openid(db: Session, user_id: int) -> None:
|
||||
db.commit()
|
||||
|
||||
|
||||
def refund_reviewing_withdraws_on_unbind(db: Session, user_id: int) -> int:
|
||||
"""解绑微信时,把该用户所有「待审核(reviewing)」提现单立即退回现金并置终态。
|
||||
|
||||
reviewing 单还没打款(钱在 create_withdraw 时已从现金扣进待审核单),解绑后这笔无法
|
||||
打款到微信,直接退回现金最干净:用户即时拿回钱、后台不再挂死单(不必等管理员审核)。
|
||||
复用 _refund_withdraw(退现金 + 流水 + 幂等)。返回退掉的单数。
|
||||
"""
|
||||
orders = (
|
||||
db.execute(
|
||||
select(WithdrawOrder).where(
|
||||
WithdrawOrder.user_id == user_id,
|
||||
WithdrawOrder.status == "reviewing",
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
for order in orders:
|
||||
_refund_withdraw(
|
||||
db,
|
||||
order,
|
||||
reason="用户解绑微信,待审核提现自动退回",
|
||||
remark="解绑微信,提现已退回现金余额",
|
||||
)
|
||||
return len(orders)
|
||||
|
||||
|
||||
_OUT_BILL_NO_RE = re.compile(r"^[0-9A-Za-z_-]{8,32}$")
|
||||
|
||||
|
||||
@@ -401,7 +428,8 @@ def _add_cash(db: Session, user_id: int, amount_cents: int) -> int:
|
||||
|
||||
|
||||
def _refund_withdraw(
|
||||
db: Session, order: WithdrawOrder, reason: str, *, final_status: str = "failed"
|
||||
db: Session, order: WithdrawOrder, reason: str, *, final_status: str = "failed",
|
||||
remark: str | None = None,
|
||||
) -> None:
|
||||
"""退回现金 + 写 withdraw_refund 流水 + 单子置终态。幂等:已是退款终态不重复退。
|
||||
|
||||
@@ -409,6 +437,7 @@ def _refund_withdraw(
|
||||
- "failed" (默认):微信转账失败/取消 → 自动退款
|
||||
- "rejected":管理员审核拒绝 → 退款(走 reject_withdraw)
|
||||
两种都已退款,用 in 判定防重复退(并发/对账/拒绝重复点)。
|
||||
remark:给用户可见的现金流水文案;省略则按 final_status 取默认(解绑退回等场景可自定义)。
|
||||
"""
|
||||
if order.status in ("failed", "rejected"):
|
||||
return # 防重复退款(并发/对账与查单/重复拒绝同时触发)
|
||||
@@ -433,7 +462,10 @@ def _refund_withdraw(
|
||||
biz_type="withdraw_refund",
|
||||
ref_id=order.out_bill_no,
|
||||
# 用户可见文案区分"未成功(自动退)"vs"审核未通过";技术原因记在 order.fail_reason
|
||||
remark="提现审核未通过,金额已退回" if final_status == "rejected" else "提现未成功,金额已退回",
|
||||
remark=(
|
||||
remark
|
||||
or ("提现审核未通过,金额已退回" if final_status == "rejected" else "提现未成功,金额已退回")
|
||||
),
|
||||
created_at=datetime.now(rewards.CN_TZ).replace(tzinfo=None),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
|
||||
**有待审核提现时的二次确认**:用户有 `reviewing`(待审核)状态的提现单且未带 `force=true` 时,**首次解绑被拦下**——不清 openid,返回 `bound`(真实绑定态)+ `needs_confirm=true` + `message`。客户端据此弹确认弹窗,用户确认后带 `force=true` 再调一次即放行解绑。
|
||||
|
||||
> 只拦 `reviewing`:这类单还没打款,解绑后审核时回读到空 openid → 自动退回现金余额(钱不丢,见 [`execute_withdraw_transfer`](../../app/repositories/wallet.py)),用户需知情。`pending`(打款在途)单转账已发起、解绑影响不到、照常打款到微信,故**不拦**。
|
||||
> 只拦 `reviewing`:这类单还没打款,用户确认解绑(`force=true`)时**立即退回现金余额**(写 `withdraw_refund` 流水、单子置终态),后台不再挂单、不必等管理员审核。`pending`(打款在途)单转账已发起、解绑影响不到、照常打款到微信,故**不拦**。
|
||||
|
||||
Reference in New Issue
Block a user