爱贝支付nodejs实现,完整的代码,复制即可执行
总结几个小坑:
1. 使用私钥进行签名的问题,爱贝支付的签名规则是采用RSA MD5数字签名算法,私钥签名、公钥验签
所以本案例中用的是 nodejs 的 crypto 模块
// 签名
crypto.createSign('md5WithRSAEncryption');
//验签
crypto.createVerify('md5WithRSAEncryption');
2. 请求下单爱贝提示请求参数错误的问题,最终使用了 nodejs 的 request 模块,案例中有代码
3. 私钥和公钥的问题,建议放在同一个目录下
代码如下:
/**
* created by zhoubotong on 2019/06/03
*/
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const ORDER_SUCCESS = 0;
const ORDER_FAILED = 1;
class IPay {
constructor(config) {
this.init();
}
init() {
this.payConfig = {
APP_NAME: "",
APP_ID: "",
APP_GATEWAY_URL: "",//用于接收异步通知
APP_PRIVATE_KEY_PATH: path.join(__dirname, './keys/rsa_private_key.pem'),//应用私钥
APP_PUBLIC_KEY_PATH: path.join(__dirname, './keys/rsa_public_key.pem'),//应用公钥
ORDER_URL: "https://cp.iapppay.com/payapi/order",
H5_PAY_GATEWAY: "https://web.iapppay.com/h5/gateway?",
H5_PAY_SUCCESS_URL: "",
H5_PAY_CANCEL_URL: "",
}
}
/**
* h5支付
* @param userId
* @param subject
* @param outTradeNo
* @param totalAmount
* @returns {Promise.<*>}
*/
async h5(userId, subject, outTradeNo, totalAmount) {
// 拼装参数
let params = this._buildParams(userId, subject, outTradeNo, totalAmount);
// 发起下单请求
let res = await this._httpsFormPost(this.payConfig.ORDER_URL, params);
// 解析结果
let orderData = this._parseOrderResult(res);
if (orderData.errcode === ORDER_SUCCESS) {
// h5 支付
let params = {
tid: orderData.transid,
app: this.payConfig.APP_ID,
url_r: this.payConfig.H5_PAY_SUCCESS_URL,
url_h: this.payConfig.H5_PAY_CANCEL_URL,
};
params = JSON.stringify(params);
let sign = this._buildSign(params);
let data = "data=" + encodeURIComponent(params) + "&sign=" + encodeURIComponent(sign) + "&signtype=RSA";
// url + data
return this.payConfig.H5_PAY_GATEWAY + data;
} else {
console.error(orderData.errmsg);
return null;
}
}
/**
* 解析下单结果
* 原始数据:transdata=%7B%22transid%22%3A%2232641906040908136002%22%7D&sign=TEpZZAbyWA2H0MQCC1Cdny6BttEqeN4vnmO1S%2FuhwUk87de911VeoGAHHADp1S7bDl02d9asPZor03TQMiuCU2VGbyc8Hk5xzgZZrybFfRO02QjXT6Pe6kvLw6FSBhnKewLWXhOvZOCRrwUtcBvh%2FDGlBxPxdrfhw7zA%2FfwoO2s%3D&signtype=RSA
* 下单成功:transdata={"transid":"32641906040908136002"}&sign=TEpZZAbyWA2H0MQCC1Cdny6BttEqeN4vnmO1S/uhwUk87de911VeoGAHHADp1S7bDl02d9asPZor03TQMiuCU2VGbyc8Hk5xzgZZrybFfRO02QjXT6Pe6kvLw6FSBhnKewLWXhOvZOCRrwUtcBvh/DGlBxPxdrfhw7zA/fwoO2s=&signtype=RSA
* 下单失败:transdata={"code":1002,"errmsg":"请求参数错误"}
* @param data
* @private
*/
_parseOrderResult(data) {
let ret = {};
// 1 进行 url 解码
data = decodeURIComponent(data);
// 2 根据 & 切割
let dataArr = data.split("&");
// 3 获取数据,
// 下单成功: transdata、sign、signtype 三个数据
// 下单失败: transdata 一个数据
if (dataArr.length === 3) {
// 获取transdata
let transdata = dataArr[0].replace("transdata=", "");
// 获取sign
let sign = dataArr[1].replace("sign=", "");
// signtype
let signtype = dataArr[2].replace("signtype=", "");
// 4 验签
if (this.verifySign(transdata, sign)) {
let res = transdata.match("\\d+");
ret.errcode = ORDER_SUCCESS;
ret.transid = res[0];
ret.transdata = transdata;
ret.sign = sign;
ret.signtype = signtype;
} else {
console.error(`验签不通过`);
ret.errcode = ORDER_FAILED;
ret.errmsg = dataArr[0];
}
} else {
ret.errcode = ORDER_FAILED;
ret.errmsg = dataArr[0];
}
return ret;
}
/**
* 构建app支付需要的参数
* @param userId 用户id
* @param subject 商品名称
* @param outTradeNo 自己的订单号
* @param totalAmount 金额
* @returns {string}
* @private
*/
_buildParams(userId, subject, outTradeNo, totalAmount) {
let params = {
waresid: 1,
appuserid: "" + userId,
price: parseFloat(totalAmount),
appid: "" + this.payConfig.APP_ID,
waresname: subject,
notifyurl: this.payConfig.APP_GATEWAY_URL,
currency: "RMB",
cporderid: outTradeNo,
};
params = JSON.stringify(params);
let sign = this._buildSign(params);
return {
transdata: decodeURIComponent(params),
sign: decodeURIComponent(sign),
signtype: "RSA",
};
}
/**
* 根据参数构建签名
* @param params
* @private
*/
_buildSign(params) {
let privateKeyStr = fs.readFileSync(this.payConfig.APP_PRIVATE_KEY_PATH, 'utf8');
// 私钥 + 参数 ras md5
let sign = crypto.createSign('md5WithRSAEncryption');
sign.update(Buffer.from(params));
return sign.sign(privateKeyStr, 'base64');
}
/**
* 验证签名
* @param params
* @param sign
* @returns {*}
*/
verifySign(params, sign) {
try {
let publicKey = fs.readFileSync(this.payConfig.APP_PUBLIC_KEY_PATH, 'utf8');
let verify = crypto.createVerify('md5WithRSAEncryption');
verify.update(Buffer.from(params));
return verify.verify(publicKey, sign, 'base64')
} catch (e) {
console.error(e);
return false;
}
}
/**
* 发起下单请求
* @param host
* @param data
* @returns {Promise}
* @private
*/
_httpsFormPost(host, data) {
let request = require('request');
let options = {
url: host,
form: data,
encoding: 'utf8'
};
return new Promise((resolve, reject) => {
request.post(options, (err, res, body) => {
if (err) {
reject('error posting json: ', err);
}
try {
let json = JSON.parse(body);
json.cookie = res.headers['set-cookie'];
resolve(json);
} catch (e) {
resolve(body);
}
});
});
}
}
module.exports = IPay;
async function main() {
try {
let iPay = new IPay();
let userId = "test1";
let subject = "test";
let outTradeNo = "15527268917182646317";
let totalAmount = 1.1;
let data = await iPay.h5(userId, subject, outTradeNo, totalAmount);
console.log(data);
} catch (e) {
console.log(e)
}
}
// main();