从github issues可以感知到,本SDK已有案例被应用到云开发及自主开发环境,从开发者角度来看,此类库还有些难以架弩,本系列将作为开发对接指导,希望能对开发有所帮助。

起步

说起开发,最忌讳的是急急火火的先堆代码,作为一款有性格的SDK,源码全程是英文注释,README我可以用母语来书写,有些同学可能对此不适应,编码讲究的是效率,这里就不展开了。

这里还有一些思考,是关于「起步」,即:如何快速的对接「微信支付」接口,下面我们来展开一下

前置条件

  1. 注册微信支付商户号
  2. 登录商户平台,设置API密钥
  3. 使用官方专用证书生成工具,生成商户证书
  4. 设置API密钥及APIv3密钥
  5. 有一个nodejs开发环境,版本最低要求是10.15.0,windows、macos、linux操作系统均支持
  6. 懂得基本的npm包管理

开始

基于一些考虑,如云开发生产环境,依赖项决定了启动速度,这里需要区分一下生产环境及开发环境。面向生产环境,此款类库仅严格依赖两个包:axiosnode-xml2js,衍生总计仅需6个包;面向开发环境需要额外增加yargs包。此文以开发环境展开。

准备环境

准备一个工作目录,使用 npm init 先构建一个基础环境,如已有环境,则略过此步。

安装软件

基础包

npm install wechatpay-axios-plugin --save

CLI依赖包

npm install yargs --save-dev

如需媒体上传,如「制券」及「拓展商户」,则需再安装 form-data 依赖

npm install form-data --save

平台证书

APIv3开发多了一个步骤,就是需要自行下载「平台证书」,本类库提供有下载器,执行命令如下:

./node_modules/.bin/wxpay crt -m {商户号} -s {商户证书序列号} -f {商户私钥证书路径} -k {APIv3密钥(32字节)} -o {保存地址}

花括弧内为变量,按需自行替换,执行结果类似如下:

1
2
3
4
5
6
7
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

快速体验

