本文详细解析在Node.js环境下实现支付宝移动支付的完整流程,涵盖从生成带签名的订单到接收并验证支付通知的全过程。重点讲解RSA-SHA1签名算法的应用、私钥与公钥的配置位置、以及生成订单字符串时双引号处理的常见坑点,帮助开发者顺利完成移动支付对接。
Node.js支付宝移动支付流程概述
感谢 http://www.jianshu.com/p/8513e995ff3a?utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=weibo 提供的参考文章,若非此文指引,开发过程或许还需耗费数日。代码实现主要借鉴其思路,但由于支付宝移动支付官方文档描述不够清晰,且缺乏Node.js的SDK与示例代码,开发过程颇为艰难。经过一下午的摸索,在支付宝技术人员的协助下,最终完成了移动支付全流程,特此记录开发中遇到的难点及整体流程梳理。

移动支付核心业务逻辑
支付宝提供的业务流程图逻辑清晰,核心步骤如下:
- 用户向服务器发起付款请求。
- 服务器生成包含签名的订单数据,并发送给客户端。
- 客户端利用该订单数据调用App SDK发起支付请求。
- SDK引导用户进入支付宝支付界面完成支付操作。
- 支付完成后,支付宝向前端返回支付结果,同时向服务器发送支付通知。
- 服务器接收通知,并验证该结果是否由支付宝官方发出。
客户端与服务器端职责分工
App客户端的操作相对简单,主要步骤包括:
- 向服务器请求获取订单信息。
- 获取订单后,向支付宝SDK发起支付请求。
- 交易结束后,返回成功或失败的状态结果。
服务器端签名与验签实现细节
服务器端处理逻辑较为复杂(注意:服务端需存储应用私钥用于签名,并存储支付宝公钥用于验签):
1. 接收客户端请求后,生成带签名的订单并返回给客户端,具体步骤如下:
1) 将相关配置数据整理为数组,并将数组数据拼接为有序字符串。
//将支付宝发来的数据生成有序数列
function getVerifyParams(params) {
var sPara = [];
if(!params) return null;
for(var key in params) {
if((!params[key]) || key == "sign" || key == "sign_type") {
continue;
};
sPara.push([key, params[key]]);
}
sPara = sPara.sort();
var prestr = '';
for(var i2 = 0; i2 < sPara.length; i2++) {
var obj = sPara[i2];
if(i2 == sPara.length - 1) {
prestr = prestr + obj[0] + '=' + obj[1] + '';
} else {
prestr = prestr + obj[0] + '=' + obj[1] + '&';
}
}
return prestr;
}
2) 对该支付字符串进行RSA-SHA1算法处理,得到签名结果后,使用服务端私钥进行签名。
//验签
function veriySign(params) {
try {
var publicPem = fs.readFileSync('./rsa_public_key.pem');
var publicKey = publicPem.toString();
var prestr = getVerifyParams(params);
var sign = params['sign'] ? params['sign'] : "";
var verify = crypto.createVerify('RSA-SHA1');
verify.update(prestr);
return verify.verify(publicKey, sign, 'base64')
} catch(err) {
console.log('veriSign err', err)
}
}
3) 将有序字符串、生成的签名以及签名方法组合,形成最终订单,并返回给客户端。
//发送订单号
sendAlipay: function(req, res) {
var code = ""
for(var i = 0; i < 4; i++) {
code += Math.floor(Math.random() * 10);
}
//订单号暂时由时间戳与四位随机码生成
AlipayConfig.out_trade_no = Date.now().toString() + code;
var myParam = getParams(AlipayConfig);
var mySign = getSign(AlipayConfig)
var last = myParam + '&sign="' + mySign + '"&sign_type="RSA"';
console.log(last)
return res.send(last)
}
2. 前半部分工作完成后,若前端支付成功,支付宝会向预留的回调接口发送POST请求,以验证用户是否支付成功。
1) 将支付宝发送过来的数据生成有序字符串。
//将支付宝发来的数据生成有序数列
function getVerifyParams(params) {
var sPara = [];
if(!params) return null;
for(var key in params) {
if((!params[key]) || key == "sign" || key == "sign_type") {
continue;
};
sPara.push([key, params[key]]);
}
sPara = sPara.sort();
var prestr = '';
for(var i2 = 0; i2 < sPara.length; i2++) {
var obj = sPara[i2];
if(i2 == sPara.length - 1) {
prestr = prestr + obj[0] + '=' + obj[1] + '';
} else {
prestr = prestr + obj[0] + '=' + obj[1] + '&';
}
}
return prestr;
}
2) 对获取的数据进行Hash处理,并根据公钥验证签名的有效性,返回true或false。
//验签
function veriySign(params) {
try {
var publicPem = fs.readFileSync('./rsa_public_key.pem');
var publicKey = publicPem.toString();
var prestr = getVerifyParams(params);
var sign = params['sign'] ? params['sign'] : "";
var verify = crypto.createVerify('RSA-SHA1');
verify.update(prestr);
return verify.verify(publicKey, sign, 'base64')
} catch(err) {
console.log('veriSign err', err)
}
}
3) 若验签成功,需生成支付宝通知URL,以验证该通知是否确实由支付宝发出(支付宝的验证机制较为繁琐,令人头疼)。若验证通过且有数据返回,则确认为支付宝发出的有效通知,本次交易有效。
//回调验签
getAlipay: function(req, res) {
console.log(req.body)
var params = req.body
var mysign = veriySign(params);
//验证支付宝签名mysign为true表示签名正确
console.log(mysign)
try {
//验签成功
if(mysign) {
if(params['notify_id']) {
var partner = AlipayConfig.partner;
//生成验证支付宝通知的url
var url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&' + 'partner=' + partner + '¬ify_id=' + params['notify_id'];
console.log('url:' + url)
//验证是否是支付宝发来的通知
https.get(url, function(text) {
//有数据表示是由支付宝发来的通知
if(text) {
//交易成功
console.log('success')
} else {
//交易失败
console.log('err')
}
})
}
}
} catch(err) {
console.log(err);
}
}
开发注意事项与常见坑点
至此,整个流程运行完毕。代码细节可参考原博客,此处主要将部分逻辑改为Sails框架写法,重点记录本次开发中遇到的几个坑及注意事项。
1. 由于移动支付文档描述不够明确,私钥实际上应上传至账户信息的mapi网关产品密钥中:

而非上传至应用密钥中。
2. 移动支付仅支持RSA(SHA1)算法。
3. ./ 路径在Sails框架中用于获取根目录下的密钥文件(对Sails的路径机制尚存疑惑)。
4. 生成订单时的有序字符串格式中,body字段值包含双引号,例如body="测试";但在验签生成的有序字符串中,不应包含双引号。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持本站!