feat(wxpay): 提现 debug 免人工审核直发 + 修复 user_display_name 被微信拒收
- WithdrawRequest 加 skip_review 字段;/withdraw 在 skip_review 且非生产(APP_ENV!=prod) 时,建单后立即 execute_withdraw_transfer 直发、跳过人工审核。双闸:仅 debug 客户端下发 + 服务端非 prod 才认,生产恒走人工审核。便于本地/联调直接测提现到账链路。 - 修 _auth_display_name:微信 user_display_name 校验极严,空格和标点也按"控制字符"拒收 (实测 'wonderable ai' 因空格被 400,'wonderableai'/'周周'/'傻瓜比价用户8888' 才过)。 原清洗只去星形面字符(ord>0xFFFF),漏掉 BMP emoji/空格/标点/控制符/零宽符。改为白名单: 只保留 Unicode 文字(L*)+ 数字(N*),其余全剔,清空兜底手机号尾号。修复昵称含空格/emoji 的用户发起免确认收款授权直接 400、提现打款失败的问题。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-1
@@ -196,9 +196,19 @@ def withdraw(req: WithdrawRequest, user: CurrentUser, db: DbSession) -> Withdraw
|
||||
except crud_wallet.InsufficientCashError as e:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="insufficient cash balance") from e
|
||||
|
||||
# 调试直发(非生产):skip_review=true 时跳过人工审核,立即发起微信转账(等价 admin approve)。
|
||||
# 双闸保护——客户端仅 debug 包下发此 flag,服务端仅非 prod 才认;任一道闸拦住即恢复正常审核,
|
||||
# 生产绝不会被客户端 flag 绕过审核。用于本地联调"提现→微信转账/免确认到账"全链路。
|
||||
if req.skip_review and not settings.is_prod and order.status == "reviewing":
|
||||
logger.warning(
|
||||
"withdraw skip_review(非prod调试直发,跳过人工审核立即打款) user_id=%d bill=%s",
|
||||
user.id, order.out_bill_no,
|
||||
)
|
||||
order = crud_wallet.execute_withdraw_transfer(db, order)
|
||||
|
||||
acc = crud_wallet.get_or_create_account(db, user.id)
|
||||
logger.info(
|
||||
"withdraw submitted user_id=%d cents=%d bill=%s status=%s(待审核)",
|
||||
"withdraw submitted user_id=%d cents=%d bill=%s status=%s",
|
||||
user.id, req.amount_cents, order.out_bill_no, order.status,
|
||||
)
|
||||
# 此刻 status=reviewing,尚未打款 → 无 package_info;App 据 status 提示"已提交,等待审核"。
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import unicodedata
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
@@ -421,10 +422,19 @@ def create_withdraw(
|
||||
|
||||
|
||||
def _auth_display_name(user: User | None) -> str:
|
||||
"""微信授权页展示的"开通账号"昵称(≤32,utf8,去表情)。取昵称兜底手机号尾号。"""
|
||||
"""微信授权页展示的"开通账号"昵称(≤32,utf8)。
|
||||
|
||||
微信对 user_display_name 校验极严:**连空格和标点(. 等)都算"控制字符"拒收**——实测
|
||||
'wonderable ai'(带空格)被 400 拒、'wonderableai'/'周周'/'傻瓜比价用户8888' 才过。
|
||||
故只保留 文字(Unicode L*,中英文/CJK)与数字(N*),其余(emoji/符号/空格/标点/控制符/
|
||||
零宽连接符/变体选择符/星形面字符)一律剔除;清空则兜底手机号尾号。
|
||||
"""
|
||||
raw = ((user.nickname if user else None) or (user.wechat_nickname if user else None) or "").strip()
|
||||
# 去掉非 BMP 字符(emoji 等),微信不支持
|
||||
name = "".join(ch for ch in raw if ord(ch) <= 0xFFFF).strip()
|
||||
kept = [
|
||||
ch for ch in raw
|
||||
if ord(ch) <= 0xFFFF and unicodedata.category(ch)[0] in ("L", "N")
|
||||
]
|
||||
name = "".join(kept).strip()
|
||||
if not name and user and user.phone:
|
||||
tail = user.phone[-4:] if len(user.phone) >= 4 else user.phone
|
||||
name = f"傻瓜比价用户{tail}"
|
||||
|
||||
@@ -119,6 +119,13 @@ class WithdrawRequest(BaseModel):
|
||||
out_bill_no: str | None = Field(
|
||||
None, description="客户端幂等键(商户单号):同号重试不重复转账。不传则服务端生成"
|
||||
)
|
||||
skip_review: bool = Field(
|
||||
False,
|
||||
description=(
|
||||
"调试直发:跳过人工审核立即打款。仅非生产(APP_ENV!=prod)生效,"
|
||||
"客户端仅 debug 包下发(开发设置开关)。生产恒走人工审核。"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class WithdrawResultOut(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user