whats-alipay
v0.1.5
Published
Yet Another Alipay OpenAPI SDK
Downloads
46
Maintainers
Readme
支付宝 OpenAPI SDK
Yet Another Alipay OpenAPI Smart Development Kit
主要功能
- OOP风格化的,可弹性扩容的,支付宝OpenAPI SDK
- 低依赖,目前仅依赖
Axios
- 使用Node原生代码实现支付宝OpenAPI的AES(
aes-128-cbc
)加/解密功能 - 使用Node原生代码实现支付宝OpenAPI的RSA(
sha1WithRSAEncryption
)及RSA2(sha256WithRSAEncryption
)签名、验签功能 - 异步通知消息验签
- 命令行网关交互工具
SDK约定
- 使用
method({请求参数}[, {公共请求参数}[, {特殊头参数}]])
作为HTTP接口驱动,释义如下:- 接口定义中
公共请求参数
的method
,即作为本SDK标准方法链,弹性扩容,示例使用方法如下,详细审查见文末 - 接口定义中的
请求参数
,以Object
对象作为第一个入参 - 接口定义中的
公共请求参数
,以Object
对象作为第二个入参 - 特别的,对于图片/视频上传,需要定义
multipart/form-data
头信息,以Object
对象作为第三个入参,见如下示例 - 特别的,对于页面表单提交型,即所谓的page类接口,第三参数须定义为
Formatter.page
,返回结果可直接作为页面/接口输出,说明用法见如下示例
- 接口定义中
- 请求数据签名以及返回数据验签均自动完成,开发者仅需关注业务代码即可;特别地,对于验签结果有依赖的情况,可以从返回值的头部获取:
headers[x-alipay-verified]
为验签结果,值可能为ok
,undefined
headers[x-alipay-signature]
为源返回数据签名值,值可能为undefined
headers[x-alipay-responder]
为源返回值字段名转译,值可能为undefined
,error
或者实际请求的method
值
使用手册
whatsCli 命令行工具
以命令行的形式,与OpenAPI网关交互,play the OpenAPI requests over command line.
Usage: cli.js [options]
Options:
-k, --privateKey The privateKey pem file path [required]
-p, --publicCert The publicCert pem file path [required]
-m, --method The method, eg: alipay.trade.query [required]
-s, --search The search parameters, eg: search.app_id=2088 [required]
-b, --biz The biz_content, eg: biz.out_trade_no=abcd1234 [required]
-d, --log Turn on the request trace log [boolean]
-u, --baseURL The OpenAPI gateway, eg: https://openapi.alipaydev.com/gateway.do
-h, --help Show help [boolean]
-V, --version Show version number [boolean]
Examples:
cli.js -k merchant.key -p alipay.pub -m alipay.trade.pay The Face2Face barCode scenario
-s.app_id=2088 -b.subject=HelloKitty
-b.out_trade_no=Kitty0001 -b.scene=bar_code
-b.total_amount=0.01 -b.auth_code=
cli.js -k merchant.key -p alipay.pub -m alipay.trade.refund The trade refund scenario
-s.app_id=2088 -b.refund_amount=0.01 -b.refund_currency=CNY
-b.out_trade_no=Kitty0001
cli.js -d -u https://openapi.alipaydev.com/gateway.do -k The trade query scenario over the sandbox environment
merchant.key -p alipay.pub -m alipay.trade.query with trace logging
-s.app_id=2088 -b.out_trade_no=Kitty0001
certHelper 命令行工具
Usage: cert.js [command] [options]
Commands:
cert.js SN Get the certificatie(s) `SN` [default]
cert.js extract Extract the chained certificate(s)
Options:
-f, --file The certificate(s) file path [required]
-p, --pattern The algo prefix or suffix, dot(.) for all
-h, --help Show help [boolean]
-V, --version Show version number [boolean]
Examples:
cert.js SN -f alipayRootCert.crt get the `sha256`(default) certificate `SN`
cert.js SN -f alipayRootCert.crt -p ec get the signatureAlgorithm whose contains `ec` words
certificate `SN`
cert.js SN -f alipayRootCert.crt -p . get all chained certificate(s) `SN`
cert.js extract -f alipayRootCert.crt extract the `sha256`(default) certificate
cert.js extract -f alipayRootCert.crt -p sha1 extract the `sha1` certificate
cert.js extract -f alipayRootCert.crt -p . extract all chained certificate(s)
cert.js extract -f alipayRootCert.crt -p sha1 | openssl x509 piped openssl x509 command
-noout -text
cert.js extract -f alipayRootCert.crt -p sha1 > tmp.pem save to a file
此命令行工具,主要是用来计算并获取 公钥证书模式 所需的 应用公钥证书SN(app_cert_sn)及 支付宝公钥证书SN(alipay_root_cert_sn)。
./bin/cert.js SN -f /path/your/app_cert.crt
./bin/cert.js SN -f /path/your/alipay_root_cert.crt -p RSAEncryption
而SN
命令是 Helpers.SN
的语法糖,可从如下API文档查看更详细用法
初始化
const { Alipay, Rsa } = require('whats-alipay');
//应用app_id
const app_id = '2014072300007148';
//商户RSA私钥,入参是'从官方工具获取到的BASE64字符串'
const privateKey = Rsa.fromPkcs1('MIIEpAIBAAKCAQEApdXuft3as2x...');
// 以上是下列代码的语法糖,格式为 'private.pkcs1://' + '从官方工具获取到的字符串'
// const privateKey = Rsa.from('private.pkcs1://MIIEpAIBAAKCAQEApdXuft3as2x...');
// Node10仅支持以下方式,须保证`private_key.pem`为完整X509格式
// const privateKey = require('fs').readFileSync('/your/openapi/private_key.pem');
//支付宝RSA公钥,入参是'从官方工具获取到的BASE64字符串'
const publicKey = Rsa.fromSpki('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCg...');
// 以上是下列代码的语法糖,格式为 'public.spki://' + '从官方工具获取到的字符串'
// const publicKey = Rsa.from('public.spki://MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCg...');
// Node10仅支持以下方式,须保证`public_key.pem`为完整X509格式
// const publicKey = require('fs').readFileSync('/the/alipay/public_key.pem');
const whats = new Alipay({ privateKey, publicKey, params: { app_id, } });
统一收单线下交易查询
whats
.alipay.trade.query({out_trade_no})
.then(({headers,data}) => ({headers,data}))
.catch(({response: {data}}) => data)
.then(console.log)
{
headers: {
server: 'Tengine/2.1.0',
date: 'Fri, 25 Sep 2020 03:27:32 GMT',
'content-type': 'text/html;charset=utf-8',
'content-length': '829',
connection: 'close',
'set-cookie': [
'JSESSIONID=6FF6F65BB1EC785992117C95EA7C27BB; Path=/; HttpOnly',
'spanner=ubOmkyHGnYLtouOsffShT4n70pJgSji6Xt2T4qEYgj0=;path=/;secure;'
],
via: 'spanner-internet-5396.sa127[200]',
'x-alipay-responder': 'alipay.trade.query',
'x-alipay-signature': 'gALtsKSWEWRG4wSx8==',
'x-alipay-verified': 'ok'
},
data: {
code: '10000',
msg: 'Success',
buyer_logon_id: '134******38',
buyer_pay_amount: '183.00',
buyer_user_id: '2088101117955611',
fund_bill_list: [ { amount: '183.00', fund_channel: 'ALIPAYACCOUNT' } ],
invoice_amount: '183.00',
out_trade_no: '6823789339978248',
point_amount: '0.00',
receipt_amount: '183.00',
send_pay_date: '2020-09-19 17:00:28',
total_amount: '183.00',
trade_no: '2013112011001004330000121536',
trade_status: 'TRADE_SUCCESS'
}
}
统一收单交易支付接口
whats
.alipay.trade.pay({
out_trade_no,
scene,
auth_code,
product_code,
subject,
total_amount,
})
.then(({data}) => data)
.catch(({response: {data}}) => data)
.then(console.log)
统一收单线下交易预创建
whats
.alipay.trade.precreate({
out_trade_no,
subject,
total_amount,
})
.then(({data}) => data)
.catch(({response: {data}}) => data)
.then(console.log)
手机网站支付接口2.0
whats
.alipay.trade.wap.pay({
out_trade_no,
subject,
total_amount,
product_code,
quit_url,
}, {}, Formatter.page)
.then(res => res)
.then(console.log)
- 注: 特别地
res
结构做了优化,直接支持literal
(独立服务模式:${res}
) 或者JSON.stringify
(二次接口模式:JSON.stringify(res)
)
统一收单下单并支付页面接口
whats
.alipay.trade.page.pay({
out_trade_no,
subject,
total_amount,
product_code,
}, {return_url}, Formatter.page)
.then(res => res)
.then(console.log)
- 注: 特别地
res
结构做了优化,直接支持literal
(独立服务模式:${res}
) 或者JSON.stringify
(二次接口模式:JSON.stringify(res)
)
订单咨询服务
whats
.alipay.trade.advance.consult({/*文档上的参数就好*/})
.then(({data}) => data)
.catch(({response: {data}}) => data)
.then(console.log)
口碑营销活动列表查询
whats
.koubei.marketing.campaign.activity.batchquery(/*文档上的参数就好*/})
.then(({data}) => data)
.catch(({response: {data}}) => data)
.then(console.log)
支付API > 图片上传
const { Multipart } = require('whats-alipay');
const form = new Multipart();
form.append(
'image_content',
require('fs').readFileSync('/path/for/uploading.jpg'),
'uploading.jpg'
);
whats
.ant.merchant.expand.indirect.image.upload(
form.getBuffer(),
{image_type: 'jpg'},
{...form.getHeaders()}
)
.then(({data}) => data)
.catch(({response: {data}}) => data)
.then(console.log)
店铺API > 上传门店照片和视频接口
const { Multipart } = require('whats-alipay');
const form = new Multipart();
form.append(
'image_content',
require('fs').readFileSync('/path/for/uploading.jpg'),
'uploading.jpg'
);
whats
.alipay.offline.material.image.upload(
form.getBuffer(),
{image_type: 'jpg', image_name: 'uploading.jpg'},
{...form.getHeaders()}
)
.then(({data}) => data)
.catch(({response: {data}}) => data)
.then(console.log)
芝麻信用API > 授权查询
whats
.zhima.auth.info.authquery(/*文档上的参数就好*/})
.then(({data}) => data)
.catch(({response: {data}}) => data)
.then(console.log)
车主服务API > 车辆驶入接口
whats
.alipay.eco.mycar.parking.enterinfo.sync(/*文档上的参数就好*/})
.then(({data}) => data)
.catch(({response: {data}}) => data)
.then(console.log)
资金API > 单笔转账接口
whats
.alipay.fund.trans.uni.transfer(/*文档上的参数就好*/})
.then(({data}) => data)
.catch(({response: {data}}) => data)
.then(console.log)
何以弹性扩容
whats
上绑定多少 method
,即扩容至多少,以上示例打印如下:
console.info(whats)
[Function (anonymous)] {
ant: [Function: ant] {
merchant: [Function: ant.merchant] {
expand: [Function: ant.merchant.expand] {
indirect: [Function: ant.merchant.expand.indirect] {
image: [Function: ant.merchant.expand.indirect.image] {
upload: [Function: ant.merchant.expand.indirect.image.upload]
}
}
}
}
},
alipay: [Function: alipay] {
trade: [Function: alipay.trade] {
query: [Function: alipay.trade.query],
precreate: [Function: alipay.trade.precreate],
advance: [Function: alipay.trade.advance] {
consult: [Function: alipay.trade.advance.consult]
},
page: [Function: alipay.trade.page] {
pay: [Function: alipay.trade.page.pay]
},
wap: [Function: alipay.trade.wap] {
pay: [Function: alipay.trade.wap.pay]
}
},
fund: [Function: alipay.fund] {
trans: [Function: alipay.fund.trans] {
uni: [Function: alipay.fund.trans.uni] {
transfer: [Function: alipay.fund.trans.uni.transfer]
}
}
},
eco: [Function: alipay.eco] {
mycar: [Function: alipay.eco.mycar] {
parking: [Function: alipay.eco.mycar.parking] {
enterinfo: [Function: alipay.eco.mycar.parking.enterinfo] {
sync: [Function: alipay.eco.mycar.parking.enterinfo.sync]
}
}
}
},
offline: [Function: alipay.offline] {
material: [Function: alipay.offline.material] {
image: [Function: alipay.offline.material.image] {
upload: [Function: alipay.offline.material.image.upload]
}
}
}
},
zhima: [Function: zhima] {
auth: [Function: zhima.auth] {
info: [Function: zhima.auth.info] {
authquery: [Function: zhima.auth.info.authquery]
}
}
},
koubei: [Function: koubei] {
marketing: [Function: koubei.marketing] {
campaign: [Function: koubei.marketing.campaign] {
activity: [Function: koubei.marketing.campaign.activity] {
batchquery: [Function: koubei.marketing.campaign.activity.batchquery]
}
}
}
}
}
异步通知消息验签
通知消息验签,依赖相关 webserver
提供的 POST
数据解析能力,以下函数在 http.createServer
上做过验证,仅供参考。
function notificationValidator(things, publicCert, withoutSignType = true) {
const {groups: {sign}} = (typeof things === 'string' ? things : '').match(/\bsign=(?<sign>[^&]+)/) || {groups: {}}
const source = [...new URLSearchParams(things)].reduce((des, [key, value]) => (des[key] = value, des), {})
const signType = source['sign_type']
const signature = (source.sign || '').replace(/ /g, '+').replace(/_/g, '/')
// The signature with `sign_type` is only for the notifications whose come from `alipay.open.public.*` APIs.
if (withoutSignType) {
delete source['sign_type']
}
return Rsa.verify(Formatter.queryStringLike(Formatter.ksort(source)), sign || signature, publicCert, signType)
}
单元测试
npm install && npm test
To disable nock
and request with the real gateway, just NOCK_OFF=true npm test