1. 前言花费了一天时间,调通了微信公众号支付。作下记录,方便以后再次填坑。先声明,微信公众号支付,不同于微信H5支付,这点在本文结束时再详细说明。
2. 微信配置2.1. 设置测试目录在微信公众平台设置,栏目见下图。支付测试状态下,设置测试目录,测试人的微信号添加到白名单,发起支付的页面目录必须与设置的精确匹配。并将支付链接发到对应的公众号会话窗口中才能正常发起支付测试。注意正式目录一定不能与测试目录设置成一样,否则支付会出错。
2.2. 设置正式支付目录根据图中栏目顺序进入修改栏目,勾选JSAPI网页支付开通该权限,并配置好支付授权目录,该目录必须是发起支付的页面的精确目录,子目录下无法正常调用支付。具体界面如图所示:
3. 交互流程3.1. 业务流程时序图首先看下微信官方给出的交互流程:
3.2. 服务器交互微信公众号支付的流程,简而言之只有两步。第一步,下单,拿到prepay_id等信息;第二步,利用第一步拿到的prepay_id等信息进行支付。
4. 代码4.1. Node端1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 exports .weixinpay = function (req, res ){ var orderId = req.params .orderId ; var selectSum = req.params .selectSum ; var token = req.params .token ; if (!req.query .code ){ var r_url = 'http://' +config.host +'/artist/weixinpay/' +orderId+'/' +selectSum+'/' +token; var url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' +config.weixin .appid +'&redirect_uri=' +urlencode (r_url)+'&response_type=code&scope=snsapi_userinfo&state=111#wechat_redirect' ; res.redirect (url); }else { var code = req.query .code ; var state = req.query .state ; var ep = new eventproxy (); ep.all ('userInfo' ,function (userInfoData ){ var userInfoData = JSON .parse (userInfoData); res.render ('artist/weixinpay' ,{ openid : userInfoData.openid , orderId : orderId, selectSum : selectSum, token : token }); }); } } exports .weixinpay2 = function (req, res ){ var wxParamUrl = config.apihost + '/order/getH5SubOrderforWechat' ; var param = { orderId : req.query .orderId , selectSum : req.query .selectSum , accessToken : req.query .token , openId : req.query .openid }; request.post ({url : wxParamUrl,form : JSON .stringify (param)},function (error, response, body ){ res.render ('artist/weixinpay2' ,{ wxParam : JSON .parse (response.body ) }); }); }
上面的代码看起来很奇怪,为什么需要一个weixinpay2函数?代码全部放在weixinpay函数里面,然后在weixinpay.html里调用微信支付接口,不是很好么?这个,就涉及到支付目录的问题了。weixinpay函数中,微信授权获取用户信息后的回调url,目录太深,而且不确定,因为我们是通过目录来传递参数的。这样,就无法在微信公众号上面设置支付授权目录。所以,我们不能在weixinpay.html这个页面发起支付请求,只能把参数转发给下一个weixinpay2.html页面,在weixinpap2.html中发起支付请求。
那么,为什么获取用户信息后的回调url目录太深?直接把orderId等信息当做参数放在回调url后面不就可以了吗?很遗憾,回调url无法带入你的自定义参数。因此,只能把参数当做回调url的一部分,也就是目录的一部分。
综上,weixinpay函数负责获取用户的openid,weixinpay.html负责跳转weixinpay2.html;weixinpay2函数负责获取调用微信JSAPI的参数,weixinpay2.html负责调用微信JSAPI。
4.2. html1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html > <html lang ="zh" > <head > <meta charset ="UTF-8" > <title > 微信支付</title > </head > <body > <a id ="weixin-link" href ="/artist/weixinpay2?orderId=<%= orderId%>&selectSum=<%= selectSum%>&token=<%= token%>&openid=<%= openid%>" style ="display: none" > 微信支付</a > <script > var link = document .getElementById ('weixin-link' ); link.click (); </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <!DOCTYPE html > <html lang ="zh" > <head > <meta charset ="UTF-8" > <title > 微信支付</title > </head > <body > <input id ="appId" type ="hidden" value ="<%= wxParam.obj.appid%>" > <input id ="timeStamp" type ="hidden" value ="<%= wxParam.obj.timestamp%>" > <input id ="nonceStr" type ="hidden" value ="<%= wxParam.obj.noncestr%>" > <input id ="package" type ="hidden" value ="<%= wxParam.obj.packagestr%>" > <input id ="signType" type ="hidden" value ="MD5" > <input id ="paySign" type ="hidden" value ="<%= wxParam.obj.sign%>" > <script > function onBridgeReady ( ){ var appId = document .getElementById ('appId' ).value ; var timeStamp = document .getElementById ('timeStamp' ).value ; var nonceStr = document .getElementById ('nonceStr' ).value ; var package = document .getElementById ('package' ).value ; var signType = document .getElementById ('signType' ).value ; var paySign = document .getElementById ('paySign' ).value ; WeixinJSBridge .invoke ( 'getBrandWCPayRequest' , { "appId" : appId, "timeStamp" :timeStamp, "nonceStr" : nonceStr, "package" : package, "signType" : signType, "paySign" : paySign }, function (res ){ WeixinJSBridge .log (res.err_msg ); if (res.err_msg == "get_brand_wcpay_request:ok" ) { window .location .href = '/artist/alipayresult?trade_status=TRADE_SUCCESS' ; } else if (res.err_msg == "get_brand_wcpay_request:cancel" ) { alert ("用户取消支付!" ); } else { alert ("支付失败!" ); } } ); } if (typeof WeixinJSBridge == "undefined" ){ if ( document .addEventListener ){ document .addEventListener ('WeixinJSBridgeReady' , onBridgeReady, false ); }else if (document .attachEvent ){ document .attachEvent ('WeixinJSBridgeReady' , onBridgeReady); document .attachEvent ('onWeixinJSBridgeReady' , onBridgeReady); } }else { onBridgeReady (); } </script > </body > </html >
为了加快执行速度,这里的两个页面中,都使用原生的js来实现页面逻辑。
5. 错误5.1. 支付验证签名失败 很明显,支付验证签名失败。怎么判断算出的签名是否正确?使用微信支付接口签名校验工具 。 校验方式选择自定义参数,把需要签名的参数名称和参数值输入页面,点击生成签名即可。
5.2. 当前页面的URL未注册不好意思,这个错误忘记截图了。借来了一张图,凑合着看。 这个问题,是由于调用支付接口的url不对!那么,怎样是对的呢?举个栗子:如果调用支付接口的url为http://wx.voidking.com/pay/weixinpay
,那么微信公众平台上的支付授权目录应该设置为http://wx.voidking.com/pay/
。也就是说,支付授权目录应该设置为调用支付接口的url的上一级目录。
6. 支付成功
7. 后记微信公众号支付,两个缺点,一是必须在微信浏览器使用,二是必须有一个拉取用户信息的步骤。但是我们发现,很多网站,在其他浏览器也可以使用微信支付,也不需要拉取用户信息,这是怎么回事?
因为,这是两种不同的支付方式!今天我们讨论的,叫做微信公众号支付;而在其他浏览器中调用微信支付,叫做微信H5支付!
更多关于微信H5支付的内容,请参考微信H5支付官方文档 。
8. 书签微信支付开发文档——公众号支付https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
微信支付接口签名校验工具https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1
非微信内置浏览器中的网页调起微信支付的方案研究http://blog.csdn.net/ahence/article/details/51317814
微信支付|商户平台开发者文档https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=15_1
【微信支付V2.0】H5支付实例http://wxpay.weixin.qq.com/pub_v2/pay/wap.v2.php