前言
微信小程序开发微信支付, 相对于微信的其他功能,实话说相比之下好太多,可能是开发文档是微信支付这边撰写的缘故吧?猜的。所以微信支付在小程序中,虽然参数十分的多,环节特别的细致,但也算不上无从下手。上个项目实施了一次微信小程序支付功能的开发,趁记忆力尚可,赶紧记录一番
本次环境平台为 小程序端(uniapp)| 原生一样,只介绍js部分 + egg.js端(node)
流程基本都一致,只是语法上有许区别
开发准备
工欲善其事,必先利其器。开发之前我们需要先准备好哪些必须的开发前提或环境呢?
资料:
1.
审核并开通微信商户号:
•微信支付页链接地址[1]
•等待审核, 审核通过后对小程序进行关联
•小程序开启支付
•小程序支付开通后
•打开微信支付页,可以进行绑定查看
•再到微信支付中去记录mch_id,和merchantkey(商户密码),记录merchantkey的方式如图:
•记录下merchantkey ,注意:merchantkey是敏感key,不可泄漏
2.
前面开发好拉取用户的授权,获取用户在当下小程序中的openid(重点必须)
3.
搭建好服务器接口调用, 记录下需要传递给微信服务器使用回调的服务器ip地址以及接口的url地址(提前准备好,可以使用postman做好测试)。(可以为本服务器也可以为另外服务器,主要作为回调)
4.
其他:微信官方文档要求 审核支付功能需要微信小程序已上线,但是当时我申请的时候小程序并未上线也过了,所以这一块我无法做出解释。另外,程序访问商户服务都是通过HTTPS,开发部署的时候需要安装HTTPS服务器
开发流程
5.
先来看看官方微信支付给出的流程图
•感觉有点懵逼
•我总结如下:
开始开发
1.根据以上流程图,我们开始进行调用
•第一步 小程序发起支付的代码如下:
async wxappay (openid, money) {
return new Promise(async (resolve, reject) => {
let Objct = {
openid, //拉取授权获取到的openid
money, //money必须是整数类型, 以RMB分为单位!
body: 'xxx'
}
let temp = await wxappWxPay(obj) //进入到第一阶段, 预支付阶段
//后面的逻辑为第二阶段
})
}
注意,强烈推荐使用promise函数来实现,可以保证逻辑代码体在实现流程的一致性
•第一步 后端node服务器接口获取支付第一部参数的代码如下:
/**
* 微信统一下单(微信支付)的接口数据(!!!!小程序专用付款方式)
* @param {OBject}
* 调用微信预支付接口(必填项)
* @@排列顺序不可以错!
* 1.appid
* 2.body: 商品描述
* 3.mch_id: 支付申请配置的商户号
* 4.NonceStr: 随机字符串
* 5.notify_url: 微信付款后的回调地址 //后端egg的接口接收此地址来响应支付成功的回调
* 6.openid:
* 7.out_trade_no: 订单号(32位)
* 8.spbill_create_ip:后端调用API支付微信的ip地址 (支持32位和64位IP地址)
* 9.total_fee: 支付金额
* 10.
* /**
* //生成微信支付的参数进行ASCII码从小到大排序
* @params:
* body: 支付内容
* totalmoney: 支付金额
*/
async getPrePayId(obj) {
const { config, ctx } = this
const { appid, merchantid, merchantkey } = config.wxapp //后台预先设置的appid,merchantid, merchantkey
const { ip, notify_url } = config.payaddress
const NonceStr = Math.random().toString(36).substr(2, 15)
const orderid = uuid.v4().replace(/-/g, '')
const { body, totalmoney, openid } = obj
//预发起支付第一次签名
const uniorderParams = {
appid,
body,
mch_id: merchantid,
nonce_str: NonceStr,
notify_url,
openid,
out_trade_no: orderid,
spbill_create_ip: ip,
total_fee: totalmoney,
trade_type: 'JSAPI'
}
uniorderParams.sign = ctx.helper.getPreSign(uniorderParams, merchantkey) //根据上面的这个uniorderParams统一下单参数根据ASCii码从小到大排序,加上商户密钥做sign加密
let xml = ' ' + //重点, 必须使用xml格式来发送给微信服务器端
'' + uniorderParams.appid + ' ' +
'' + uniorderParams.body + ' ' +
'' + uniorderParams.mch_id + ' ' +
'' + uniorderParams.nonce_str + ' ' +
'' + uniorderParams.notify_url + '' +
'' + uniorderParams.openid + ' ' +
'' + uniorderParams.out_trade_no + '' +
'' + uniorderParams.spbill_create_ip + ' ' +
'' + uniorderParams.total_fee + ' ' +
'' + uniorderParams.trade_type + ' ' +
'' + uniorderParams.sign + ' ' +
'';
const temp = await ctx.curl('https://api.mch.weixin.qq.com/pay/unifiedorder', { //统一下单的地址
method: 'POST',
data: xml
})
let result = {}
if (temp.status == 200) {
result = await ctx.helper.xmlToJson(temp.data.toString())
}
/**
* 获取预支付的sign签名
* 带字符串QUERY的URL的&拼接
*/
getPreSign: (signParams, merchantkey) => {
let keys = Object.keys(signParams).sort()
let newArgs = {}
keys.forEach( val => {
if(signParams[val]) {
newArgs[val] = signParams[val]
}
})
const string = queryString.stringify(newArgs) + '&key=' + merchantkey
return crypto.createHash('md5').update(queryString.unescape(string), 'utf8').digest("hex").toUpperCase()
}
//二次签名...
1.
第一步中,如果 参数没问题,发送给微信服务器中会响应到一个prepay_id,而这个 prepay_id 就是预支付的code
2.
第二步,node服务器向微信服务器发起第二次签名,小程序端无感知
//二次签名
const paysign2 = {
appId: result.appid,
nonceStr: result.nonce_str,
package: `prepay_id=${result.prepay_id}`,
timeStamp: parseInt((Date.now() / 1000)).toString(), //注意:时间必须为秒
signType: 'MD5'
}
paysign2.paySign = ctx.helper.getPreSign(paysign2, merchantkey)
const data = { paysign2, orderid }
await ctx.model.Ordertable.create({ orderid, openid, money: totalmoney * 100, status: 0 }) //在这里我做了一个支付预处理落地到数据库的操作,当预支付
return data
}
在这里我做了一个支付预处理落地到数据库的操作,当预支付通过,支付数据库插入一条status为0的待确认支付状态的数据
1.第二步,第二次签名中的回调数据,一起通过接口返回给小程序端
async wxappay (openid, money) {
return new Promise(async (resolve, reject) => {
let Objct = {
openid, //拉取授权获取到的openid
money, //money必须是整数类型, 以RMB分为单位!
body: 'xxx'
}
let temp = await wxappWxPay(obj) //进入到第一阶段, 预支付阶段
//前面的逻辑为第一阶段
//下面的逻辑为第二阶段
//第二次签名
let einfo = temp.data
//小程序使用wx.requestPayment拉去支付逻辑
wx.requestPayment({
timeStamp: parseInt(einfo.paysign2.timeStamp).toString(),
nonceStr: einfo.paysign2.nonceStr,
package: einfo.paysign2.package,
signType: 'MD5',
paySign: einfo.paysign2.paySign,
success: async res => {
if (res) {
let checkdata = {
nonceStr: einfo.paysign2.nonceStr,
out_trade_no: einfo.orderid,
sign: einfo.paysign2.paySign
}
//下面是第四步,省略...
}
},
fail: res => {
console.log('@', res)
reject(res)
}
})
}
})
}
注意, wx.requestPayment的回调success , 并不一定获取到正确结果,严谨的说。由于发起支付后,后端(node) 发送成功支付后,微信服务器会要求后端进行支付成功数据回调的响应。微信支付的官方说明如下:
1.第三步 回调
notify_url填写注意事项
•notify_url需要填写商户自己系统的真实地址,不能填写接口文档或demo上的示例地址。
•notify_url必须是以https://或http://开头的完整全路径地址,并且确保url中的域名和IP是外网可以访问的,不能填写localhost、127.0.0.1、192.168.x.x等本地或内网IP。
•notify_url不能携带参数。
1.node服务器中回调地址代码如图:
/**
* 确认支付之后的订单
* 回调(微信)再次签名(响应支付成功的结果)
* @param {Object}
* 1.必须给微信一个响应。支fu的结果,
*
*/
async wxPayNotify(xmldata) {
const { ctx } = this
let result = await ctx.helper.xmlToJson(xmldata)
if (result) {
let resxml = ' ' +
'' + '' + '' +
'' + '' + '' +
' '
if (result.result_code == 'SUCCESS') {
await ctx.model.Ordertable.update({ status: 1, transactionid: result.transaction_id, money: result.total_fee }, { where: { orderid: result.out_trade_no, openid: result.openid } })
}
return resxml
}
}
> 在这里我上面落地存储数据的支付表,在收到微信的支付成功的回调后,将状态status :0 改为1 表示支付明确
6. 第四步 主动查询
> 由于微信的回调是异步,前端不可能等待微信的回调再来进行下一步逻辑处理,万一网络波动或者其他因素导致微信服务器的回调迟迟没有到我们的数据库中来呢?所以我们需要自己主动发起查询支付结果的API
> 此API为:[微信查询支付接口](https://api.mch.weixin.qq.com/pay/orderquery)
> 小程序端发起查询请求给后端,后端再向微信服务器调取查询结果:
> node服务器的代码如下:(本次逻辑十分重要,关系我们支付的闭环)
/** * 微信支付主动调取查询订单状态API */ async checkWxPayResult(obj) { const { ctx, config } = this const { appid, merchantid, merchantkey } = config.wxapp const { nonceStr, out_trade_no } = obj let Params = { appid, mch_id: merchantid, nonce_str: nonceStr, out_trade_no: out_trade_no } Params.sign = ctx.helper.getPreSign(Params, merchantkey) let xml = ''; const temp = await ctx.curl('https://api.mch.weixin.qq.com/pay/orderquery', { method: 'POST', data: xml }) let result = {} if (temp.status == 200) { result = await ctx.helper.xmlToJson(temp.data.toString()) return result }
}
7. 将响应结果发送给小程序端
+ 小程序端支付的完整逻辑就呈现出来了,代码如下:
async wxappay (openid, money) { return new Promise(async (resolve, reject) => { let Objct = { openid, //拉取授权获取到的openid money, //money必须是整数类型, 以RMB分为单位! body: 'xxx' } let temp = await wxappWxPay(obj) //进入到第一阶段, 预支付阶段 //前面的逻辑为第一阶段
//下面的逻辑为第二阶段
//第二次签名
let einfo = temp.data
//小程序使用wx.requestPayment拉去支付逻辑
wx.requestPayment({
timeStamp: parseInt(einfo.paysign2.timeStamp).toString(),
nonceStr: einfo.paysign2.nonceStr,
package: einfo.paysign2.package,
signType: 'MD5',
paySign: einfo.paysign2.paySign,
success: async res => {
if (res) { //虽然是res调取成功,但是我们并不需要这个参数的逻辑回调
let checkdata = {
nonceStr: einfo.paysign2.nonceStr,
out_trade_no: einfo.orderid,
sign: einfo.paysign2.paySign
}
//下面是第四步,主动查询订单的支付情况
const result = await getWxappPayResult(checkdata)
if (result.code == 200) {
resolve(result.data) //最后,作为promise进行返回,此刻的支付是100%正确的。还可以参照落地的数据库表进行辅助对照
}
}
},
fail: res => {
console.log('@', res)
reject(res)
}
})
}
})
} ```
至此,小程序的支付就完成了。
后续,此文仅仅是使用的node 作为后端, 实际上流程来说,JAVA,PHP等等语言来说,逻辑思路都基本一致的。
原创不易,喜欢的朋友麻烦点点关注!后续会写java版本的支付流程 ,敬请关注
References
[1]
微信支付页链接地址: https://pay.weixin.qq.com/
云开发
支付涉及每日资金管理如对账,提现、退款,波动图 等等,还有异地备份可以完美解决吗?
不需要服务器的云开发实现支付,已完美解决各种问题。