编程 微信支付退款接口:退款成功≠钱已到账,这几个边界最容易被坑

2026-08-30 09:01:01 views 6

做支付接入的人,第一课往往是被"退款接口返回 200 就以为钱退完了"坑一次。

申请退款接口返回 200,只是微信支付受理了你的退款单。它既不代表钱已经退到用户账户,也不代表这笔退款一定成功。真正的结果要看退款单状态机,以及微信主动推过来的退款结果回调。

这篇把退款链路里那些文档写了、但初次对接容易忽略的边界过一遍。都是踩过再整理出来的,能省你上线的第一轮对账事故。

先看状态机,别拿"受理"当"成功"

微信支付退款单只有这几个状态,全部要你在业务侧正确映射:

状态含义你的业务怎么处理
PROCESSING退款处理中,已受理未完成标记"退款中",等回调/查单
SUCCESS退款成功,钱已原路退回终态,释放退款单
CLOSED退款关闭(多为超时或不允许退)终态,按失败处理,可重新发起
ABNORMAL退款异常,原路退回失败(如银行卡冻结/作废)需人工介入或走异常退款接口

最容易写错的点:不要把申请退款接口的 200 当成 SUCCESS。受理成功到真正到账是有延时的——零钱支付的退款约 20 分钟内到账,银行卡支付可能要到 3 个工作日后才落账。业务状态里"退款成功"必须由 SUCCESS 触发,不是由 HTTP 200 触发。

out_refund_no 是幂等命门,也是资损元凶

申请退款接口是幂等的,幂等键就是商户退款单号 out_refund_no

两条硬规则:

  1. out_refund_no 在商户号下全局唯一。同一个单号配不同订单号或不同金额,会直接报 INVALID_REQUEST / 支付单号校验不一致。也正因为唯一,重复提交同一个单号只退一笔,这是防重复退款的设计。
  2. 重试必须用原单原参数。网络超时、返回 5xx、FREQUENCY_LIMITED 时,都该用同一个 out_refund_no 和同一组参数重试,而不是新造一个单号——否则一旦第一次其实受理成功了,第二次就成了新退款,资金重复流出。

反直觉的点:申请退款接口具备幂等,但官方明确不建议拿它当查询接口用。确认退款单状态,请调查询单笔退款接口,别靠"再提交一次看返回"来判断。

金额是分,别用元;并发退款要加锁

  • 所有金额字段单位是,整数,不能带小数点。传错会报 REFUND_FEE_MISMATCH(金额与之前请求不一致)。
  • 退款金额不能超过订单原支付金额。
  • 部分退款最多 50 次。第 50 次之后要再退,得换 out_refund_no,并且间隔 1 分钟后再调。
  • 并发场景是真坑:两个线程同时对同一订单发起部分退款,各自校验"我退的这单不超",但累加后就超了。微信侧会拦,但你的状态可能已经写脏。

正确做法是调用前在本地加分布式锁,并校验「已退款金额 + 本次退款金额 <= 订单总金额」。服务端能兜底,但你不该让脏状态先落地。

回调:要验签、要应答、要重入

退款结果变了,微信会 POST 到你配置的 notify_url(或商户平台退款配置里的地址)。注意:

  • 申请退款时传的 notifyUrl,优先级高于商户平台配置的全局地址。
  • 5 秒内完成验签并应答。验签通过回 HTTP 200/204(无需报文);验签失败回 4xx/5xx 带应答报文。不答或答错,微信会按 15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h 的重试节奏,最多重发 15 次,累计约 24 小时。
  • 同一通知可能重复到达。收到通知先查本地退款单状态,已处理过就直接回 200,别再执行一遍,否则资金/状态重复。处理前建议上数据锁防函数重入。

还有个小坑:微信偶尔会发 Wechatpay-SignatureWECHATPAY/SIGNTEST/ 开头的签名探测流量,用来验证你的验签逻辑。别把它当真回调,按验签失败处理即可。

主动查单兜底:别只靠回调

回调不是 100% 到达(微信自己也声明"不保证通知最终成功")。所以退款状态的最终一致性要靠主动查询兜底。

推荐节奏:回调没来时,每 1 分钟查一次查询单笔退款接口;超过 5 分钟仍是 PROCESSING,逐步衰减频率(5 分钟、10 分钟、20 分钟、30 分钟……)。别高频猛刷——退款接口失败时频率限制只有 6 QPS,成功时是 150 QPS,查单同样要克制。

错误码速查

HTTP错误码含义处理
400INVALID_REQUEST参数符合格式但不符合业务规则看具体 message
401SIGN_ERROR签名错误查签名算法/私钥/证书
403NOT_ENOUGH商户账户余额不足充值后原单原参数重试
404RESOURCE_NOT_EXISTS订单不存在/未支付确认单号与支付状态
429FREQUENCY_LIMITED频率受限或退款受理中降频,原单重试,勿换单号
500SYSTEM_ERROR微信侧超时原单原参数重试

注意官方一句提醒:错误描述可能因业务调整变更,别拿它做自动化判断。逻辑要锚定在错误码和状态字段上,message 只给人看。

ABNORMAL 退款异常:自动不了的才显真章

退款到银行发现用户卡被冻结/作废,原路退回失败,退款单进入 ABNORMAL。这时没有自动解法,两条路:

  • 商户平台「交易中心」人工审核退款;
  • 发起异常退款接口,退到用户其他账户或退到商户自己的账户再线下转。

ABNORMAL 这块建议直接做成告警 + 人工队列,别试图全自动,涉及资金的事,留人审比留代码更稳。


一句话总结的判断

  • 接口返回 200 = 受理,不是成功。
  • 退款成功唯一标准是状态机 SUCCESS,靠回调 + 查单双通道确认。
  • out_refund_no 是幂等键,重试永远原单原参数。
  • 金额单位分、部分退款 50 次上限、并发要加锁校验累计金额。
  • ABNORMAL 走人工/异常退款接口,别写死全自动。

(错误码、频率限制、重试节奏均对照微信支付官方退款接口/退款最佳实践文档整理,具体以你的商户类型——直连 or 服务商——对应的接口文档为准。文中未实测的部分为文档结论。)

复制全文 生成海报 支付 接口对接 微信支付 后端

推荐文章

程序员茄子在线接单