微信支付接入踩的几个坑
前言
微信支付的文档其实挺全,但接入的时候总有些”文档写了、你却未必留意”的坑。下面是我自己踩过或者帮人排查过的几条,不绑具体项目,只说通用经验。
平台公钥模式 vs 证书模式,怎么选
微信支付回调验签现在主要有两种配置:
- 证书模式(RSAAutoCertificateConfig):SDK 自动下载并轮换平台证书。
- 平台公钥模式(RSAPublicKeyNotificationConfig):用一份固定的平台公钥验签。
证书模式省事,但在平台证书切换的那段时间,有可能 SDK 还没更新到新证书,验签就会失败。公钥模式稳定些,缺点是真到了换公钥的时候,得自己手动换文件。
我的选择是:商户号稳定、回调量不大的,用公钥模式,自己可控;商户号活跃、证书换得勤的,用证书模式,但切换那几天盯着点失败率。
回调 body 必须取原始字符串
这是最容易踩的坑。微信支付回调的签名是对原始请求体算的,所以接回调时 body 得原封不动拿到,不能让框架先帮你反序列化:
|
一旦让 Spring 先反序列化成对象再转回字符串,字段顺序、空白字符都可能变,签名就对不上了。
金额全链路用整数分
微信支付接口里金额单位是分,整数。从下单、落库、回调到查单,一路都用 int 传,中途别转 BigDecimal 或 double:
int amountFen = 19900; // 199 元 |
这样浮点精度问题压根不存在,比较金额直接 ==、!= 就行。
顺带一提,微信有些字段给的是元(比如下载下来的账单),对接的时候盯紧单位,别把元和分混了。
商户单号要不要和订单号复用
微信接口要传 out_trade_no(商户订单号),常见的两种做法:
- 复用自己的订单号:省事,回调带回来的
out_trade_no直接就能定位订单,少维护一列映射。 - 单独生成商户单号:更规整,但订单表要多一列,回调时多查一次。
业务简单,复用订单号就够;订单系统复杂、要对接多个支付渠道的,建议单独维护。另外 out_trade_no 有长度限制(32 位以内),订单号别设计得太长。
notify-url 必须是外网可访问的 HTTPS
微信回调只走 HTTPS,而且得是外网能访问到的地址。本地开发要用内网穿透(cpolar、ngrok、natapp 之类的)把 localhost:8080 暴露成公网 HTTPS。
有些穿透工具默认给的是 HTTP,得自己在前面套一层 nginx 或 CDN 转成 HTTPS。
回调应答格式要对
回调处理完,得回一个固定格式的成功应答,不然微信会接着重试:
{"code":"SUCCESS","message":"成功"} |
如果是验签失败或者处理时抛异常,返回非 200,让微信重试;但如果是这笔已经处理过了(幂等命中),要回 200 + SUCCESS,告诉微信别再发了。
别全信回调里的字段
回调里虽然带了金额、状态这些,但关键字段(尤其是金额)一定得自己校验。不能看见 trade_state=SUCCESS 就放行,至少要查:
- 实付金额是不是等于应付金额;
- 订单还在不在有效期;
- 这笔交易之前处理过没有(幂等)。
测试和生产商户号分开
不少人图省事,联调和生产用同一个商户号。建议分开。不然测试时一堆异常回调、重复下单会污染生产数据,出问题排查非常难受。
上面这些坑其实不止微信支付,支付宝、Apple Pay 换过来也差不多,记住这几条,换个渠道照样能少走弯路。
