微信公众号开发

开发微信公众号真是一堆坑, 自己踩过了, 开发前耐着性子看一遍.
授权回调必须在微信浏览器里, 不同Ajax调用会跨域…
接口调试都是在线的, 还好有NATAPP这玩意…
MP_verify_xxxxxxxx.txt只在你设置域名时才校验…

[TOC]

1 公众号开发准备

1.0 微信公众平台与开放平台

(1) 公众平台: https://mp.weixin.qq.com/ 微信客户端里的公众号开发相关;
(2) 开放平台: https://open.weixin.qq.com/ App相关接入微信;
(3) 商户平台: https://pay.weixin.qq.com/ 你把钱支付给谁;
(4) 区别: 开发App支付,分享, 登录就去开放平台. 开发公众号网页里的分享,支付就去公众平台.

1.1 申请公众号(服务号)

(1) 类型: 订阅号, 服务号, 企业号, 小程序

(2) 区别: 服务号可以使用公众号支付, 个人不能申请服务号;
(3) 服务认证每年要认证, 认证费300元/年.
(4) 服务号认证后可不受限制调用接口; 没有认证无法使用公众号分享等接口;
(5) 服务号及个人的订阅号都可以使用开发者工具里的”公众平台测试帐号”去测试相关接口, 测试时需要把相关测试人员加到测试组;

(6) 可以使用开发者工具里的”在线接口调试工具”去验证接口相关参数

2 公众平台

2.1 公众号分享

2.1.1 登录公众号, 设置业务域名

业务域名每月只能修改3次, 且要在相关目录下放验证MP_verify_xxxxxxxx.txt文件. 这个可能是微验证域名是否真实有效才加的, ICP备案域名设置完后就不在input框弹出”防欺诈盗号…”提醒.

2.1.2 设置JS安全域

设置了JS安全域名后就可以在这个域调用jsapi接口

2.1.3 设置授权回调域

这个域名在微信OAuth2.0获了用户授权时会回校验这个域名; 同样需要校验MP_verify_xxxxxxxx.txt
公众分享暂时用不到这个接口.

2.1.4 公众号AppId和AppSecret在那里

这两个参数在公众号开发过程中比较重要, 一直会用到. 注意开放平台也有AppId;不要搞混了;

2.1.5 开发文档

开发者工具 -> 开发者文档 -> https://mp.weixin.qq.com/wiki ->
微信网页开发-> 微信JS-SDK说明文档: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
另一个神一样链接: http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
内容差不多, 我参考是下面这个;

2.1.6 引入JS文件

http://res.wx.qq.com/open/js/jweixin-1.0.0.js
支持https

2.1.7 通过config接口注入权限验证配置, 并设置分享菜单功能;

完整微信分享代码:

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
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识, 登录公众号后 开发-> 基本配置里获取;
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名,见附录1
jsApiList: [
onMenuShareAppMessage,
onMenuShareQQ,
onMenuShareWeibo,
onMenuShareQZone
] // 必填,需要使用的JS接口列表
});

let shareParam = {
title: shopName, // 分享标题
link: location.href, // 分享链接
desc: '热门推荐、新品上架、促销优惠,尽在这里!', // 分享描述
type: 'link', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
imgUrl:'http://pic.qianmi.com/yunxiao/point/demo/img/download/icon04.png',//分享图标
success: function () {},
cancel: function () {}
};

wx.ready(function(){
wx.onMenuShareAppMessage(shareParam); // 分享给朋友 wx.onMenuShareQQ(shareParam); // 分享到QQ
wx.onMenuShareWeibo(shareParam); // 分享到腾讯微博
wx.onMenuShareQZone(shareParam); // 分享到QQ空间
});

wx.error(function(){});

看到这个里大家可能要问这些参数是从那里来的? 看下面, 文档组织太烂了
先要获取access_token再获取jspai_ticket,再经过签名算法生成config所需要的几个参数;

2.1.8 先获取access_token

access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。
参考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN
Requset:

1
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

Response:

1
{"access_token":"ACCESS_TOKEN","expires_in":7200}

APPID和APPSECRET 开发-> 基本配置里获取.
ACCESS_TOKEN时效7200秒
为这安全这步最好在后台实现

2.1.9 再获取jsapi_ticket

Request:

1
https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

Response:

1
2
3
4
5
6
{
"errcode":0,
"errmsg":"ok",
"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
"expires_in":7200
}

