Giter Club home page Giter Club logo

wechatpay-axios-plugin's Introduction

微信支付 OpenAPI SDK

Promise based and chained WeChatPay OpenAPI client SDK for NodeJS

GitHub actions GitHub release Vulnerabilities types Node NPM downloads per month NPM license

主要功能

  • 使用Node原生crypto实现微信支付APIv3的AES加/解密功能(aes-256-gcm with aad)
  • 使用Node原生crypto实现微信支付APIv3的RSA加/解密、签名、验签功能(sha256WithRSAEncryption with RSA_PKCS1_OAEP_PADDING)
  • 支持微信支付APIv3的HTTP GET/POST/PUT/PATCH/DELETE多方法链式操作,依赖 Axios, 示例代码如下
  • 支持微信支付APIv3的媒体文件上传(图片/视频)功能,由内置 Multipart 类驱动,示例代码如下
  • 支持微信支付APIv3的平台证书下载功能,需手动安装 yargs, 使用手册如下
  • 支持微信支付APIv3的帐单下载及解析功能,示例代码如下
  • 支持微信支付APIv2 & APIv3面向对象编程模式,示例代码如下
  • 支持 Typescript
  • 支持微信支付XML风格的接口(通常所说v2)调用,依赖 node-xml2js, 示例代码如下
  • 支持微信支付APIv2版的 AES-256-ECB/PKCS7PADDING 通知消息加/解密
  • 微信支付APIv2 & APIv3 与微信交互的各种数据签名用法示例
  • 支持 企业微信-企业支付-企业红包/向员工付款 功能,示例用法及代码如下

系统要求

NodeJs >= 10.15.0

安装

$ npm install wechatpay-axios-plugin

起步

v3平台证书

微信支付APIv3使用 (RESTful API with JSON over HTTP)接口设计,数据交换采用非对称(RSA-OAEP)加/解密方案。 API上行所需的商户API私钥,可以由商户官方专用证书生成工具生成, API下行所需的平台证书须从v3/certificates接口获取(应答证书还经过了对称AES-GCM加密,须采用APIv3密钥才能解密)。 本项目也提供了命令行下载工具,使用手册如下:

$ ./node_modules/.bin/wxpay crt --help (点击显示)
wxpay crt

The WeChatPay APIv3's Certificate Downloader

cert
  -m, --mchid       The merchant's ID, aka mchid.  [string] [required]
  -s, --serialno    The serial number of the merchant's certificate aka serialno.  [string] [required]
  -f, --privatekey  The path of the merchant's private key certificate aka privatekey.  [string] [required]
  -k, --key         The secret key string of the merchant's APIv3 aka key.  [string] [required]
  -o, --output      Path to output the downloaded WeChatPay's platform certificate(s)  [string] [default: "/tmp"]

Options:
      --version  Show version number  [boolean]
      --help     Show help  [boolean]
  -u, --baseURL  The baseURL  [string] [default: "https://api.mch.weixin.qq.com/"]

注: 像其他通用命令行工具一样,--help 均会打印出帮助手册,说明档里的[required]指 必选参数; [string]指 字符串类型,[default]指默认值

$ ./node_modules/.bin/wxpay crt -m N -s S -f F.pem -k K -o .
The WeChatPay Platform Certificate#0
  serial=HEXADECIAL
  notBefore=Wed, 22 Apr 2020 01:43:19 GMT
  notAfter=Mon, 21 Apr 2025 01:43:19 GMT
  Saved to: wechatpay_HEXADECIAL.pem
You may confirm the above infos again even if this library already did(by Rsa.verify):
    openssl x509 -in wechatpay_HEXADECIAL.pem -noout -serial -dates

注: 提供必选参数且运行后,屏幕即打印出如上信息,提示证书序列号起、止格林威治(GMT)时间及证书下载保存位置。

Important

当下载证书后,屏显有几条证书信息,就在应用中配置certs几条,尤其是在新旧平台证书交替灰度时,需要把新旧证书都配上,应用才不会出现事故。

命令行请求

命令行工具可用来做快速接入体验,用法如下:

帮助信息

$ ./node_modules/.bin/wxpay req --help
wxpay req <uri>

Play the WeChatPay OpenAPI requests over command line

<uri>
  -c, --config   The configuration  [required]
  -b, --binary   True for the `arraybuffer` response, two for without-verifier-response, otherwise for showing the origin
  -m, --method   The request HTTP verb  [choices: "DELETE", "GET", "POST", "PUT", "PATCH", "delete", "get", "post", "put", "patch"] [default: "POST"]
  -h, --headers  The request HTTP header(s)
  -d, --data     The request HTTP body
  -p, --params   The request HTTP query parameter(s)

Options:
      --version  Show version number  [boolean]
      --help     Show help  [boolean]
  -u, --baseURL  The baseURL  [string] [default: "https://api.mch.weixin.qq.com/"]

v3版Native付

$ ./node_modules/.bin/wxpay v3.pay.transactions.native
./node_modules/.bin/wxpay v3.pay.transactions.native \
  -c.mchid 1230000109 \
  -c.serial MCHSERIAL \
  -c.privateKey /path/your/merchant/mchid.key \
  -c.certs.PLATSERIAL /path/the/platform/certificates/HEXADECIAL.pem \
  -d.appid wxd678efh567hg6787 \
  -d.mchid 1230000109 \
  -d.description 'Image形象店-深圳腾大-QQ公仔' \
  -d.out_trade_no '1217752501201407033233368018' \
  -d.notify_url 'https://www.weixin.qq.com/wxpay/pay.php' \
  -d.amount.total 100 \
  -d.amount.currency CNY

v2版付款码付

$ ./node_modules/.bin/wxpay v2.pay.micropay
./node_modules/.bin/wxpay v2.pay.micropay \
  -c.mchid 1230000109 \
  -c.serial nop \
  -c.privateKey any \
  -c.certs.any \
  -c.secret your_merchant_secret_key_string \
  -d.appid wxd678efh567hg6787 \
  -d.mch_id 1230000109 \
  -d.device_info 013467007045764 \
  -d.nonce_str 5K8264ILTKCH16CQ2502SI8ZNMTM67VS \
  -d.detail 'Image形象店-深圳腾大-QQ公仔' \
  -d.spbill_create_ip 8.8.8.8 \
  -d.out_trade_no '1217752501201407033233368018' \
  -d.total_fee 100 \
  -d.fee_type CNY \
  -d.auth_code 120061098828009406

v2版付款码查询openid

$ ./node_modules/.bin/wxpay v2/tools/authcodetoopenid
./node_modules/.bin/wxpay v2/tools/authcodetoopenid \
  -c.mchid 1230000109 \
  -c.serial nop \
  -c.privateKey any \
  -c.certs.any \
  -c.secret your_merchant_secret_key_string \
  -d.appid wxd678efh567hg6787 \
  -d.mch_id 1230000109 \
  -d.nonce_str 5K8264ILTKCH16CQ2502SI8ZNMTM67VS \
  -d.auth_code 120061098828009406

面向对象模式

