之前并没有用node.js做过H5相关的支付开发,查询了很多的资料,自己做一个总结,写了一点儿demo。
如果有问题,感谢指出。
参考博客:https://www.jianshu.com/p/10089108da21
一、微信的手机网站支付开发
H5的微信支付相关的开发,比微信公众号内的支付要相对简单一些。
微信h5支付开发的文档地址:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1。
首先要签名相关的通用方法,签名的逻辑https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_3。
我对于签名的代码实现如下:
'use strict';
const crypto = require('crypto');
/**
* 构建参数
*/
function composeParam(params)
{
let signedData = getSignedData(params);
if(params){
signedData = signature(signedData);
}
if(signedData){
params.sign = signedData;
}
return composeXml(params);
}
/**
* 构建xml
*/
function composeXml(params)
{
let result = '';
for(let key in params){
if(params.hasOwnProperty(key)){
result += '<' + key +'>' + params[key] + '</' + key + '>';
// result += '<' + key +'>' + '
对于请求的代码我的实现如下:
const request = require('request');
function sendPostHttpRequest(url, param, callback)
{
request.post(
{
url : url,
body: JSON.stringify(param)
},
function (error, response, body) {
if(error){
return callback(error, null);
}
callback(null, body);
}
);
}
解析微信返回的xml数据:
const xml2js = require('xml2js').parseString;
/**
* xml转对象
*/
function xmlToObject(xml, callback)
{
xml2js(xml, {explicitArray:false}, function(error, result){
if (error) {
return callback(error, null);
}else{
callback(null, result);
}
})
}
剩下的工作就是跟着文档走了。
不过微信的开发文档在表述上有一些歧义,多注意文档中标红的信息。如果是在手机网站上进行的支付,那么就不需要获取access_token和openid,另外,需要确认你的商户是否有接入H5支付的功能,公众号支付和app支付对应的商户平台是不一样的。
将参数通过请求调用方法,调用微信的统一下单接口,获取的微信的相应数据,如果请求的数据没有问题,微信返回的数据将包含一个支付跳转链接mweb_url,直接跳转到这个链接就可以调起微信支付。
还有一些坑,在调起这个连接时,要保证请求头中refer是有值的。
随后,用户支付后,微信会向回调地址(notify_url)发送一个支付信息:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=8
需要对微信所发的数据进行验签,将除了sign字段的所有参数都拿去签名,如果签名一致,则验签成功。
二、支付宝的手机网站支付开发
支付宝H5开发文档:https://docs.open.alipay.com/203
个人感觉,支付宝的开发相对简单一些。
支付宝的加密方式是sha-256,有两对公私钥。
在本地生成一对密钥(支付宝的文档中有写),将生成的应用公钥保存到支付宝的应用后台,将支付宝提供的公钥保存到本地。
我对于签名的代码实现如下:
const fs = require('fs');
const crypto = require('crypto');
/**
* 构建 参数
*/
function composeParam(params)
{
let requestParam = getRequestUrl(params);
let sign = signature(params);
let result = requestParam + '&sign_type=RSA2&sign=' + sign;
return result;
}
/**
* 构建签名参数
*/
function getSignedData(params)
{
let data = [];
if(!params){
return null;
}
/* 参数为空与sign不参与签名 */
for(let key in params) {
if((!params[key]) || key === "sign") {
continue;
}else{
data.push([key, params[key]]);
}
}
/* 为参数排序 */
data = data.sort();
/* 转成url传值所需 */
let result = '';
for(let i = 0; i < data.length; i++) {
let obj = data[i];
if(i == data.length - 1) {
result = result + obj[0] + '=' + obj[1] + '';
} else {
result = result + obj[0] + '=' + obj[1] + '&';
}
}
return result;
}
/**
* 为最后的地址构建参数
*/
function getRequestUrl(params)
{
let data = [];
if(!params){
return null;
}
for(let key in params) {
if((!params[key]) || key === "sign" || key === "sign_type") {
continue;
}else{
data.push([key, params[key]]);
}
}
/* 为参数排序 */
data = data.sort();
/* 转成url传值所需 */
let result = '';
for(let i = 0; i < data.length; i++) {
let obj = data[i];
if(i == data.length - 1) {
result = result + obj[0] + '=' + encodeURIComponent(obj[1]) + '';
} else {
result = result + obj[0] + '=' + encodeURIComponent(obj[1]) + '&';
}
}
return result;
}
/**
* 签名
*/
function signature(params)
{
try {
/* 读取私钥 */
let privatePem = fs.readFileSync(__dirname + '/privateKey/app_private_key.pem');
let key = privatePem.toString();
let str = getSignedData(params)
let sign = crypto.createSign('RSA-SHA256');
sign.update(str);
sign = sign.sign(key, 'base64');
return encodeURIComponent(sign);
} catch(err) {
console.log('签名错误:', err)
}
}
module.exports.composeParam = composeParam;
将生成的参数,直接挂在请求地址后,用浏览器打开该地址,就可以调起支付宝支付界面。
支付宝跟微信都有一个回调地址(notify_url),需要对请求的数据进行验签。
验证签名代码:
const fs = require('fs');
const crypto = require('crypto');
/**
* 将参数做urldecode
*/
function decodeVerifyParams(params)
{
let result = {};
for(let key in params){
result[key] = params[key];
}
return result;
}
/**
* 获取需要验签的参数
*/
function getVerifyParams(params)
{
let verifyParams = [];
if(!params) return null;
for(let key in params) {
if((!params[key]) || key == "sign" || key == "sign_type" ||
key == "filter_params" || key == "access_token") {
continue;
};
verifyParams.push([key, params[key]]);
}
verifyParams = verifyParams.sort();
let result = '';
for(let i2 = 0; i2 < verifyParams.length; i2++) {
let obj = verifyParams[i2];
if(i2 == verifyParams.length - 1) {
result = result + obj[0] + '=' + obj[1] + '';
} else {
result = result + obj[0] + '=' + obj[1] + '&';
}
}
return result;
}
/**
* 验证签名
*/
function veriySign(params)
{
try {
let publicPem = fs.readFileSync(__dirname + '/privateKey/app_public_key.pem');
let publicKey = publicPem.toString();
let str = getVerifyParams(params);
var sign = params['sign'] ? params['sign'] : "";
sign = decodeURIComponent(sign);
var verify = crypto.createVerify('RSA-SHA256');
verify.update(str);
return verify.verify(publicKey, sign, 'base64');
} catch(err) {
console.log('验签错误:', err);
}
}
/**
* 验证签名
*/
function verifySignature(params)
{
params = decodeVerifyParams(params);
return veriySign(params);
}
module.exports.verifySignature = verifySignature;
最后方法会返回true|false,代表验签是否成功。
在对回调地址的数据进行校验时,除了验证签名外,不要忘记对业务数据也进行验证。
demo地址:https://github.com/shen4030/pay
demo仅供参考