最近公司要求开发一个微信公众号涉及支付,首次开发微信支付,于是乎我自觉地打开了微信公众平台的开发文档,看到零散的开发步骤及各种参数的定义,加之后来痛苦的调试过程中, 我觉得非常有必要花时间记录一下,文字功底不好,不喜勿喷。
这里主要说下JSAPI与H5的区别:
JSAPI:依赖于微信的浏览器,所以公众号一定是选这个API了。
H5:只允许微信以外的浏览器使用(主要用于呼起微信客户端同时唤起支付功能)
建议不知道如何选API的小白,可以参考 https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=2_1
1.首先你需要有三个账号:微信账号,微信公众号,微信商户号,同时将公众号接入到商户号。
微信公众号可以由个人注册,但是微信商户号官方可是不支持的个人申请的,在提交资料审核的时候你会知道了,
这里看到网上个人申请商户号的一个办法,https://blog.csdn.net/weixin_39927850/article/details/79161872 ,不过没试过,等有缘人来校验吧。
2.获取基础信息(开发中会使用到的几个变量)
AppID:由公众号自动分配
AppSecret:在公众号的开发平台手动生成
商户号(mch_id):由商户号自动分配
API密码(key):在商户号的开发平台手动生成
3.开发配置
a) 公众号后台 - 配置域名(JS接口安全域名以及网页授权域名都要设置)
设置路径:开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息 - 修改
官方参考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
注意:域名必须是已备案的可用域名;
b) 商户平台 - 设置支付目录(用于接收支付后的回调目录)
设置路径:商户平台 - 产品中心 - 开发配置
注意:此处的目录必须是调用支付请求的html文件所在的目录。
举个栗子:访问地址为http://pay.qq.com/pay/index.html ,授权目录就是http://pay.qq.com/pay/
要发起支付不是只用调一个接口就可以实现的,先上个流程图:
第一步:获取用户授权及code
开发步骤:
a) 打开H5页面时,先页面跳转至静默授权页面,
b) 通过授权后,页面会自动跳转至首页,但此时跳转的url会新增一个code的参数
c) 可以获取code了
var local = window.location.href ;
var code = getParam('code');
if (code === null || code === '') {
// 跳转至授权地址,该地址只支持微信浏览器打开
window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri=" + encodeURIComponent(local) + "&response_type=code&scope=snsapi_base#wechat_redirect";
}else{
getOpenId(code);
}
// 获取url中的参数
function getParam(paramName) {
var paramValue = "", isFound = !1;
if (this.location.search.indexOf("?") == 0 && this.location.search.indexOf("=") > 1) {
arrSource = unescape(this.location.search).substring(1, this.location.search.length).split("&"), i = 0;
while (i < arrSource.length && !isFound) arrSource[i].indexOf("=") > 0 && arrSource[i].split("=")[0].toLowerCase() == paramName.toLowerCase() && (paramValue = arrSource[i].split("=")[1], isFound = !0), i++
}
return paramValue == "" && (paramValue = null), paramValue;
}
参数说明
appid 公众号的唯一标识
redirect_uri 授权后重定向的回调链接地址, 请使用 urlEncode对链接进行处理
response_type 返回类型,直接填写code
scope 应用授权作用域
snsapi_base(不弹出授权页面,直接跳转,只能获取用户openid),
snsapi_userinfo(弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )#wechat_redirect 无论直接打开还是做页面302重定向时候,必须带此参数
注意:每次打开页面的code都会改变,过期时间只有5分钟,且每个code只能使用一次
第二步:用code获取用户openId
如下提供的参考文档是服务器端的,也明确提出该请求必要由服务器端发起,所以后台同事必须提供一个能由前端被调起的接口
官方参考(后端):https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
function getOpenId(code) {
$("#loading").show();
var data = new FormData();
data.append("code", code);
$.ajax({
type: "POST",
url: "http://127.0.0.0:8888/wx/getWxOpenId.do",
cache: false,
data: data,
processData: false,
contentType: false,
dataType: "json",
success: function (json) {
if (json.errcode) {
alert("获取openId失败 :" + json.errmsg);
} else {
openId = json.openid;
}
}, complete: function (res) {
$("#loading").hide();
}, error: function (res) {
alert("服务器报错:" + res.statusText);
}
})
}
注意:openId是微信用户的唯一标识,每个微信用户都拥有自己固定的openId
第三步:点击支付时,先用openId,key获取prepayid;
同第2步,这里需要后台同事配合实现数据转发,在后台调用"统一下单"的接口,生成订单并将prepayid返回至前端
官方参考(后端):https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
// 获取商品id
function getProuctId() {
$("#loading").show();
var data = new FormData();
data.append("openid", openId);
data.append("apiKey", key); // 在商户号的开发平台手动生成,API密码
$.ajax({
url: "http://127.0.0.0:8888/wx/weixinPay.do",
type: 'POST',
cache: false,
data: data,
asyns: false,
processData: false,
contentType: false,
success: function (res) {
productId = res.prepayid;
productInfo = res;
if (productId) {
onBridgeReady();
}
}, complete: function (res) {
$("#loading").hide();
}, error: function (res) {
alert("服务器报错:" + res);
}
})
}
第四步:生成签名,并吊起微信支付, 此处使用的是SHA256加密
官方参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
// 发起支付
function onBridgeReady() {
var data = {
"appId": appid, //公众号名称,由商户传入
"nonceStr": productInfo.noncestr, //随机串
"package": "prepay_id=" + productInfo.prepayid, // 单号
"signType": "HMAC-SHA256",
"timeStamp": parseInt(new Date().getTime() / 1000).toString() //时间戳,自1970年以来的秒数
}
data.paySign = createSign(data); // 微信签名
WeixinJSBridge.invoke(
'getBrandWCPayRequest', data,
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
alert("支付成功");
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
alert("支付过程中用户取消");
} else if (res.err_msg == "get_brand_wcpay_request:fail") {
alert("支付失败");
}
});
}
// 生成签名
function createSign(data) {
var stringA;
var array = [];
for (var obj in data) {
array.push(obj + "=" + data[obj]);
}
stringA = array.join("&") + "&key=" + key;
$("#SHA256").val(CryptoJS.HmacSHA256(stringA, key));
return $("#SHA256").val().toUpperCase();
}
问题总结:
签名部分是问题高发的关键一步,基本调用支付不成功都是因为签名无效导致的,生成签名的方式不多说,看官网https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3,很详细。
重点说一下,生成签名的几个需要注意的参数规则:
1、timeStamp(时间戳):实际类型是 String(10), 取值为秒级别的,而不是默认的毫秒级别;
2、nonceStr(随机字符串): 必须是String(32),不要相信官网推荐的调用随机数函数(16位),必须是32位
3、signType(加密方式):可能跟官方的SDK版本有关,默认不一定是MD5;
4、参数的顺序,已参数名的ASCII码从小到大排序(字典序);
5、上诉参数规则必须跟后台签名规则一致,在调用统一下单时,也会有一个签名过程,前端吊起支付这一次算是二次签名了,两次的签名规则必须一致;
6、后台签名规则的sign_type其实是必传字段(官网提示的非必传);
7、注意前后端签名参数的大小写是不一样的,一定不要弄错了;
8、生成的签名中字母转化成大写
PS:
任何一个参数不对都会导致签名失败;
微信官方虽然提供了签名校验工具,但是通过校验的也只能说明你的MD5或SHA256算法跟微信官方的一致;
签名校验工具通过校验,但还是经常出现签名失败的报错,多是因为参数规则不对,或两次签名规不一致
第一次签名校验通过,也不代表第一次签名是没有问题的,要吊起支付,需要前后台的两次签名共同反复多次调试。
签名校验工具
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1