做微信支付有一段时间了,今天有时间梳理一下流程,其实下面流程官网都有详解,在这我只是简单梳理一下毕竟是小白,解释一下自己遇到的一些问题。
微信支付
1、首先开通微信支付,即申请或复用微信支付商户号 申请完小程序后,登录小程序后台(mp.weixin.qq.com)。点击左侧导航栏的微信支付,在页面中进行开通。(开通申请要求小程序已发布上线)
2、点击开通按钮后,有2种方式可以获取微信支付能力,新申请微信支付商户号或绑定一个已有的微信支付商户号,请根据你的业务需要和具体情况选择,只能二选一。开通指引:kf.qq.com/faq/140225M…
业务流程
1、小程序内调用登录接口,获取到用户的openid,api参见小程序登录API
2、商户server调用支付统一下单,api参见统一下单API
3、商户server调用再次签名,api参见再次签名
4、商户server接收支付通知,api参见支付结果通知API
5、商户server查询支付结果,api参见查询订单API
支付流程
后台流程图
前端流程
- 点击立即支付先创建订单生成订单编号
- (第二次以后再次使用)先判断本地的OpenId是否有效,有效的话直接请求加签接口,无效则重新登录
- 通过 wx.login() 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。
- 拿到code请求后台接口获取OpenId并存到本地
- 拿到OpenId请求后台加签接口并唤起微信支付
该接口应在服务器端调用。 链接:auth.code2Session
接下来贴代码
// 1、获取code
getlogin() {
let _this = this;
wx.login({
success: function (res) {
if (res.code) {
//发起网络请求
console.log(res.code)
// 签名校验以及数据加解密涉及用户的会话密钥 session_key。 开发者应该事先通过 wx.login 登录流程获取会话密钥 session_key 并保存在服务器。为了数据不被篡改,开发者不应该把 session_key 传到小程序客户端等服务器外的环境。
_this.gtOpenid(res.code);
} else {
console.log('获取用户登录态失败!' + res.errMsg)
}
}
});
}
// 2、获取openid
async gtOpenid(_c){
let _data = {
code: _c,
}
let obj = await gtopenid(_data);
if(obj.openid){
// 3、代金券加签接口
this.getBuyCashCoupon(obj.openid)
this.$apply()
} else {
if (obj.errcode == 40125) {
wx.hideToast({
title: 'appsecret失效',
icon: 'fail',
duration: 2000
})
}
}
}
// 3、代金券加签 并唤起微信支付
async getBuyCashCoupon(openid) {
let _this = this;
let params = {
memberId: wx.getStorageSync('memberId'),
mallId: wx.getStorageSync('mall').mallId,
sellerId: this.detail.sellerId,
orderType: '3', // 1普通订单 2停车缴费3代金券订单
paymentMethod: '2', // 1支付宝 2微信
oppenId: openid,
serviceVersion: this.$parent.globalData.serviceVersion,
// couponCode: this.detail.couponCode,
poolId: this.detail.couponPoolId,
}
let res = await buyCashCoupon(params)
// 返回订单编号以及唤起微信支付需要的参数
if (res.resultCode == 200){
let paySignObj = res.resultInfo.paySign
this.orderFormNumber = res.resultInfo.orderFormNumber
wx.requestPayment({
appId: paySignObj.appid, // 小程序ID
timeStamp: paySignObj.timestamp, // 时间戳
nonceStr: paySignObj.noncestr, // 随机串不长于32位。
package: paySignObj.prepayid, // 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=wx2017033010242291fcfe0db70013231072
signType: 'MD5', // 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致
paySign: paySignObj.sign, // 签名
success(res) {
// 调用支付成功
wx.navigateTo({
url: './paysuccess?type=1&resultMoney='+_this.detail.couponPrice+'&voucher=1'
})
},
fail(res) {
// 用户取消支付 | 调用支付失败,其中 detail message 为后台返回的详细失败原因
_this.isDisabled = false
_this.getupdateOrderStatus(_this.orderFormNumber)
}
})
this.$apply()
} else {
wx.showModal({
title: '提示',
content: res.resultMsg
})
}
}
流程目前没啥问题但是上线一段时间之后,带来了一个大问题,统计订单数据不一致,通过后端小伙伴的努力,发现支付接口重复调用,具体原因
1、有可能是多次点击,但是前端以检验防止多次点击
2、网络重发或者nginx重发。
下面我们技术老大让我们采用了以下的方法。
支付流程测试重复提交-->幂等性测试
幂等性的概念
幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。
在编程中.一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“getUsername()和setTrue()”函数就是一个幂等函数.
更复杂的操作幂等保证是利用唯一交易号(流水号)实现.
a、适用场景:
1.前端重复提交选中的数据,后端应该只产生这个数据的一个反应结果。
2.当遇到网络重发或系统bug重发,只能扣除用户的一笔金额,
3.创建业务订单,一次只能创建一个。
4.合同还款一次业务只能成功一次。
b、测试幂等性
1.前端重复快速点击(一般前端会做提交)
2.网络重发,比如在扫码支付时,商户在扫码时,先断网扫码一次再重连扫码
3.对同一笔订单,不同商户同时扫码
4.对同一笔业务并发请求,比如并发提现
5.Nginx重发情况(这种情况还没试过,要对nginx比较熟悉才行)
c、token机制,防止页面重复提交
业务要求:
页面的数据只能被点击提交一次
发生原因:
由于重复点击或者网络重发,或者nginx重发等情况会导致数据被重复提交
解决办法:
集群环境:采用token加redis(redis单线程的,处理需要排队)
单JVM环境:采用token加redis或token加jvm内存
处理流程:
- 数据提交前要向服务的申请token,token放到redis或jvm内存,token有效时间
- 提交后后台校验token,同时删除token,生成新的token返回
token特点: 要申请,一次有效性,可以限流
注意:redis要用删除操作来判断token,删除成功代表token校验通过,如果用select+delete来校验token,存在并发问题,不建议使用
幂等性测试是看别人总结的,目前也是用该方法解决的线上问题。
有兴趣的朋友可以去看一下链接 高并发的核心技术-幂等的实现方案
结尾
目前我是这样做的 有问题 希望大家留言指出, 我会及时回复。谢谢!