编程 微信支付退款 ABNORMAL:钱已从基本户扣出,停在退款中间账户

2026-11-12 00:03:35

微信支付退款 ABNORMAL:钱已从基本户扣出,停在退款中间账户

退款单状态为 ABNORMAL 时,钱其实已经从商户基本户扣出去了,只是停在退款中间账户,既没到用户手里,也没回到商户账上。它不是“退款失败”的同义词,而是一个需要继续处理的中间状态。把它当失败收尾,账上就会一直挂着一笔钱。

退款单共有四个状态:SUCCESS 退款成功、CLOSED 退款关闭、PROCESSING 退款处理中、ABNORMAL 退款异常。仅仅因为口语里都说“退款异常”,这三种情况在实际处理时完全不同。

三种“退款异常”,处理方式完全不同

申请阶段 403 USER_ACCOUNT_ABNORMAL:没扣钱

调用申请退款接口时返回 403 USER_ACCOUNT_ABNORMAL(用户账号注销),表示退款申请没有被受理。此时钱没有从基本户扣出,也没有产生 ABNORMAL 状态的退款单。它属于请求失败,不是退款单异常。

申请退款接口的常见错误码:

  • 400 INVALID_REQUEST
  • 401 SIGN_ERROR
  • 403 NOT_ENOUGH(余额不足,充值后用原单原参数重试)
  • 403 USER_ACCOUNT_ABNORMAL
  • 404 MCH_NOT_EXISTS
  • 404 RESOURCE_NOT_EXISTS
  • 429 FREQUENCY_LIMITED
  • 500 SYSTEM_ERROR

429 表示请求已经受理中。应先查单,然后降低请求频率,并用原单号重试,不要更换单号。同一个 out_refund_no 多次请求只会退一笔;申请失败重试必须使用原商户退款单号。一笔订单最多支持 50 次部分退款,多次部分退款必须更换单号,并且间隔 1 分钟。

CLOSED:7 天 + 余额不足,终态

退款进入 PROCESSING 后,如果受理超过 7 天且出资账户余额不足,状态会流转到 CLOSED。这是终态,原单号无法继续使用,重新发起需要补足资金并生成新的商户退款单号。

另外,支付成功后 365 天内可以退款;amount.total 填的是原订单金额,不是本次退款金额。

ABNORMAL:原路退卡失败,零钱也注销了

PROCESSING 期间,如果用户账户异常(银行卡作废或冻结)导致无法原路退回,微信支付会优先把退款金额转入用户的微信零钱。只有当用户的微信零钱账户也已注销时,状态才会流转为 ABNORMAL。此时资金停留在退款中间账户,商户可以在商户平台手动处理,也可以调用发起异常退款接口,成功后流转为 SUCCESS。

状态流转与终态判断

申请退款受理成功 → PROCESSING。从 PROCESSING 出发有两个分支:超过 7 天且余额不足 → CLOSED;原路退卡失败但零钱可用 → 退零钱成功;原路退卡失败且零钱也注销 → ABNORMAL。ABNORMAL 通过异常退款接口处理后 → SUCCESS。

终态只有 SUCCESS 和 CLOSED 两个。所以 ABNORMAL 必须被处理,不能当成失败收尾;它没有被处理完,退款这件事就还没有结束。

发起异常退款接口

两个路径,按商户形态选择:

  • 普通商户:POST https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/{refund_id}/apply-abnormal-refund
  • 平台商户 / 收付通:POST https://api.mch.weixin.qq.com/v3/ecommerce/refunds/{refund_id}/apply-abnormal-refund(body 需带 sub_mchid)

path 参数 refund_id 是微信支付退款单号,也就是申请退款受理成功时返回的那个单号,不是商户自己的单号。

body 关键字段:

  • out_refund_no 必填,传商户申请退款时传入的原退款单号。
  • type 必填,枚举 USER_BANK_CARD / MERCHANT_BANK_CARD。
  • bank_type:退款至用户时必填,仅支持 15 家直联银行借记卡——招行、交通、农行、建行、工商、中行、平安、浦发、中信、光大、民生、兴业、广发、邮储、宁波。
  • bank_account 与 real_name:需用微信支付公钥加密,Wechatpay-Serial 头传公钥 ID,也支持平台证书公钥。

接口请求频率 150qps。个人收款只支持退款至用户银行账户。

应答里的 channel 有四个取值:ORIGINAL 原路退款、BALANCE 退回余额、OTHER_BALANCE 原账户异常退到其他余额账户、OTHER_BANKCARD 原银行卡异常退到其他银行卡(发起异常退款成功后返回)。

Go 侧状态分流(示意,未实测)

// 示意代码:仅表达状态分流逻辑,未实测
switch refund.Status {
case "SUCCESS":
    // 终态,收尾
case "CLOSED":
    // 终态,且不可复用原单号:补足资金后生成新的 out_refund_no 重新发起
case "PROCESSING":
    // 非终态:查单,1 分钟后一次,超过 5 分钟仍 PROCESSING 按 5/10/20/30 分钟衰减
case "ABNORMAL":
    // 非终态,必须处理:资金停在退款中间账户
    body := map[string]any{
        "out_refund_no": refund.OutRefundNo, // 必须是原商户退款单号
        "type":          "MERCHANT_BANK_CARD", // 或 USER_BANK_CARD
    }
    // POST /v3/refund/domestic/refunds/{refund_id}/apply-abnormal-refund
    // refund_id 为微信支付退款单号
}

产品取舍:退到结算户,对账口径就变了

type=MERCHANT_BANK_CARD 的效果是把钱退回商户结算银行账户,再由商户线下退给用户;USER_BANK_CARD 则是退到用户指定的银行卡。前者绕开了用户零钱账户已注销的死结,但代价是这笔钱在对账上不再走“退款给用户”的路径,而是回到自己的结算户,退款口径和流水匹配方式都要跟着调整。

供应商侧还有一个开关:商户号开启「异常退款默认退商户」功能后,退款异常时系统自动退回商户结算银行账户,商户不需要再手动处理或调接口。

开关之外,还需要注意的几件事

  • 分账订单退款不会自动回退已分账金额,出资为商户基本账户可用余额。
  • 合单支付只能用子单退款。
  • 退款回调地址至少配置一处:申请时传 notify_url,或在商户平台交易中心-退款配置。接口传入后平台配置不再生效。
  • 未收到回调时可查单:建议 1 分钟后查一次,超过 5 分钟仍为 PROCESSING,按 5/10/20/30 分钟衰减。

有一个点需要自行验证:部分渠道下 channel 的实际取值要以接口应答为准,文档枚举与实际返回之间存在差异的可能,接入时最好把应答原样落库再判断。

文档

推荐文章

程序员茄子在线接单