评论

微信支付APIv3的Nodejs版SDK,让开发变得简单不再繁琐

微信支付APIv3的Nodejs版SDK,让v3开发简单高效。

在向云端推送这个 wechatpay-axios-plugin 业务实现时,发现0.1系列还不够好用,还需要进行更多层级的包裹包装,遂再次做了重大更新,让SDK使用起来更简单、飘逸。

先看官方文档,每一个接口,文档都至少标示了请求URL 请求方式 请求参数 返回参数 这几个要素,URL 可以拆分成 BaseURI,按照这种思路,封装SDK其实完全就可以不用动脑,即,对URI资源的 POSTGET 请求(条件带上参数),取得返回参数

更近一步,我们设想一下,如果把众多接口的URI按照斜线(/ slash)分割,然后组织在一起,是不是就可以构建出一颗树,这颗树的每个节点(实体Entity)都存在有若干个方法(HTTP METHODs),这是不是就能把接口SDK实现更简单化了?!

例如:

  • /v3/certificates
  • /v3/bill/tradebill
  • /v3/ecommerce/fund/withdraw
  • /v3/ecommerce/profitsharing/orders
  • /v3/marketing/busifavor/users/{openid}/coupons/{coupon_code}/appids/{appid}

树形化即:

v3
├── certificates
├── bill
│   └── tradebill
├── ecommerce
│   ├── fund
│   │   └── withdraw
│   └── profitsharing
│       └── orders
└── marketing
    └── busifavor
        └── users
            └── {openid}
                └── coupons
                    └── {coupon_code}
                        └── appids
                            └── {appid}

按照这种树形构想,我们来看下需要做的封装实现工作:

  1. 把实体对象,按照实体的排列顺序,映射出请求的URI;
  2. 每个对象实体,包含有若干操作方法,其中可选带参数发起RPC请求;
  3. 随官方放出更多的接口,SDK需要能够弹性扩容;

wechatpay-axios-plugin~0.2.0 版本实现了上述这3个目标,代码包如下截屏:

我们用伪代码来校验看一下这个封装实现

require('util').inspect.defaultOptions.depth = 10;

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

const wxpay = new Wechatpay({mchid: '1', serial: '2', privateKey: '3', certs: {'4': '5'}});

wxpay.v3.certificates;
wxpay.v3.bill.tradebill;
wxpay.v3.ecommerce.fund.withdraw;
wxpay.v3.marketing.busifavor.users['{openid}'].coupons.$coupon_code$.appids['wx233544546545989'];