2.1.10 签名算法

参考: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
(1). 签名生成规则如下:
参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。
(2) 示例:

1
2
3
4
noncestr=Wm3WZYTPz0wzccnW
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg
timestamp=1414587457
url=http://mp.weixin.qq.com?params=value

对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:

1
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

对string1进行sha1签名,得到signature:

1
0f9de62fce790f9a083d5c99e95740ceb90c27ed

看到这里wx.config里需要参数就都有了
timestamp: , // 必填,生成签名的时间戳
nonceStr: ‘’, // 必填,生成签名的随机串
signature: ‘’,// 必填,签名,见附录1

2.1.11 代码调试

(1) 下载微信开发者工具
https://mp.weixin.qq.com/wiki?action=doc&id=mp1455784140&t=0.2745196293017851&token=&lang=zh_CN#6
(2) 开发者工具使用
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455784140&token=&lang=zh_CN
(3) 注册项: android按提示一步步进行没有多大问题. ios除了要在同一网段,还要注意手机上代理的配置, 见下图手机

2.2 公众号支付

2.2.1 支付目录设置

注这里是公众号支付在微信浏览器里面运行, 如果是微信App支付请到公众平台里操作; 公从平台提供jar里来调用微信支付; 微信App支付是跨Apk的, 公众里支付就是在微信里操作的;
(1) 登录公众号设置网页授权域名
(2) 设置支付目录

如果是测试可以设置支付测试目录, 添测试白名单的人才要可以使用支付;

2.2.2 公众号里拉调微信支付

参考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
跟上面分享一样, 先引用http://res.wx.qq.com/open/js/jweixin-1.0.0.js, 再设置wx.config,注意jsApiList参数中加上chooseWXPay接口

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
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识, 登录公众号后 开发-> 基本配置里获取;
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名,见附录1
jsApiList: [
onMenuShareAppMessage,
onMenuShareQQ,
onMenuShareWeibo,
onMenuShareQZone,
chooseWXPay, // 调用微信支付;
] // 必填,需要使用的JS接口列表
});

......

wx.chooseWXPay({
timestamp: 0, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: '', // 支付签名随机串,不长于 32 位
package: '', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
signType: '', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: '', // 支付签名
success: function (res) {
// 支付成功后的回调函数
}
});

看到这里, 问题又来了, 这些参数从那里来? 且看下面.

2.2.3 公众号支付整体流程

参考商户平台文档: https://pay.weixin.qq.com/wiki/doc/api/img/chapter7_4_1.png

简单描述:

  • A. 后台获调用统一下单接口完成下单生成(prepay_id), 注意: App支付和公众号支付(JSAPI)的统一下单接口一样, 参数不一样, 公众号支付(JSAPI)需要多传一个用户的openId;
  • B. 用户点击公众号页面的支付按钮 -> JSAPI(wx.chooseWXPay({prepay_id …})) -> 打开微信支付 -> 完成支付
  • C. 跳转公众号页面

2.3.4 统一下单(商户平台):