本类库是把 URL.pathname/做切分,取出 segments 映射成实例对象属性,同时支持APIv2的实例对象属性映射,编码书写方式有如下约定:

  1. 请求 segments 按照顺序作为级联对象,例如 v3/pay/transactions/native 即链接成 v3.pay.transactions.native;
  2. 每个 segments 所支持的 HTTP METHOD,即作为 请求对象的末尾执行方法,例如: v3.pay.transactions.native.post({});
  3. 每个 segments 级联对象默认为HTTPPOST方法,其同时隐式内置GET/POST/PUT/PATCH/DELETE 方法链,小写verb格式,说明见变更历史;
  4. 每个 segments 有中线(dash)分隔符的,可以使用驼峰camelCase风格书写,例如: merchant-service可写成 merchantService,或者字面量属性,如 v3['merchant-service'];
  5. 每个 segments 中,若有动态参数,例如 business_code/{business_code} 可写成 business_code.$business_code$ 或者字面量属性风格,如 business_code['{business_code}'];
  6. 如果 segmentsv2 开始,其特殊标识为APIv2级联对象开始位,之后串接其他segments,如源 pay/micropay 即串接成 v2.pay.micropay 即以XML形式请求远端接口;
  7. 建议 segments 按照 PascalCase 风格书写, TS Definition 已在路上(还有若干问题没解决),将是这种风格,代码提示将会很自然;

以下示例用法,均以PromiseAsync/Await结合此种编码模式展开。

初始化

const { Wechatpay } = require('wechatpay-axios-plugin');
const { readFileSync } = require('fs');

// 商户号,支持「普通商户/特约商户」或「服务商商户」
const merchantId = '190000****';

// 「商户API证书」的「证书序列号」
const merchantCertificateSerial = '3775B6A45ACD588826D15E583A95F5DD********';

// 从本地文件中加载「商户API私钥」
const merchantPrivateKeyFilePath = '/path/to/merchant/apiclient_key.pem';
const merchantPrivateKeyInstance = readFileSync(merchantPrivateKeyFilePath);

// 「微信支付平台证书」的「证书序列号」,下载器下载后有提示`serial`序列号字段
const platformCertificateSerial = '7132d72a03e93cddf8c03bbd1f37eedf********';

// 从本地文件中加载「微信支付平台证书」,用来验证微信支付请求响应体的签名
const platformCertificateFilePath = '/path/to/wechatpay/cert.pem';
const platformCertificateInstance = readFileSync(platformCertificateFilePath);

const wxpay = new Wechatpay({
  mchid: merchantId,
  serial: merchantCertificateSerial,
  privateKey: merchantPrivateKeyInstance,
  certs: {
    // 这里设计成Key/Value结构,是为了支持多「微信支付平台证书」
    // 尤其是在「新旧平台证书交替灰度时」需要把新旧证书都配上。
    [platformCertificateSerial]: platformCertificateInstance,
  },
  // 使用APIv2时,需要至少设置 `secret`字段,示例代码未开启
  // APIv2密钥(32字节)
  // secret: 'your_merchant_secret_key_string',
  // // 接口不要求证书情形,例如仅收款merchant对象参数可选
  // merchant: {
  //   cert: readFileSync('/path/to/merchant/apiclient_cert.pem'),
  //   key: merchantPrivateKeyInstance,
  //   // {cert,key}或者{passphrase,pfx}格式,两组组合任选其一
  //   // passphrase: 'your_merchant_id',
  //   // pfx: fs.readFileSync('/your/merchant/cert/apiclient_cert.p12'),
  // },
});

注: 证书序列号(「商户证书」序列号及「平台证书」序列号)均可用OpenSSL命令获取到,例如: openssl x509 -in /path/to/merchant/apiclient_cert.pem -noout -serial | awk -F= '{print $2}'

初始化字典说明如下:

  • mchid 为你的商户号,一般是10字节纯数字
  • serial 为你的商户证书序列号,一般是40字节字符串
  • privateKey 为你的商户API私钥,一般是通过官方证书生成工具生成的文件名是apiclient_key.pem文件,支持纯字符串或者文件流buffer格式
  • certs{[serial_number]:string} 为通过下载工具下载的平台证书key/value键值对,键为平台证书序列号,值为平台证书pem格式的纯字符串或者文件流buffer格式
  • secret 为APIv2版的密钥,商户平台上设置的32字节字符串
  • merchant.cert 为你的商户证书,一般是文件名为apiclient_cert.pem文件,支持纯字符串或者文件流buffer格式
  • merchant.key 为你的商户API私钥,一般是通过官方证书生成工具生成的文件名是apiclient_key.pem文件,支持纯字符串或者文件流buffer格式
  • merchant.passphrase 一般为你的商户号
  • merchant.pfx 为你的商户PKCS12格式的证书,文件名一般为apiclient_cert.p12,支持二进制文件流buffer格式

注: APIv2&APIv3以及Axios初始参数,均融合在一个型参上,APIv2已不推荐使用,推荐优先使用APIv3。

APIv3

Native下单

wxpay.v3.pay.transactions.native
  .post({
    mchid: '1900006XXX',
    out_trade_no: 'native12177525012014070332333',
    appid: 'wxdace645e0bc2cXXX',
    description: 'Image形象店-深圳腾大-QQ公仔',
    notify_url: 'https://weixin.qq.com/',
    amount: {
      total: 1,
      currency: 'CNY'
    },
  })
  .then(({data: {code_url}}) => console.info(code_url))
  .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

查询订单

wxpay.v3.pay.transactions.id._transaction_id_ // _placeholder_ 语法糖会转换成 '{placeholder}' 格式
  .get({
    params: {
      mchid: '1230000109'
    },
    transaction_id: '1217752501201407033233368018'
  })
  .then(({data}) => console.info(data))
  .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

关闭订单

wxpay.v3.pay.transactions.outTradeNo.$out_trade_no$.close // $placeholder$ 语法糖会转换成 '{placeholder}' 格式
  .post({
    mchid: '1230000109'
  }, {
    out_trade_no: 'P1217752501201407033233368018' //当商户订单号有大写字符时,只能这样参数化传递
  })
  .then(({status, statusText}) => console.info(status, statusText))
  .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

合单支付下单

wxpay.v3.combineTransactions.jsapi
  .post({/*文档参数放这里就好*/})
  .then(res => console.info(res.data))
  .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

H5下单

wxpay.v3.pay.transactions.h5
  .post({/*文档参数放这里就好*/})
  .then(({data: {h5_url}}) => console.info(h5_url))
  .catch(console.error)

对账单下载及解析

const assert = require('assert')
const {Hash: {sha1}} = require('wechatpay-axios-plugin')

wxpay.v3.bill.tradebill.get({
  params: {
    bill_date: '2021-02-12',
    bill_type: 'ALL',
  }
}).then(({data: {download_url, hash_value}}) => wxpay.v3.billdownload.file.get({
  params: (new URL(download_url)).searchParams,
  responseType: 'arraybuffer', // To prevent the axios:utils.stripBOM feature
  transformResponse: [function csvDigestValidator(data) {
    assert(sha1(data) === hash_value, 'verify the SHA1 digest failed.')
    return data
  }, function csvCastor(data) { return Formatter.castCsvBill(data) }]
})).then(res => {
  console.info(res.data.summary)
}).catch(error => {
  console.error(error)
})

