微信小程序支付对接开发笔记
2019-9-23更新
试了一下支付宝
发现支付宝网页有个收银台,PC网站下单完事之后会给一个自动提交的form,然后302跳转到另一个URL
感觉实际对接的时候可以把那个URL返回给前端
新单位新项目,微信小程序对接微信支付+退款,由于开发测试是真的繁琐,在此记录一下笔记。
下载微信SDK,编写后端代码
……随便吐槽一下,微信SDK被人挖出来漏洞之后就不放github了,只能从微信官网下载,然后阿里规约扫描报了一大堆错……也没法改,万一后续SDK更新了呢
2019-8-28更新:
我真是认识鹅厂微信支付Java SDK作者的美,你弄个WxPayConfig的抽象类给我们扩展,然后getAppId()这种方法的访问权限是default,用Maven等依赖管理引入之后,根本没法扩展,因为必须得在同一个包下。。
然后其他WxPay之类的类又必须依赖这个类的子类才能干活。。 难怪官方都只给源码下载,而不是托管到什么依赖仓库。。
配置类
首先创建一个SDK内WxPayConfig类的实现类,由于项目名是微商城,取名叫WscWxConfig。
使用Spring配置读取的方式,在类里声明AppId、MchId(商户号),Key,证书内容(字节数组)几个成员变量,然后AppId MchId key的getter直接用lombok生成。
getCertStream这个方法需要重写为return new ByteArrayInputStream(this.certData);的形式,getWxPayDomain更麻烦:
1 | IWXPayDomain getWXPayDomain() { |
2019-8-28更新:
后续改成按公司独立设置微信支付参数,所以用了ThreadLocal维护这些东西。。支付退款证书也放在了OSS上,反正是流,无所谓……
顺带给WxPay类做一个单例,声明一个WxPay类型的变量,然后用@PostConstruct注解初始化方法:
注意这里的构造函数传参第三个参数,表示使用沙箱环境
1 | public void initWxPay(){ |
这样配置类就写完了。
Service
微信SDK给了轻度封装,至少使用Map<String, String>就可以完成参数填写了
统一下单
由于负责用户模块的同事的设计是不存储OpenId,因此该方法直接接受code,订单号,金额,公司名(用于微信订单描述)作为参数。
接下来是调用微信SDK发送请求,并且将拿到的prepay_id返回前端。
接受支付通知
一定要校验签名和金额!!!!
幂等性处理后,将接收到的支付信息持久化一份到数据库,并且根据业务结果+支付金额调订单Service。
扯一下我们的幂等性方案吧,防重入一开始打算用悲观锁锁住订单号对应的行,但是怕应用意外崩溃然后锁无法释放,最后用的是redis的setnx。防重入之后就做一下状态判断就行了。
退款
这回是由商户服务器发起请求,所以不用OpenId了,指定订单号、金额、退款金额、售后单号即可
接受退款通知
类似支付通知的幂等性处理,持久化一份到数据库,并根据业务结果调售后Service。
Controller
主要是两个接收通知的接口,以及退款接口信息的解密方法。
先从request.getInputStream()中获取XML,然后用微信SDK转成Map,再用微信SDK做签名校验。
测试
后端码狗自测微信小程序支付接口简直要人命……居然没找到描述全流程的博文
调个微信支付和玩tmd解谜RPG一样,到处找线索
小程序支付交付需要在沙箱模拟过一遍之后向微信提交验收通过申请,当然如果已经有通过微信支付验收的小程序就不必走一遍沙箱流程了。
写完代码自己测试时,一般还是直接用一个能用的小程序支付参数,而不是沙箱,理由后面讲。。
数据准备
AppId
小程序所属的公司账号将开发者的微信号加入小程序开发者后,开发者使用微信扫码登录微信小程序后台即可看到。
AppSecret
需要超级管理员在小程序后台开发设置生成,用于小程序Code换OpenId用。。
MchId
在商户后台将开发者账号加入商户的员工账号后,员工账号会收到包含MchId的通知。
Key
商户后台API安全处填写,需要超级管理员验证,需要商户账号开通操作密码
其中沙箱开发要用到SignKey,使用Key请求微信的API生成。
解析退款信息时使用。
证书
商户后台API安全处可以重设,最好能找到第一次申请的证书文件。
解析退款信息时使用。
内网穿透
本地请求支付的接口需要一个带https的内网穿透给wx.request用,即使有CI/CD,也懒得改一行代码提一个pr……图省事直接买了NATAPP。
吐槽一下,要调试小程序需要买他的国内隧道+二级域名,15元一年的域名+9元一个月的隧道;
倒是可以选择用自己的域名,但是我域名没备案……而阿里云买的域名要备案需要配阿里云的实例……
还有香港流量包月的隧道,买完才发现这个不能开https……香港流量不包月的倒是可以配自己域名,但是自己域名443端口必须空着,我域名有个开了https的小网盘跑着……
幸好微信的支付通知回调可以不用https,就用免费隧道顶上。
2019-8-28更新:
后续在自己的服务器上搭了个ngrok,不用额外付冤枉钱了。。
统一下单
在微信开发者工具中新建一个小程序,修改AppId为公司小程序的AppId(也可以直接调公司小程序的代码,但是单独测一个支付我还是选择新起一个小程序)。
加一个按钮,测试支付,绑定一个函数,把返回的requestPayment用的数据打印出来,顺带请求支付:
1 | testPay: function (e) { |
结果是这样的:
1 | code:200 |
调起支付
果↓然↑啊,扫描开发者工具给的二维码时提示错误:调用支付JSAPI缺少参数:total_fee
我寻思统一下单都成功了你在说你吗呢,会报错你就多报几句,是不是把你妈杀了你也只能呜咽着说出你缺少total_fee?真的憨批一样
package传的也带了prepay_id=,后端生成随机数+签名用的是微信JavaSDK的工具类,签名字段大小写也没问题
发现后端生成签名时,工具类会自动拼上key=key,而我手动在map里加了一个key……也就是现在的签名内容有问题。。
改掉之后还是报错,看到微信开放社区里一个帖子 说即使报错也受到了微信支付的成功通知,又看到segmentfault里的一个帖子 说沙箱就是这样。。
我真是艹了
接受回调
坑点:微信SDK送了一个判断支付结果通知中的sign是否有效的方法isPayResultNotifySignatureValid(),这方法默认没有传入签名类型的时候,选的是MD5.
但是微信支付主工具类 初始化签名方式时,根据传入是否沙箱来切换加密方式和URL,此时加密方式是HMAC!!
1 | public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception { |
于是支付回调返回的sign也当然是HMAC,但是支付回调是没有写明签名类型的……这里一定做处理,比较优雅的做法是接受回调时读取配置加入签名类型。。不过我当时是直接改了微信的SDK……
然后是支付回调里没有trade_state,只有result_code,因为只推送成功结果,这点要和主动拉取支付结果做区分。
退款回调
发起退款的流程和发起支付类似,略过不表
退款回调没有sign字段,而且需要JDK装密钥长度无限扩展包 。
直接解密req_info字段即可,解密过程有几个坑,MD5哈希之后要转成小写,算法名是AES/ECB/PKCS7Padding不能写错,如果抛出需要IV的异常就是算法名写错了……
解密代码如下:
1 | private String decrypt(String reqInfo) throws Exception { |
微信小程序支付对接开发笔记