console.info(wxpay);
//以下是输出内容
{
  entities: [],
  withEntities: [Function: withEntities],
  get: [AsyncFunction: get],
  post: [AsyncFunction: post],
  upload: [AsyncFunction: upload],
  v3: {
    entities: [ 'v3' ],
    withEntities: [Function: withEntities],
    get: [AsyncFunction: get],
    post: [AsyncFunction: post],
    upload: [AsyncFunction: upload],
    certificates: {
      entities: [ 'v3', 'certificates' ],
      withEntities: [Function: withEntities],
      get: [AsyncFunction: get],
      post: [AsyncFunction: post],
      upload: [AsyncFunction: upload]
    },
    bill: {
      entities: [ 'v3', 'bill' ],
      withEntities: [Function: withEntities],
      get: [AsyncFunction: get],
      post: [AsyncFunction: post],
      upload: [AsyncFunction: upload],
      tradebill: {
        entities: [ 'v3', 'bill', 'tradebill' ],
        withEntities: [Function: withEntities],
        get: [AsyncFunction: get],
        post: [AsyncFunction: post],
        upload: [AsyncFunction: upload]
      }
    },
    ecommerce: {
      entities: [ 'v3', 'ecommerce' ],
      withEntities: [Function: withEntities],
      get: [AsyncFunction: get],
      post: [AsyncFunction: post],
      upload: [AsyncFunction: upload],
      fund: {
        entities: [ 'v3', 'ecommerce', 'fund' ],
        withEntities: [Function: withEntities],
        get: [AsyncFunction: get],
        post: [AsyncFunction: post],
        upload: [AsyncFunction: upload],
        withdraw: {
          entities: [ 'v3', 'ecommerce', 'fund', 'withdraw' ],
          withEntities: [Function: withEntities],
          get: [AsyncFunction: get],
          post: [AsyncFunction: post],
          upload: [AsyncFunction: upload]
        }
      }
    },
    marketing: {
      entities: [ 'v3', 'marketing' ],
      withEntities: [Function: withEntities],
      get: [AsyncFunction: get],
      post: [AsyncFunction: post],
      upload: [AsyncFunction: upload],
      busifavor: {
        entities: [ 'v3', 'marketing', 'busifavor' ],
        withEntities: [Function: withEntities],
        get: [AsyncFunction: get],
        post: [AsyncFunction: post],
        upload: [AsyncFunction: upload],
        users: {
          entities: [ 'v3', 'marketing', 'busifavor', 'users' ],
          withEntities: [Function: withEntities],
          get: [AsyncFunction: get],
          post: [AsyncFunction: post],
          upload: [AsyncFunction: upload],
          '{openid}': {
            entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}' ],
            withEntities: [Function: withEntities],
            get: [AsyncFunction: get],
            post: [AsyncFunction: post],
            upload: [AsyncFunction: upload],
            coupons: {
              entities: [
                'v3',
                'marketing',
                'busifavor',
                'users',
                '{openid}',
                'coupons'
              ],
              withEntities: [Function: withEntities],
              get: [AsyncFunction: get],
              post: [AsyncFunction: post],
              upload: [AsyncFunction: upload],
              '$coupon_code$': {
                entities: [
                  'v3',
                  'marketing',
                  'busifavor',
                  'users',
                  '{openid}',
                  'coupons',
                  '{coupon_code}'
                ],
                withEntities: [Function: withEntities],
                get: [AsyncFunction: get],
                post: [AsyncFunction: post],
                upload: [AsyncFunction: upload],
                appids: {
                  entities: [
                    'v3',
                    'marketing',
                    'busifavor',
                    'users',
                    '{openid}',
                    'coupons',
                    '{coupon_code}',
                    'appids'
                  ],
                  withEntities: [Function: withEntities],
                  get: [AsyncFunction: get],
                  post: [AsyncFunction: post],
                  upload: [AsyncFunction: upload],
                  wx233544546545989: {
                    entities: [
                      'v3',
                      'marketing',
                      'busifavor',
                      'users',
                      '{openid}',
                      'coupons',
                      '{coupon_code}',
                      'appids',
                      'wx233544546545989'
                    ],
                    withEntities: [Function: withEntities],
                    get: [AsyncFunction: get],
                    post: [AsyncFunction: post],
                    upload: [AsyncFunction: upload]
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

注: API树实体节点,存储在每个 entities 属性上,方便后续的get, post 抑或 upload 方法调用调用前,反构成最终请求的URI;特别地,对于动态树实体节点来说,每个实体节点均提供了 withEntities 方法,用来在最终请求前,把动态实体节点替换成实际的值。

正常用法示例如下:

const {Wechatpay} = require('wechatpay-axios-plugin');
const wxpay = new Wechatpay({/*初始化参数,README有*/}, {/*可选调整axios的参数*/});

//拿证书
wxpay.v3.certificates.get();

//带参申请交易账单
wxpay.v3.bill.tradebill.get({params: {bill_date}});

//带参发起账户余额提现
wxpay.v3.ecommerce.fund.withdraw.post({sub_mchid, out_request_no, amount, remark, bank_memo});

//查询用户单张券详情
wxpay.v3.marketing.busifavor.users['{openid}'].coupons.$coupon_code$.appids['wx233544546545989'].withEntities({openid, coupon_code}).get();

请求APIv3是不是就“丧心病狂”般的简单了?!

详细功能说明及用法示例,npmjs及github的README均有。

如果喜欢,就给来个 Star 吧。

最后一次编辑于  2020-07-17  
点赞 11
收藏
评论

2 个评论

  • 北望沣渭
    北望沣渭
    2020-11-19

    回复楼上同学,云函数发小程序红包参考代码

    // file: cloudfunctions/thanksgivenday/index.js
    const cloud = require('wx-server-sdk')
    const {Formatter, Wechatpay, Hash} = require('wechatpay-axios-plugin')
    
    cloud.init({env: cloud.DYNAMIC_CURRENT_ENV})
    
    const v2SecretKey = ''
    const mch_id = ''
    const SUCCESS = 'SUCCESS'
    
    const wxpay = Wechatpay.xmlBased({
      mchid: mch_id,
      secret: v2SecretKey,
      merchant: {
        cert: '-----BEGIN CERTIFICATE-----' + '...' + '-----END CERTIFICATE-----',
        key: '-----BEGIN PRIVATE KEY-----' + '...' + '-----END PRIVATE KEY-----',
      },
    })
    
    exports.main = async (event, context) => {
      const wxContext = cloud.getWXContext()
    
      const {data: {return_code, result_code, package: pkgSource}} = await wxpay.post('/mmpaymkttransfers/sendminiprogramhb', {
        mch_id,
        wxappid: wxContext.APPID,
        re_openid: wxContext.OPENID,
        nonce_str: Formatter.nonce(),
        mch_billno: `HB${+new Date}`,
        send_name: '感恩节真情回馈',
        total_amount: 1,
        total_num: 1,
        wishing: '感恩有你',
        act_name: '云开发红包活动',
        remark: '云开发红包活动20201126',
        notify_way: 'MINI_PROGRAM_JSAPI',
        scene_id: 'PRODUCT_2',
      })
    
      let toMiniPage = {package: void 0, paySign: void 0, message: '出错了'}
      if (return_code == SUCCESS && result_code == SUCCESS) {
        // 二次签名数据结构
        toMiniPage = {
          timeStamp: `${Formatter.timestamp()}`,
          nonceStr: Formatter.nonce(),
          package: encodeURIComponent(pkgSource),
          signType: 'MD5',
        }
        const paySign = Hash.md5(Formatter.queryStringLike(Formatter.ksort(toMiniPage)), v2SecretKey)
        toMiniPage = Object.assign(toMiniPage, {paySign, openid: wxContext.OPENID})
      }
    
      return toMiniPage
    }
    
    //file: miniprogram/pages/index/index.js
    const app = getApp()
    Page({
      data: {},
      async onLoad(options) {
        const opts = wx.getLaunchOptionsSync()
        if ([1047, 1011, 1025, 1124].includes(opts.sence)) {
          wx.cloud.callFunction({
            name: 'thanksgivenday',
            data: {},
            success(res) {
              let fromCloud = {...res.result}
              console.log('[cloud] [thanksgivenday] success', fromCloud)
    
              const {openid, paySign, message} = fromCloud
              if (openid && paySign && message == void 0) {
                delete fromCloud.openid
                fromCloud = Object.assign(fromCloud, {
                  success(hb) {
                    console.info(hb)
                  },
                  fail(hb) {
                    console.error(hb)
                  },
                  complete(hb) {
                    console.log(hb)
                  }
                })
                wx.sendBizRedPacket(fromCloud)
              }
            },
            fail(err) {
              console.error('[cloud] [thanksgivenday] failed', err)
            }
          })
        }
      }
    })
    
    2020-11-19
    赞同
    回复 4
    • 韦不吕🙈🙉🙊
      韦不吕🙈🙉🙊
      2020-11-19
      非常感谢大牛,我问的是‘现金红包’,不过这个‘小程序红包’应该也似类似。稍后试一下。
      2020-11-19
      回复
    • 韦不吕🙈🙉🙊
      韦不吕🙈🙉🙊
      2020-11-22
      API 3.0是否支持‘现金红包?’
      2020-11-22
      回复
    • 北望沣渭
      北望沣渭
      2020-11-23回复韦不吕🙈🙉🙊
      v3还没开放出来“现金红包”功能;另外,你的云函数多半用在小程序场景下的吧?“现金红包”产品适用的是服务号场景,小程序发红包,目前看只能用微信支付的“小程序”红包产品功能。
      2020-11-23
      回复
    • 韦不吕🙈🙉🙊
      韦不吕🙈🙉🙊
      2020-11-24回复北望沣渭
      谢谢回复。SCF是在小程序场景。但是‘小程序红包’只能扫码领取,这个场景不能适用于我的应用场景。
      2020-11-24
      回复
  • 韦不吕🙈🙉🙊
    韦不吕🙈🙉🙊
    2020-11-19

    请教一下,使用您这个模块,在‘云函数’里是否可以实现‘现金红包’功能?

    2020-11-19
    赞同
    回复 1
    • 北望沣渭
      北望沣渭
      2020-11-19
      这里回复看不全,看如下回复,我黑着写的,你把关键参数替换掉试试看
      2020-11-19
      1
      回复
登录 后发表内容