Skip to content

微信接完了,支付宝照着抄一遍抄不了 ​

微信支付接完了,回头接支付宝,心想「都是支付,照着抄一遍」。

抄不了。同一套系统里,类名带 Wechat 的有 69 个,带 AliPay 的只有 7 个 ——
差的这 62 个不是废代码,是微信那边多出来的公众号、小程序、视频号、
Native 扫码、App 分安卓和 iOS。

而且金额单位不一样、签名放的位置不一样、回调要回什么也不一样。
照抄的每一处,都是一个坑。

踩错的代价 ​

这两套的差异,最恶心的地方在于它们长得像但不一样。

完全不同的东西你会认真看文档。而这两个都是「下单-支付-回调-退款」,流程一模一样,字段名也差不多——于是你放松警惕,直接复制粘贴改个名字。

代价通常是这三个:

金额差 100 倍。 微信收分,支付宝收元。测试的时候用 1 块钱测,微信传 100、支付宝传 1.00。抄错了,要么传给支付宝变成 100 倍,要么变成一百块。一分钱那种你可能上线好几天才发现。

回调一直重推。 你收到了、处理了、返回了 200,但没按人家要的格式返回。对方认为你没收到,于是隔一会儿再推一次。你的订单逻辑如果不幂等,一笔订单发了五次货。

微信没有沙箱。 你在支付宝沙箱里舒舒服服调了两天,转头接微信,发现只能拿真钱测。

先看这两套在代码里各占多大 ​

「差在哪」是个感觉。数一下就变成事实了。

这套系统两边都接了,类名前缀一数:

微信支付宝
相关类69 个7 个
支付服务实现815 行506 行
回调服务实现1089 行23 行

最后一行的比例是 47 : 1。

先说清楚这个数为什么不能直接当「难度比」:微信那 1089 行里塞了不止支付——
退款回调、分账回调、发货信息上传,全在同一个类里;支付宝那 23 行是纯转发,
真正的处理在别处。所以 47 倍是文件组织方式的差异,不完全是工作量的差异。

但把这层折掉,剩下的部分仍然是真的:69 比 7。

微信那 69 个类里,有相当一部分是支付宝这边根本不存在的东西——
公众号、小程序、视频号、Native 扫码、App 分安卓和 iOS,
每一种调起方式的参数组装都不一样。而支付宝这边,
手机网页、App、PC 三种调起共用一套参数结构。

「微信支付」不是一个接口,是一族接口。「支付宝」更接近一个。

这才是接第二家时真正的落差来源:你以为在接一个对等的东西,
你第一次接的那个才是最复杂的,第二次接的会轻松得多——
而反过来先接支付宝再接微信的人,会觉得被骗了。

六个真实差异 ​

一、金额单位:分 vs 元 ​

这是最容易翻车、后果最直接的一个。

微信支付支付宝
单位分元
类型整数字符串
1 元怎么传100"1.00"
java
// 同一个订单金额,两边要转两次
// 1.00 元
BigDecimal amt = order.getAmount();

// 微信收「分」,要整数 → 100
int wxFee = amt.multiply(HUNDRED)
               .setScale(0, HALF_UP)
               .intValue();

// 支付宝收「元」,要两位小数 → "1.00"
String aliFee = amt.setScale(2, HALF_UP)
                   .toPlainString();

别用 double 做这个转换,浮点精度会在某些金额上给你惊喜。用 BigDecimal,setScale 明确写出来。

二、签名放在哪:请求头 vs 业务参数 ​

这个差异会直接影响你的代码结构,抄不过来。

微信 v3:签名放在 HTTP 请求头的 Authorization 里,格式是一串带商户号、序列号、时间戳、随机串、签名的结构化文本。

支付宝:签名是业务参数里的一个 sign 字段,跟其他参数平级,一起提交。

所以:微信的签名逻辑在拦截器/请求包装那一层;支付宝的在参数组装那一层。你要是想写一个统一的支付抽象层,这里是第一个分叉点。

两边都是 RSA-SHA256,但签名的内容和位置完全不同,能复用的只有「调用 RSA 签名」那一个方法。

三、回调:一个是加密的,一个是明文 ​

微信 v3 的回调是加密的。 收到的是密文,得用 APIv3 密钥(AES-256-GCM)解密才能看到订单信息。这也是为什么 APIv3 密钥配错的时候,表现是「下单成功但回调解密失败」——签名那套是对的,解密那套不对。

支付宝的回调是明文表单 POST,参数直接可读,你要做的是验签:用支付宝公钥验证这堆参数没被篡改。

别因为参数可读就跳过验签。 明文意味着任何人都能构造一个假回调打到你的接口,说某笔订单支付成功了。验签是唯一的防线。

四、回调怎么算「收到了」:一个看状态码,一个看报文 ​

这条最容易被忽略,而且错了不会立刻报错,只会一直重推。

