这几天做微信支付功能,可谓是一步一个坑,在这里总结一下,供以后查看(详细代码在最后)
大致的步骤:
- 通过微信的登录接口获取到code
- 用code去请求微信接口换取openid
- 用openid生成统一订单得到prepay_id
- 调用微信接口完成支付
详细步骤:
实现的前提:你要有正确的appid,serect,mch_id(商户号) ,out_trade_no(订单号)
appid 和 serect 在小程序的公众平台里(开发 -> 开发设置)可以找到。然后创建项目,项目的appid一定要和前面提到的appid 保持一致。
-
获取code
调用微信的登陆接口
wx.login({
success: function(res){
// 接口调用成功后。返回值中包含有code
}
})
-
code换取openid
调用微信的接口
这个接口请求参数有三个:appid , secret , js_code , grant_type
wx.request({
// 这里的url参数需要拼接,appid和secret 填写对应的数据 js_code:就是刚才登陆接口返回的code, grant_type=authorization_code 这个参数是固定的 不用改变
url: "https://api.weixin.qq.com/sns/jscode2session?appid=aaaaaaaaaaaaa&secret=sssssssssssss&js_code=" + code + "&grant_type=authorization_code",
method: 'GET',
success: function (res) {
// 请求成功 这里会返回openid
},
fail: function () {
}
})
这里碰到的问题:报错说code失效
后来发现是因为建小程序项目时用的appid和上面接口请求参数中的appid不一样,所以这就是为什么前面说一定要提前把appid这些东西弄好,把项目建立在正确的appid下面
-
生成统一订单
这里就是最麻烦的地方了,需要处理很多数据,下面是即将发出去的下一个请求,为了它我们需要提前准备好它需要的口粮(参数)
var formData = "<xml>"
formData += "<appid>" + obj.appid + "</appid>" //appid
formData += "<body>" + obj.body + "</body>" //标题
formData += "<mch_id>" + obj.mch_id + "</mch_id>" //商户号
formData += "<nonce_str>" + obj.nonce_str + "</nonce_str>" //随机字符串,不长于32位。
formData += "<notify_url>" + obj.notify_url + "</notify_url>" //异步接收微信支付结果通知的回调地址
formData += "<openid>" + msg.openid + "</openid>" //openid
formData += "<out_trade_no>" + obj.out_trade_no + "</out_trade_no>" //商户订单号
formData += "<spbill_create_ip>" + obj.spbill_create_ip + "</spbill_create_ip>" //用户Id
formData += "<total_fee>" + obj.total_fee + "</total_fee>" //金额
formData += "<trade_type>" + obj.trade_type + "</trade_type>" //公共号支付
formData += "<sign>" + obj.sign + "</sign>"//签名
formData += "</xml>"
wx.request({
// 这就是即将请求的地址,它需要的参数formData是xml类型的,长的就是上面那个变量的样子
url: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
method: 'POST',
data: formData,
success: function (res) {
}
})
首先要知道它需要什么样的参数:
参考H5支付功能参数:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1
注意:这里面对参数进行签名的函数如下,传入的对象是:将需要加密的参数以对象形式传进去,就可以得到加密后的结果
函数中涉及到key 根据自己的情况填写
/* 签名 */
makeSign: function (obj){
var arr = [];
for(var i in obj){
arr.push(i + "=" + obj[i]);
}
// 字典排序
arr = arr.sort();
// 将参数拼接成url
var str = arr.join('&');
// 签名第二步:在str后面加上key
str += "&key=1111111111111";
// 签名第三步:MD5加密
str = md5.hexMD5(str);
// 签名第四步:所有字符转大写
str = str.toUpperCase();
return str;
},
用户ip获取函数:(提示:这里的请求地址只适合用在练手阶段,实际开发中请换成后台获取ip的对应接口,否则小程序上线会报错)
/* 用户端ip */
getIP: function(){
var that = this;
wx.request({
url: 'http://ip-api.com/json',
success: function (e) {
that.setData({
ip: e.data.query
})
}
})
}
还有一个坑:当你这一步过去以后,请求成功了!发现返回一堆xml形式的东西,这下该处理xml了,这里就是我自己找的可以用在微信小程序中的xml解析js :dom-parser.js dom.js (单独下载地址)
const parser = require('../../lib/dom-parser');
const Dom = require('../../lib/dom.js');
...
wx.request({
url: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
method: 'POST',
data: formData,
success: function (res) {
// xml转json
var XMLParser = new parser.DOMParser()
var doc = XMLParser.parseFromString(res.data);
// 找到prepay_id
var prepay_id_node = doc.getElementsByTagName('prepay_id')[0];
var prepay_id;
if (prepay_id_node){
prepay_id = prepay_id_node.firstChild.nodeValue;
}else{
wx.showToast({
title: '支付失败',
icon: 'none',
duration: 1800
})
console.log(res)
return;
}
},
fail: function () {
}
})
-
支付
最后一步了,现在有了prepay_id,就可以去挑起支付接口了,支付前先对参数进行罗列与处理
这里还有个坑:明明官方文档中参数没有说传appid 可是我当时不传appid时,发现支付一直不能成功,后来碰到一篇帖子说他加了appid就好了,我想要不试一下,加上后,果然支付成功了!内心是多么绝望,这坑谁能想到,哎!!
// timeStamp nonceStr package signType paySign
var payData = {
appId: 'aaaaaaaaaaaaa',
timeStamp: Date.now() + '',
nonceStr: that.rdmNum(),
package: "prepay_id="+prepay_id,
signType: 'MD5'
}
var paySign = that.makeSign(payData); // 加密参数,方法跟上面统一订单中的一样
payData.paySign = paySign;
payData.out_trade_no = msg.out_trade_no; //订单号,用来关闭订单用
that.pay(payData); // 调用支付函数
支付函数
/* 支付 */
pay: function (param) {
var that = this;
wx.requestPayment({
appId: param.appId,
timeStamp: param.timeStamp,
nonceStr: param.nonceStr,
package: param.package,
signType: param.signType,
paySign: param.paySign,
success: function (res) {
wx.showToast({
title: '支付成功',
icon: 'none',
duration: 1800
})
},
fail: function (res) {
wx.showToast({
title: '支付失败',
icon: 'none',
duration: 1800
})
// 支付失败,关闭订单
that.closeOrder({
appid: 'aaaaaaaaaaaaa',
mch_id: '999999999',
out_trade_no: param.out_trade_no,
nonce_str: that.rdmNum()
})
},
complete: function () {
}
})
},
关闭订单函数
/* 关闭订单 order:关闭订单的对象 */
closeOrder: function(order){
// appid mch_id out_trade_no nonce_str sign
// 对传进来的order对象进行md5加密
var sign = this.makeSign(order);
var formData = "<xml>"
formData += "<appid>" + order.appid + "</appid>" //appid
formData += "<mch_id>" + order.mch_id + "</mch_id>" //商户号
formData += "<nonce_str>" + order.nonce_str + "</nonce_str>" //随机字符串,不长于32位。
formData += "<out_trade_no>" + order.out_trade_no + "</out_trade_no>" //商户订单号
formData += "<sign>" + sign + "</sign>"//签名
formData += "</xml>"
wx.request({
url: 'https://api.mch.weixin.qq.com/pay/closeorder ',
method: 'POST',
data: formData,
success: function(res){
console.log(res)
}
})
},
-
最后粘贴上所有代码:
// 假数据,请根据自己的情况替换
// appid:aaaaaaaaaaaaa
// secret: sssssssssssss
// mch_id: 999999999 商户号
// out_trade_no: this.data.msg.recordId 订单号(后台接口得到)
// 通知地址notify_url:http://xxxxxxx
// key:1111111111111
const md5 = require('../../utils/md5.js');
const parser = require('../../lib/dom-parser');
const Dom = require('../../lib/dom.js');
Page({
data: {
msg: {}, // 这里是订单的一些信息
ip: '' // 用户ip
},
onLoad: function (options) {
this.setData({
msg: app.globalData.payData
})
this.getIP(); // 获取用户的ip,存放在data的ip中农,供后面使用
},
// 点击支付按钮触发
payBtn: function(){
var that = this;
wx.login({
success: function (res) {
// 通过登录接口返回的code去换取openid
wx.request({
url: "https://api.weixin.qq.com/sns/jscode2session?appid=aaaaaaaaaaaaa&secret=sssssssssssss&js_code=" + res.code + "&grant_type=authorization_code",
method: 'GET',
success: function (res) {
// 生成统一订单
that.order({
mch_id: '999999999',
out_trade_no: that.data.msg.recordId,
total_fee: that.data.msg.payCharge,
openid: res.data.openid
});
console.log(res.data.openid)
},
fail: function () {
wx.showToast({
title: '支付失败',
icon: 'none',
duration: 1800
})
}
})
}
});
},
/* 生成统一订单
* mch_id:商户号
* out_trade_no:商户订单号
* total_fee:总金额
* openid: openid
*/
order: function (msg){
var that = this;
// 针对商户订单号重复的问题,在实际订单号后面加上_随机数来实现
// mch_id, out_trade_no, total_fee, openid
// var out_trade_no = msg.out_trade_no + '_' + that.rdmNum(6);
console.log(msg)
var obj = {
appid: 'aaaaaaaaaaaaa', //appid
mch_id: msg.mch_id, //商户号
nonce_str: this.rdmNum(), //随机字符串
body: 'payTest', //商品描述
out_trade_no: msg.out_trade_no, //商户订单号
total_fee: msg.total_fee, //总金额
openid: msg.openid, //openid
spbill_create_ip: this.data.ip, //终端ip
notify_url: 'http://xxxxxxx', //通知地址
trade_type: 'JSAPI' // 交易类型:公众号支付
}
// 签名
var sign = this.makeSign(obj);
obj.sign = sign;
var formData = "<xml>"
formData += "<appid>" + obj.appid + "</appid>" //appid
formData += "<body>" + obj.body + "</body>" //标题
formData += "<mch_id>" + obj.mch_id + "</mch_id>" //商户号
formData += "<nonce_str>" + obj.nonce_str + "</nonce_str>" //随机字符串,不长于32位。
formData += "<notify_url>" + obj.notify_url + "</notify_url>" //异步接收微信支付结果通知的回调地址
formData += "<openid>" + msg.openid + "</openid>" //openid
formData += "<out_trade_no>" + obj.out_trade_no + "</out_trade_no>" //商户订单号
formData += "<spbill_create_ip>" + obj.spbill_create_ip + "</spbill_create_ip>" //用户Id
formData += "<total_fee>" + obj.total_fee + "</total_fee>" //金额
formData += "<trade_type>" + obj.trade_type + "</trade_type>" //公共号支付
formData += "<sign>" + obj.sign + "</sign>"//签名
formData += "</xml>"
wx.request({
url: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
method: 'POST',
data: formData,
success: function (res) {
// xml转json
var XMLParser = new parser.DOMParser()
var doc = XMLParser.parseFromString(res.data);
// 找到prepay_id
var prepay_id_node = doc.getElementsByTagName('prepay_id')[0];
var prepay_id;
if (prepay_id_node){
prepay_id = prepay_id_node.firstChild.nodeValue;
}else{
wx.showToast({
title: '支付失败',
icon: 'none',
duration: 1800
})
return;
}
// timeStamp nonceStr package signType paySign
var payData = {
appId: 'aaaaaaaaaaaaa',
timeStamp: Date.now() + '',
nonceStr: that.rdmNum(),
package: "prepay_id=" + prepay_id,
signType: 'MD5'
}
var paySign = that.makeSign(payData);
payData.paySign = paySign;
payData.out_trade_no = msg.out_trade_no; //订单号,用来关闭订单用
that.pay(payData);
}
})
},
/* 支付 */
pay: function (param) {
var that = this;
wx.requestPayment({
appId: param.appId,
timeStamp: param.timeStamp,
nonceStr: param.nonceStr,
package: param.package,
signType: param.signType,
paySign: param.paySign,
success: function (res) {
wx.showToast({
title: '支付成功',
icon: 'none',
duration: 1800
})
},
fail: function (res) {
wx.showToast({
title: '支付失败',
icon: 'none',
duration: 1800
})
// 支付失败,关闭订单
that.closeOrder({
appid: 'aaaaaaaaaaaaa',
mch_id: '999999999',
out_trade_no: param.out_trade_no,
nonce_str: that.rdmNum()
})
},
complete: function () {
}
})
},
/* 关闭订单 order:关闭订单的对象 */
closeOrder: function(order){
// appid mch_id out_trade_no nonce_str sign
// 对传进来的order对象进行md5加密
var sign = this.makeSign(order);
var formData = "<xml>"
formData += "<appid>" + order.appid + "</appid>" //appid
formData += "<mch_id>" + order.mch_id + "</mch_id>" //商户号
formData += "<nonce_str>" + order.nonce_str + "</nonce_str>" //随机字符串,不长于32位。
formData += "<out_trade_no>" + order.out_trade_no + "</out_trade_no>" //商户订单号
formData += "<sign>" + sign + "</sign>"//签名
formData += "</xml>"
wx.request({
url: 'https://api.mch.weixin.qq.com/pay/closeorder ',
method: 'POST',
data: formData,
success: function(res){
console.log(res)
}
})
},
/* 生成随机字符串 */
rdmNum: function (len) {
len = len || 32;
var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
var maxPos = chars.length;
var num = '';
for (var i = 0; i < len; i++) {
num += chars.charAt(Math.floor(Math.random() * maxPos));
}
return num;
},
/* 签名 */
makeSign: function (obj){
var arr = [];
for(var i in obj){
arr.push(i + "=" + obj[i]);
}
// 字典排序
arr = arr.sort();
// 将参数拼接成url
var str = arr.join('&');
// 签名第二步:在str后面加上key
str += "&key=1111111111111";
// 签名第三步:MD5加密
str = md5.hexMD5(str);
// 签名第四步:所有字符转大写
str = str.toUpperCase();
return str;
},
/* 用户端ip */
getIP: function(){
var that = this;
wx.request({
url: 'http://ip-api.com/json',
success: function (e) {
that.setData({
ip: e.data.query
})
}
})
}
})