注意:以下测试用例的后端语言选取的是node.js,框架采用的是express。希望读者在看这篇博客前已经了解了相关知识,如果你想学习node.js,建议你上菜鸟教程。框架的话可以看下面这个链接,个人感觉讲解得很详细了。https://blog.csdn.net/qq_empire/article/details/80933726
开发步骤:
1.小程序内调用登录接口,拿到code,然后发送给后端,后端获取到openid(用户唯一标识符)。
2.获取到openid后,商户server调用支付统一下单。
3.商户server调用再次签名。
4.支付完成。
前端
wxml
js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
},
payMoney: function (e) {
wx.login({
success: res => {
console.log(res.code)
var that = this;
wx.request({
url: 'http://127.0.0.1:3030/get_openid?code=' + res.code + '&money=' + 0.01,
success: function (res) {
console.log(res.data, '统一下单接口返回信息')
wx.requestPayment({
timeStamp: res.data.timeStamp , //时间搓
nonceStr: res.data.nonceStr, //随机字符串
package: 'prepay_id='+res.data.package, //prepay_id
signType: 'MD5', //签名算法
paySign: res.data.paySign, //签名
success(res) {
console.log(res, '微信支付成功!!!')
},
fail(error){
console.log(error, '微信支付失败!!!')
}
})
}
})
}
})
},
})
后端
var express = require('express');
var router = express.Router();
var request = require('request') //引入request请求模块(这三个模块需要重新下载一次,配置到服务器上时)
var md5 = require('md5-node') //引入md5加密模块
var xml2js = require('xml2js'); //引入xml解析模块
/* GET home page. */
router.get('/', function(req, res, next) {
let query = req.query;
console.log(query,'获取请求参数')
let out_trade_no = new Date().getTime()+'_chaoxian'; //商户订单号
let nonce_str = randomStr();
let openid = null;
let total_fee = Number(query.money)*100;
let appid = ''; //自己的小程序appid
let mch_id =''; //自己的商户号id
console.log("第一次签名的信息:\n appid:"+appid);
console.log("mch_id:"+mch_id);
console.log("nonce_str:"+nonce_str);
console.log("total_fee:"+total_fee);
console.log("out_trade_no:"+out_trade_no);
getOpenid(query.code,appid).then(function(res1){
openid = res1;//执行完同步函数之后将获取到的openid赋值
console.log("获取到openid:"+openid)
let sign = createSign({ //签名
appid: appid,//小程序的appid
body: 'test',//描述
mch_id: mch_id,//商户号
nonce_str: nonce_str,//32位随机字符串
notify_url: 'https://www.chaoxian2018.cn:3030/',//随便一个可以访问的https都行,记住不能带参数
openid: openid,
out_trade_no: out_trade_no,
spbill_create_ip: '127.0.0.1',//可以写固定的ip,这里就用本地的,你可以更改为获取用户ip也行
total_fee: total_fee,
trade_type: 'JSAPI'
});
console.log("第一次签名之后的sign:"+sign);
let reqUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';//统一下单请求的url
let formData = "";
formData += ""+appid+" "; //appid
formData += "" +mch_id+ " "; //商户号
formData += "" +nonce_str+ " "; //随机字符串,不长于32位。
formData += "" +sign+ " ";
formData += "test";
formData += "" +out_trade_no+ " ";
formData += "" +total_fee+ " ";
formData += "127.0.0.1 ";
formData += "https://www.chaoxian2018.cn:3030/ ";
formData += "JSAPI ";
formData += ""+openid+" ";
formData += " ";
request({
url: reqUrl,
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: formData
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body,'统一下单接口返回的数据') // 请求成功的处理逻辑
xml2js.parseString(body,function(error,result){
console.log(JSON.stringify(result),'xml解析成json字符串')
let reData = result.xml;
let timestamp=timest();
let noncestr=reData.nonce_str[0];
let package=reData.prepay_id[0];
let signtype='MD5';
console.log("进行二次签名需要的参数:\n appid:"+appid);
console.log("nonceStr:"+noncestr);
console.log("package:"+package);
console.log("signType:"+signtype);
console.log("timeStamp:"+timestamp);
//二次签名
let paySign=creatPaySign({
appId:appid,
noncestr:noncestr,
package:package,
signtype: signtype,
timestamp: timestamp,
});
console.log("第二次签名之后的sign:"+paySign);
let responseData = {
timeStamp: timestamp,
nonceStr: noncestr,
package: package,
paySign: paySign,
}
res.end(JSON.stringify(responseData))
})
}
});
});
});
function getOpenid(code,appid){ //发起请求获取用户的openID
return new Promise(function(resolve,reject){
request('https://api.weixin.qq.com/sns/jscode2session?appid='+appid+'&secret='+
'&js_code='+code+'&grant_type=authorization_code',function(error,response,body){
if(!error && response.statusCode == 200){
var bodyJson = JSON.parse(body)
//console.log(bodyJson,'获取openID返回信息')
resolve(bodyJson.openid);
}
})
})
}
function createSign(obj){ //签名算法(把所有的非空的参数,按字典顺序组合起来+key,然后md5加密,再把加密结果都转成大写的即可)
var stringA = 'appid='+obj.appid+'&body='+obj.body+'&mch_id='+obj.mch_id+'&nonce_str='+obj.nonce_str+'¬ify_url='+obj.notify_url+'&openid='+obj.openid+'&out_trade_no='+obj.out_trade_no+'&spbill_create_ip='+obj.spbill_create_ip+'&total_fee='+obj.total_fee+'&trade_type='+obj.trade_type;
var stringSignTemp = stringA+'&key=';//这里的key需要改成自己的key
console.log("第一次加密前的字符串:"+stringSignTemp)
stringSignTemp = md5(stringSignTemp);
var signValue = stringSignTemp.toUpperCase();
return signValue
}
function creatPaySign(obj){
//二次签名
var string_paysign="appId="+obj.appId+'&nonceStr='+obj.noncestr+'&package=prepay_id='+obj.package+
'&signType='+obj.signtype+'&timeStamp='+timest();
var pay_link=string_paysign+'&key=';//这里的key需要改成自己的key
console.log("第二次加密前的字符串:"+pay_link)
pay_link= md5(pay_link);
var signResult=pay_link.toUpperCase();
return signResult;
}
function randomStr(){ //产生一个随机字符串
var str = "";
var arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
for(var i=1;i<=32;i++){
var random = Math.floor(Math.random()*arr.length);
str += arr[random];
}
return str;
}
//从1970年开始的毫秒数然后截取10位变成 从1970年开始的秒数
function timest() {
var tmp = Date.parse( new Date() ).toString();
tmp = tmp.substr(0,10);
return tmp;
}
module.exports = router;
记得填写自己的小程序appid和绑定的商户号
填写自己的小程序秘钥
填写自己的商户Key
最后在小程序点击一下看一下支付效果:
每天进步一点点,开心也多一点点