微信公众号开发
前面做过 HG 项目的微信端,里面用到微信扫码、支付、图片选取、拍照、分享的功能。用到 weixin-js-sdk 和 WeixinJSBridge。
前端开发以Vue代码为例,后端以NodeJS为例。后端开发还需详细阅读开发者文档。
1、开发准备
1.1 新建测试号
用于开发和测试环境,产品经理需准备
步骤:
登录【公众号平台】 → 侧边栏开发选择【开发者工具】→ 进入【公众平台测试帐号】→ 扫码确认 → 成功
注意:测试号的配置都在此页面
如图:
1.2 配置公众号
保存 appID 、appSecret 和 测试号二维码
注意:及时保存,以免需要的时候找不到
修改授权回调页面地址
注意:用户确认授权后重定向的地址
修改接口配置信息、JS接口安全域名
注意:调用 weixin-js-sdk 功能或调用 WeixinJSBridge 页面所在域名
修改JS安全域名:
公众号主页侧边栏设置选择【公众号设置】→ 选择上面第二个tab【功能设置】第三项
如果有支付功能需要配置授权目录
注意:这个需要在商户平台修改,不是在公众平台
举例:
现在需要在 http://www.behuntergatherer.com/pay/payment 页面进行支付,就需要设置一个授权目录为 http://www.behuntergatherer.com/pay/ 。在支付页面路由的前一个目录。
建议:
统一一个页面进行支付。比如 http://www.behuntergatherer.com/pay/payment 是支付页面,我在 http://www.behuntergatherer.com/vip/order 页面需要支付,则生成好订单后跳转 http://www.behuntergatherer.com/pay/payment 页面进行支付。
下载安装【Web开发者工具】
注意:开发同学准备,下载地址
目的:
页面开发兼容性、布局和浏览器表现不一致
微信接口调试、调用接口有日志
注意事项:
支付功能不能在本地测试,不能使用测试公众号测试
开发工具可能存在未知 bug,请保持更新
##2、简单config封装
2.1 开发文档
示例 用手机端微信打开体验
微信JS接口 微信接口详细文档
微信JS接口调试工具 可以在线上测试接口返回的数据结构
2.2 配置封装
这些接口需要配置以后才能调用
API可以配需要的几个,亦可以全部
一次配置只对当前页面有效,跳转页面需要重新配置
2.2.1 JS-SDK接口配置
注意:需要调用微信JSAPI接口的页面,不能使用h5 pushState进行跳转,会造成无效的签名问题。请使用location.href。
// utils.js//微信配置接口通用方法constALL_API_LIST = ['scanQRCode','...']exportfunctionconfigWxApi(jsApiList = ALL_API_LIST){returnnewPromise((resolve, reject) =>{//使用当前的href获取签名,后端返回配置接口所需要的参数get('/mdm2/api/getSignature.do', {url:encodeURIComponent(window.location.href),}).then(data=>{wx.config({debug: process.env.NODE_ENV !=='production',appId: data.appId,timestamp:Number(data.timestamp),//秒数,不是毫秒nonceStr: data.nonceStr,signature: data.signature,jsApiList,})wx.error(function(res){Toast('调用微信jsapi返回的状态:'+ res.errMsg)reject()})wx.ready(resolve)})})}
2.2.2 WeixinJSBridge
// utils.jsexportfunctioncheckWxBridge(){returnnewPromise((resolve, reject) =>{if(typeofWeixinJSBridge =="undefined") {if(document.addEventListener) {document.addEventListener('WeixinJSBridgeReady', resolve,false)}elseif(document.attachEvent){document.attachEvent('WeixinJSBridgeReady', resolve)document.attachEvent('onWeixinJSBridgeReady', resolve)}}else{resolve()}})}
2.3 配置函数使用
2.3.1 JS-SDK使用
注意事项:
所有调用 JS-SDK 的接口都必须 queryWxApi
都必须处理错误情况,增加用户体验
//采用Promise封装有两种使用方法,统一选择一个// app.jsimport{ configWxApi }from'utils'// Promise写法mounted () { configWxApi() .then(res=>{//配置完成wx.chooseImage() }) .catch(err=>{//配置失败})}//同步函数写法asyncmounted () {try{constconfigResult =awaitqueryWxApi(['scanQRCode'])//成功处理}catch(err) {//错误处理}}
2.3.2 WeixinJSBridge
注意事项:
所有调用JS-SDK的的接口都必须 checkWxBridge
都必须处理错误情况,增加用户体验
// someComponent.jsimport{ checkWxBridge }from'utils'checkWxBridge().then(()=>{ WeixinJSBridge.call('hideOptionMenu') WeixinJSBridge.invoke('getBrandWCPayRequest')})
3、后端开发示例
后端一两个常见例子说明
3.1 Access_token
这是微信开发中最基础的东西,详情见官方文档 详细文档 => 任意门
这是什么东西?
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token
有什么属性?
access_token的存储至少要保留512个字符空间
access_token的有效期目前为2个小时(7200秒)
重复获取将导致上次获取的access_token失效
怎么获取?
调用接口时,请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,点击查看设置方法,否则将无法调用成功。
https请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
参数说明
参数是否必须说明
grant_type是获取access_token填写client_credential
appid是第三方用户唯一凭证
secret是第三方用户唯一凭证密钥,即appsecret
返回说明
正常情况下,微信会返回下述JSON数据包给公众号:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
开发建议:统一获取和刷新Access_token,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务。
// accessToken.jsmodule.exports = {getToken:function(callback){//判断token是否过期,如果过期就重取,未过期就返回vartokenPath = path.join(__dirname,'./token.json')vartoken =JSON.parse(fs.readFileSync(tokenPath))varcurrTime = utils.getTimeStamp().secondvarisExpired = currTime > token.timestampif(isExpired) fetchToken(callback)elsecallback(token.value)},updateToken:function(callback){fetchToken(callback)},}functionfetchToken(callback){varconfigPath = path.join(__dirname,'./config.json')varconfig =JSON.parse(fs.readFileSync(configPath))varparam = {grant_type:'client_credential',appid: config.appId,secret: config.secret,}varparamStr = utils.obj2Params(param)varurl =`https://api.weixin.qq.com/cgi-bin/token?${paramStr}`request(url,function(err, res, body){//记录获取token时间,两小时后过期重取try{varresReult =JSON.parse(body);varnewToken = {value: resReult.access_token,timestamp: utils.getTimeStamp().second +7200, }varfilePath = path.join(__dirname,'token.json') fs.writeFileSync(filePath,JSON.stringify(newToken)) callback(newToken.value) }catch(e) {console.log('获取token出错,', e) callback() }})}// somejs.jsimport{ getToken }from'accessToken'getToken(function(token){//其他业务})
3.2 获取用户openid
公众号开发中常见的需求 详细文档 => 任意门
openid是啥?
是加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同。
openid能干啥?
公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间
提示:
用户头像链接http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0 修改链接最后一个数字也是就是0,能获取不同尺寸的头像,(有0、46、64、96、132数值可选,0代表640*640正方形头像)。
{"subscribe": 1, //用户是否订阅该公众号0:未关注1:已关注"openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", "nickname": "Band",//用户的昵称"sex": 1, //性别,1:男性,2:女性,0:未知"language": "zh_CN", //用户的语言,简体中文为zh_CN "city": "广州", "province": "广东", "country": "中国", "headimgurl": "LINK_URL",//用户头像图片链接"subscribe_time": 1382694957,//关注公众号时间"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL", // "remark": "",//公众号运营者对粉丝的备注"groupid": 0,//用户所在的分组ID(兼容旧的用户分组接口)"tagid_list":[128,2]//用户被打上的标签ID列表}
怎么拿到openid?
两种获取的方法(相同接口所带参数不同而已):
静默授权 snsapi_base,体验较好,获取到的数据较少
用户授权 snsapi_userinfo,体验较差,用户可能拒绝,获取到的数据多
注意:对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是scope为snsapi_userinfo,也是静默授权,用户无感知。
3.3 获取签名
签名在微信开发过程中扮演了一个非常重要的角色,很容易出错 详细文档 => 任意门
签名是啥?
公众号用于调用微信接口的临时票据
怎么获取?
官方文档说明 详细文档 => 任意门
3.4 支付功能实现
官方详细文档 => 任意门
4、前端开发示例
4.1 关注快捷入口
微信是反对引导关注的,但是,为了能留住访客。we have to。使用的链接是公众号详情页面下【历史消息】页面。可以点进页面,然后右上角【点点点】点击然后复制链接获取。
4.2 接口使用【支付】(WeixinJSBridge)
官方详细文档 => 任意门
4.2.1 功能开发
注意事项
时间戳为秒数,部分系统取到的值为毫秒级,需要转换成秒(10位数字)
package 内容是"prepay_id=" 是预订单id(统一下单任意门)
paySign 是 params 所有参数根据微信签名规则生成
import{ checkWxBridge }from'utils'someFunction () {get('/wx/fetchSign').then(data=>{// data需要的数据// appId, timeStamp, nonceStr, package, paySignconstparams =Object.assign(data, {signType:'MD5'}) checkWxBridge().then(()=>{ WeixinJSBridge.invoke('getBrandWCPayRequest', params, res => {// res数据结构{err_msg: 'get_brand_wcpay_request:ok'}// err_msg可能的值ok、fail、cancel}) }) })}
4.2.2 测试号测试支付
测试号是没有支付功能,需要用正式的公众号,而且需要有支付权限的公众号(个人公众号不可支付)。在测试环境使用正式环境的openid去实现支付。
varopenIdMap = {'测试公众号openId':'正式公众号openId','正式公众号openId':'测试公众号openId',}//测试环境1.【前端】购买请求2.【后端】用户购买(测试公众号openId)3.【后端】统一下单用(openIdMap[测试公众号openId])4.【前端】拿到prepay_id,WeixinJSBridge.invoke('getBrandWCPayRequest')支付5.【前端】用户支付请求=>微信6.【后端】收到支付成功或回调(可异步或同步),通知前端7.【后端】保存相应数据到(openIdMap[正式公众号openId])
4.3 头像剪切功能(JS-SDK)
CRMWeb 项目使用的 VueJS,使用的是npm相关包 vue-croppa
4.3.1 通过Input选择
vue-croppa 直接选择图片
输出裁剪后的图片
4.3.2 通过weixin-js-sdk选择
页面签名
import{ queryWxApi }from'utils'mounted () {queryWxApi().then(res=>this.wxConfSucc =true)}
选择本地图片或拍照,获取到 localId
wx.chooseImage({count:1,//可选择张数sizeType: ['original','compressed'],sourceType: ['album',//从相册选'camera',//拍照],success:(res) =>{letlocalId = res.localIds[0] },})
通过 localId 上传到微信服务器拿到 serverId(只能保存3天)
wx.uploadImage({localId: localId,//本地IDisShowProgressTips:1,//是否显示上传进度success:res=>{constserverId = res.serverId },})
服务端通过 serverId 下载图片到自己服务器,并返回图片链接给前端
croppa( v-model="croppa", :width="coroppaStyle.width":height="coroppaStyle.height":disable-click-to-choose="false":show-remove-button="false":initial-position="position") img( :src="showImgUrl"slot="initial")
5、常见错误
调用 config 接的时候传入参数 debug: true 可以开启 debug 模式,页面会 alert 出错误信息。以下为常见错误及解决方法:
5.1 invalid url domain
当前页面所在域名与使用的 corpid 没有绑定(可在该企业号的应用可信域名中配置域名)。
5.2 invalid signature
签名错误,建议按如下顺序检查:
确认签名算法正确,可用 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 页面工具进行校验。
确认 config 中 nonceStr, timestamp 与用以签名中的对应 noncestr, timestamp一致。
确认 url 是页面完整的 url (请在当前页面 alert(location.href.split('#')[0]) 确认),包括 http(s):// 部分,以及'?'后面的 GET 参数部分,但不包括 '#'hash 后面的部分。
确认 config 中的 appid 与用来获取 jsapi_ticket 的 corpid 一致。
确保一定缓存 access_token 和 jsapi_ticket。
可能获取 access_token 次数超过一天限制的数量。
使用 pushState 来实现 web app 的页面会导致签名失败,改用 location.href。
确保你获取用来签名的 url 是动态获取的,动态页面可参见实例代码中 php 的实现方式。如果是 html 的静态页面在前端通过 ajax 将 url 传到后台签名,前端需要用js获取当前页面除去'#'hash部分的链接(可用 location.href.split('#')[0] 获取,而且需要encodeURIComponent),因为页面一旦分享,微信客户端会在你的链接末尾加入其它参数,如果不是动态获取当前链接,将导致分享后的页面签名失败。
5.3 the permission value is offline verifying
这个错误是因为 config 没有正确执行,或者是调用的 JSAPI 没有传入 config 的 jsApiList 参数中。建议按如下顺序检查:
确认 config 正确通过。
如果是在页面加载好时就调用了 JSAPI,则必须写在 wx.ready 的回调中。
确认 config 的 jsApiList 参数包含了这个 JSAPI。
5.4 permission denied
该企业号没有权限使用这个 JSAPI(部分接口需要认证之后才能使用)
5.5 function not exist
当前客户端版本不支持该接口,请升级到新版体验。
5.6 6.0.1版本config:ok,6.0.2版本之后不ok
因为6.0.2版本之前没有做权限验证,所以 config 都是ok,但这并不意味着你 config 中的签名是 ok 的,请在6.0.2检验是否生成正确的签名以保证 config 在高版本中也 ok。
5.7 在iOS和Android都无法分享
请确认企业号已经认证,只有认证的企业号才具有分享相关接口权限,如果确实已经认证,则要检查监听接口是否在wx.ready回调函数中触发)
5.8 服务上线之后无法获取jsapi_ticket
因为 access_token 和 jsapi_ticket 必须要在自己的服务器缓存,否则上线后会触发频率限制。请确保一定对token 和 ticket 做缓存以减少服务器请求,不仅可以避免触发频率限制,还加快你们自己的服务速度。目前为了方便测试提供了1w的获取量,超过阀值后,服务将不再可用,请确保在服务上线前一定全局缓存 access_toke和 jsapi_ticket,两者有效期均为7200秒(以返回结果中的 expires_in 为准),否则一旦上线触发频率限制,服务将不再可用。
5.9 uploadImage传多图
目前只支持一次上传一张,多张图片需等前一张图片上传之后再调用该接口。
5.10 对本地选择的图片进行预览
chooseImage 接口本身就支持预览,不需要额外支持。
5.11 出现config:fail错误
这是由于传入的 config 参数不全导致,请确保传入正确 appId、timestamp、nonceStr、signature 和需要使用的 jsApiList。
5.12 使用pushState来实现web app的页面会导致签名失败
此问题已在Android6.2中修复。在IOS中此问题依然存在。
5.13 Android uploadImage在chooseImage的回调中有时候会不执行
Android6.2 会解决此问题,若需支持低版本可以把调用 uploadImage 放在 setTimeout 中延迟 100ms 解决。
5.14 getLocation坐标在openLocation有偏差
因为 getLocation 返回的是gps坐标,openLocation 打开的腾讯地图为火星坐标,需要第三方自己做转换,6.2 版本开始已经支持直接获取火星坐标。
5.15 未关注测试号
通过 snsapi_base 方式获取未关注公众号的用户 openid。测试号是不可以的。
6、附录
6.1 微信JSAPI列表
['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone','startRecord','stopRecord','onVoiceRecordEnd','playVoice','pauseVoice','stopVoice','onVoicePlayEnd','uploadVoice','downloadVoice','chooseImage','previewImage','uploadImage','downloadImage','translateVoice','getNetworkType','openLocation','getLocation','hideOptionMenu','showOptionMenu','hideMenuItems','showMenuItems','hideAllNonBaseMenuItem','showAllNonBaseMenuItem','closeWindow','scanQRCode',]
6.2 WeixinJSBridge 接口列表
[//类型一'hideOptionMenu',//隐藏右上角按钮'showOptionMenu',//显示右上角按钮'hideToolbar',//隐藏底部工具栏'showToolbar',//显示底部工具栏//类型二'getNetworkType',//获取网络状态'getBrandWCPayRequest',//调用支付]//类型一WeixinJSBridge.call('hideToolbar')//类型二WeixinJSBridge.invoke('getBrandWCPayRequest')
6.3 所有菜单项列表
[//基本类'menuItem:exposeArticle'//举报'menuItem:setFont'//调整字体'menuItem:dayMode'//日间模式'menuItem:nightMode'//夜间模式'menuItem:refresh'//刷新'menuItem:profile'//查看公众号(已添加)'menuItem:addContact'//查看公众号(未添加)//传播类'menuItem:share:appMessage'//发送给朋友'menuItem:share:timeline'//分享到朋友圈'menuItem:share:qq'//分享到QQ'menuItem:share:weiboApp'//分享到Weibo'menuItem:favorite'//收藏'menuItem:share:facebook'//分享到FB//保护类'menuItem:jsDebug'//调试'menuItem:editTag'//编辑标签'menuItem:delete'//删除'menuItem:copyUrl'//复制链接'menuItem:originPage'//原网页'menuItem:readMode'//阅读模式'menuItem:openWithQQBrowser'//在QQ浏览器中打开'menuItem:openWithSafari'//在Safari中打开'menuItem:share:email'//邮件'menuItem:share:brand'//一些特殊公众号]