Appearance
微信小程序支付
1、支付流程

大概流程就是:
- 前端请求后端支付接口,并把登录的 code(因为是自己测试所以要把 code 传给后端,用来获取用户的 openID)和支付的 money 传给后端;
- 后端调用小程序的登录 API 获取用户的 openID,然后调用统一下单 API,把必要的参数传过去,就可以获取预付单信息(prepay_id),
- 然后获取当前的时间搓(timeStamp),整合预付单信息(prepay_id,参数是 packpage)、随机字符串(nonceStr)、支付签名(paySign),签名算法(signType)一起返回给前端就可以。
- 最后前端调用 wx.requestPayment 接口,并把后端返回的数据一起传给微信就可以发起支付了。
示例:
json
{
"appId": "wx2fefab******256ef0",
"nonceStr": "crf3pkxsezcdx****ps7s89ygo15nveu",
"package": "prepay_id=wx191016483***503b1440142100",
"signType": "MD5",
"timeStamp": "1560910608",
"paySign": "B4354A3583B****028F97A8896"
}
2、基础配置
js
appId: 'wxb5c6********10b', // 小程序ID
secret: 'fd3a92*******56443ca', // 小程序Secret
merchantKey: '1bb294c********2a0d487',//商户API密钥
merchantId: '153******431' //商户ID
3、小程序支付接口
js
wx.requestPayment({
timeStamp: '', //时间戳
nonceStr: '', //随机字符串,长度为32个字符以下
package: '', //统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***
signType: '', //签名算法 默认MD5
paySign: '', //签名,具体签名方案参见
success(res) {},
fail(res) {},
});
signType 直接写死 Md5,timeStamp 也容易获取到,那主要有nonceStr,package,paySign需要后端生成。
4、生成 nonceStr 和 timeStamp
js
// 随机字符串,不长于32位。推荐随机数生成算法
const createNonceStr = () => Math.random().toString(36).substr(2, 15);
const createTimestamp = () => parseInt(+new Date() / 1000).toString();
5、生成 package 和 paySign
通过统一下单接口,生成 package 和 paySign,调用统一下单接口之前要生成一个统一下单签名,调用接口后在二次签名生成paySign
工具方法:
js
const config = require('./config');
const crypto = require('crypto');
const parseString = require('xml2js').parseString;
const wxpay = {
// 随机字符串,不长于32位。推荐随机数生成算法
createNonceStr: () => Math.random().toString(36).substr(2, 15),
//时间戳秒
createTimestamp: () => parseInt(+new Date() / 1000).toString(),
//生成统一下单签名
paysignjsapi: (obj) => {
let str = wxpay.raw(obj);
str = str + '&key=' + config.merchantKey;
return crypto.createHash('md5').update(str).digest('hex').toUpperCase();
},
//小程序paySign
paysign: (obj) => {
let str = wxpay.raw1(obj);
str = str + '&key=' + config.merchantKey;
return crypto.createHash('md5').update(str).digest('hex');
},
//非空参数值的参数按照参数名ASCII码从小到大排序(字典序)
raw: (args) => {
let keys = Object.keys(args).sort();
let newArgs = {};
keys.forEach((key) => (newArgs[key.toLowerCase()] = args[key]));
let str = '';
for (let k in newArgs) {
str += '&' + k + '=' + newArgs[k];
}
return str.substr(1);
},
//二次签名时候的参数不需要转换为小写的
raw1: (args) => {
let keys = Object.keys(args).sort();
let newArgs = {};
keys.forEach((key) => (newArgs[key] = args[key]));
let str = '';
for (let k in newArgs) {
str += '&' + k + '=' + newArgs[k];
}
return str.substr(1);
},
//商品金额转分
getmoney: (num, precision = 12) =>
+parseFloat((num * 100).toPrecision(precision)),
//发送xml格式数据
getfromData: (obj) => {
let formData = `<xml>
<appid>${obj.appid}</appid>
<attach>${obj.attach}</attach>
<body>${obj.body}</body>
<mch_id>${obj.mch_id}</mch_id>
<nonce_str>${obj.nonce_str}</nonce_str>
<notify_url>${obj.notify_url}</notify_url>
<openid>${obj.openid}</openid>
<out_trade_no>${obj.out_trade_no}</out_trade_no>
<spbill_create_ip>${obj.spbill_create_ip}</spbill_create_ip>
<total_fee>${obj.total_fee}</total_fee>
<trade_type>${obj.trade_type}</trade_type>
<sign>${obj.sign}</sign>
</xml>`;
return formData;
},
//获取请求的IP
getClientIp: (request) => {
let ip =
request.headers['x-forwarded-for'] ||
request.ip ||
request.connection.remoteAddress ||
request.socket.remoteAddress ||
request.connection.socket.remoteAddress ||
'';
if (ip.split(',').length > 0) {
ip = ip.split(',')[0];
}
return ip;
},
// 支付成功后再次验证签名
payResultSign: (result) => {
const ret = {
appid: result.appid,
attach: result.attach,
bank_type: result.bank_type,
cash_fee: result.cash_fee,
fee_type: result.fee_type,
is_subscribe: result.is_subscribe,
mch_id: result.mch_id,
nonce_str: result.nonce_str,
openid: result.openid,
out_trade_no: result.out_trade_no,
result_code: result.result_code,
return_code: result.return_code,
time_end: result.time_end,
total_fee: result.total_fee,
trade_type: result.trade_type,
transaction_id: result.transaction_id,
};
let string = wxpay.raw1(ret);
//key为在微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
string = string + '&key=' + config.merchantKey;
return crypto
.createHash('md5')
.update(string, 'utf8')
.digest('hex')
.toUpperCase();
},
//解析支付结果
notifyResult: (ctx) =>
new Promise((resolve, reject) => {
try {
let data = '';
ctx.req.on('data', (chunk) => {
data += chunk;
});
ctx.req.on('end', () => {
parseString(data, { explicitArray: false }, (err, result) => {
ctx.logger.info('解析结果成功了');
ctx.logger.info(result);
resolve(result.xml);
});
});
} catch (error) {
ctx.logger.info('解析结果失败了');
ctx.logger.info(error);
reject(error);
}
}),
//支付回调结果通知给微信
notifyTowx: (obj) => {
if (obj.return_code == 'SUCCESS') {
return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
} else {
return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[FAIL]]></return_msg></xml>';
}
},
};
module.exports = wxpay;
生成统一下单签名 paysignjsapi 还需要获取用户的 openID 参数。
主要是要调用接口
js
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
主要代码逻辑:
js
'use strict';
var xml2js = require('xml2js');
const Controller = require('../core/base_controller');
const fly = require('flyio');
const { config } = require('../utils/config');
const parseString = require('xml2js').parseString;
const {
paysignjsapi,
createNonceStr,
getmoney,
getfromData,
paysign,
createTimestamp,
getClientIp,
} = require('../utils/index');
class WxpayController extends Controller {
//微信支付
async orderWxpay() {
const params = this.ctx.request.body;
const reqUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
const ip = getClientIp(this.ctx.request);
console.log('请求的IP');
console.log(ip);
console.log(this.ctx.request);
const payObj = {
//签名
appid: config.appId,
body: '微信支付,商品详细描述',
mch_id: config.merchantId,
nonce_str: createNonceStr(),
notify_url: 'https://www.XXX.com',
openid: params.openId,
out_trade_no: params.orderId,
spbill_create_ip: ip,
total_fee: getmoney(params.money),
trade_type: 'JSAPI',
};
//返回微信小程序的请求结果
const xcxParams = {
code: 0,
data: null,
msg: '',
};
//统一下单签名
payObj.sign = paysignjsapi(payObj);
//发送XML数据
const fromData = getfromData(payObj);
console.log(fromData);
await fly.request(reqUrl, fromData, { method: 'POST' }).then((res) => {
console.log('支付');
console.log(res.response.statusCode);
if (res.response.statusCode === 200) {
parseString(res.response.body, (err, result) => {
console.log(err);
console.log(result);
if (result.xml.result_code[0] === 'SUCCESS') {
xcxParams.code = 1;
xcxParams.msg = '获取成功';
//调用成功后,在二次签名生成小程序的paySign
xcxParams.data.appId = result.xml.appid[0];
xcxParams.data.nonceStr = result.xml.nonce_str[0];
xcxParams.data.package = `prepay_id=${result.xml.prepay_id[0]}`;
xcxParams.data.signType = 'MD5';
xcxParams.data.timeStamp = createTimestamp();
console.log('未二次签名钱');
console.log(xcxParams);
xcxParams.data.paySign = paysign(xcxParams.data);
console.log('二次签名后');
console.log(xcxParams);
} else {
xcxParams.code = 0;
xcxParams.data = null;
xcxParams.msg = `${result.xml.err_code_des[0]}-订单ID${params.orderId}`;
}
});
}
});
this.ctx.body = xcxParams;
}
}
module.exports = WxpayController;
返回的给前端这些支付参数,就可以发起支付了。
6、支付回调 notify_url
上面只是成功发起了支付,用户有没有支付,钱有没有到账,还需要一个支付回调的接口,异步处理支付结果通知。
[坑一]
使用this.ctx.request一直接受不到回调数据,不是按着正常 post 发过来的,用 stream 获取要用,req 监听获取 node 原始请求数据。
[坑二]
把通知的结果在返还给为微信,不然微信会一直重复调用支付通知接口,会掉 10 次. 执行顺序有问题。
支付回调接口:
js
//微信支付回调
async wxpayNotify() {
/* { xml:
{ appid: 'wxb5c6c3c7812ae10b',
attach: 'course',
bank_type: 'CFT',
cash_fee: '1',
fee_type: 'CNY',
is_subscribe: 'N',
mch_id: '1538354631',
nonce_str: 'n6yvy15s1td',
openid: 'omrS15GeN1iBsL-kpALggFh5LGPM',
out_trade_no: '5d0c9d0818c8a1159efe1936',
result_code: 'SUCCESS',
return_code: 'SUCCESS',
sign: '8C1C6FD8535C2240232A91060FABBCFB',
time_end: '20190621170219',
total_fee: '1',
trade_type: 'JSAPI',
transaction_id: '4200000320201906213026967473' }
} */
//解析数据
let notifyXML = '',
resultTowx = '',
money = 0,
orderInfo = {};
await wxpay.notifyResult(this.ctx).then(res => (notifyXML = res));
//签名校验
const signed = wxpay.payResultSign(notifyXML) === notifyXML.sign;
//如果支付成功
if (
signed &&
notifyXML.result_code === 'SUCCESS' &&
notifyXML.return_code === 'SUCCESS'
) {
resultTowx = wxpay.notifyTowx(notifyXML);
//处理支付成功后的业务逻辑
}
}
this.ctx.logger.info('返回微信的结果');
this.ctx.logger.info(resultTowx);
this.ctx.body = resultTowx;
}
node egg 框架处理微信支付成功回调 xml 数据
基于 node 实现微信支付功能
egg 之微信支付微信小程序支付服务端实现——nodejs
微信支付接口签名校验工具