375
iPhone_iPad_Mac_手机_平板_苹果apple
为微信开发填坑:微信网页支付的开发流程及填坑技巧
微信支付在整个微信体系中承担着相当重要的一个角色,甚至开辟了国内乃至世界独有的全民支付模式。现在大到商场超市,小到街边摊贩,都用到了微信支付,所以微信支付的开发只会越来越多,越来越应该成为每个开发人员需要掌握的一项经验,之所以没有说是技能,是因为微信开发本身并没有涉及到新的技术,只是按照微信官方给出的协议和接口,做了技术对接而已。
一旦提到技术对接,必然会涉及到文档、接口、参数这些双方交互规则。然而,不知道出于何种原因,微信支付这块的文档相当混乱,某些地方语焉不详,甚至demo也只是片段代码,想要从无到有的把微信支付打通有相当大的难度。难在于不知道从何下手。也正因为如此,网上有大量的教程文档来说明微信支付如何开发,但是要么只是寥寥几笔说下关键点,要么只是做个框架性的总结,目前为止,几乎没有从准备工作到支付打通的一个全面的文档。为了让后面做微信支付的朋友不再浪费大量的时间和精力,所以记录下这篇微信开发的实战教程。
为了方便大家开发是拿来参考,我会完全按照开发流程来记录微信网页支付的开发过程,主要从文档、接口、开发三个部分来说明。一篇文章很难解决所有问题,所以在文末,我会留下技术交流的方式,以便大家互相讨论。
文档
开发在接触到一个新的技术点的时候,最直接的做法就是查看官方文档。但是微信关于支付这块的文档有两套,关于网页支付这块有两个版本,这就无形中挖了很多坑,只能一遍一遍的试错和查资料才能避过。到目前为止官方也没有考虑整合优化一下这块的文档。
同时,网上不少趟过坑的网友也把自己的开发经历写成文档发布出来,这也是一个很大的帮助。
所以,整个过程,我用到的文档有三个:
微信公众平台首页最下方提供的开发文档(文档一)
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
这里,主要用到该文档对JSAPI在网页上初始化配置,以及调起微信支付这两部分的代码。
登录微信公众平台后,最后一个菜单“接口权限”里的微信支付接口,点击进去的文档(文档二)
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
这里用到统一下单接口的说明,以及该接口中的参数生成规则。
网友提供的经验分享文档(文档三)
https://blog.sina.com.cn/s/blog_48422a050102w364.html
这里用到网页支付的整个流程以及每个流程中需要用到的接口及参数
注意:只要研究透这三个文档,重点看我标注的地方就可以,这样就不用花大量的时间在网上搜索教程,并且很多教程要么语焉不详,要么错误百出,会造成误导,浪费更多的时间。(文档三也有一个错误,导致我浪费了半个下午的时间,后面我会提到。)
接口
整个支付过程中,需要用到的官方接口有三个:
统一下单接口是微信支付的核心接口,主要是将订单信息发送给微信服务端,微信服务端将订单信息存储后,返回的参数中有一个参数prepay_id,这个参数就是当前订单在微信服务端的唯一标记,可以理解为主键ID。这也是我们调用这个接口,需要获取的主要参数。
获取accessToken接口:
String getTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
这个接口的作用很单一,就是获取accessToken参数,以备下一个参数使用。其中有两个请求参数,分别是微信公众平台的APPID和APPSECRET,在开发者模式下可以拿到,如下图:
获取jsapi_ticket接口:
String getTicketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken
这个接口的作用也是获取一个参数ticket,以备后面生成签名时用,请求参数accessToken已经通过上一个接口获得,所以可以直接获取目标参数ticket。
整个支付过程,需要跟微信服务端交互的官方接口,只有这三个,以上三个接口的具体使用场景,下面会详细讲到,这里只需要了解一下。
开发
整个开发流程如下:
流程图
基础配置
登录微信公众号后台,开通微信支付功能
申请微信支付功能。开通微信支付功能时,需要提供各种资质和联系方式,按照要求申请即可,这里不会有任何问题。
微信支付菜单如下图:
微信支付开通之后,如下图:
开发配置
还是在微信支付这个功能页面,第二个页签,需要配置支付授权目录。
注意:支付授权目录:实际上用户发起微信支付后,我们的服务端访问处理微信支付的那个请求路径的上一层。
举个例子:用户在微信公众号上发起请求后,会调用我们服务端的地址:
https://www.test.com/wechat/pay/send 。
那么,他的支付授权目录就是:
https://www.test.com/wechat/pay/。
如果没有配置这个目录,或者配置的目录与实际支付目录不一致,支付都会失败,并且报的错误是:{"errMsg":"chooseWXPay:fail"}。支付授权目录可以配置五个,如果真的拿不准,可以多配置几个,但是这样会存在安全风险,慎用。
安全域名
微信公众平台升级过很多次,在2015年的时候,还没有强制填写安全域名。这次做微信支付时,遇到了这个问题。
在开发中,跟微信交互都是通过我方服务端的接口,所以需要在微信公众平台对接口的域名进行安全设置,方法如下:
登录微信公众平台,在左侧菜单中找 设置》公众号设置》功能设置。在这里,有三个需要填写域名的地方,业务域名,JS接口安全域名,网页授权域名。微信支付只跟JS接口安全域名相关,但是我们把这三个地方的安全域名全部都填写下。
在菜单中选择“公众号设置”
选择功能设置
注意:
这里的域名必须是有备案的域名,否则在填写的时候无法验证通过;
在配置域名时,需要将官方提供的验证文件放在服务端接口项目根目录下,这个按照要求处理即可。
本地调试
在所有的软件开发中,都需要在本地一边开发一边调试。但是,微信公众号开发比较特殊,无法直接在本地调试。因为必须在微信公众平台上配置服务端的接口地址,微信公众号上的所有请求,都会通过这个接口地址来调用服务端的服务。微信公众平台是外网,本地环境是内网,不能直接通过外网直接调用内网接口,所以,这里必须用到内网映射,将本地的内网映射成一个外网,这样就可以通过这个映射调用本地服务,进行接口调试。
内网映射,也被叫做内网穿透。实现方式一般有两种:
通过第三方代理,将内网地址和端口映射到一个外网地址和端口。现在有很多免费的内网映射工具,最主流的是ngrok,用法可以自己百度。但是2015年以后,微信公众号要求接口域名必须备案,因为ngrok映射的外网域名是没有经过备案的,所以,ngrok就无法使用了。目前只能去购买第三方收费版的内网映射服务,我们这次用的花生壳的服务。
如果公司有网络专线,同时有外网IP,就非常简单,将已经备案的域名解析到自己的外网IP,通过路由器进行内网映射就可以。
关于内网映射是另外一个话题,这里就不做赘述,感兴趣的话我们可以单开话题讨论。
接口开发
以上准备工作完成以后,就可以正式进入开发阶段。因为不同的支付场景支付的流程可能会有差异,这里抽取出主要的流程举例。
支付相关页面三个:
支付三个页面(wx_pay.jsp):收集openid和支付金额,并传递给订单页面。
订单页面(wx_order.jsp):封装订单,封装微信支付参数,发起微信支付请求。
订单页面(wx_success.jsp):成功信息展示。
流程如下:
流程图
支付页面开发:
这个页面需要拿到openid和支付金额两个参数传递给订单页面去使用,支付金额是用户输入的内容,直接获取就可以,获取openid最直接的方式就是通过网页授权接口获取。将支付页面的访问地址配置成微信公众号的底部菜单,当用户点击菜单时,进入微信支付页面,我们通过这个请求,就可以拿到openid,或者直接调用网页授权接口来获取openid。
获取网页授权的接口文档:
网页授权有两种模式,(基础授权)和 (用户信息授权),我们这里只需要拿openid,所以(基础授权)就可以。
获取openid比较简单,也有什么坑,所以严格按照上面的文档说明去获取,不会有任何问题。
订单页面开发:
微信网页支付的核心就在这一步,这里也是坑最多,流程最复杂的地方,如果把这里搞明白了,微信网页支付基本上就算完成了80%。
订单支付分为两部分,一个是前端页面,一个是后端业务接口。微信网页支付主要是依赖前端js方法实现,后端接口仅是给前端js方法提供必要的参数。
前端页面(wx_order.jsp)基本结构如下:
引入微信支付的js版jdk
调用微信支付
添加支付按钮
确认付款
这里做了一个ajax的异步请求,在订单页面,当用户确认支付信息无误,就点击确认付款按钮,这时调用后端接口,后端接口进行“封装订单,封装微信支付参数”的处理后,将微信支付参数返回到前端页面。如果参数无误,则会自动调起微信支付,用户输入支付密码后,完成支付。
这里,主要是wx.config和wx.chooseWXPay两个方法,第一个方法进行微信支付验证,验证用户身份,支付参数是否合法。当第一个方法验证通过后,就可以调用wx.chooseWXPay,如果这个方法的参数也都准确无误,则会自动弹出微信支付页面。
所以,关键就在这两个方法中的参数。
wx.config所需参数:
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
timestamp: payReq.timestamp , // 必填,生成签名的时间戳
nonceStr: payReq.noncestr, // 必填,生成签名的随机串
signature: payReq.signature,// 必填,签名,见附录1
jsApiList: [ chooseWXPay ] //必填,需要使用的JS接口列表,所有JS接口列表见附录2
这里需要我们获取的参数有timestamp,nonceStr,signature 其他参数都是现成的,直接用就可以。
wx.chooseWXPay所需参数:
timestamp:payReq.timestamp,//支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr:payReq.noncestr, // 支付签名随机串,不长于 32 位
package: payReq.prepayId,//统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=*)
signType: MD5 , // 签名方式,默认为 SHA1 ,使用新版支付需传入 MD5
paySign: payReq.paySign, // 支付签名
这里需要我们获取的参数有:timestamp,nonceStr,package,paySign其他参数都是现成的,直接用就可以。
合并起来,我们总共要获取的参数是:timestamp,nonceStr,package,signature,paySign
这些参数,我们通过后端接口获取,然后传递给前端页面。当用户在订单页面点击确认支付后,ajax请求后端接口,后端接口可以接到参数:openid,订单金额。封装订单信息根据自身的业务封装对象即可。重点说前端页面所需参数的获取。
调用“统一下单”接口,获取参数:timestamp,nonceStr,package
接口文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
timestamp,nonceStr:按照文档中“统一下单”接口中参数的算法,可以直接生成。
package:这个参数获取比较麻烦,文档中需要用到两个特殊的参数:mch_id(商户号),key(商户密码),这两个参数要去商户平台拿。另外需要一个签名参数sign,这个需要用其他参数合并生成。
商户号:
商户KEY:
注意:登录商户平台的浏览器,必须是IE内核,否则无法登陆。
拿到这些参数后,按照签名算法,可以得到sign,注意,此处是MD5加密。
最后,按照统一下单接口的文档,传递参数调用接口,在返回结果中可以拿到参数名为:的参数值,就是package。
signature:获取这个签名参数,sha1加密,需要参数,所有的签名算法都是一样的,只是所需要的参数不一样,所以,这里的关键就是获取。这块比较绕,通过查阅大量文档最终确定了下面的关系:
获取signature需要用到参数获取参数需要调用前面说的第三个接口,并且用到参数获取参数需要调用前面说的第二个接口。
所以,按照这个顺序依次获取,当拿到参数后,我们来生成签名signature。
String signatureStr = "jsapi_ticket=" + ticket + "&noncestr=" + payReq.get("noncestr").toString()
signatureStr = Encoder.SHA1Encode(signatureStr.getBytes());
这里payReq是上面统一下单接口的返回结果集,url是当前接口的URL路径。将签名拼出来之后,直接加密,就拿到了我们要的签名。
paySign:签名,MD5加密。
整个签名生成相对容易些,因为需要用到的参数现在已经全部具备了,只需要拼接签名,然后加密即可。
String paySignStr = "appId=" + APP_ID + "&nonceStr=" + payReq.get("noncestr").toString() + "&package="
这里注意两点,为了方便,我自己把需要用到的参数全部放到了payReq对象中,所以很多参数都是从这里取出来的。另外,prepayId这个参数key的拼接不太一样,需要这样来处理:
payReq.put("prepayId", "prepay_id=" + payReq.get("prepayId")); 可以看出来,key的参数名后面,要加上“=”,这个坑也趟了很久才出来。
同时,关于timeStamp中S大小写的问题,很多文档说的模糊不清,这里做一下总结:
只需要记住一点,所有使用timeStamp的地方,只有在生成paySignStr签名时,用的到timeStamp作为key的S是大写,其他地方,都小写。
至此,微信网页支付所需要的所有参数获取完毕,只需要将这些参数从服务端返回给前端页面的两个JS方法(wx.config和wx.chooseWXPay),当在页面点击支付按钮时,调用这两个方法,就可以看到期待已久的微信支付弹层。
总结
微信网页支付技术上基本上没什么难度,难的是流程复杂以及官方文档的不足。为了拿到一个参数,需要调用两个接口获取,这种情况之前也是没遇见过。所以,我们这里只需要记住几个关键点:
两个方法(wx.config和wx.chooseWXPay)
五个参数(timestamp,nonceStr,package,signature,paySign)
三个接口
三个页面以及三个文档。
另外需要注意的,就是签名的生成,整个过程用到了三个签名:
第一个签名sign。在统一下单接口中的请求参数中,sign做为这个接口的请求参数之一,需要用其他参数生成之后,才能作为请求参数再去请求,这里用的是MD5加密。
第二个签名signature。这个签名是给wx.config用的,加密方式是SHA256。
第三个签名paySign。这个签名是给wx.chooseWXPay用的,这里用的是MD5加密。
备注:这里说明一下前面提到的文档三中的一处错误,其中提到的参数“appid”,应该是“appId”,i是需要大写的。这个细节很容易被忽视,我已第一时间通知作者,希望能尽快更正。
只要在把以上几个关键点搞清楚,那么微信网页支付就非常简单,而为了搞清楚这些,我花了大量的时间并查阅了各种文档,同时跟微信商户支付的技术支持沟通了五六封邮件,最终才调试通过。
最后更新:2017-10-08 00:02:03