创建商家券

wxpay.v3.marketing.busifavor.stocks
  .post({/*商家券创建条件*/})
  .then(({data}) => console.info(data))
  .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

查询用户单张券详情

;(async () => {
  try {
    const {data: detail} = await wxpay.v3.marketing.busifavor
      .users.$openid$.coupons['{coupon_code}'].appids['wx233544546545989']
      .get({openid: '2323dfsdf342342', coupon_code: '123446565767'})
    console.info(detail)
  } catch({response: {status, statusText, data}}) {
    console.error(status, statusText, data)
  }
})()

服务商模式Native下单

;(async () => {
  try {
    const res = await wxpay.v3.pay.partner.transactions.native({
      sp_appid,
      sp_mchid,
      sub_mchid,
      description,
      out_trade_no,
      time_expire: new Date( (+new Date) + 33*60*1000 ), //after 33 minutes
      attach,
      notify_url,
      amount: {
        total: 1,
      }
    })
    console.info(res.data.code_url)
  } catch (error) {
    console.error(error)
  }
})()

支付即服务

;(async () => {
  try {
    const {status, statusText} = await wxpay.v3.smartguide.guides.$guide_id$.assign
      .post({sub_mchid, out_trade_no}, {guide_id})
    console.info(status, statusText)
  } catch({response: {status, statusText, data}}) {
    console.error(status, statusText, data)
  }
})()

商家转账到零钱

const {Rsa} = require('wechatpay-axios-plugin');

;(async () => {
  try {
    const res = await wxpay.v3.transfer.batches.post({
      appid: 'wxf636efh567hg4356',
      out_batch_no: 'plfk2020042013',
      batch_name: '2019年1月深圳分部报销单',
      batch_remark: '2019年1月深圳分部报销单',
      total_amount: 4000000,
      total_num: 200,
      transfer_detail_list: [
        {
          out_detail_no: 'x23zy545Bd5436',
          transfer_amount: 200000,
          transfer_remark: '2020年4月报销',
          openid: 'o-MYE42l80oelYMDE34nYD456Xoy',
          user_name: Rsa.encrypt('张三', platformCertificateInstance),
        }
      ],
      transfer_scene_id: '1001',
    }, {
      headers: {
        'Wechatpay-Serial' => platformCertificateSerial,
      },
    });
  } catch({response: {status, statusText, data}}) {
    console.error(status, statusText, data)
  }
})()

商业投诉查询

;(async () => {
  try {
    const res = await wxpay.v3.merchantService.complaintsV2.get({
      params: {
        limit      : 5,
        offset     : 0,
        begin_date : '2020-03-07',
        end_date   : '2020-03-14',
      }
    })
    console.info(res.data)
  } catch (error) {
    console.error(error)
  }
})()

图片上传

const { Multipart } = require('wechatpay-axios-plugin')
const {createReadStream} = require('fs')

const imageMeta = {
  filename: 'hellowechatpay.png',
  sha256: '1a47b1eb40f501457eaeafb1b1417edaddfbe7a4a8f9decec2d330d1b4477fbe',
}

const imageData = new Multipart()
imageData.append('meta', JSON.stringify(imageMeta), 'meta.json')
imageData.append('file', createReadStream('./hellowechatpay.png'), 'hellowechatpay.png')

;(async () => {
  try {
    const res = await wxpay.v3.marketing.favor.media.imageUpload.post(imageData, {
      meta: imageMeta,
      headers: imageData.getHeaders()
    })
    console.info(res.data.media_url)
  } catch (error) {
    console.error(error)
  }
})()

查询优惠券详情

;(async () => {
  try {
    const res = await wxpay.v3.marketing.favor.stocks.$stock_id$.get({
      params: {
        stock_creator_mchid,
      },
      stock_id,
    })
    console.info(res.data)
  } catch(error) {
    console.error(error)
  }
})()

优惠券委托营销

(async () => {
  try {
    const res = await wxpay.v3.marketing.partnerships.build.post({
      partner: {
        type,
        appid
      },
      authorized_data: {
        business_type,
        stock_id
      }
    }, {
      headers: {
        'Idempotency-Key': 12345
      }
    })
    console.info(res.data)
  } catch (error) {
    console.error(error)
  }
})()

优惠券核销记录下载

(async () => {
  try {
    let res = await wxpay.v3.marketing.favor.stocks.$stock_id$.useFlow.get({stock_id})
    res = await wxpay.v3.billdownload.file.get({
      params: (new URL(res.data.url)).searchParams,
      responseType: 'arraybuffer', // To prevent the axios:utils.stripBOM feature
      transformResponse: [function csvDigestValidator(data) {
        assert(sha1(data) === res.data.hash_value, 'verify the SHA1 digest failed.')
        return data
      }]
    })
    // 备注:此接口下载的文件格式与商户平台下载的不完全一致,Formatter.castCsvBill解析有差异
    console.info(res.data.toString())
  } catch (error) {
    console.error(error)
  }
})()

视频文件上传

const { Multipart } = require('wechatpay-axios-plugin')
const {createReadStream} = require('fs')

const videoMeta = {
  filename: 'hellowechatpay.mp4',
  sha256: '1a47b1eb40f501457eaeafb1b1417edaddfbe7a4a8f9decec2d330d1b4477fbe',
}

const videoData = new Multipart()
videoData.append('meta', JSON.stringify(videoMeta))
videoData.append('file', createReadStream('./hellowechatpay.mp4'), 'hellowechatpay.mp4')

;(async () => {
  try {
    const res = await wxpay.v3.merchant.media.video_upload.post(videoData, {
      meta: videoMeta,
      headers: videoData.getHeaders()
    })
    console.info(res.data.media_id)
  } catch (error) {
    console.error(error)
  }
})()

GZIP下载资金账单

const {unzipSync} = require('zlib')
const assert = require('assert')
const {Hash: {sha1}} = require('wechatpay-axios-plugin')

;(async () => {
  try {
    const {data: {download_url, hash_value}} = await wxpay.v3.bill.fundflowbill.get({
      params: {
        bill_date: '2020-02-12',
        bill_type: 'BASIC',
        tar_type: 'GZIP',
      }
    })
    const {data} = await wxpay.v3.billdownload.file.get({
      params: (new URL(download_url)).searchParams,
      responseType: 'arraybuffer', // To prevent the axios:utils.stripBOM feature
      transformResponse: [function csvDigestValidator(data) {
        // note here: previous `hash_value` was about the source `csv`, not the `gzip` data
        //            so it needs unziped first, then to compare the `SHA1` degest
        const bill = unzipSync(data)
        assert.ok(hash_value === sha1(bill), 'SHA1 verification failed')
        return bill
      }, function csvCastor(data) { return Formatter.castCsvBill(data) }]
    })
    console.info(data.summary)
  } catch (error) {
    console.error(error)
  }
})()

APIv2

付款码(刷卡)支付

