微信支付开发经历 - 坑爹的微信

唠叨几句

因为被微信那个破烂文档坑了我两个星期,导致项目进度慢了很多。本来微信的 API 的确是设计得烂,但烂我也觉得不要紧了,文档也烂那我就真的火了,跟人捉迷藏似的东一块西一块(玩猜谜吗你)。这里也记录一下我做开发遇到的坑。

如何申请公众号以及商户平台不在本文范畴内,因为项目经理已经拿到这这些东西了,我所做的就是完成代码的编写。

环境

这里使用了 com.github.binarywang 的 jar,下面默认都是在这个前期下讨论。其他自己实现的或者其他人的库请配合文档和其他人分享的资料看。

<dependency>
  <groupId>com.github.binarywang</groupId>
  <artifactId>weixin-java-mp</artifactId>
  <version>2.8.0</version>
</dependency>

<dependency>
  <groupId>com.github.binarywang</groupId>
  <artifactId>weixin-java-pay</artifactId>
  <version>2.8.0</version>
</dependency>

正文

首先有一个十分重要的提醒:

配合前端开发人员测试微信支付的时候千万千万不能用微信的沙箱。

那个沙箱没有他们文档说的那么厉害,只能确认微信回调我们服务器没问题,不能用来模拟测试整个支付流程,而且这个沙箱设计得十分垃圾,连沙箱都算不上(支付金额只允许用例内的。你见过只能画指定几个图案的沙箱吗??),所以还是乖乖地测一次给一分钱吧。

开发

一般开发主要用到微信的两种支付方式

  • JSAPI : 用于在微信自己的浏览器里面唤起微信支付
  • NATIVE : 用于扫码支付

JSAPI 不用多说,就是触发之后会唤起微信的支付对话框给用户选择支付与否。

NATIVE 就是生成订单之后给用户用微信扫码付款的。这个方式我看了文档,微信给本来的设计貌似是给那些自动贩卖机用的,不过稍微改变一下使用方法就可以适用于任意扫码支付。

JSAPI 和 NATIVE 两种支付方式均使用统一下单接口先获取微信预付款订单信息,然后再进行剩下的操作。两者传参几乎一样,不同的是:

  • JSAPI 需要传入 openId,NATIVE 不需要
  • NATIVE 需要传入 productId,JSAPI 不需要。

上面所说的稍微改变一下使用方法,就是在 NATIVE 支付的时候 productId 使用自己的订单 ID 就好了。

NATIVE 支付方式

首先说这个支付方式是因为,这个方式很简单,而且我也很推荐用这个,但扫码就需要用另外一台手机了。

根据微信的文档向统一下单接口传入相应的参数之后,就得到预付款订单了。这里得到的预付款订单包含了参数 codeUrl ,传这个给前端开发的去生成二维码或者自己服务器端生成二维码都可以,扫码之后就可以用微信付款。付款成功之后,微信会通过预先指定的回调 API 发送订单支付的结果。根据结果完成自己的订单逻辑这个就不说了。

JSAPI 支付方式

我觉得这个就是最坑爹的地方了。

首先传入参数之后,微信返回了预付款订单信息,然后这个信息需要返回给前端。但是,这之前需要对预付款订单的某些字段拼接起来,作一次签名,签名需要严格按照字段序排序以及注意大小写:

  • appId
  • nonceStr
  • package
  • signType
  • timeStamp
StringBuilder params = new StringBuilder()
    .append("appId=").append(wxPayService.getConfig().getAppId()).append("&")
    .append("nonceStr=").append(nonceStr).append("&")
    .append("package=").append("prepay_id=").append(orderResult.getPrepayId()).append("&")
    .append("signType=").append("MD5").append("&")
    .append("timeStamp=").append(timestamp);
//appId={appId}&nonceStr={nonceStr}&package=prepay_id={prepayId}&signType=MD5&timeStamp={timestamp}

(真心对微信大小写随便来表示很无语)其中

  • nonceStr
  • prepay_id

需要与微信返回的预付款订单内的一致。timeStamp 也要传给前端,到时候前端需要把这个 timeStamp 传进唤起微信支付对话框的函数。

拼接好这个之后,再在后面拼接参数 key 并进行一次 MD5。

params.append("&").append("key=").append(wxPayService.getConfig().getMchKey());
String prepay_sign = DigestUtils.getMD5(true, params.toString());

所以所需要传给前端的参数如下:

{
  "appId":"你的 appId",
  "nonceStr":"订单内的 nonceStr",
  "timeStamp":"订单参数签名的时间",
  "prepay_sign":"订单签名结果"
}

这时候不要急着去唤起微信的支付窗口,因为还有后面一系列步骤。

这里需要注意这些返回的参数:

  • nonceStr
  • timeStamp

这两个参数在整个支付的过程中要一致,而且参数大小写也需要注意。流程内的 API 有的地方给弄驼峰命名法有的地方则用全小写。

然后,前端需要再拿当前调用 JSAPI 支付的浏览器地址栏的路径,向服务器请求一个签名,这个签名就是前端唤起 JSAPI 所需要的签名,我这里请求的 API 以及示例如下:

POST -> https://shinonometn.com/WC/ticket

{
  "url":"https://shinonometn.com/?#/order/11",
  "nonceStr":"8897djsk09ll",
  "timeStamp":1560789
}

url 那里一定一定要注意,对于 SPA 应用,路由前缀那里,绝对不能只有一个#,微信的这个安全机制很傻屄。首先你需要去商户平台那里注册 JSAPI 支付允许的“支付目录”(我晕,目录),然后在调用 JSAPI 支付的时候他们会校验你地址栏”在“不”在“已注册的“支付目录”,不在就拒绝下单。我猜他们这个机制是做给服务器端渲染页面的应用做的:你会发现你的 SPA 应用拿到的 URL 经常跟他们微信拿到的 URL 不匹配,从而一直提示你 URL 未注册 然后拒绝下单。这其实不算坑,文档在很隐蔽的地方提及需要前端拿这个 sign 去调用 JSAPI 支付 and 支付前需要调用 config 一次才是坑死人。

这个签名是这样的,如下参数全小写,严格按照字典顺序排序:

  • jsapi_ticket(我一直不知道这个东西的存在,因为文档里面没有提及)
  • noncestr(小写,小心)
  • timestamp (小写,小心)
  • url (就是上面提及的 URL)

然后如此拼接:

jsapi_ticket={你拿到的 JSAPI TICKET}&noncestr={订单上的 nonceStr}&timestamp={你订单的 timeStamp}&url={URL}

//不算大括号,只是为了好看加上去的

然后对这这个拼接好的字符串,SHA1 一次,拿小写的字符串,返回给前端,那么前端就可以很愉快地填上对应的参数去 wx.config 一下,唤起微信支付窗口了。

那么这个 URL 在商户平台注册的时候需要注意什么呢?对于服务器端渲染页面,你需要填写支付页面的地址,删掉最后的”目录“:

//订单支付页面
https://shinonometn.com/order/pay/1
//注册的 URL
https://shinonometn.com/order/pay/

对于 SPA (单页应用)来说,你只需要填写你的应用地址,路由那里怎么方便怎么做手脚。

//带上路由的 SPA 的页面
https://shinonometn.com/?#/order/pay/1
                        ^我就弄了个问号
//注册的 URL
https://shinonometn.com/

唉,就是因为 JSAPI 的沙雕设计我加班到凌晨 2 点陪前端的人调试。

参考链接

在Web应用中接入微信支付的流程之极简清晰
微信开发,分享部分出现的问题
微信支付:“当前页面的URL未注册”

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容