项目需要对接(阿里)蚂蚁金服和(腾讯)财付通的收款。业务场景比较简单,就是在网页上产生一个付款二维码,分别使用微信或者支付宝App扫码完成付款即可(支付宝:当面付业务;微信:JSAPI支付业务)。不得不说,微信在这一块的文档是比较完整的,对接很顺利的就完成了。
但是在接入支付宝时遇到了一些问题。因为项目使用NodeJS开发,在这方面蚂蚁金服对待NodeJS对态度相比Java、.NET、PHP和Python起来,似乎就是小娘养的,所有SDK对使用案例均未提及,仅仅提供了一个npm的一来包和源码地址。前往源码github查看,也只有聊聊几句说明和一个在后端根本无法运行的demo代码。
而搜索NodeJS接入支付宝的内容,仅有的几个案例几乎都是自己封装了部分功能的代码的残次品。无奈之下,只有两条路可走。要么自己写一套调用逻辑,要么试试看使用官方的Node版本SDK。
抱着不重复造轮子的观念,先克隆了官方SDK研究。好在项目中有测试代码,连蒙带猜的开始了一波操作。
密钥设置
1. 支付宝官方推荐RSA2,于是先用openssl生成私钥和公钥
openssl
OpenSSL> genrsa -out alipay_pri.pem 2048 # 私钥RSA2
OpenSSL> rsa -in alipay_pub.pem -pubout -out app_public_key.pem # 公钥
OpenSSL> exit
2. 登录蚂蚁金服,进入开发者中心,选中相关应用,在应用信息中的开发设置接口加签方式中设置公钥,将刚才生成的文件内容粘贴到文本框保存即可。再将支付宝公钥保存到本地文件alipay_key.pem(或者直接写到程序中)。
项目设置
3. 在项目中安装alipay-sdk包:
npm i -s alipay-sdk
const AlipaySdk = require('alipay-sdk').default
const fs = require('fs')
const alipaySdk = new AlipaySdk({
appId: '20191017684131213',
privateKey: fs.readFileSync('./alipay_pri.pem', 'utf8'), // 你生成的私钥
alipayPublicKey:fs.readFileSync('./alipay_key', 'utf8') // 支付宝的公钥(不是你生成的那个公钥,是支付宝的那个!)
})
根据分析源码,引入的方式如上,仅需配置appId、私钥(用于加密)、回调地址即可。
4. 调用预创建订单业务。
具体的参数参考:官方文档。(画外音:这类文档一看就是出自理工男之手:当我看到“公共参数”和“请求参数”时,犹如自己是个外国人,每个字每个词都认识,但是组合在一起就不懂了)
后来看了源码才知道:公共参数是指在调用api时都会带上的参数,跟使用那项业务无关,其部分存在于url中都参数(诸如appId,业务名等,详见源码alipay.js:60,urlArgs)。请求参数则是指请求某个业务API,以POST方式,在form中的参数(诸如:bizContent,notify_url等)。其实作为开发者,更需要关心等是实例怎么创建,参数怎么配就行了。以生成预支付订单为例:
alipaySdk.exec('alipay.trade.precreate', {
notify_url: global.PAY_RECALL_URL_ALIPAY, // 支付完成通知地址
bizContent: {
out_trade_no: 'LEADLEO_010341_1',
subject: '头豹报告付费',
goods_detail: [{
goods_id: '1eca8621s',
goods_name: '《市场行业概览》',
quantity: 1,
merchant_order_no: 'LEADLEO_010341',
qr_code_timeout_express: '30m',
price: 2000
}],
body: '头豹报告购买',
total_amount: '0.01'
}// 业务(API)参数
}, { log: { info: console.log } }).then(r => {
console.log('res', r)
}).catch(er => {
console.log('err', er)
})
exec方法接受3个参数:
- 第一个参数:字符串,具体业务。
- 第二个参数:POST的Form内容,具体某个API的参数在bizContent字段中(JSON格式,这点比微信号,微信是XML),还可能包括一些“公共参数”,可参考相关业务的文档(请求参数部分)。
- 第三个参数:配置类参数,注入log对象,包括info方法和error方法,可以是 console.log和console.error,或者上传的文件(必须是formData格式)
5. 处理返回
除非是通信过程的异常和签名错误,否则都是正常(then中)的返回。返回对象参考具体业务文档。
消息验证
当用户完成支付后,会收到来自支付宝当消息通知:POST方式提交到你调用时提供的notify_url地址。你可以不对其真实性进行验证,而直接执行业务逻辑(那样的话,实例化时的那个构造函数中就不需要alipayPublicKey参数)。不过强烈建议还是需要验证一下的。我们先看看送来的对象:
{
pp_id: "2019101768409753",
auth_app_id: "2019101768409753",
body: "头豹报告购买",
buyer_id: "2088002005278604",
buyer_logon_id: "anu***@gmail.com",
buyer_pay_amount: "0.01",
charset: "utf-8",
fund_bill_list: '[{amount:"0.01",fundChannel:"ALIPAYACCOUNT"}]',
gmt_create: "2020-01-17 15:47:23",
gmt_payment: "2020-01-17 15:47:27",
invoice_amount: "0.01",
notify_id: "2020011700222154727078601406465216",
notify_time: "2020-01-17 15:47:27",
notify_type: "trade_status_sync",
out_trade_no: "TEST_S6gJLiy1nfNnrCE",
point_amount: "0.00",
receipt_amount: "0.01",
seller_email: "zhang.hui@frostchina.com",
seller_id: "2088631530585833",
sign: "ha4inINyICBKLMiCID/sqEITrrHlzZZacTFmgvcV3FYs5EucA81rZlqagwgzeK+SXarXxd0Qa9+1y/0b5JD/YL5wNti2KoiwfhDF9llvNZx2W/xhfbZNdykenmnMUSpUszyH2bp4FbWr8+25j8rexz26prG1SbDkslCJXVqBvyLoDxHDnaUhkvKDU4yWpVIfGipMROmyfeYqzRbHzHhXfAemSfus0toDXvVlW1gtcJOlWhP3qugmNgXdJ5CfPHKGqhE4Kdg2g5Mzw0nkSSZUV28OQKkEUXWhSeM+xZPWgyO6PHfvmSmeZ4zwGbbN8pV6u95A+aXyoEOmOdeBcLCzbA==",
sign_type: "RSA2",
subject: "头豹报告付费",
total_amount: "0.01",
trade_no: "2020011722001478601439721848",
trade_status: "TRADE_SUCCESS",
version: "1.0"
}
6 验证消息可靠。以express为例,代码如下:
// 支付宝支付通知
router.post('/alipayPayed', async (req, res) => {
try {
// 验证是否合法
if (alipayApi.checkNotifySign(req.body)) {
// 具体业务逻辑
res.send('success')
} else {
console.error('支付成功回调消息未通过验证')
}
} catch (err) {
console.error(err)
res.send('error')
}
})
截止目前的版本,3108aa3,作者未将验证过程中的调试信息去除。可以修改位于node_modules/alipay-sdk/lib/alipay.js:178 console.log代码注释。