TheNorthMemory / whats-alipay

支付宝 Alipay OpenAPI SDK
MIT License
15 stars 2 forks source link
aes-cbc alipay alipay-openapi axios-ecosystem chainable-openapi locale-date-time rsa-sha1 rsa-sha256

支付宝 OpenAPI SDK

Yet Another Alipay OpenAPI Smart Development Kit

GitHub actions GitHub issues GitHub release Vulnerabilities NPM module version NPM module downloads per month NPM module license

主要功能

SDK约定

使用手册

whatsCli 命令行工具

以命令行的形式,与OpenAPI网关交互,play the OpenAPI requests over command line.

$ ./node_modules/.bin/whatsCli -h (click to toggle display) ``` 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 命令行工具

$ ./node_modules/.bin/certHelper -h (click to toggle display) ``` 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)
$ console.log sample result (click to toggle display) ```javascript { 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)

统一收单下单并支付页面接口

whats
  .alipay.trade.page.pay({
    out_trade_no,
    subject,
    total_amount,
    product_code,
  }, {return_url}, Formatter.page)
  .then(res => res)
  .then(console.log)

订单咨询服务

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 sample result (click to toggle display) ```javascript 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

链接

MIT