(1). URL地址:POST https://api.mch.weixin.qq.com/pay/unifiedorder
注: 商户平台的接口都是https + POST + XML方式, 且有些接口需要证书
又是不很绕的地方, 两个统一下单文档:
公众号支付: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
App支付: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
两个里参数有些不一样: appId, openId(App支付不需要, 公众号支付需要openId, 下面[获取用户授权](#2.3.5 获取用户授权)会介绍获取OpenId).
(2) 签名算法:
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
(3) 签名验证工具:
https://pay.weixin.qq.com/wiki/tools/signverify/
(4) 统一下单完成就得到wx.chooseWXPay所需要参数了;

商户平台没有登录过, 这里不做过多介绍.

2.3.5 获取用户授权后得到openId

参考: 网页授权获取用户基本信息 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842&token=&lang=zh_CN
另一个文档: https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html

2.3.5.1 注意事项

(1) 登录公众号设置网页授权域名
(2) 授权有静默授权和非静默授权
静默授权, 用户无感知 只能获取openId; 非静默会弹出授权页面(如下:), 可以获取用户昵称,头像等(详见:https://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html).

(3) 获取授权流程: JS调用同步接口回调用得到授权码 -> 用授权码获取OpenId和access_token -> 再获取用户其它信息;
(4) openId是一个微信号对一个公众号是唯一, 一个微信不同公众号是不同的.
openId = 加密(微信号, 公众号)
(5) openId不会变, 每次获取是一样的;
(6) 获取授权接口比较繁, 具体可以了解OAuth2.0, 另一篇阮一峰: 理解OAuth 2.0

2.3.5.2 用户授权,回调获取授权码code

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

(1) 必须在微信浏览器执行;
(2) 不能后台发送HTTP请求, 会返回必须在浏览执行提示;
(3) 不能Ajax方式因为会跨域;
(4) 不能iframe方式, 微信浏览器不会招待;
(5) 只使用url跳转方式, 猜想这个接口直接就右微信浏览器里redirect了;
(6) APPID - 公众号里基本配置里
(7) REDIRECT_URI - 回调的url可以带参数http://xxx.xzv/sdf?a=b&c=d, 必须encodeURIComponent编码否则回调时参数就丢失了;
(8) STATE 随你传吧, 感觉REDIRECT_URI带参数就可以了. STATE会保证回调给你;
(9) 示例

1
2
3
4
5
// 请求
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxdc38337d747d8cf9&scope=snsapi_base&state=hello-world&redirect_uri=http%3a%2f%2fwxydh.test.com%3a8080%2fopened%2fweixin%2fcallback%3fadminId%3dA864098%26userId%3dA876959

// 回调
http://wxydh.test.com:8080/opened/weixin/callback?adminId=A864098&userId=A876959&code=041bDlRu1J399b0LFGSu16FqRu1bDlRE&state=hello-world

(10) 回调会在你传的REDIRECT_URI参数后再添加code和state参数
(11) 通过code换取网页授权access_token和openId

2.3.5.3 通过code换取网页授权access_token和openId

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

(1) APPID - 公众号里基本配置里AppId;
(2) SECRET - 公众号里基本配置里AppSecret;
(3) CODE - 上一步获取的;
(4) 考虑安全这一步必须在后台进行;
(5) 接口返回:

1
2
3
4
5
6
7
{ 
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}

(6) 这里已经获取openId, 就可以进统一下单了; 下单完成获取prepay_id -> wx.chooseWXPay -> 进入微信支付流程;
(7) access_token是有时效的, 可以能过这里获取REFRESH_TOKEN调用刷新refresh_token接口重新获取
(8) 获用access_token可以进一步获取用户详细信息

3 本地开发

3.1 使用NATAPP(ngork)进行微信本地开发调试

NATAPP基于ngrok的国内高速内网穿透服务,
https://natapp.cn/
(1) 下载natapp
(2) 运行, 参考: https://natapp.cn/article/natapp_newbie

(3) 默认是绑定在80端口, 免费版不可修改, 收费版本可修改;
(4) 免费版本使用nginx转发端口
(5) 用Natapp(ngrok)进行微信本地开发调试 https://natapp.cn/article/wechat_local_debug

3.2 Nginx端口转发

  • 安装Nginx
1
2
brew search nginx
brew install nginx

/usr/local/var/ww
/usr/local/etc/nginx/nginx.conf
nginx will load all files in /usr/local/etc/nginx/servers/.

  • 代理, 把80端口请求转到proxy_pass指定地址;
1
2
3
4
5
6
7
isten       80;
server_name localhost;
location / {
root html;
index index.html index.htm;
proxy_pass http://127.0.0.1:8080;
}
  • 启动/停止
1
2
3
4
brew services start nginx  // 随系统自启动;
sudo nginx // 手动启动; 注Mac OS 1024以下端口必须root权限才能bind();
sudo nginx -s reload
sudo nginx -s stop

3.3 Spring中处理MP_verify_XXXX.txt问题

1
2
3
4
5
6
7
8
9
10
@InterceptorPassport
@NoCrossDomain
@RequestMapping(value = "/MP_verify_*.txt", method = RequestMethod.GET)
@ResponseBody
public String wechatVerify(HttpServletRequest request){
String code = request.getRequestURI(); // 返回: /wechat/MP_verify_zJOxhEzHeS84yyI7.txt
int end = code.lastIndexOf(".txt");
int start = code.lastIndexOf("_");
return code.substring(start + 1, end);
}

3.4 微信android手上链接不跳转

如果链接上有你自己wifi的Ip地址就不会跳转, 很奇葩的

1
window.location.href='http://www.baidu.com?t=你手机wifi的ip地址'

参考: