微信小程序运营支付规范
微信官方针对有虚拟支付功能的小程序下发了整改通知.截至5月8号,平台将对账号屏蔽iOS系统的支付接口调用.我去查了<<微信小程序运营规则>>,如上图.发现小程序的支付规范中已经做了明示.
这次整改主要含2方面内容:
1.虚拟支付
不是所有提供支付功能的小程序都要整改,仅仅只有涉及”虚拟支付”的小程序需要调整.什么是虚拟支付呢?说白了就是购买非实物,摸不到的物品.比如:VIP会员,充值,在线课程,虚拟物品等等.
2.iOS系统
本次整改,只涉及iOS系统内运行的小程序,安卓系统不受影响,开发者们依然可以在安卓版本内调用微信支付.另外小游戏内的安卓购功能,也不受影响.
针对规则,开发者们可以如何绕过呢?我整理后发现主要有4种方式.
1.小程序跳转APP或者引导用户去APP支付(例如得到小程序)
2.引导至公众号支付或者利用公众号支付(例如乐乐国学)
3.将虚拟变成实体
4.收费变免费
最后公司决定选用第二种支付逻辑,也就是利用公众号支付来实现绕过小程序支付.
前端操作流程如下图:其本质还是利用公众号支付来实现支付功能
接下来我们来一步一步看代码是如何实现这套流程的.
第一步:现在WXML中引入按钮组和苹果支付的组件
<!-- 屏幕下方的功能按钮 拼团 原价购买 集赞 -->
<course_actionBtn id="course-actionBtn" bindDialogEvent="onDialogEvent" bindApplePayEvent="onApplePayEvent" bindAndroidPayEvent="onAndroidPayEvent" bindPriseEvent="onPriseEvent" data-mode="{{data}}"> </course_actionBtn>
<!--苹果手机支付验证框 -->
<payment-dialog id="payment-dialog" data-mode="{{isGroup}}" payArgumentStr="{{payArgumentStr}}"></payment-dialog>
第二步:在页面的js里在点击拼团按钮的triggerEvent方法里弹出苹果支付的组件,因为专栏详情页下方的入口按钮组也封装成了组件,为了减少专栏详情页的代码数量.也是基于面向对象的考虑,单独的业务逻辑抽取出来,方便其他页面如果使用类似代码的话,方便移植.比如产品新加入的集赞入口,这样就可以直接修改按钮组组件的逻辑就好,不需要大范围的修改专栏详情页的代码逻辑.又比如将来如果有一个新的业务功能也需要这套按钮入口,直接引入组件就可以了.利于扩展.可以看到这里按钮组是一个组件,当点击拼团支付或者原价购买时,因为区分了苹果支付和安卓支付,我在这里将苹果支付也抽成了一个组件.这样可以在安卓系统上走小程序原生支付,在苹果系统上走我们特殊处理的苹果支付.目的也是为了将来方便扩展.这里有一个隐藏的坑.这里记录下来防止遗忘.
官方给的tip:
注意:必须在两个组件定义中都加入relations定义,否则不会生效。下图是官方文档给出的relations支持的type类型.
综上,我的理解是微信目前只支持类似这种包含关系的组件嵌套组件.它必须满足的是一种UI层面的嵌套,而我现在实现的是一种逻辑意义上的嵌套,所以不满足.这里我选用了第二套方案.利用组件的事件机制,将所有的组件都引入page对象中,然后监听每个组件的点击事件,并将触发结果从子组件中全部传到page对象中进行回调处理.
我们重新回到苹果支付这里.弹出苹果支付的组件后(就是流程图的第一幅图).
//弹出苹果支付
onApplePayEvent: function (e) {
var isGroup = e.detail.isGroup
var payArgumentStr = e.detail.payArgumentStr
this.setData({ isGroup: isGroup, payArgumentStr: payArgumentStr})
this.paymentDialog.showPopup()
},
// 三人拼团
tapPuzzle: function(e) {
var result = this.dataset.mode
var courseID = result.id
var that = this
//防止按钮重复点击
if (util.isReClick() || this.data.clickStatus == 1) return
//前置验证是否需要弹出收集手机号的验证弹框
util.checkoutShowMessageCodeDialog(this, function(e) {
if (e == 1) {
that.setData({
clickStatus: 1
})
pm = new PayManager()
pm.groupBuyPayment(courseID, {
iosCallback: res => {
var payArgumentStr = res
//调起iOS支付逻辑, payArgumentStr也是在这里通过组件事件给传递到page对象里面,PayManager()是项目里我封装的支付管理类,一种es6的写法------------这里最重要
that.triggerEvent("ApplePayEvent", {
isGroup: 1,
payArgumentStr: payArgumentStr
})
that.setData({
clickStatus: 0
})
},
androidCallback: res => {
var code = res.code
var result = res.data
if (code == -1) {
that.setData({
clickStatus: 0
})
console.log("调起拼团接口失败");
util.toast(result)
} else if (code == -2) {
that.setData({
clickStatus: 0
})
console.log("拼团调起微信支付失败");
// util.toast(result.errMsg)
} else if (code == 0) {
that.setData({
clickStatus: 0
})
console.log("拼团调起微信支付成功");
var result = pm.getGroupResult();
//跳转到拼团详情页面
wx.navigateTo({
url: '../group-payment/group-payment?groupBuyId=' + result.groupBuyId,
})
}
}
})
} else if (e == -1) {
console.log("手机号验证失败")
} else if (e == 2) {
that.triggerEvent("DialogEvent")
}
})
},
下面这段代码是支付管理类里关于拼团购买的实现
//拼团购买
groupBuyPayment(courseId, callBack) {
var that = this
//获取当前用户手机类型
var isIOS = util.isIOSPlatform(app.data)
if (isIOS == true) {
//组装sessionFrom,带到客服会话列表里面
if (courseId) {
var object = {};
object.courseId = courseId;
object.isGroupBuy = "1";//1是拼团购买 0是原价购买
object.groupBuyId = "";//groupBuyId如果有值就是参与拼团 没有值是发起拼团
var argument = JSON.stringify(object);
} else {
console.log("courseId为空")
}
//这里这个argument就是上面那个payArgumentStr
callBack.iosCallback(argument)
} else {
//安卓支付
var courseID = courseId;
//1.发起拼团,获取支付的前置信息获取
network.POST("/groupBuy/startGroupBuy", {
params: { courseId: courseID },
success: function (result) {
// success
var r = result.data.data;
that.groupResult = r
var groupId = r.groupBuyId;
var timestamp = r.timestamp;
var nonceStr = r.nonceStr;
var pack = r.package;
var paysign = r.paySign;
//2.调起微信支付
util.requestWXPayment(timestamp, nonceStr, pack, paysign, function (res) {
callBack.androidCallback({ code: 0, data: res })
}, function (res) {
callBack.androidCallback({ code: -2, data: res })
})
},
fail: function (error) {
console.log("error:")
console.log(error)
callBack.androidCallback({ code: -1, data: error })
}
})
}
}
第三步:
<button class='popup_ok' open-type='contact' session-from='{{payArgumentStr}}'>确定</button>
打开客服消息时,将这个payArgumentStr传给后台.它是怎么传给后台的呢?看上图,是我在小程序API中截取的图片.我们先看第一幅图红框位置.它的意思就是当用户在小程序中点击一个openType=”contact”的按钮时就会产生一个数据包,这个数据包里有一个SessionFrom字段.我们的payArgumentStr就是通过这个字段传给后台的.在看第二副图,就是微信服务器会将消息(或事件)的数据包(JSON或者XML格式)POST请求开发者填写的URL。开发者收到请求后可以使用发送客服消息接口进行异步回复。简单解释就是从用户点击客服会话按钮,就会产生第一个数据包,可以利用这个数据包从外面带一些自定义的参数到会话列表中,这个数据包会先发到微信服务器,微信服务器收到这个数据包后,会将这个数据包发给我们自己的服务器,因为小程序后台配置的开发者URL就是我们自己的服务器地址,服务器收到微信服务器的请求后获取到这个payArgumentStr,也就是拿到了订单信息.再根据这些信息生成支付链接,当监听到用户发送客服消息时将支付链接回调回去.显示在客服会话列表中.
现在我们再来看看这个payArgumentStr到底是什么?它其实是我和后台约定好的一个json对象转成的字符串.这个json对象会带一些必要信息到后台,让后台知道我当前发起的什么样的支付请求,是原价购买,是我要拼团,还是参与拼团.如果是我要拼团,需要课程ID,如果是参与拼团的话,需要拼团ID.这些参数都是我在打开客服列表会话时,传给后台,将来后台拿到这些参数,处理后,把这些信息拼在支付链接后面返回给我.就是流程图第3幅图里面的点击立即购买,它其实后台返回的一条带有订单信息的支付url链接.(这里是有安全隐患的).这个url链接是我自己写的并发布好的一个web页面,在这个页面里我会先拿到订单信息,然后调起公众号支付完成对该订单的付款.后台监听到这个单子付过款后,就会更新相关字段,这样我跳回小程序后,来到专栏详情页就可以去服务器更新专栏课程的最新状态,就专栏解锁.
function wxConfig() {
//通过config接口注入权限验证设置
$.ajax(
{
type: 'GET',
url: app.url.config_url + 'wechat-js-config/xcx',
data: {
url: window.location.href //window.location.href指的永远是访问该网页时用的URL.
},
success:function (msg,req) {
if(req == 'success') {
console.log("=====获取成功======");
var data = msg.data.js_config;
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: data.appid, // 必填,企业号的唯一标识,此处填写企业号corpid
timestamp:data.timestamp , // 必填,生成签名的时间戳
nonceStr: data.nonceStr, // 必填,生成签名的随机串
signature: data.signature,// 必填,签名,见附录1
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后
wx.ready(function(){
//获取页面URL链接的查询参数
var args = app.getQueryStringArgs();
console.log(args);
//1.获取支付类型
var paymentType = args.isGroupBuy;
var courseId = args.courseId;
var groupBuyId = args.groupBuyId;
if (paymentType == 1) {
if (groupBuyId != "null") {
//参团人获取拼团预付单信息
joinGroupBuy(groupBuyId);
}else {
//拼团发起人获取拼团预付单信息
startGroupBuy(courseId);
}
}else {
//原价购买的预付单信息
unlockCourse(courseId);
}
});
}else {
app.showTip('请求超时');
}
},
error:function (msg) {
app.showTip(JSON.stringify(msg));
}
})
}
//支付成功后跳转到公众号文章页面,就是流程图中第6幅图.这个页面是在公众号后台配置生成的公众号文章,在编辑的时候,可以在文章里面关联小程序的图文消息.这样就可以通过点击公众号里的小程序图文消息实现从公众号向小程序的跳转,完成闭环.这里有个小坑,就是生成的文章链接是一个临时链接,是有时效的,可以百度一下如何获取公众号文章的永久链接拿到永久链接,很简单,这里就不给传送门了.这里还有一个小需求,就是在web页面调起支付后,如果取消支付,就关闭当前页面,回到客服会话列表.我的思路是在支付的封装方法里,监听到支付取消后跳回处理.
//跳转到公众号文章页面
function nextArticePage() {
window.open("https://mp.weixin.qq.com/s/IxGJRB5oUtE8sP292KATOw");
}
if(res.errMsg == "chooseWXPay:cancel" ) {
WeixinJSBridge.call('closeWindow');
}
综上,就是我前端的整体实现逻辑,可能会有很多不准确的地方,感兴趣的朋友看到后可以给我指出来.提前谢谢了.
参考连接:
2小时后,小程序“虚拟支付”更改规则,这有4种“曲线救国”的方式!