1.聚合收银台-手把手教你uniapp小程序间跳转以及webview接sass服务,完成支付等;
选型:vue3+typeScript+uniapp
原因:符合旧技术栈;结合ts校验,方便数据格式确定; 需要同时开发h5端和小程序端; 插件支付审核未通过,只能以小程序的形式;
1.目的
公司旗下有多个小程序未来可能都涉及到支付,有的小程序未开通支付功能,或者支付的appId与商户号不符等无法在本app内部发起支付,因此想要开发一个app专做公司的聚合支付,包括小程序支付,h5支付,小程序内嵌webview支持支付等。
2.需求
在聚合收银台需要满足以下几个场景:
1.收银台小程序内部可以选择是积分支付还是现金支付;
2.如果选择积分支付,积分充足直接积分支付,积分不足先用剩余积分支付一部分,剩余金额拉起微信支付;
3.从小程序跳转过来,拉起支付小程序,支付结束后跳转回原小程序;
4.从webview跳转,跳转进编译好的h5页面,选择支付方式后如果涉及到现金支付,跳转回原小程序页拉起支付小程序,直接进入微信支付流程;
5.从微信公众号等h5页进入,跳转到h5页面,通过jssdk拉起微信支付(暂无需实现);
3.流程图(前两种)
4.功能实现
1.跳转小程序支付
从小程序a跳转到支付小程序;
1.原小程序
选购好商品,在支付详情页生成该商品的参数信息,携带生成信息跳转到支付小程序
// 小程序a订单使用
export function orderJumpToMini(res) {
return new Promise((resolve, reject) => {
uni.navigateToMiniProgram({
appId: PAY_APPID, //收银小程序appid
path: 'pages/pay/pay', //收银小程序中对应的支付页
extraData: {
goodsInfo: goodsInfo
},
envVersion: env === 'production' ? 'release' : 'trial',
success(res) {
resolve();
},
fail(err) {
reject(err);
}
});
});
}
//在调用函数的地方通过.catch,.then来进入对应的事件处理
2.收银-小程序
调起微信支付的情况
requestWxPay(params: ToPayParams) {
uni.requestPayment({
provider: "wxpay",
orderInfo: "orderInfo",
timeStamp: params?.timeStamp,
nonceStr: params?.nonceStr, // 支付签名随机串,不长于 32 位
package: params?.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
signType: params?.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: params?.paySign, // 支付签名
success: (res) => {
//告知支付成功的处理
},
fail: (err) => {
//告知支付失败的处理
},
complete: (res) => {
//集成处理支付结束的情况
this.backToOrigin();
},
});
},
调用返回原小程序方法,携带extraData回去,告诉原小程序当前订单支付状态
uni.navigateBackMiniProgram({
extraData: {
state: this.afterPayState
},
success(res) {
console.log("返回原小程序页成功");
},
});
2.webview跳转支付(注意坑)
注:在webview内部是无法通过jssdk拉起微信原生支付,且无法跳转到别的小程序的,所以需要跳转到小程序原生页面,然后再跳转支付小程序。
1.收银-H5
1.webview在结算时会通过路由重定向,携带参数跳到收银h5页
App.vue
//判断当前为h5环境时
//!options?.referrerInfo?.appId(未携带小程序appid,非小程序跳转)
// #ifdef H5 (条件编译)
//h5平台的代码
// #endif
originWay = "H5";
const query = options.query;
orderData = {
goodsInfo:goodsInfo
};
//存储到全局或者状态机中
2.选择对应支付方式
1.如果积分充足,直接支付接口积分支付成功,无需微信原生能力支持,重定向到原sass的支付结果页;
if (this.payPlace === PayPlace.H5) {
//积分充足时(不存在微信支付参数的情况)
const jumpUrl = await this.getPayOverH5Url();
window.location.replace(jumpUrl); //jumpUrl是需要跳转到的h5的路由
}
2.若积分不足,需要用微信补足现金部分
调后台支付接口,后台会返回小程序支付需要的支付参数:payInfo
携带支付参数,跳转到原小程序空白页(触发onMessage事件),拉起支付小程序,发起微信支付
注意:小程序webview与小程序间的数据传递需要通过postMessage来完成(wx.miniProgram.postMessage),且需要特定时机触发
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| bindmessage | eventhandler | 否 | 网页向小程序 postMessage 时,会在以下特定时机触发并收到消息:小程序后退、组件销毁、分享、复制链接(2.31.1)。e.detail = { data }, | 1.6.4 |
在uniapp中会用到uni.webview.1.5.5.js(原生小程序开发下载jssdk)
注意:uniapp中引入uni.webview.1.5.5.js文件时,跟其余的script标签不同,其script标签应该放置于标签后
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
<script src="https://xxx/jweixin-1.6.0.js"></script>
<script
type="text/javascript"
src="https://xxx/uni.webview.1.5.5.js"
></script>
<script>
document.addEventListener("UniAppJSBridgeReady", function () {
uniWebview.getEnv(function (res) {
console.log("当前环境:" + JSON.stringify(res));
});
});
</script>
/**
* @description: 传参给原小程序
* @param {*} params
* @return {*}
*/
async h5PostMessage(params: ToPayParams) {
const jumpUrl = await this.getJumpUrl();
uniWebview.redirectTo({
url: "/pages/xxx/xxx", //通过该动作触发postMessage方法
});
uniWebview.postMessage({
data: {
action: "navigateToAnotherMiniProgram", //触发行为
extraData: {
payInfo: params, //携带参数
webviewUrl: jumpUrl, //完成支付后页面的跳转地址
},
},
});
console.log("传递成功");
},
2.原小程序
点击对应图标,跳转到对应的sass服务中,在sass服务中选购好对应商品后,结算时跳转到收银台的h5页面同收银小程序页面内容);用户选择支付方式,如果是积分支付,且积分充足,直接完成支付,重定向到sass订单页;除此以外,需要拉起收银小程序进行微信支付;
1.在webview中响应h5页面的事件;
//webview.vue
<div class="container">
<web-view id="web-view" :src="webviewUrl" @message="onMessageHandler"> </web-view>
</div>
onMessageHandler: function (e) {
const data = e.detail.data[0]; //传递的参数
const extraData = data.extraData; //对应h5的结构
this.extraData = extraData;
extraData?.webviewUrl && uni.setStorageSync('backWebviewUrl', extraData?.webviewUrl); //全局存储webviewUrl(支付后的回调跳转路由)
uni.setStorageSync('miniProgramjump', true); //判断是由webview中事件导致的跳转,而非切换微信前后台(场景值)
if (data.action === 'navigateToAnotherMiniProgram') { //响应webview中的h5触发的事件
uni.navigateToMiniProgram({ //跳转到对应收银台小程序中
appId: 'appid',
path: 'pages/pay/pay',
extraData: extraData,
envVersion: env === 'production' ? 'release' : 'trial',
success(res) {
// 打开成功
uni.removeStorageSync('miniProgramjump');
},
fail(err) { //第二次从h5传递消息回来调起navigateToMiniProgram,可能会直接自动进入失败回调,需要手动让用户点按钮二次调起收银台
uni.redirectTo({
url: `/pages/xxx/xxx?open=mini&extraData=${encodeURIComponent(
JSON.stringify(extraData)
)}`
});
}
});
}
}
//blank.vue
<view class="container">
<button class="pay-button" v-if="open === 'mini'" @click="toPay">若无反应,点我继续支付</button>
</view>
<script>
methods: {
toPay() {
uni.navigateToMiniProgram({
appId: 'appId',
path: 'pages/pay/pay',
extraData: this?.extraData && JSON.parse(decodeURIComponent(this.extraData)), //支付参数
envVersion: env === 'production' ? 'release' : 'trial',
success(res) {
// 打开成功
uni.removeStorageSync('miniProgramjump');
},
fail(err) {
uni.removeStorageSync('miniProgramjump');
}
});
}
},
onLoad(options) {
const baseUrl = options.url;
this.open = options.open;
this.extraData = options?.extraData || '';
let myExtraData = this?.extraData && JSON.parse(decodeURIComponent(this.extraData));
// 如果是原小程序跳转到webview页url
if (options.url) {
uni.redirectTo({
url: this.concatUrl(baseUrl)
});
}
if (myExtraData.webviewUrl) {
this.backWebviewUrl = myExtraData.webviewUrl;
}
},
onShow() {
const backWebviewUrl = uni.getStorageSync('backWebviewUrl') || this.backWebviewUrl;
const scene = uni.getStorageSync('scene');
const miniProgramjump = uni.getStorageSync('miniProgramjump');
if (miniProgramjump) return; //如果是本小程序切后台再切前台则无需处理
if (scene === 1038 && backWebviewUrl) { //支付成功后跳转到webview中支付详情或者订单页
uni.redirectTo({
url: this.concatUrl(backWebviewUrl),
success(res) {
uni.removeStorageSync('backWebviewUrl');
uni.removeStorageSync('scene');
},
});
} else {
//如果是原小程序跳转到webview页url
if (this.$mp.query.url) {
uni.redirectTo({
url: this.concatUrl(this.$mp.query.url)
});
}
}
},
</script>
注意:之所以需要blank页是因为webview与小程序的通信(postmessage)并非实时的,需要小程序后退、组件销毁、分享、复制链接等事件来触发组件的message事件,所以需要一个空白页来承接后退行为。
所以到sass平台h5页面的顺序是: 小程序首页-blank页-sass webview页-收银台h5页
结算时的顺序是:sass webview页-收银台h5页-blank页-支付小程序
3.收银-小程序
1.原小程序携带支付参数跳转到收银小程序中;
2.在收银小程序onShow中获得传递的payInfo数据,存在全局或状态机中,在支付页中获取该状态;
3.当支付页中,该状态有值时直接调起支付;
支付结束后,回调统一的返回方法,根据进入方式来返回。
/**
* @description: 返回原小程序/原h5页
* @return {*}
*/
async backToOrigin() {
const extraData = {
state: this.afterPayState,
};
if (this.payPlace === PayPlace.H5) {
if (!this.wxPayParams) {
const jumpUrl = await this.getJumpUrl();
window.location.replace(jumpUrl);
} else {
navigateBack(extraData, "h5");
}
} else {
navigateBack(extraData, "mini");
}
function navigateBack(extraData, type) {
uni.navigateBackMiniProgram({
extraData,
success(res) {
console.log(`返回原小程序列表页成功, ${type}`);
},
});
}
}