微信、支付宝及第三方 H5 支付,各平台分享总结
微信支付
微信内 H5 支付(公众号支付)
走统一引入 JSSDK
的新方式实现支付逻辑。
明确两种 access_token
- 网页授权
access_token
:基于Oauth 2.0
,需要code
换取,用于维持登录状态。 - 普通
access_token
:后台可直接生成,换取jsapi_ticket
进而加密生成signature
作为wx.config
参数。
整个流程
支付统一下单接口需要 openid
,所以必须要授权获取。因为此处只需要 openid
,所以 snsapi_base
为 scope
走静默授权。(为了方便起见,此处每次进入都获取 code
走静默授权。)
进入页面判断链接上是否有
code
,没有则请求接口得到url
并重定向:js{ url: 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx1232123123&redirect_uri=带参数(包括锚点)的编码后的重定向的回调地址&response_type=code&scope=snsapi_base&connect_redirect=1&state=xxxx#wechat_redirect'; } // connect_redirect=1 表示只只触发一次请求
微信内部多次重定向之后,最后带上
code
和state
301 重定向回设置的回调地址:html301: https://www.flqin.com/test.html?id=135price=1322&code=ASDJIAJD13D823D&state=13212313#/
前端判断到链接有
code
,将code
及一些其他页面需要的参数一起传给后端,后端拿到code
换取网页授权access_token
和openid
,此处后端可将access_token/refresh_token
存入cookie
或者通过其他方式jwt
维持登录状态,就无需重复获取code
。openid
为统一下单jsapi
接口必传参数,用于得到prepay_id
参数值,即为package
参数。最后接口统一返回wx.confg
和wx.chooseWXPay
所需参数,前端调用JSSDK
即可。wx.chooseWXPay
的成功、完成回调仅代表支付完成,
代码演示:
async queryWxCode() {
//...
if (!code) {
const { data } = await api.wxToPay({url:'当前页面完整链接'}).catch((e) => e); //获取重定向链接(微信授权地址)
data && (window.location.href = data);
return;
}
return api.wxPay({code,url:'当前页面包含参数的url'}); //获得wx.config、wx.choosepay全部参数
},
async registerWx() {
const { data } = await this.queryWxCode().catch((e) => {});
this.wxSdkInfo = data;
wx.config({
debug: false, // 开启调试模式
appId: data.appId, // 必填,公众号的唯一标识
timestamp: data.timeStamp, // 必填,生成签名的时间戳
nonceStr: data.nonceStr, // 必填,生成签名的随机串
signature: data.signature, // 必填,签名 通过普通access_token生成,无需授权
jsApiList: ['chooseWXPay', 'hideMenuItems'] // 必填,需要使用的JS接口列表
});
wx.ready(() => {
this.hideWxMenus();
this.createWxpay();
});
},
hideWxMenus() {
// 禁用支付分享, 防止订单未生成而进入该页面导致的报错
const menuList = ['menuItem:share:appMessage', 'menuItem:share:timeline', 'menuItem:copyUrl'];
wx.hideMenuItems({
menuList // 要隐藏的菜单项,只能隐藏“传播类”和“保护类”按钮,所有menu项见附录3
});
},
createWxpay() {
const data = this.wxSdkInfo;
wx.chooseWXPay({
timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
package: `prepay_id=${data.prepayId}`, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*) 该参数需要openid
signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: data.paySign, // 支付签名 参与签名的参数为:appId、timeStamp、nonceStr、package、signType
success: this.queryPayment, //公共查询结果接口
fail: this.showFail, //失败处理
cancel: this.showRefail, //取消处理
complete({ errMsg }) {
const SUCCESS = /:ok/gi.test(errMsg);
const CANCEL = /:cancel/gi.test(errMsg);
if (SUCCESS) this.queryPayment();
else if (CANCEL) this.showRefail();
else this.showFail();
}
});
},
支付宝支付
支付宝内 H5 支付
必须接入支付宝 JSAPI
。
注意点:
- 不涉及读取用户优惠券之类的都走普通无单号支付,即无需授权。
- 授权过程与微信大致一致。
代码演示:
async registerAlipay() {
const alipayInit = async () => {
await this.queryAliOrder().catch((e) => e);
this.createAlipay();
};
if (window.AlipayJSBridge) alipayInit();
else document.addEventListener('AlipayJSBridgeReady', alipayInit, false);
},
async queryAliOrder() {
// 无单号支付
const {data: orderStr } = await api.aliPayH5({orderId:1}).catch((e) => e);//请求接口获取 orderStr
},
async queryAliCode() {
// 有单号支付tradeNo(后台开通当面付),同微信授权流程
const { href, search } = window.location;
const query = search && search.replace(/[?\/]/g, '');
const { auth_code: code } = qs.parse(query);
if (!code) {
const { data } = await api.aliToPay(href).catch((e) => e); //请求接口获取支付宝授权地址
data && (window.location.href = data);
return;
}
const {data: tradeNO } = await api.aliPayH5({orderId:1,code}).catch((e) => e); //请求接口获取 tradeNO
},
async createAlipay() {
AlipayJSBridge.call('tradePay', { 'orderStr/tradeNO' }, ({ resultCode }) => {
const SUCCESS_CODES = ['9000', '8000', '6004'];
const UNKNOW_CODES = ['7001', '6001', '6002'];
if (~SUCCESS_CODES.indexOf(resultCode)) _this.queryPayment(); //支付查询
else if (~UNKNOW_CODES.indexOf(resultCode)) _this.showRefail();
else _this.showFail();
});
},
第三方浏览器中 支付宝支付
无需授权,直接将 url
和订单号传给后端,后端返回一个 form
表单添加到页面即可唤起支付宝 APP
,支付完成后,根据后端配置支付宝会自动回跳到支付结果页并携带一堆参数。此时需要查询公共结果接口来确定支付是否成功。
注意点:
- 通过链接上是否有参数
alipay.trade.wap.pay.return
来判断是否是支付回调回来的页面。
代码演示:
registerH5() {
const { origin, pathname, search } = window.location;
const { method } = qs.parse(search.replace(/[?\/]/g, ''));
if (method === 'alipay.trade.wap.pay.return') {
this.queryPayment(); //执行公共查询结果接口
return;
}
const { data: form } = await api.aliPayH5(orderId,url).catch((e) => e); //请求接口返回form表单
if (!form) return;
const div = document.createElement('div');
div.innerHTML = form;
document.body.appendChild(div);
document.forms[0].submit();
}
公共查询结果 queryPayment
需要轮询支付结果。
代码演示:
async queryPayment() {
const TIMES_REACH = 0;
const TIMES_MAX = 5;
const SUCCESS_CODE = '1'; // 0待支付,需要继续查询 1支付成功 -1异常
const WAITING_CODE = '0';
if (this.qTimes === TIMES_REACH) { //qTimes为最大查询次数
this.qTimes = TIMES_MAX;
return;
}
const { data } = await api.payStatus(orderId).catch((e) => e); // 查询支付结果
if (data == SUCCESS_CODE) { //成功状态
this.showSuccess();
this.qTimes = TIMES_MAX;
} else if (data == WAITING_CODE) { //等待状态
console.log('查询支付结果中...');
clearTimeout(this.timer);
this.timer = setTimeout(async () => {
await this.queryPayment(); //递归轮询
this.qTimes -= 1;
}, 1000);
} else {
this.showFail();
}
},
支付总结
- 微信支付宝均需要配置合法域名,可精确到文件夹路径。
- 开发可用微信开发工具,结合 alert 调试,直接把代码发到已配置好域名的测试环境测试最佳。
- 注意参数的大小写及传递的内容,支付主要复杂度都集中在后端,前端需要配合后端传参。
分享
微信- > 微信(微信联系人,微信朋友圈,QQ 联系人,QQ 空间)
必须引入 JSSDK
,否则分享出来无法设置分享描述及分享图片。
需要给后端传入带有参数的当前页面 url
(无需编码),让后端返回 wx.config
所需参数即可:
const { data } = await api.getWxConfig({ url: '当前页面包含参数的url' }); //接口返回 appId,timeStamp,nonceStr,signature.
wx.config({
debug: true,
appId: data.appId, // 必填,公众号的唯一标识
timestamp: data.timeStamp, // 必填,生成签名的时间戳
nonceStr: data.nonceStr, // 必填,生成签名的随机串
signature: data.signature, // 必填,签名
jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareQZone'], // 必填,需要使用的JS接口列表
});
wx.ready(() => {
const shareList = ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareQZone'];
shareList.forEach((item) => {
wx[item]({
title: this.shareInfo.title, // 分享标题
desc: this.shareInfo.description, // 分享描述
link: window.location.href,
imgUrl: this.shareInfo.image, // 分享图标
success: function () {
// 设置成功
},
});
});
});
注意点:
- 首先需要在微信后台设置接口安全域名:
www.xxxx.com
,无需到具体路径及参数。 - 前端仅需传给后台当前页面带参数的
url
即可,甚至接口可以自己取请求页面带参数的url
,前端无需传。 - 注意
timeStamp
的大小写。 - 注意
jsApiList
选择即将废弃的分享接口,新分享接口反而不好用。 - 分享无需授权,因:参数
nonceStr <- jsapi_ticket <- access_token <- appid,secret
。此access_token
为普通access_token
,appid
和secret
均在微信后台获取。区别于支付等网页授权access_token
。
QQ(TIM) -> QQ(QQ 联系人,QQ 空间,微信联系人,微信朋友圈)
需要引入 api
:
<script src="//open.mobile.qq.com/sdk/qqapi.js"></script>
js
执行:
const share = {
title: '分享标题,最大45字节',
desc: '分享内容,最大60字节',
image_url: '图片URL,最小需要200 * 200',
share_url: '分享链接与页面URL同',
};
mqq.data.setShareInfo(share, callback);
另 mqq.ui.showShareMenu();
可直接唤起 QQ
分享面板。
支付宝 -> 支付宝(朋友动态,联系人)
直接按如下设置 meta
即可:
<meta name="Alipay:title" content="分享标题" />
<meta name="Alipay:imgUrl" content="分享图片url" />
<meta name="Alipay:desc" content="分享描述" />
<meta name="Alipay:link" content="分享链接" />
微博,头条,知乎等其他平台浏览器 -> 微信,QQ
微博可注册轻应用使用 JS-SDK 完成分享设置。收益不大就没做了。 知乎实测 IOS 可以取写死的描述及动态标题,图片无法设置,安卓只能设置标题。
对于其他平台分享,统一兜底处理方式:
分享标题及描述设置 head
元素:
<head>
<title>分享标题</title>
<meta name="description" content="分享描述" />
</head>
分享图及描述设置(一般默认取页面第一张大于 300px
图及第一段描述):
<body>
<div style="display: none;">
<p>分享描述</p>
<img src="图片地址url" />
</div>
</body>
使用 ogp
:
<head>
<meta property="og:type" content="website">
<meta property="og:title" content="分享标题">
<meta property="og:description" content="分享描述">
<meta property="og:img" content="完整的分享图片链接">
<meta property="og:url" content="完整的分享页面地址">
</head>