wxpay.v2.pay.micropay.post({
  appid: 'wx8888888888888888',
  mch_id: '1900000109',
  nonce_str: Formatter.nonce(),
  sign_type: 'HMAC-SHA256',
  body: 'image形象店-深圳腾大-QQ公仔',
  out_trade_no: '1217752501201407033233368018',
  total_fee: '888',
  fee_type: 'CNY',
  spbill_create_ip: '8.8.8.8',
  auth_code: '120061098828009406',
})
.then(res => console.info(res.data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

H5支付

wxpay.v2.pay.unifiedorder.post({
  appid: 'wx2421b1c4370ec43b',
  attach: '支付测试',
  body: 'H5支付测试',
  mch_id: '10000100',
  nonce_str: Formatter.nonce(),
  notify_url: 'http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php',
  openid: 'oUpF8uMuAJO_M2pxb1Q9zNjWeS6o',
  out_trade_no: '1415659990',
  spbill_create_ip: '14.23.150.211',
  total_fee: '1',
  trade_type: 'MWEB',
  scene_info: JSON.stringify({
    h5_info: {
      type:"IOS",
      app_name: "王者荣耀",
      package_name: "com.tencent.tmgp.sgame"
    }
  }),
}).then(({data: {mweb_url}}) => console.info(mweb_url)).catch(console.error);

申请退款

wxpay.v2.secapi.pay.refund.post({
  appid: 'wx8888888888888888',
  mch_id: '1900000109',
  out_trade_no: '1217752501201407033233368018',
  out_refund_no: '1217752501201407033233368018',
  total_fee: '100',
  refund_fee: '100',
  refund_fee_type: 'CNY',
  nonce_str: Formatter.nonce(),
})
.then(res => console.info(res.data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

现金红包

wxpay.v2.mmpaymkttransfers.sendredpack.post({
  nonce_str: Formatter.nonce(),
  mch_billno: '10000098201411111234567890',
  mch_id: '10000098',
  wxappid: 'wx8888888888888888',
  send_name: '鹅企支付',
  re_openid: 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
  total_amount: '1000',
  total_num: '1',
  wishing: 'HAPPY BIRTHDAY',
  client_ip: '192.168.0.1',
  act_name: '回馈活动',
  remark: '会员回馈活动',
  scene_id: 'PRODUCT_4',
})
.then(res => console.info(res.data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

企业付款到零钱

wxpay.v2.mmpaymkttransfers.promotion.transfers.post({
  mch_appid: 'wx8888888888888888',
  mchid: '1900000109',// 注意这个商户号,key是`mchid`非`mch_id`
  partner_trade_no: '10000098201411111234567890',
  openid: 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
  check_name: 'FORCE_CHECK',
  re_user_name: '王小王',
  amount: '10099',
  desc: '理赔',
  spbill_create_ip: '192.168.0.1',
  nonce_str: Formatter.nonce(),
}, {
  // 返回值无`sign`字段,无需数据校验
  transformResponse: [Transformer.toObject],
})
.then(res => console.info(res.data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

企业付款到银行卡-获取RSA公钥

wxpay.v2.risk.getpublickey.post({
  mch_id: '1900000109',
  sign_type: 'MD5',
  nonce_str: Formatter.nonce(),
}, {
  baseURL: 'https://fraud.mch.weixin.qq.com/',
  // 返回值无`sign`字段,无需数据校验
  transformResponse: [Transformer.toObject],
})
.then(res => console.info(res.data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

下载交易账单

wxpay.v2.pay.downloadbill.post({
  mch_id,
  nonce_str: fmt.nonce(),
  appid,
  bill_date,
  bill_type,
}, {
  responseType: 'arraybuffer', // To prevent the axios:utils.stripBOM feature
  transformResponse: [function detector(data) {
    // 无账单时返回值为`xml`,抛到异常`catch`处理
    assert.notDeepStrictEqual(data.slice(0, 5), Buffer.from('<xml>'), data.toString())
    return data
  }, function csvCastor(data) { return Formatter.castCsvBill(data) }]
})
.then(res => console.info(res.data.summary))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))

企业微信

企业微信的企业支付,数据请求包需要额外的签名,仅需做如下简单扩展适配,即可支持;以下签名注入函数所需的两个参数agentId agentSecret来自企业微信工作台,以下为示例值。

const agentId = 1001001
const agentSecret = 'from_wework_agent_special_string'
const {Hash} = require('wechatpay-axios-plugin')

企业红包-注入签名规则

Wechatpay.client.v2.defaults.transformRequest.unshift(function workwxredpack(data, headers) {
  const {act_name, mch_billno, mch_id, nonce_str, re_openid, total_amount, wxappid} = data

  if (!(act_name && mch_billno && mch_id && nonce_str && re_openid && total_amount && wxappid)) {
    return data
  }

  data.workwx_sign = Hash.md5(
    Formatter.queryStringLike(Formatter.ksort({
      act_name, mch_billno, mch_id, nonce_str, re_openid, total_amount, wxappid
    })), agentSecret, agentId
  ).toUpperCase()

  return data
})

发放企业红包

wxpay.v2.mmpaymkttransfers.sendworkwxredpack.post({
  mch_billno: '123456',
  wxappid: 'wx8888888888888888',
  sender_name: 'XX活动',
  sender_header_media_id: '1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0',
  re_openid: 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
  total_amount: '1000',
  wishing: '感谢您参加猜灯谜活动,祝您元宵节快乐!',
  act_name: '猜灯谜抢红包活动',
  remark: '猜越多得越多,快来抢!',
  mch_id: '1900000109',
  nonce_str: Formatter.nonce(),
})
.then(res => console.info(res.data))
.catch(console.error)

向员工付款-注入签名规则

Wechatpay.client.v2.defaults.transformRequest.unshift(function wwsptrans2pocket(data, headers) {
  const {amount, appid, desc, mch_id, nonce_str, openid, partner_trade_no, ww_msg_type} = data

  if (!(amount && appid && desc && mch_id && nonce_str && openid && partner_trade_no && ww_msg_type)) {
    return data
  }

  data.workwx_sign = Hash.md5(
    Formatter.queryStringLike(Formatter.ksort({
      amount, appid, desc, mch_id, nonce_str, openid, partner_trade_no, ww_msg_type
    })), agentSecret, agentId
  ).toUpperCase()

  return data
})

向员工付款

wxpay.v2.mmpaymkttransfers.promotion.paywwsptrans2pocket.post({
  appid: 'wxe062425f740c8888',
  device_info: '013467007045764',
  partner_trade_no: '100000982017072019616',
  openid: 'ohO4Gt7wVPxIT1A9GjFaMYMiZY1s',
  check_name: 'NO_CHECK',
  re_user_name: '张三',
  amount: '100',
  desc: '六月出差报销费用',
  spbill_create_ip: '10.2.3.10',
  ww_msg_type: 'NORMAL_MSG',
  act_name: '示例项目',
  mch_id: '1900000109',
  nonce_str: Formatter.nonce(),
})
.then(res => console.info(res.data))
.catch(console.error)

自定义打印日志

// APIv2 日志
Wechatpay.client.v2.defaults.transformRequest.push(data => (console.log(data), data))
Wechatpay.client.v2.defaults.transformResponse.unshift(data => (console.log(data), data))
// APIv3 日志
Wechatpay.client.v3.defaults.transformRequest.push((data, headers) => (console.log(data, headers), data))
Wechatpay.client.v3.defaults.transformResponse.unshift((data, headers) => (console.log(data, headers), data))

获取RSA公钥

非标准接口地址,也可以这样调用

Wechatpay.client.v2.post('https://fraud.mch.weixin.qq.com/risk/getpublickey', {
  mch_id: '1900000109',
  nonce_str: Formatter.nonce(),
  sign_type: 'HMAC-SHA256',
}, {
  // 返回值无`sign`字段,无需数据校验
  transformResponse: [Transformer.toObject],
})
.then(({data}) => console.info(data))
.catch(({response}) => console.error(response))

XML形式通知应答

const {Transformer} = require('wechatpay-axios-plugin')
const xml = Transformer.toXml({
  return_code: 'SUCCESS',
  return_msg: 'OK',
})

console.info(xml)

aes-256-ecb/pcks7padding

解密

const {Aes: {AesEcb}, Transformer, Hash} = require('wechatpay-axios-plugin')
const secret = 'exposed_your_key_here_have_risks'
const xml = '<xml>' + ... '</xml>'
const obj = Transformer.toObject(xml)
const res = AesEcb.decrypt(obj.req_info, Hash.md5(secret))
obj.req_info = Transformer.toObject(res)
console.info(obj)

加密

const obj = Transformer.toObject(xml)
const ciphertext = AesEcb.encrypt(obj.req_info, Hash.md5(secret))
console.assert(
  obj.req_info === ciphertext,
  `The notify hash digest should be matched the local one`
)

APIv2数据签名

JSAPI

const {Hash, Formatter} = require('wechatpay-axios-plugin')
const v2Secret = 'exposed_your_key_here_have_risks'
const params = {
  appId: 'wx8888888888888888',
  timeStamp: `${Formatter.timestamp()}`,
  nonceStr: Formatter.nonce(),
  package: 'prepay_id=wx201410272009395522657a690389285100',
  signType: 'HMAC-SHA256',
}
params.paySign = Hash.sign(params.signType, params, v2Secret)

console.info(params)

APP

const {Hash, Formatter} = require('wechatpay-axios-plugin')
const v2Secret = 'exposed_your_key_here_have_risks'
const params = {
  appid: 'wx8888888888888888',
  partnerid: '1900000109',
  prepayid: 'WX1217752501201407033233368018',
  package: 'Sign=WXPay',
  timestamp: `${Formatter.timestamp()}`,
  noncestr: Formatter.nonce(),
}
params.sign = Hash.sign('MD5', params, v2Secret)

console.info(params)

APIv3数据签名

JSAPI

const {Rsa, Formatter} = require('wechatpay-axios-plugin')
const privateKey = require('fs').readFileSync('/your/merchant/priviate_key.pem')

const params = {
  appId: 'wx8888888888888888',
  timeStamp: `${Formatter.timestamp()}`,
  nonceStr: Formatter.nonce(),
  package: 'prepay_id=wx201410272009395522657a690389285100',
  signType: 'RSA',
}
params.paySign = Rsa.sign(Formatter.joinedByLineFeed(
  params.appId, params.timeStamp, params.nonceStr, params.package
), privateKey)

console.info(params)

商家券-小程序发券v2版签名规则

const {Hash, Formatter} = require('wechatpay-axios-plugin')
const v2Secret = 'exposed_your_key_here_have_risks'

// flat the miniprogram data transferring structure for sign
const busiFavorFlat = ({send_coupon_merchant, send_coupon_params = []} = {}) => {
  return {
    send_coupon_merchant,
    ...send_coupon_params.reduce((des, row, idx) => (
      Object.keys(row).map(one => des[`${one}${idx}`] = row[one]), des
    ), {}),
  }
}

// the miniprogram data transferring structure
const busiFavor = {
  send_coupon_params: [
    {out_request_no:'1234567',stock_id:'abc123'},
    {out_request_no:'7654321',stock_id:'321cba'},
  ],
  send_coupon_merchant: '10016226'
}

busiFavor.sign = Hash.sign('HMAC-SHA256', busiFavorFlat(busiFavor), v2Secret)

console.info(busiFavor)

商家券-H5发券v2版签名规则

const {Hash, Formatter} = require('wechatpay-axios-plugin')
const v2Secret = 'exposed_your_key_here_have_risks'
const params = {
  stock_id: '12111100000001',
  out_request_no: '20191204550002',
  send_coupon_merchant: '10016226',
  open_id: 'oVvBvwEurkeUJpBzX90-6MfCHbec',
  coupon_code: '75345199',
}
params.sign = Hash.sign('HMAC-SHA256', params, v2Secret)

console.info(params)

常见问题

Q: 平台证书下载工具,一直抛异常AssertionError [ERR_ASSERTION]: The response's Headers incomplete是为何?

命令行下的 -s参数,即:商户证书序列号参数给错了,就会抛上述异常,这个时候服务端其实返回的是401状态码,下一个版本会优化一下下载工具,对异常进行捕获,当前请校对你的商户证书序列号并且确保正确;

Q: APIv3消息通知,AES-256-GCM加密字段,应该如何解密?

官方文档有介绍,APIv3平台证书及消息通知关键信息均使用AesGcm加解密,依赖APIv3密钥,商户侧解密可参考bin/cli/cert.js证书下载工具,例如:

AesGcm.decrypt(nonce, secret, ciphertext, aad);

Q: 敏感信息或者幂等操作要求额外头信息上送时,应该如何构建请求参数?

DELETE/GET请求的第一个参数,POST/PUT/PATCH请求的第二个参数,是 AxiosRequestConfig 对象,可以按需上送额外头参数,例如:

wxpay.v3.applyment4sub.applyment.$noop$(
  {},
  { noop: '', headers: { 'Wechatpay-Serial': '123456' } },
).then(console.info).catch(console.error);

可参考 #17

Q: 接口地址为slash(/)结尾的,应该如何构建请求参数?

动态参数uri_template或者属性property方式构建,可参考 #16

单元测试

npm install && npm test

技术交流

如果遇到困难或建议可以 提ISSUE 或 加群,交流技术,分享经验。

QQ群: 684379275

链接

如果你觉得这个library不错,你可以扫如下赞赏码以资鼓励作者,博客更有部分"实战"内容,也可能对你的开发对接有所帮助。

许可证

MIT

wechatpay-axios-plugin's People

Contributors

ipoa avatar starrah avatar thenorthmemory avatar wptad avatar zhoulingfengofcd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

wechatpay-axios-plugin's Issues

支持h5支付吗

APPID、MCHID、自己设置的32位KEY, 我看php sdk有个版本,只需要上面那3个参数即可H5

初始化报错,求解答,谢谢

初始化的时候,报
ERROR 438408 nodejs.unhandledRejectionError: Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream
这是什么原因导致的,求助,谢谢

如何向请求的Http Header中添加信息?

”特约商户进件API“中要求

商户上送敏感信息时使用微信支付平台公钥加密,证书序列号包含在请求HTTP头部的Wechatpay-Serial,

请问可以从哪里进行这一步操作,向http header 中添加自定义的内容?
我在./lib/interceptor.js中找到的一部分代码

    config.headers = {
      ...config.headers,
      'User-Agent': utils.userAgent(),
      'Content-Type': `application/json`,
      Accept: `application/json`,
      // @see {fmt.authorization} APIv3 `Authorization` schema
      Authorization: fmt.authorization(mchid, nonce, signature, timestamp, serial),
    }

直接添加字段是可以的。

    config.headers = {
      ...config.headers,
      'User-Agent': utils.userAgent(),
      'Content-Type': `application/json`,
      Accept: `application/json`,
      // @see {fmt.authorization} APIv3 `Authorization` schema
      Authorization: fmt.authorization(mchid, nonce, signature, timestamp, serial),
      'wechatpay-serial': '1234567890',
    }

不过我如何在应用程序中做这个添加的动作?

        this.wxapi = new Wechatpay({
            mchid: '1270921201',
            serial: '623FFEC95EF4B583D2157DFE81E66FFA15DE88C3',
            certs: {
                '1C38E366EE3D40AF84E321A2B2A371B8CED57255': '-----BEGIN CERTIFICATE-----\n' + `...` + '\n-----END CERTIFICATE-----',
            },
            privateKey: '-----BEGIN PRIVATE KEY-----\n' + `...` + '\n-----END PRIVATE KEY-----',
        
        });

初始化的时候提供了平台证书,是否应该自己在需要的时候在interceptor.js中自行添加?
还是有其他的配置,可以在接口调用的时候通过配置来添加?
谢谢。

动态路径.get的入参的类型错误

版本:0.8.11

写如下代码:

this.wxpay.v3.transfer.batches.outBatchNo.$out_batch_no$.get({
    params: {
        mchid: '123456',
    },
    out_batch_no: params.out_batch_no,
})

得到TS提示:

Object literal may only specify known properties, and 'out_batch_no' does not exist in type 'AxiosRequestConfig<any>'.ts(2353)

cli tool

命令行交互工具集

play the openapi requests over command line

v2 刷卡支付验收用例3 沙箱测试报错

@TheNorthMemory

v2 刷卡支付验收用例3 沙箱测试报错, 请帮忙看看如何解决,谢谢!

用例说明: “用例3:【刷卡-正常】订单金额0.03元(含0.01元代金券和0.02元免充值现金券),用户支付成功 “
使用版本: 0.7.13
刷卡支付验收用例1和2 沙箱测试成功。 用例网址
我的代码:

const express = require("express");
const app = express();

const { Wechatpay, Formatter } = require("wechatpay-axios-plugin");
const { readFileSync } = require("fs");

const mchid = "16*********";
const appid = "wx*****************";
//已更换为沙箱密钥
const sandbox_signkey = "33*************************";
const mch_id = mchid;

// 商户号
const merchantId = "16*********";
// 商户证书序列号
const merchantCertificateSerial = "17*********";
// 商户私钥
const merchantPrivateKeyFilePath = "/home/test/apiclient_key.pem";
const merchantPrivateKeyInstance = readFileSync(merchantPrivateKeyFilePath);
// 平台证书
const platformCertificateFilePath =
  "/home/test/platformTools/wechat********.pem";
const platformCertificateInstance = readFileSync(platformCertificateFilePath);
// 平台证书序列号
const platformCertificateSerial = "26*********";
const wxpay = new Wechatpay({
  mchid: merchantId,
  serial: merchantCertificateSerial,
  privateKey: merchantPrivateKeyInstance,
  certs: { [platformCertificateSerial]: platformCertificateInstance },
  // APIv2密钥(32字节)
  secret: sandbox_signkey,
});

app.use(express.json());

app.post("/pay_test", async (req, res) => {
  let authcode = req.body.paycode;
  let totalfee = JSON.parse(req.body.payamount);
  // 模拟一个商户订单号
  let out_trade_no = `No${+new Date()}_100`;

  try {
    // 付款码沙箱用例:请求支付
    console.log("付款码沙箱用例:请求支付");
    console.log(
      (
        await wxpay.v2.sandboxnew.pay.micropay({
          appid,
          mch_id,
          nonce_str: Formatter.nonce(),
          out_trade_no,
          body: "sandbox_goods_test",
          total_fee: totalfee,
          spbill_create_ip: "127.0.0.1",
          auth_code: authcode,
        })
      ).data
    );
    // 付款码沙箱用例:获取支付结果
    console.log("付款码沙箱用例:获取支付结果");
    console.log(
      (
        await wxpay.v2.sandboxnew.pay.orderquery({
          appid,
          mch_id,
          nonce_str: Formatter.nonce(),
          out_trade_no,
        })
      ).data
    );
  } catch (error) {
    console.log("以下打印回调报错:");
    console.log(error);
  }

  res.send("remote server sandbox test done!");
});

const port = process.env.PORT || 8888;
app.listen(port, () => {
  console.log("Server listening on post", port);
});

请求支付成功,获取支付结果时报错信息:

付款码沙箱用例:请求支付(成功)
{
  coupon_fee: '3',
  cash_fee_type: 'CNY',
  nonce_str: 'k29n9z4bBZp20vIFr4TL96VLi2Hwgewm',
  time_end: '202109*********',
  sign: '******************',
  coupon_id_0: '10000',
  coupon_id_1: '10001',
  coupon_fee_0: '1',
  coupon_fee_1: '2',
  fee_type: 'CNY',
  attach: 'sandbox_attach',
  device_info: 'sandbox',
  out_trade_no: 'TestNo1632**********_10',
  transaction_id: '************************',
  openid: '****************',
  trade_type: 'MICROPAY',
  return_code: 'SUCCESS',
  err_code_des: 'ok',
  mch_id: '*******************',
  settlement_total_fee: '1',
  coupon_batch_id_1: '56789',
  coupon_batch_id_0: '12345',
  cash_fee: '0',
  is_subscribe: 'Y',
  return_msg: 'OK',
  bank_type: 'CMC',
  coupon_type_1: 'NO_CASH',
  coupon_type_0: 'CASH',
  total_fee: '3',
  appid: '*****************',
  coupon_count: '2',
  result_code: 'SUCCESS',
  err_code: 'SUCCESS'
}
付款码沙箱用例:获取支付结果
以下打印回调报错:
AssertionError [ERR_ASSERTION]: the response's sign(***F5477552*********) doesn't matched the local calculated(****69C84B**********)
    at Object.verifier (/home/test/pay-sandbox-server-0.713/node_modules/wechatpay-axios-plugin/lib/transformer.js:93:12)
    at transform (/home/test/pay-sandbox-server-0.713/node_modules/axios/lib/core/transformData.js:18:15)
    at Object.forEach (/home/test/pay-sandbox-server-0.713/node_modules/axios/lib/utils.js:245:10)
    at Object.transformData (/home/test/pay-sandbox-server-0.713/node_modules/axios/lib/core/transformData.js:17:9)
    at onAdapterResolution (/home/test/pay-sandbox-server-0.713/node_modules/axios/lib/core/dispatchRequest.js:57:35)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async /home/test/pay-sandbox-server-0.713/index.js:77:9 {
  generatedMessage: false,
  code: 'ERR_ASSERTION',
  actual: false,
  expected: true,
  operator: '=='
}

Originally posted by @qaoo8 in #35 (comment)

Interceptor函数的几个问题

  1. Interceptor是函数,不是类,首字母应小写
  2. 函数的参数secret未使用,应当删除
  3. 函数的参数默认值指定方式不正确,应当为:
const Interceptor = (axios, {
  mchid,
  serial,
  privateKey,
  publicCert,
  certs,
} = {
  mchid: '',
  serial: '',
  privateKey: '',
  publicCert: '',
  certs: null,
}) => {

jsapi 请求路径有误

插件v3/combine-transactions/jsapi 官网/v3/pay/transactions/jsapi,这种情况请求结果未处理。插件打印{"code":"SYSTEM_ERROR","message":"系统繁忙,请稍后重试"},AssertionError [ERR_ASSERTION]: It's allowed time offset in ± 5 minutes, the response was on undefined, your's localtime on 1623291910.

调试发给包的接口报以下的错误。

wxpay.v2.mmpaymkttransfers.sendredpack.post();

AssertionError [ERR_ASSERTION]: the response's sign(undefined) doesn't matched the local calculated(483441D8A1B327CFFDED84EC3DE510B8)
at Object.verifier (D:\wwwroot\haoyunqingdaojia\xiaochenxu\cloudfunctions\user\node_modules\wechatpay-axios-plugin\lib\transformer.js:93:12)
at transform (D:\wwwroot\haoyunqingdaojia\xiaochenxu\cloudfunctions\user\node_modules\axios\lib\core\transformData.js:18:15)
at Object.forEach (D:\wwwroot\haoyunqingdaojia\xiaochenxu\cloudfunctions\user\node_modules\axios\lib\utils.js:245:10)
at Object.transformData (D:\wwwroot\haoyunqingdaojia\xiaochenxu\cloudfunctions\user\node_modules\axios\lib\core\transformData.js:17:9)
at onAdapterResolution (D:\wwwroot\haoyunqingdaojia\xiaochenxu\cloudfunctions\user\node_modules\axios\lib\core\dispatchRequest.js:57:35)

v2付款接口无法使用的问题

事情是这样的:微信的向用户付款到零钱的接口,目前没有v3、只有v2,文档在这里
令人吐血的是,这个接口的商户号字段名称是"mchid",而非绝大多数接口使用的"mch_id"。
image

因此我的代码如下:

        const reqObj = {
            mch_appid: wxmpAppId,
            mchid: wxpayMchid,
            nonce_str: nanoid(32),
            partner_trade_no: dbObj._id,
            openid: user.openId,
            check_name: "NO_CHECK",
            amount: Math.round(amount * 100),
            desc: description
        }
        const resp = await wxpay.v2.mmpaymkttransfers.promotion.transfers.post(reqObj)

这样的代码产生如下报错:

"AssertionError [ERR_ASSERTION]: The data.mch_id(undefined) doesn't matched init one(*****)\n" +
    '    at signer (*****/node_modules/wechatpay-axios-plugin/lib/transformer.js:37:14)\n' +
    '    at transform (*****/node_modules/axios/lib/core/transformData.js:16:12)\n' +
    '    at Object.forEach (*****/node_modules/axios/lib/utils.js:247:10)\n' +
    '    at transformData (*****/node_modules/axios/lib/core/transformData.js:15:9)\n' +
    '    at dispatchRequest (*****/node_modules/axios/lib/core/dispatchRequest.js:30:17)\n' +
    ......

经过我分析,原因是:

static signer(data) {
const { sign_type: type = 'MD5', mch_id: mchid } = data;
if (Transformer.mchid) {
assert.ok(Transformer.mchid === mchid, `The data.mch_id(${mchid}) doesn't matched init one(${Transformer.mchid})`);
}

这段代码是判断请求数据中的mch_id字段的值和创建WechatPay对象时配置的mchid是否一致,但偏偏有些接口的商户号字段不叫mch_id而叫mchid......(这波微信的锅很大)

我的建议是将上面第35行改为:

const { sign_type: type = 'MD5 } = data;
const mchid = data.mch_id || data.mchid;

应该就可以解决这个问题。

不知道您觉得是否可以,如果可以的话,我可以发布pull request上来。

最后,对您长期以来编写和维护这样一个很好用的库,表示衷心的感谢!

正则匹配有个问题,铁子

比如下面这串代码:
wxpay.v3.transfer.batches.outBatchNo['1234567899900011'].details.outDetailNo['x16683940311151111X'].get();
后面的outDetailNo参数最后一位X会被正则处理成 ‘-x’;

尝试恢复APIv2版的`Transformer`返回数据为强验签功能

APIv2版数据验签规则是,解析出xml后,读取对象内的 sign字段,对返回串做key/value排序转字符串加 Hash 验签。

  • 目前发现,有部分接口,返回的xml中无 sign 字段(然而数据是有效的),目前的实现对这种是直接返回处理;
  • 另外就是v2版的账单下载接口返回值存在二象性,即无账单时返回xml,有账单时返回 csv buffer;

尝试对这两种异构接口做特殊transformResponse处理,恢复大部分接口的强校验返回内容功能。

“特约商户进件” 以slash(/)结尾的接口调用接口返回404.

感谢你提供的开发库。
我在使用的过程中碰到了一个问题。

  try {
    const res = await wxpay.v3.pay.partner.transactions.native({
      sp_appid,
      sp_mchid,
      sub_mchid,
      description,
      out_trade_no,
      time_expire: new Date( (+new Date) + 33*60*1000 ), //after 33 minutes
      attach,
      notify_url,
      amount: {
        total: 1,
      }
    })
    console.info(res.data.code_url)
  } catch (error) {
    console.error(error)
  }
})()

按照示例可以正常调用微信支付的API。

现在我想调用另外一个接口,“特约商户进件”,https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/

 try {
            const result = await this.wxapi.v3.applyment4sub.applyment({});
            return console.log(result);
        } catch (err) {
            return console.error(err)
        }

这次接口调用返回404 Not Found.
通过catch 到的内容,我检查了请求的信息,如下:

config: {
    url: '/v3/applyment4sub/applyment',
    method: 'post',
    data: '{}',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      'User-Agent': 'wechatpay-axios-plugin/0.4.5 axios/0.21.1 node/14.15.4 darwin/x64',
      Authorization: 'WECHATPAY2-SHA256-RSA2048 mchid="1270921201",nonce_str="KKuwhzeDvaIU6eykvDn4oYM7QZkjBVRu",signature="RXOv9YfWJBw/DjHpqQgsjzHJjkRXxIOthtXAL+J/mx0CfUnGlSwMciZagHgCAG8S5x8XR3q1B/khavW8YNPHM2n4Q/ooHc8EKFCRH+uPSN6iqIqluG5iTNd53a7h92/vk6zMKcMCH58ocJRvm/vMH/3CUUzRfpSJkCVccAVIgL3WpVGvzhBrUbTfP0PepwNselJqAXjCzC6MQgo83QrUHaHbdx8llz2Cs6v0tw8yD5faT+eaybHhPCOuc/qk9Y1ITRSSgRv6mfWUIE19LHPDDbI2hKjkzQUPbPwPNj9XK/OY0BV1NIgfxdYlK8RPX1HBquwwDiC4EKmUFr6v0/6NNA==",timestamp="1616655313",serial_no="623FFEC95EF4B583D2157DFE81E66FFA15DE67C3"',
      'Content-Length': 2
    },
    baseURL: 'https://api.mch.weixin.qq.com',
    ...

看起来请求的数据没有什么异常。
我把headers里面的内容,通过postman 发起一个请求,是可以得到微信平台的应答的。
image

所以不知道是不是其他的问题导致的,希望能帮我看一下,谢谢。

服务端返回204状态码时,验签`verifier`拦截器抛验签失败

the response debug info:

status: 204,
statusText: 'No Content',
headers: {
    server: 'nginx',
    date: 'Tue, 14 Jul 2020 08:50:03 GMT',
    'content-type': 'application/json; charset=utf-8',
    'content-length': '0',
    connection: 'close',
    'cache-control': 'no-cache, must-revalidate',
    'x-content-type-options': 'nosniff',
    'request-id': '3v0fd7',
    'content-language': 'zh-CN',
    'wechatpay-nonce': '...',
    'wechatpay-signature': '...',
    'wechatpay-timestamp': '...',
    'wechatpay-serial': '...'
},
data: ''

下单接口返回无法将 JSON 输入源映射到目标字段的错误

const body = {
            amount: {
                total: 0.01,
                currency: "CNY"
            },
            appid: appID,
            description: 'app下单测试',
            mch_id: mchid,
            notify_url: 'https://www.xxx.com/wechatPayNotifyUrl',
            out_trade_no: '20210910123456789'
};

const result = await wxpay.v3.pay.transactions.native.post(body)
console.info('[下单结果]',result)

返回结果

{\"code\":\"PARAM_ERROR\",\"detail\":{\"location\":\"body\",\"value\":0.01},\"message\":\"无法将 JSON 输入源“/body/amount/total”映射到目标字段“总金额”中,此字段需要 一个合法的 64 位有符号整数\"}

PEM routines:get_name:no start line

Error: error:0909006C:PEM routines:get_name:no start line
at Sign.sign (internal/crypto/sig.js:105:29)
at Function.sign (E:\work\ad\payment-applet\cloudfunctions\login\node_modules_wechatpay-axios-plugin@0.4.4@wechatpay-axios-plugin\lib\rsa.js:60:23)
at signer (E:\work\ad\payment-applet\cloudfunctions\login\node_modules_wechatpay-axios-plugin@0.4.4@wechatpay-axios-plugin\lib\interceptor.js:71:27)
at processTicksAndRejections (internal/process/task_queues.js:97:5)

有的机器验证返回签名报错

TypeError [ERR_INVALID_ARG_TYPE]: The "key" argument must be of type string or an instance of ArrayBuffer, Buffer, TypedArray, DataView, KeyObject, or CryptoKey. Received undefined
at new NodeError (node:internal/errors:363:5)
at prepareAsymmetricKey (node:internal/crypto/keys:570:9)
at preparePublicOrPrivateKey (node:internal/crypto/keys:581:10)
at Verify.verify (node:internal/crypto/sig:217:7)
at Function.verify (/root/payapi2/node_modules/wechatpay-axios-plugin/lib/rsa.js:78:23)
at verifier (/root/payapi2/node_modules/wechatpay-axios-plugin/lib/interceptor.js:115:11)
at runMicrotasks ()

奇怪的是 有的机器没有问题,有的有问题
certs[serial]=wechatpayPublicCert
const client = wxp(instance, {
mchid: global.微信服务商Id,
serial: serial,
privateKey: merchantPrivateKey,
certs: certs
});

serial 变量是传入的参数
interceptor.js 文件第98行 有定义变量const serial = response.headers[wechatpay-serial]
这个是不是有冲突

在113行 应该是检查返回数据的签名
certs[serial] 这个是获取不到内容的
不知道出错的原因是不是这个

遇到个问题

感谢作者的开源项目,帮了很多忙,await wxpay.v3.profitsharing.orders.post({}) ,在云函数,使用这样调用请求分账,返回{},不知问题出在哪。

重构 `interceptor.js` 文件

  • lib/interceptor.js文件,在验签的时候,特殊处理了204状态码的返回值,有缺陷(官方有可能返回201状态码)
  • 引申需要翻新 命令行模式下的 平台证书下载器
  • 引申需要翻新类库默认导出的函数为新的入口函数,有可能要放弃兼容0.1系列

尝试对`multipart/form-data`的优化支持

form-data 包是一款非常好的包,不过有一个 form-data/form-data#396 貌似停留好久没有解决,这个造成其在nodejs环境上有点小问题就是 Object.prototype.toString.call 不准,而直接调用实例的toString也是有一些问题,尝试用ES6重写一遍。

目标:

  • ES6 结合 stream.Readable 实现pipe功能
  • 增强 form-data/form-data#396 遗留事项
  • 可通过 node-request 包的 is.formData 探测

多商户(实例)相关问题

我想在同一个服务中使用两个商户,创建两个实例后,调用前一个实例会返回如下信息(使用API v3):

{
  code: 'PARAM_ERROR',
  message: 'http header中的mchid与post payload中的mchid不匹配'
}

需要说明的是,注释掉后一个实例,使用前一个实例正常。安装依赖版本为:v0.8.6

不过看到老师您之前说支持多商户 #44,来请教一下

建议补充函数注释

const Interceptor = (axios, {

建议:

/**
 * 向axios实例中注入拦截器
 * @param {AxiosStatic} axios
 * @param {string} mchid
 * @param {string} serial
 * @param {string} privateKey
 * @param {string} publicCert
 * @param {Object} certs
 * @returns {AxiosStatic}
 * @constructor
 */
const Interceptor = (axios, {...} = {}) => 

计划升级大版本至1.0版本

  • 移除对axios/lib/util 内置方法的依赖
  • 刨除废弃的方法,如Hash.hmacSha256, Aes.encrypt, Aes.decrypt
  • 调整 AesGcm.encrypt, AesGcm.decrypt 型参顺序
  • 调整repo及包名,相应地调整user-agent
  • 搭建 wechatpay.js.org 作为library的主站

jsapi下单成功回调在catch里面

原本应该出现在then里面的回调,出现在catch里面
wxpay.v3.pay.transactions.jsapi .post(payParames) .then((res) => console.log(res,'回调成功')) .catch(({response: {status, statusText, data}}) => console.log(status, statusText, data,'回调失败'));
log打印 undefined undefined {"prepay_id":"---------"} 回调失败

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.