而且两家判定成功的位置根本不在一个层面:

微信 v3 看 HTTP 状态码。 返回 200 或 204 就算成功,body 可以是空的。
失败才要回 4XX/5XX 加一段 JSON:

json
{"code": "FAIL", "message": "签名验证失败"}

注意这里最容易记反:带 code 的报文是失败时用的。
回一个 {"code":"SUCCESS"} 不会报错,因为微信压根不看 body——
但很多人以为「必须返这串」,其实返空就行。

支付宝看报文内容。 要纯文本,就七个字母:

success

小写,不带引号,不带 JSON 结构,不带任何其他内容。
你返回 {"code":"success"} 它不认,返回 SUCCESS 它也不认。

这个差异会一路渗到你的方法签名上。真实项目里两家的回调方法长这样:

java
// 微信:成败靠状态码表达,得能控制它
ResponseEntity<String> wechatCallback(
        String notifyData,
        HttpServletRequest req);

// 支付宝:返回那个单词就行,String 够了
String alipayCallback(
        HttpServletRequest req);

连返回类型都不一样。 你想把两家塞进同一个方法签名,从这一步就开始别扭了。

回错了会怎样:对方认为投递失败,按策略重推。支付宝会在一天多的时间里推好几次。如果你的回调处理不幂等,一笔订单会被处理多次——发多次货、加多次余额、送多次积分。

所以回调幂等不是「最佳实践」,是这两家的重试机制逼你必须做的事。这块坑够多,我另外单独写一篇。

五、沙箱:一个有,一个没有 ​

支付宝有完整的沙箱环境:独立的账号、独立的密钥、假的余额,随便测。

微信支付 v3 没有沙箱。 v2 时代有过,现在接 v3 只能拿真实商户号测。

实际做法是用一分钱订单在生产环境测,测完退款。听起来糙,但这是目前的常规做法。

这个差异会影响你的排期:支付宝那边可以并行开发,微信那边必须等商户号和证书都到位才能开始真正的联调。

六、错误信息:一个说人话,一个不说 ​

支付宝的错误返回里有 sub_code 和 sub_msg,通常能直接告诉你哪个参数不对。

微信很多时候只给你一个 SIGN_ERROR 或者 401,然后你自己猜是八个配置里的哪一个错了。

这不是吐槽,是排查策略上的实际差别:接支付宝时看错误信息,接微信时看走到哪一步断的。(微信那边怎么按环节反推,我在证书密钥那篇里写了。)

一张总表 ​

维度微信支付 v3支付宝
金额单位分,整数元,字符串两位小数
签名算法RSA-SHA256RSA2(RSA-SHA256)
签名位置HTTP 头 Authorization业务参数 sign
回调内容加密,需 APIv3 密钥解密明文表单,需验签
回调应答看状态码:200/204 即可,body 可空看报文:必须是纯文本 success
沙箱无有
错误信息含糊相对明确
凭据数量多(证书、序列号、公钥、APIv3 密钥)少(应用私钥、支付宝公钥)

我的判断 ​

如果两个都要接,先接微信。

不是因为微信更重要,是因为微信的坑更多、更早暴露。证书、序列号、公钥、APIv3 密钥、没有沙箱——这些约束会逼你把支付这块的代码结构做扎实。等你把微信趟完,接支付宝会顺很多。

反过来,先接支付宝(有沙箱、凭据少、错误信息友好)会让你产生一种「支付挺简单」的错觉,然后在微信这边全部还回来。

别写「统一支付抽象层」,至少一开始别写。

我见过的通用做法是先抽一个 PayService 接口,然后发现两边的差异根本不在接口那一层——在参数结构、签名位置、回调格式上。硬抽出来的抽象层,最后变成一堆 if (channel == WECHAT)。

先各写各的,两边都跑通、都上线过、都踩过坑之后,再看哪些是真共性。 那时候抽出来的东西才有用。

常见报错对照表 ​

现象真正的原因解法
支付宝金额变成 1/100 或 100 倍微信的分直接传给了支付宝微信传整数分,支付宝传元字符串两位小数
回调收到了、也处理了,但对方一直重推微信:返了非 2xx,或超过 5 秒才应答;支付宝:报文不是 success微信先应答再异步处理业务;支付宝检查返回的字节
一笔订单发了多次货重推 + 回调处理不幂等按商户订单号做幂等,先判重再处理
微信回调解密失败,但下单正常APIv3 密钥错了(签名那套是对的)回商户平台核对是 APIv3 密钥不是 v2 的
支付宝验签失败用了应用公钥去验,或密钥格式带了换行/空格验签用支付宝公钥,不是自己的;检查密钥字符串
金额用 double 算完对不上一分钱浮点精度用 BigDecimal + 明确 setScale
想在微信沙箱里测,找不到入口v3 没有沙箱用一分钱真实订单测,测完退款

这条链路上的其他几篇

签名-B

大粽子