接续上一篇: 一行命令即可体验「微信支付」全系接口能力,现在就可以在开发环境上,验证及调试官方接口了,额外地, v0.5.1做了优化,命令行现在可以这么执行了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
./node_modules/.bin/wxpay v2/pay/micropay \
  -c.mchid 1230000109 \
  -c.serial any \
  -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版的「付款码支付」接口,any 为CLI模式保留字,意思即无需此参数(不能不给),1230000109 替换为你的商户号,your_merchant_secret_key_string 替换为你的API密钥,这条命令就可以验证支付能力了。屏幕打印的日志类似如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
{
  status: 200,
  statusText: 'OK',
  headers: {
    server: 'nginx',
    date: 'Tue, 30 Mar 2021 00:49:30 GMT',
    'content-type': 'text/plain',
    'content-length': '105',
    connection: 'keep-alive',
    'keep-alive': 'timeout=8',
    'request-id': '089AEB89830610A10518B9BF8C5820EA442882F601-47002, 089AEB89830610F6041896C2EEA30620F98004289CF805-0',
    'mmlas-verifyresult': 'CAA='
  },
  config: {
    url: '/pay/micropay',
    method: 'post',
    data: '<xml><appid>wxd678efh567hg6787</appid><mch_id>1230000109</mch_id><device_info>013467007045764</device_info><nonce_str>5K8264ILTKCH16CQ2502SI8ZNMTM67VS</nonce_str><detail>Image形象店-深圳腾大-QQ公仔</detail><spbill_create_ip>8.8.8.8</spbill_create_ip><out_trade_no>1217752501201407033233368018</out_trade_no><total_fee>100</total_fee><fee_type>CNY</fee_type><auth_code>120061098828009406</auth_code><sign>C5524E8CEB11D1060A9FF0E696F52F51</sign></xml>',
    headers: {
      Accept: 'application/json, text/plain, */*',
      'Content-Type': 'text/xml; charset=utf-8',
      'User-Agent': 'wechatpay-axios-plugin/0.5.1 axios/0.21.1 node/14.14.0 darwin/x64',
      'Content-Length': 458
    },
    baseURL: 'https://api.mch.weixin.qq.com/',
    transformRequest: [ [Function: signer], [Function: toXml] ],
    transformResponse: [ [Function: toObject], [Function: verifier] ],
    timeout: 0,
    adapter: [Function: httpAdapter],
    responseType: 'text',
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    maxBodyLength: -1,
    httpsAgent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 443,
      protocol: 'https:',
      options: [Object],
      requests: {},
      sockets: {},
      freeSockets: [Object],
      keepAliveMsecs: 1000,
      keepAlive: true,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      scheduling: 'fifo',
      maxTotalSockets: Infinity,
      totalSocketCount: 0,
      maxCachedSessions: 100,
      _sessionCache: [Object],
      [Symbol(kCapture)]: false
    },
    validateStatus: [Function: validateStatus],
    mchid: 1230000109,
    serial: 'any',
    privateKey: 'any',
    certs: { any: true },
    secret: 'your_*********************tring'
  },
  request: <ref *1> ClientRequest {
    _events: [Object: null prototype] {
      socket: [Function (anonymous)],
      abort: [Function (anonymous)],
      aborted: [Function (anonymous)],
      connect: [Function (anonymous)],
      error: [Function (anonymous)],
      timeout: [Function (anonymous)],
      prefinish: [Function: requestOnPrefinish]
    },
    _eventsCount: 7,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    destroyed: true,
    _last: false,
    chunkedEncoding: false,
    shouldKeepAlive: true,
    _defaultKeepAlive: true,
    useChunkedEncodingByDefault: true,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: null,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    socket: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      secureConnecting: false,
      _SNICallback: null,
      servername: 'api.mch.weixin.qq.com',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 9,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'api.mch.weixin.qq.com',
      _readableState: [ReadableState],
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: [TLSWrap],
      _requestCert: true,
      _rejectUnauthorized: true,
      parser: null,
      _httpMessage: null,
      timeout: 0,
      [Symbol(res)]: [TLSWrap],
      [Symbol(verified)]: true,
      [Symbol(pendingSession)]: null,
      [Symbol(async_id_symbol)]: -1,
      [Symbol(kHandle)]: [TLSWrap],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(connect-options)]: [Object],
      [Symbol(RequestTimeout)]: undefined
    },
    _header: 'POST /pay/micropay HTTP/1.1\r\n' +
      'Accept: application/json, text/plain, */*\r\n' +
      'Content-Type: text/xml; charset=utf-8\r\n' +
      'User-Agent: wechatpay-axios-plugin/0.5.1 axios/0.21.1 node/14.14.0 darwin/x64\r\n' +
      'Content-Length: 458\r\n' +
      'Host: api.mch.weixin.qq.com\r\n' +
      'Connection: keep-alive\r\n' +
      '\r\n',
    _keepAliveTimeout: 0,
    _onPendingData: [Function: noopPendingOutput],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 443,
      protocol: 'https:',
      options: [Object],
      requests: {},
      sockets: {},
      freeSockets: [Object],
      keepAliveMsecs: 1000,
      keepAlive: true,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      scheduling: 'fifo',
      maxTotalSockets: Infinity,
      totalSocketCount: 0,
      maxCachedSessions: 100,
      _sessionCache: [Object],
      [Symbol(kCapture)]: false
    },
    socketPath: undefined,
    method: 'POST',
    maxHeaderSize: undefined,
    insecureHTTPParser: undefined,
    path: '/pay/micropay',
    _ended: true,
    res: IncomingMessage {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 3,
      _maxListeners: undefined,
      socket: null,
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      headers: [Object],
      rawHeaders: [Array],
      trailers: {},
      rawTrailers: [],
      aborted: false,
      upgrade: false,
      url: '',
      method: null,
      statusCode: 200,
      statusMessage: 'OK',
      client: [TLSSocket],
      _consuming: false,
      _dumped: false,
      req: [Circular *1],
      responseUrl: 'https://api.mch.weixin.qq.com/pay/micropay',
      redirects: [],
      [Symbol(kCapture)]: false,
      [Symbol(RequestTimeout)]: undefined
    },
    aborted: false,
    timeoutCb: null,
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: false,
    host: 'api.mch.weixin.qq.com',
    protocol: 'https:',
    _redirectable: Writable {
      _writableState: [WritableState],
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      _options: [Object],
      _ended: true,
      _ending: true,
      _redirectCount: 0,
      _redirects: [],
      _requestBodyLength: 458,
      _requestBodyBuffers: [],
      _onNativeResponse: [Function (anonymous)],
      _currentRequest: [Circular *1],
      _currentUrl: 'https://api.mch.weixin.qq.com/pay/micropay',
      [Symbol(kCapture)]: false
    },
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      accept: [Array],
      'content-type': [Array],
      'user-agent': [Array],
      'content-length': [Array],
      host: [Array]
    }
  },
  data: { return_code: 'FAIL', return_msg: '缺少参数' }
}

当你遇到困难,需要社区帮助的时候,类库也做了安全规范,把secret做了脱敏处理,上述日志你可以无脑地直接拷贝粘贴过来(再无脑也需要注意格式,以javascript代码形式粘贴,会方便阅读)。

海外主体

同样适用,在CLI驱动时,仅需在给一个 --baseURL 参数即可,如香港:

下载平台证书

1
2
3
4
5
6
7
./node_modules/.bin/wxpay crt \
  --baseURL 'https://apihk.mch.weixin.qq.com' \
  -m {商户号} \
  -s {商户证书序列号} \
  -f {商户私钥证书路径} \
  -k {APIv3密钥(32字节)} \
  -o {保存地址}

付款码支付

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
./node_modules/.bin/wxpay v2/pay/micropay \
  --baseURL 'https://apihk.mch.weixin.qq.com' \
  -c.mchid 1230000109 \
  -c.serial any \
  -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

官方接口至少70%以上的接口,均可以在CLI模式上做快速体验,验证入参出参、调试,甚至可以作为CI工具集,希望能为你的开发带来帮助。

下一回

正统WEB编程。