距离上一版(0.3)过去了有几个月了,这几个月没少折腾微信团队,春节前后“突然”利好不断,许多几乎不可能的事项都有显著改善,这也许就是认(一)真(本)做(正)事(经)的回报吧。2月下旬的时候,有一小伙伴,开了个issue#10 反馈,lib在变量重入的时候,存在bug,无法正确获取订单信息。仔细推演之后,决定重构一下,把树形结构Wechatpay SDK
起个底儿,许多(BT)用法,真的是事后测试用例覆盖时才发现,啪一下吧。
0.4版做了这些改进
README
文档中文化- 重构
Wechatpay
类,同时支持APIv2&v3
链式调用 - 改变
Wechatpay.client
返回值为Wechatpay.client.v3
,Wechatpay.client.v2
为xmlBased
接口客户端 - 废弃
withEntities
方法,其在链式多次调用时,有可能达不到预期 - 解决
AES-GCM
在Node10
上的解密兼容性问题,程序在Node10
上有可能崩溃,建议Node10
用户升级至此版本 - 支持 企业微信-企业支付 链式调用,需要额外注入签名规则,见用法示例
- 优化
Wechatpay
在多次实例化时赋值Symbol(CLIENT)
异常问题,增加wechatpay.test.js
测试用例覆盖
链式chain调用
这个想法是自0.2版开始,是把URL.pathname
以/
做切分,映射成对象属性,0.4版做了极致优化,支持APIv2的pathname
映射,效果较上一贴 微信支付APIv3的Nodejs版SDK,打印的树形更优美了,效果如下:
[Function (anonymous)] {
v2: [Function: /v2] {
risk: [Function: /v2/risk] {
getpublickey: [Function: /v2/risk/getpublickey]
},
pay: [Function: /v2/pay] { micropay: [Function: /v2/pay/micropay] },
secapi: [Function: /v2/secapi] {
pay: [Function: /v2/secapi/pay] {
refund: [Function: /v2/secapi/pay/refund]
}
},
mmpaymkttransfers: [Function: /v2/mmpaymkttransfers] {
sendredpack: [Function: /v2/mmpaymkttransfers/sendredpack],
promotion: [Function: /v2/mmpaymkttransfers/promotion] {
transfers: [Function: /v2/mmpaymkttransfers/promotion/transfers],
paywwsptrans2pocket: [Function: /v2/mmpaymkttransfers/promotion/paywwsptrans2pocket]
},
sendworkwxredpack: [Function: /v2/mmpaymkttransfers/sendworkwxredpack]
}
},
v3: [Function: /v3] {
pay: [Function: /v3/pay] {
transactions: [Function: /v3/pay/transactions] {
native: [Function: /v3/pay/transactions/native],
id: [Function: /v3/pay/transactions/id] {
'{transaction_id}': [Function: /v3/pay/transactions/id/{transaction_id}]
},
outTradeNo: [Function: /v3/pay/transactions/out-trade-no] {
'{out_trade_no}': [Function: /v3/pay/transactions/out-trade-no/{out_trade_no}]
}
}
favor: [Function: /v3/marketing/favor] {
media: [Function: /v3/marketing/favor/media] {
imageUpload: [Function: /v3/marketing/favor/media/image-upload]
},
stocks: [Function: /v3/marketing/favor/stocks] {
'$stock_id$': [Function: /v3/marketing/favor/stocks/{stock_id}] {
useFlow: [Function: /v3/marketing/favor/stocks/{stock_id}/use-flow]
}
}
},
partnerships: [Function: /v3/marketing/partnerships] {
build: [Function: /v3/marketing/partnerships/build]
}
},
smartguide: [Function: /v3/smartguide] {
guides: [Function: /v3/smartguide/guides] {
'$guide_id$': [Function: /v3/smartguide/guides/{guide_id}] {
assign: [Function: /v3/smartguide/guides/{guide_id}/assign]
}
}
}
}
}
上述 /v2
树节点,是本SDK“创造”出来的,用以区分APIv3调用,默认不是 /v2
开头的节点,均以APIv3方式调用;
这么做是因为,微信支付APIv3版,URL.pathname
目前已知至少有一个不是以 /v3
开头的;
/v2
官方又没用到,咱就先占着用吧,所以如是APIv2版的调用,可以这么 chain
, v2.pay.micropay = /v2/pay/micropay
即以XML负载形式请求远端接口;
较0.2版,每个级联对象为APIv2做了改变,每个节点本身是个Function
,链接的是HTTP POST方法,入参接受两个参数 (data, config)
,返回值是Promise
对象;
另外每个节点同时隐式内置了GET/POST/PUT/PATCH/DELETE
操作方法链,支持全大写及全小写(未来有可能会删除)两种编码方式,完美支持APIv3的多HTTP verbs协议(目前统计出来,有至少5处蓝瘦的地方,有空了再说)。
怎么用
一句话可能就说完了,实例化后,按pathname
拆分,然后再执行文档上说的HTTP verbs方法,附带所需参数,然后就没有然后了~
实例化
const {Wechatpay, Formatter} = require('wechatpay-axios-plugin')
const wxpay = new Wechatpay({
mchid: 'your_merchant_id',
serial: 'serial_number_of_your_merchant_public_cert',
privateKey: '-----BEGIN PRIVATE KEY-----' + '...' + '-----END PRIVATE KEY-----',
certs: {
'serial_number': '-----BEGIN CERTIFICATE-----' + '...' + '-----END CERTIFICATE-----',
},
// APIv2参数 >= 0.4.0 开始支持
secret: 'your_merchant_secret_key_string',
// 注: 如果不涉及资金变动,如仅收款,merchant参数可选,仅需 `secret` 一个参数,注意其为v2版的。
merchant: {
cert: '-----BEGIN CERTIFICATE-----' + '...' + '-----END CERTIFICATE-----',
key: '-----BEGIN PRIVATE KEY-----' + '...' + '-----END PRIVATE KEY-----',
// or
// passphrase: 'your_merchant_id',
// pfx: fs.readFileSync('/your/merchant/cert/apiclient_cert.p12'),
},
})
APIv3 Native下单
wxpay.v3.pay.transactions.native
.post({/*文档参数放这里就好*/})
.then(({data: {code_url}}) => console.info(code_url))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))
APIv3 查询订单
wxpay.v3.pay.transactions.id['{transaction_id}']
.get({params: {mchid: '1230000109'}, transaction_id: '1217752501201407033233368018'})
.then(({data}) => console.info(data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))
APIv3 关单
wxpay.v3.pay.transactions.outTradeNo['1217752501201407033233368018']
.post({mchid: '1230000109'})
.then(({status, statusText}) => console.info(status, statusText))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))
APIv3 对账单下载及解析
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,
signed: hash_value,
responseType: 'arraybuffer',
})).then(res => {
assert(sha1(res.data.toString()) === res.config.signed, 'verify the SHA1 digest failed.')
console.info(Formatter.castCsvBill(res.data))
}).catch(error => {
console.error(error)
})
APIv2 Native下单
wxpay.v2.pay.unifiedorder
.post({/*文档参数放这里就好*/})
.then(({data: {code_url}}) => console.info(code_url))
.catch(console.error)
APIv2 付款码(刷卡)支付
wxpay.v2.pay.micropay({
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(console.error)
APIv2 现金红包
wxpay.v2.mmpaymkttransfers.sendredpack({
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(console.error)
APIv2 企业付款到零钱
wxpay.v2.mmpaymkttransfers.promotion.transfers({
appid: 'wx8888888888888888',
mch_id: '1900000109',
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(),
})
.then(res => console.info(res.data))
.catch(console.error)
APIv2 企业付款到银行卡-获取RSA公钥
这个用法,是测试用例发掘出来的,注意请求带了第二个参数 {baseURL:'https://fraud.mch.weixin.qq.com'}
wxpay.v2.risk.getpublickey({
mch_id: '1900000109',
sign_type: 'MD5',
nonce_str: Formatter.nonce(),
}, {
baseURL: 'https://fraud.mch.weixin.qq.com'
})
.then(res => console.info(res.data))
.catch(console.error)
企业微信
企业微信的企业支付,数据请求包需要额外的签名,仅需做如下简单扩展适配,即可支持;以下签名注入函数所需的两个参数agentId
agentSecret
来自企业微信工作台,以下为示例值。
const agentId = 1001001
const agentSecret = 'from_wework_agent_special_string'
const {Hash} = require('wechatpay-axios-plugin')
APIv2 企业红包-注入签名规则
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
})
APIv2 发放企业红包
wxpay.v2.mmpaymkttransfers.sendworkwxredpack({
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)
APIv2及APIv3之间的编程体验,几乎是完全一致的了~
更多用法示例,已经完全中文化发在github上,地址是:https://github.com/TheNorthMemory/wechatpay-axios-plugin 花式开发玩法,下一耙再叙~
感谢分享!