wxpay-nodejs-sdk 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -22
- package/dist/index.d.mts +660 -6
- package/dist/index.d.ts +660 -6
- package/dist/index.js +798 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +778 -84
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import crypto2 from 'crypto';
|
|
3
4
|
|
|
4
5
|
// src/core/client.ts
|
|
5
6
|
var CertificateManager = class {
|
|
@@ -11,6 +12,8 @@ var CertificateManager = class {
|
|
|
11
12
|
wxpayPublicKeyId = null;
|
|
12
13
|
/** API V3 密钥 */
|
|
13
14
|
apiV3Key;
|
|
15
|
+
/** 自动更新定时器 */
|
|
16
|
+
autoUpdateTimer = null;
|
|
14
17
|
constructor(apiV3Key, certificates) {
|
|
15
18
|
this.apiV3Key = apiV3Key;
|
|
16
19
|
if (certificates) {
|
|
@@ -25,6 +28,21 @@ var CertificateManager = class {
|
|
|
25
28
|
get serialNos() {
|
|
26
29
|
return Array.from(this.certificates.keys());
|
|
27
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* 获取最新的证书序列号或公钥ID
|
|
33
|
+
*
|
|
34
|
+
* 优先返回微信支付公钥ID(公钥模式),否则返回第一个平台证书序列号。
|
|
35
|
+
* 用于设置请求头 Wechatpay-Serial,告知微信支付客户端支持验签的证书。
|
|
36
|
+
*
|
|
37
|
+
* @returns 序列号字符串,无配置时返回 undefined
|
|
38
|
+
*/
|
|
39
|
+
getNewestSerial() {
|
|
40
|
+
if (this.wxpayPublicKeyId) {
|
|
41
|
+
return this.wxpayPublicKeyId;
|
|
42
|
+
}
|
|
43
|
+
const keys = Array.from(this.certificates.keys());
|
|
44
|
+
return keys.length > 0 ? keys[0] : void 0;
|
|
45
|
+
}
|
|
28
46
|
/**
|
|
29
47
|
* 设置微信支付公钥(公钥模式)
|
|
30
48
|
*
|
|
@@ -57,7 +75,7 @@ var CertificateManager = class {
|
|
|
57
75
|
return Buffer.from(ciphertext, "base64").toString("utf-8");
|
|
58
76
|
}
|
|
59
77
|
try {
|
|
60
|
-
const decipher =
|
|
78
|
+
const decipher = crypto2.createDecipheriv(
|
|
61
79
|
"aes-256-gcm",
|
|
62
80
|
Buffer.from(this.apiV3Key, "utf-8"),
|
|
63
81
|
Buffer.from(nonce, "utf-8")
|
|
@@ -108,9 +126,63 @@ var CertificateManager = class {
|
|
|
108
126
|
this.wxpayPublicKey = null;
|
|
109
127
|
this.wxpayPublicKeyId = null;
|
|
110
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* 启动自动更新
|
|
131
|
+
*
|
|
132
|
+
* 定期调用更新函数刷新平台证书。适用于生产环境的证书自动维护。
|
|
133
|
+
*
|
|
134
|
+
* @param updateFn - 证书更新函数,返回最新的证书 Map
|
|
135
|
+
* @param options - 自动更新配置选项
|
|
136
|
+
* @returns 停止更新的函数
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* const manager = new CertificateManager(apiV3Key);
|
|
141
|
+
* const stop = manager.startAutoUpdate(
|
|
142
|
+
* async () => {
|
|
143
|
+
* const certs = await downloadCertificates();
|
|
144
|
+
* return certs;
|
|
145
|
+
* },
|
|
146
|
+
* { intervalMs: 60 * 60 * 1000 }
|
|
147
|
+
* );
|
|
148
|
+
* // 稍后停止
|
|
149
|
+
* stop();
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
startAutoUpdate(updateFn, options) {
|
|
153
|
+
const intervalMs = options?.intervalMs ?? 60 * 60 * 1e3;
|
|
154
|
+
this.stopAutoUpdate();
|
|
155
|
+
const doUpdate = async () => {
|
|
156
|
+
try {
|
|
157
|
+
const certs = await updateFn();
|
|
158
|
+
for (const [serialNo, publicKey] of certs) {
|
|
159
|
+
this.setPublicKey(serialNo, publicKey);
|
|
160
|
+
}
|
|
161
|
+
options?.onSuccess?.(Array.from(certs.keys()));
|
|
162
|
+
} catch (error) {
|
|
163
|
+
options?.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
void doUpdate();
|
|
167
|
+
this.autoUpdateTimer = setInterval(() => {
|
|
168
|
+
void doUpdate();
|
|
169
|
+
}, intervalMs);
|
|
170
|
+
return () => {
|
|
171
|
+
this.stopAutoUpdate();
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 停止自动更新
|
|
176
|
+
*/
|
|
177
|
+
stopAutoUpdate() {
|
|
178
|
+
if (this.autoUpdateTimer) {
|
|
179
|
+
clearInterval(this.autoUpdateTimer);
|
|
180
|
+
this.autoUpdateTimer = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
111
183
|
};
|
|
112
184
|
|
|
113
|
-
// src/utils/
|
|
185
|
+
// src/utils/exceptions.ts
|
|
114
186
|
var WxPayError = class extends Error {
|
|
115
187
|
/** HTTP 状态码 */
|
|
116
188
|
status;
|
|
@@ -133,7 +205,64 @@ var WxPayError = class extends Error {
|
|
|
133
205
|
get isServerError() {
|
|
134
206
|
return this.status >= 500 && this.status < 600;
|
|
135
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* 判断是否为特定错误码
|
|
210
|
+
*
|
|
211
|
+
* @param code - 错误码
|
|
212
|
+
* @returns 是否匹配
|
|
213
|
+
*/
|
|
214
|
+
isApiError(code) {
|
|
215
|
+
return this.detail.code === code;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var ServiceException = class extends WxPayError {
|
|
219
|
+
/** 微信支付业务错误码 */
|
|
220
|
+
errorCode;
|
|
221
|
+
/** 微信支付业务错误信息 */
|
|
222
|
+
errorMessage;
|
|
223
|
+
constructor(status, headers, detail) {
|
|
224
|
+
super(status, headers, detail);
|
|
225
|
+
this.name = "ServiceException";
|
|
226
|
+
this.errorCode = detail.code;
|
|
227
|
+
this.errorMessage = detail.message;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
var HttpException = class extends WxPayError {
|
|
231
|
+
constructor(message, cause) {
|
|
232
|
+
super(0, {}, { code: "NETWORK_ERROR", message });
|
|
233
|
+
this.name = "HttpException";
|
|
234
|
+
if (cause) {
|
|
235
|
+
this.cause = cause;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var ValidationException = class extends WxPayError {
|
|
240
|
+
constructor(message) {
|
|
241
|
+
super(0, {}, { code: "SIGN_ERROR", message });
|
|
242
|
+
this.name = "ValidationException";
|
|
243
|
+
}
|
|
136
244
|
};
|
|
245
|
+
var DecryptionException = class extends WxPayError {
|
|
246
|
+
constructor(message) {
|
|
247
|
+
super(0, {}, { code: "DECRYPT_ERROR", message });
|
|
248
|
+
this.name = "DecryptionException";
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
var MalformedMessageException = class extends WxPayError {
|
|
252
|
+
constructor(message) {
|
|
253
|
+
super(0, {}, { code: "PARSE_ERROR", message });
|
|
254
|
+
this.name = "MalformedMessageException";
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// src/utils/http.ts
|
|
259
|
+
var SDK_VERSION = "0.2.1";
|
|
260
|
+
function getUserAgent() {
|
|
261
|
+
const platform = os.platform();
|
|
262
|
+
const arch = os.arch();
|
|
263
|
+
const nodeVersion = process.version;
|
|
264
|
+
return `wxpay-nodejs-sdk/${SDK_VERSION} (${platform} ${arch}) Node.js/${nodeVersion}`;
|
|
265
|
+
}
|
|
137
266
|
function buildUrl(base, path, params) {
|
|
138
267
|
const url = new URL(path, base);
|
|
139
268
|
if (params) {
|
|
@@ -163,13 +292,16 @@ function parseResponseHeaders(headers) {
|
|
|
163
292
|
return result;
|
|
164
293
|
}
|
|
165
294
|
function createRequestHeaders(options) {
|
|
166
|
-
|
|
295
|
+
const headers = {
|
|
167
296
|
Authorization: options.authorization,
|
|
168
297
|
Accept: options.accept ?? "application/json",
|
|
169
298
|
"Content-Type": options.contentType ?? "application/json",
|
|
170
|
-
"User-Agent":
|
|
171
|
-
...options.additional
|
|
299
|
+
"User-Agent": getUserAgent()
|
|
172
300
|
};
|
|
301
|
+
if (options.wechatPaySerial) {
|
|
302
|
+
headers["Wechatpay-Serial"] = options.wechatPaySerial;
|
|
303
|
+
}
|
|
304
|
+
return { ...headers, ...options.additional };
|
|
173
305
|
}
|
|
174
306
|
async function parseResponse(response, verify) {
|
|
175
307
|
const headers = parseResponseHeaders(response.headers);
|
|
@@ -192,7 +324,7 @@ async function parseResponse(response, verify) {
|
|
|
192
324
|
message: `HTTP ${response.status}: ${response.statusText}`
|
|
193
325
|
};
|
|
194
326
|
}
|
|
195
|
-
throw new
|
|
327
|
+
throw new ServiceException(response.status, headers, errorDetail);
|
|
196
328
|
}
|
|
197
329
|
if (verify) {
|
|
198
330
|
const signature = headers["wechatpay-signature"];
|
|
@@ -202,10 +334,7 @@ async function parseResponse(response, verify) {
|
|
|
202
334
|
if (signature && timestamp && nonce && serial) {
|
|
203
335
|
const valid = verify(rawBody, signature, timestamp, nonce, serial);
|
|
204
336
|
if (!valid) {
|
|
205
|
-
throw new
|
|
206
|
-
code: "SIGN_ERROR",
|
|
207
|
-
message: "\u5E94\u7B54\u7B7E\u540D\u9A8C\u8BC1\u5931\u8D25"
|
|
208
|
-
});
|
|
337
|
+
throw new ValidationException("\u5E94\u7B54\u7B7E\u540D\u9A8C\u8BC1\u5931\u8D25");
|
|
209
338
|
}
|
|
210
339
|
}
|
|
211
340
|
}
|
|
@@ -217,10 +346,9 @@ async function parseResponse(response, verify) {
|
|
|
217
346
|
}
|
|
218
347
|
data = parsed;
|
|
219
348
|
} catch (error) {
|
|
220
|
-
throw new
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
349
|
+
throw new MalformedMessageException(
|
|
350
|
+
error instanceof Error ? error.message : "\u54CD\u5E94\u6570\u636E\u89E3\u6790\u5931\u8D25"
|
|
351
|
+
);
|
|
224
352
|
}
|
|
225
353
|
return {
|
|
226
354
|
status: response.status,
|
|
@@ -238,7 +366,13 @@ ${body}
|
|
|
238
366
|
`;
|
|
239
367
|
}
|
|
240
368
|
function sign(signString, privateKey) {
|
|
241
|
-
|
|
369
|
+
if (!signString) {
|
|
370
|
+
throw new Error("\u7B7E\u540D\u4E32\u4E0D\u80FD\u4E3A\u7A7A");
|
|
371
|
+
}
|
|
372
|
+
if (!privateKey) {
|
|
373
|
+
throw new Error("\u5546\u6237\u79C1\u94A5\u4E0D\u80FD\u4E3A\u7A7A");
|
|
374
|
+
}
|
|
375
|
+
const signer = crypto2.createSign("RSA-SHA256");
|
|
242
376
|
signer.update(signString);
|
|
243
377
|
signer.end();
|
|
244
378
|
return signer.sign(privateKey, "base64");
|
|
@@ -247,36 +381,92 @@ function buildAuthorization(mchid, serialNo, timestamp, nonce, signature) {
|
|
|
247
381
|
return `WECHATPAY2-SHA256-RSA2048 mchid="${mchid}",nonce_str="${nonce}",timestamp="${timestamp}",serial_no="${serialNo}",signature="${signature}"`;
|
|
248
382
|
}
|
|
249
383
|
function generateNonce() {
|
|
250
|
-
return
|
|
384
|
+
return crypto2.randomUUID().replace(/-/g, "");
|
|
385
|
+
}
|
|
386
|
+
var RESPONSE_EXPIRED_SECONDS = 5 * 60;
|
|
387
|
+
function isTimestampValid(timestamp) {
|
|
388
|
+
const responseTime = parseInt(timestamp, 10);
|
|
389
|
+
if (isNaN(responseTime)) return false;
|
|
390
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
391
|
+
return Math.abs(now - responseTime) < RESPONSE_EXPIRED_SECONDS;
|
|
251
392
|
}
|
|
252
393
|
function verifySignature(body, signature, timestamp, nonce, publicKey) {
|
|
394
|
+
if (!signature) {
|
|
395
|
+
throw new Error("\u7B7E\u540D\u503C(signature)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
396
|
+
}
|
|
397
|
+
if (!timestamp) {
|
|
398
|
+
throw new Error("\u65F6\u95F4\u6233(timestamp)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
399
|
+
}
|
|
400
|
+
if (!nonce) {
|
|
401
|
+
throw new Error("\u968F\u673A\u4E32(nonce)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
402
|
+
}
|
|
403
|
+
if (!publicKey) {
|
|
404
|
+
throw new Error("\u516C\u94A5(publicKey)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
405
|
+
}
|
|
406
|
+
if (!isTimestampValid(timestamp)) {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
253
409
|
const signString = `${timestamp}
|
|
254
410
|
${nonce}
|
|
255
411
|
${body}
|
|
256
412
|
`;
|
|
257
|
-
const verifier =
|
|
413
|
+
const verifier = crypto2.createVerify("RSA-SHA256");
|
|
258
414
|
verifier.update(signString);
|
|
259
415
|
verifier.end();
|
|
260
416
|
return verifier.verify(publicKey, signature, "base64");
|
|
261
417
|
}
|
|
262
418
|
function oaepEncrypt(plaintext, publicKey) {
|
|
263
|
-
|
|
419
|
+
if (!publicKey) {
|
|
420
|
+
throw new Error("\u52A0\u5BC6\u516C\u94A5\u4E0D\u80FD\u4E3A\u7A7A");
|
|
421
|
+
}
|
|
422
|
+
if (!plaintext) {
|
|
423
|
+
return "";
|
|
424
|
+
}
|
|
425
|
+
const encrypted = crypto2.publicEncrypt(
|
|
264
426
|
{
|
|
265
427
|
key: publicKey,
|
|
266
|
-
padding:
|
|
267
|
-
oaepHash: "
|
|
428
|
+
padding: crypto2.constants.RSA_PKCS1_OAEP_PADDING,
|
|
429
|
+
oaepHash: "sha1"
|
|
268
430
|
},
|
|
269
431
|
Buffer.from(plaintext, "utf-8")
|
|
270
432
|
);
|
|
271
433
|
return encrypted.toString("base64");
|
|
272
434
|
}
|
|
435
|
+
function oaepDecrypt(ciphertext, privateKey) {
|
|
436
|
+
if (!privateKey) {
|
|
437
|
+
throw new Error("\u89E3\u5BC6\u79C1\u94A5\u4E0D\u80FD\u4E3A\u7A7A");
|
|
438
|
+
}
|
|
439
|
+
if (!ciphertext) {
|
|
440
|
+
return "";
|
|
441
|
+
}
|
|
442
|
+
const decrypted = crypto2.privateDecrypt(
|
|
443
|
+
{
|
|
444
|
+
key: privateKey,
|
|
445
|
+
padding: crypto2.constants.RSA_PKCS1_OAEP_PADDING,
|
|
446
|
+
oaepHash: "sha1"
|
|
447
|
+
},
|
|
448
|
+
Buffer.from(ciphertext, "base64")
|
|
449
|
+
);
|
|
450
|
+
return decrypted.toString("utf-8");
|
|
451
|
+
}
|
|
273
452
|
|
|
274
453
|
// src/core/client.ts
|
|
275
454
|
var WxPayClient = class _WxPayClient {
|
|
276
|
-
/** 生产环境 API
|
|
455
|
+
/** 生产环境 API 主域名 */
|
|
277
456
|
static PRODUCTION_BASE = "https://api.mch.weixin.qq.com";
|
|
457
|
+
/** 生产环境 API 备用域名(跨城容灾) */
|
|
458
|
+
static PRODUCTION_BACKUP = "https://api2.mch.weixin.qq.com";
|
|
278
459
|
/** 沙箱环境 API 地址 */
|
|
279
460
|
static SANDBOX_BASE = "https://api.mch.weixin.qq.com/sandboxnew";
|
|
461
|
+
/** SDK 版本号 */
|
|
462
|
+
static SDK_VERSION = "0.2.1";
|
|
463
|
+
/** 动态 User-Agent 字符串 */
|
|
464
|
+
static USER_AGENT = (() => {
|
|
465
|
+
const platform = os.platform();
|
|
466
|
+
const arch = os.arch();
|
|
467
|
+
const nodeVersion = process.version;
|
|
468
|
+
return `wxpay-nodejs-sdk/${_WxPayClient.SDK_VERSION} (${platform} ${arch}) Node.js/${nodeVersion}`;
|
|
469
|
+
})();
|
|
280
470
|
/** 商户号 */
|
|
281
471
|
mchid;
|
|
282
472
|
apiV3Key;
|
|
@@ -284,6 +474,7 @@ var WxPayClient = class _WxPayClient {
|
|
|
284
474
|
privateKey;
|
|
285
475
|
timeout;
|
|
286
476
|
baseUrl;
|
|
477
|
+
backupUrl;
|
|
287
478
|
enableResponseVerification;
|
|
288
479
|
/** 平台证书管理器 */
|
|
289
480
|
certificates;
|
|
@@ -294,6 +485,7 @@ var WxPayClient = class _WxPayClient {
|
|
|
294
485
|
this.privateKey = this.resolvePrivateKey(options.privateKey);
|
|
295
486
|
this.timeout = options.timeout ?? 3e4;
|
|
296
487
|
this.baseUrl = options.sandbox ? _WxPayClient.SANDBOX_BASE : _WxPayClient.PRODUCTION_BASE;
|
|
488
|
+
this.backupUrl = options.sandbox ? null : _WxPayClient.PRODUCTION_BACKUP;
|
|
297
489
|
this.enableResponseVerification = options.enableResponseVerification ?? true;
|
|
298
490
|
this.certificates = new CertificateManager(this.apiV3Key, options.platformCertificates);
|
|
299
491
|
if (options.wxpayPublicKeyId && options.wxpayPublicKey) {
|
|
@@ -365,8 +557,12 @@ var WxPayClient = class _WxPayClient {
|
|
|
365
557
|
const headers = {
|
|
366
558
|
Authorization: authorization,
|
|
367
559
|
Accept: "application/json",
|
|
368
|
-
"User-Agent":
|
|
560
|
+
"User-Agent": _WxPayClient.USER_AGENT
|
|
369
561
|
};
|
|
562
|
+
const serial = this.certificates.getNewestSerial();
|
|
563
|
+
if (serial) {
|
|
564
|
+
headers["Wechatpay-Serial"] = serial;
|
|
565
|
+
}
|
|
370
566
|
const controller = new AbortController();
|
|
371
567
|
const timeoutId = setTimeout(() => {
|
|
372
568
|
controller.abort();
|
|
@@ -488,8 +684,12 @@ var WxPayClient = class _WxPayClient {
|
|
|
488
684
|
Authorization: authorization,
|
|
489
685
|
Accept: "application/json",
|
|
490
686
|
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
491
|
-
"User-Agent":
|
|
687
|
+
"User-Agent": _WxPayClient.USER_AGENT
|
|
492
688
|
};
|
|
689
|
+
const serial = this.certificates.getNewestSerial();
|
|
690
|
+
if (serial) {
|
|
691
|
+
headers["Wechatpay-Serial"] = serial;
|
|
692
|
+
}
|
|
493
693
|
const controller = new AbortController();
|
|
494
694
|
const timeoutId = setTimeout(() => {
|
|
495
695
|
controller.abort();
|
|
@@ -530,67 +730,90 @@ var WxPayClient = class _WxPayClient {
|
|
|
530
730
|
}
|
|
531
731
|
/**
|
|
532
732
|
* 通用 HTTP 请求方法
|
|
733
|
+
*
|
|
734
|
+
* 支持跨城容灾:当主域名请求失败(网络错误、超时)时,自动切换到备用域名重试。
|
|
533
735
|
*/
|
|
534
736
|
async request(method, path, params, body, extraHeaders) {
|
|
535
|
-
const url = buildUrl(this.baseUrl, path, params);
|
|
536
737
|
const bodyStr = body ? JSON.stringify(body) : "";
|
|
537
738
|
const timestamp = Math.floor(Date.now() / 1e3);
|
|
538
739
|
const nonce = generateNonce();
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
path: signPath,
|
|
544
|
-
timestamp,
|
|
545
|
-
nonce,
|
|
546
|
-
body: bodyStr
|
|
740
|
+
const headers = createRequestHeaders({
|
|
741
|
+
authorization: "",
|
|
742
|
+
wechatPaySerial: this.certificates.getNewestSerial(),
|
|
743
|
+
additional: extraHeaders
|
|
547
744
|
});
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
this.
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
method,
|
|
564
|
-
headers,
|
|
565
|
-
body: bodyStr || void 0,
|
|
566
|
-
signal: controller.signal
|
|
745
|
+
const urls = [this.baseUrl];
|
|
746
|
+
if (this.backupUrl) {
|
|
747
|
+
urls.push(this.backupUrl);
|
|
748
|
+
}
|
|
749
|
+
let lastError;
|
|
750
|
+
for (const baseUrl of urls) {
|
|
751
|
+
const url = buildUrl(baseUrl, path, params);
|
|
752
|
+
const urlObj = new URL(url);
|
|
753
|
+
const signPath = urlObj.pathname + urlObj.search;
|
|
754
|
+
const signString = buildSignString({
|
|
755
|
+
method: method.toUpperCase(),
|
|
756
|
+
path: signPath,
|
|
757
|
+
timestamp,
|
|
758
|
+
nonce,
|
|
759
|
+
body: bodyStr
|
|
567
760
|
});
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
761
|
+
const signature = sign(signString, this.privateKey);
|
|
762
|
+
headers["Authorization"] = buildAuthorization(
|
|
763
|
+
this.mchid,
|
|
764
|
+
this.serialNo,
|
|
765
|
+
timestamp,
|
|
766
|
+
nonce,
|
|
767
|
+
signature
|
|
768
|
+
);
|
|
769
|
+
const controller = new AbortController();
|
|
770
|
+
const timeoutId = setTimeout(() => {
|
|
771
|
+
controller.abort();
|
|
772
|
+
}, this.timeout);
|
|
773
|
+
try {
|
|
774
|
+
const response = await fetch(url, {
|
|
775
|
+
method,
|
|
776
|
+
headers,
|
|
777
|
+
body: bodyStr || void 0,
|
|
778
|
+
signal: controller.signal
|
|
779
|
+
});
|
|
780
|
+
return await parseResponse(response, this.createVerifier());
|
|
781
|
+
} catch (error) {
|
|
782
|
+
lastError = error;
|
|
783
|
+
const isNetworkError = !(error instanceof WxPayError) && !(error instanceof DOMException && error.name === "AbortError");
|
|
784
|
+
if (!isNetworkError || urls.indexOf(baseUrl) === urls.length - 1) {
|
|
785
|
+
if (error instanceof WxPayError) throw error;
|
|
786
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
787
|
+
throw new WxPayError(
|
|
788
|
+
0,
|
|
789
|
+
{},
|
|
790
|
+
{
|
|
791
|
+
code: "REQUEST_TIMEOUT",
|
|
792
|
+
message: `\u8BF7\u6C42\u8D85\u65F6 (${this.timeout}ms)`
|
|
793
|
+
}
|
|
794
|
+
);
|
|
580
795
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
796
|
+
throw new WxPayError(
|
|
797
|
+
0,
|
|
798
|
+
{},
|
|
799
|
+
{
|
|
800
|
+
code: "NETWORK_ERROR",
|
|
801
|
+
message: error instanceof Error ? error.message : "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25"
|
|
802
|
+
}
|
|
803
|
+
);
|
|
589
804
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
805
|
+
} finally {
|
|
806
|
+
clearTimeout(timeoutId);
|
|
807
|
+
}
|
|
593
808
|
}
|
|
809
|
+
throw new WxPayError(
|
|
810
|
+
0,
|
|
811
|
+
{},
|
|
812
|
+
{
|
|
813
|
+
code: "NETWORK_ERROR",
|
|
814
|
+
message: lastError instanceof Error ? lastError.message : "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25"
|
|
815
|
+
}
|
|
816
|
+
);
|
|
594
817
|
}
|
|
595
818
|
/**
|
|
596
819
|
* 解析私钥:支持直接传入内容或文件路径
|
|
@@ -631,6 +854,117 @@ var WxPayClient = class _WxPayClient {
|
|
|
631
854
|
}
|
|
632
855
|
};
|
|
633
856
|
|
|
857
|
+
// src/utils/sensitive.ts
|
|
858
|
+
var sensitiveFields = /* @__PURE__ */ new Map();
|
|
859
|
+
function registerSensitiveFields(typeName, fields) {
|
|
860
|
+
sensitiveFields.set(typeName, new Set(fields));
|
|
861
|
+
}
|
|
862
|
+
function getSensitiveFields(typeName) {
|
|
863
|
+
return sensitiveFields.get(typeName) ?? /* @__PURE__ */ new Set();
|
|
864
|
+
}
|
|
865
|
+
function encryptSensitiveFields(obj, typeName, publicKey) {
|
|
866
|
+
const fields = getSensitiveFields(typeName);
|
|
867
|
+
if (fields.size === 0) return obj;
|
|
868
|
+
const result = { ...obj };
|
|
869
|
+
for (const field of fields) {
|
|
870
|
+
const value = result[field];
|
|
871
|
+
if (typeof value === "string" && value.length > 0) {
|
|
872
|
+
result[field] = oaepEncrypt(value, publicKey);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return result;
|
|
876
|
+
}
|
|
877
|
+
function decryptSensitiveFields(obj, typeName, privateKey) {
|
|
878
|
+
const fields = getSensitiveFields(typeName);
|
|
879
|
+
if (fields.size === 0) return obj;
|
|
880
|
+
const result = { ...obj };
|
|
881
|
+
for (const field of fields) {
|
|
882
|
+
const value = result[field];
|
|
883
|
+
if (typeof value === "string" && value.length > 0) {
|
|
884
|
+
try {
|
|
885
|
+
result[field] = oaepDecrypt(value, privateKey);
|
|
886
|
+
} catch {
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return result;
|
|
891
|
+
}
|
|
892
|
+
function encryptSensitiveFieldsInArray(arr, typeName, publicKey) {
|
|
893
|
+
return arr.map((item) => encryptSensitiveFields(item, typeName, publicKey));
|
|
894
|
+
}
|
|
895
|
+
function decryptSensitiveFieldsInArray(arr, typeName, privateKey) {
|
|
896
|
+
return arr.map((item) => decryptSensitiveFields(item, typeName, privateKey));
|
|
897
|
+
}
|
|
898
|
+
var CertificateService = class {
|
|
899
|
+
client;
|
|
900
|
+
apiV3Key;
|
|
901
|
+
certificateManager;
|
|
902
|
+
constructor(client, apiV3Key, certificateManager) {
|
|
903
|
+
this.client = client;
|
|
904
|
+
this.apiV3Key = apiV3Key;
|
|
905
|
+
this.certificateManager = certificateManager;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* 下载平台证书列表
|
|
909
|
+
*
|
|
910
|
+
* 调用微信支付 /v3/certificates 接口获取当前可用的平台证书。
|
|
911
|
+
* 返回的证书内容已解密为 PEM 格式。
|
|
912
|
+
*
|
|
913
|
+
* @returns 解密后的平台证书列表
|
|
914
|
+
*/
|
|
915
|
+
async downloadCertificates() {
|
|
916
|
+
const response = await this.client.get("/v3/certificates");
|
|
917
|
+
const decryptedCerts = response.data.data.map((cert) => {
|
|
918
|
+
const pem = this.decryptCertificate(cert.encryptCertificate);
|
|
919
|
+
return {
|
|
920
|
+
serialNo: cert.serialNo,
|
|
921
|
+
effectiveTime: cert.effectiveTime,
|
|
922
|
+
expireTime: cert.expireTime,
|
|
923
|
+
certificatePem: pem
|
|
924
|
+
};
|
|
925
|
+
});
|
|
926
|
+
return {
|
|
927
|
+
status: response.status,
|
|
928
|
+
headers: response.headers,
|
|
929
|
+
data: decryptedCerts
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* 下载并更新本地平台证书缓存
|
|
934
|
+
*
|
|
935
|
+
* 下载最新的平台证书列表,解密后自动更新 CertificateManager 中的缓存。
|
|
936
|
+
* 适用于定时更新证书的场景。
|
|
937
|
+
*
|
|
938
|
+
* @returns 更新后的证书列表
|
|
939
|
+
*/
|
|
940
|
+
async downloadAndUpdate() {
|
|
941
|
+
const response = await this.downloadCertificates();
|
|
942
|
+
for (const cert of response.data) {
|
|
943
|
+
this.certificateManager.setPublicKey(cert.serialNo, cert.certificatePem);
|
|
944
|
+
}
|
|
945
|
+
return response.data;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* 使用 AES-256-GCM 解密证书内容
|
|
949
|
+
*
|
|
950
|
+
* @param encrypted - 加密的证书信息
|
|
951
|
+
* @returns PEM 格式的证书内容
|
|
952
|
+
*/
|
|
953
|
+
decryptCertificate(encrypted) {
|
|
954
|
+
const key = Buffer.from(this.apiV3Key, "utf-8");
|
|
955
|
+
const nonce = Buffer.from(encrypted.nonce, "utf-8");
|
|
956
|
+
const aad = Buffer.from(encrypted.associatedData, "utf-8");
|
|
957
|
+
const ciphertextBuffer = Buffer.from(encrypted.ciphertext, "base64");
|
|
958
|
+
const authTag = ciphertextBuffer.subarray(-16);
|
|
959
|
+
const encryptedData = ciphertextBuffer.subarray(0, -16);
|
|
960
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
|
|
961
|
+
decipher.setAuthTag(authTag);
|
|
962
|
+
decipher.setAAD(aad);
|
|
963
|
+
const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
964
|
+
return decrypted.toString("utf-8");
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
|
|
634
968
|
// src/services/jsapi.ts
|
|
635
969
|
var JsapiService = class {
|
|
636
970
|
client;
|
|
@@ -2421,6 +2755,9 @@ var BillService = class {
|
|
|
2421
2755
|
return this.client.downloadRaw(downloadUrl);
|
|
2422
2756
|
}
|
|
2423
2757
|
};
|
|
2758
|
+
var SUPPORTED_SIGNATURE_TYPES = /* @__PURE__ */ new Set(["WECHATPAY2-SHA256-RSA2048"]);
|
|
2759
|
+
var DEFAULT_SIGNATURE_TYPE = "WECHATPAY2-SHA256-RSA2048";
|
|
2760
|
+
var SUPPORTED_ALGORITHMS = /* @__PURE__ */ new Set(["AEAD_AES_256_GCM"]);
|
|
2424
2761
|
var CallbackHandler = class {
|
|
2425
2762
|
apiV3Key;
|
|
2426
2763
|
certificates;
|
|
@@ -2432,12 +2769,30 @@ var CallbackHandler = class {
|
|
|
2432
2769
|
* 验证回调通知签名
|
|
2433
2770
|
*
|
|
2434
2771
|
* 使用微信支付平台公钥验证回调通知的签名。
|
|
2772
|
+
* 支持读取 Wechatpay-Signature-Type 头识别签名类型。
|
|
2435
2773
|
*
|
|
2436
2774
|
* @param headers - 回调请求头
|
|
2437
2775
|
* @param body - 回调请求体(原始 JSON 字符串)
|
|
2438
2776
|
* @returns 签名验证是否通过
|
|
2777
|
+
* @throws 如果必填参数缺失、签名类型不支持或找不到对应的证书
|
|
2439
2778
|
*/
|
|
2440
2779
|
verifySignature(headers, body) {
|
|
2780
|
+
if (!headers["wechatpay-serial"]) {
|
|
2781
|
+
throw new Error("\u56DE\u8C03\u5934 wechatpay-serial \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2782
|
+
}
|
|
2783
|
+
if (!headers["wechatpay-signature"]) {
|
|
2784
|
+
throw new Error("\u56DE\u8C03\u5934 wechatpay-signature \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2785
|
+
}
|
|
2786
|
+
if (!headers["wechatpay-timestamp"]) {
|
|
2787
|
+
throw new Error("\u56DE\u8C03\u5934 wechatpay-timestamp \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2788
|
+
}
|
|
2789
|
+
if (!headers["wechatpay-nonce"]) {
|
|
2790
|
+
throw new Error("\u56DE\u8C03\u5934 wechatpay-nonce \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2791
|
+
}
|
|
2792
|
+
const signatureType = headers["wechatpay-signature-type"] ?? DEFAULT_SIGNATURE_TYPE;
|
|
2793
|
+
if (!SUPPORTED_SIGNATURE_TYPES.has(signatureType)) {
|
|
2794
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u7B7E\u540D\u7C7B\u578B: ${signatureType}`);
|
|
2795
|
+
}
|
|
2441
2796
|
const serialNo = headers["wechatpay-serial"];
|
|
2442
2797
|
const publicKey = this.certificates.getPublicKey(serialNo);
|
|
2443
2798
|
if (!publicKey) {
|
|
@@ -2458,10 +2813,22 @@ var CallbackHandler = class {
|
|
|
2458
2813
|
*
|
|
2459
2814
|
* @param notification - 回调通知 JSON 对象
|
|
2460
2815
|
* @returns 解密后的业务数据
|
|
2461
|
-
* @throws
|
|
2816
|
+
* @throws 如果通知结构无效、算法不匹配或解密失败
|
|
2462
2817
|
*/
|
|
2463
2818
|
decryptNotification(notification) {
|
|
2464
2819
|
const { resource } = notification;
|
|
2820
|
+
if (!resource.algorithm) {
|
|
2821
|
+
throw new Error("\u56DE\u8C03\u901A\u77E5 resource \u7F3A\u5C11 algorithm \u5B57\u6BB5");
|
|
2822
|
+
}
|
|
2823
|
+
if (!resource.ciphertext) {
|
|
2824
|
+
throw new Error("\u56DE\u8C03\u901A\u77E5 resource \u7F3A\u5C11 ciphertext \u5B57\u6BB5");
|
|
2825
|
+
}
|
|
2826
|
+
if (!resource.nonce) {
|
|
2827
|
+
throw new Error("\u56DE\u8C03\u901A\u77E5 resource \u7F3A\u5C11 nonce \u5B57\u6BB5");
|
|
2828
|
+
}
|
|
2829
|
+
if (!SUPPORTED_ALGORITHMS.has(resource.algorithm)) {
|
|
2830
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u52A0\u5BC6\u7B97\u6CD5: ${resource.algorithm}`);
|
|
2831
|
+
}
|
|
2465
2832
|
const plaintext = this.aesGcmDecrypt(
|
|
2466
2833
|
resource.ciphertext,
|
|
2467
2834
|
resource.associated_data,
|
|
@@ -2709,13 +3076,26 @@ var CallbackHandler = class {
|
|
|
2709
3076
|
* @param associatedData - 附加数据(用于 AEAD 认证)
|
|
2710
3077
|
* @param nonce - 随机串
|
|
2711
3078
|
* @returns 解密后的明文字符串
|
|
3079
|
+
* @throws 如果密文格式无效或解密失败
|
|
2712
3080
|
*/
|
|
2713
3081
|
aesGcmDecrypt(ciphertext, associatedData, nonce) {
|
|
3082
|
+
if (!ciphertext) {
|
|
3083
|
+
throw new Error("\u5BC6\u6587(ciphertext)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3084
|
+
}
|
|
3085
|
+
if (!nonce) {
|
|
3086
|
+
throw new Error("\u968F\u673A\u4E32(nonce)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3087
|
+
}
|
|
3088
|
+
if (this.apiV3Key.length !== 32) {
|
|
3089
|
+
throw new Error("APIv3 \u5BC6\u94A5\u65E0\u6548\uFF0C\u957F\u5EA6\u5FC5\u987B\u4E3A 32 \u4E2A\u5B57\u8282");
|
|
3090
|
+
}
|
|
2714
3091
|
const key = Buffer.from(this.apiV3Key, "utf-8");
|
|
2715
3092
|
const ciphertextBuffer = Buffer.from(ciphertext, "base64");
|
|
3093
|
+
if (ciphertextBuffer.length <= 16) {
|
|
3094
|
+
throw new Error("\u5BC6\u6587\u957F\u5EA6\u65E0\u6548\uFF0C\u5FC5\u987B\u5927\u4E8E 16 \u5B57\u8282\uFF08\u542B AuthTag\uFF09");
|
|
3095
|
+
}
|
|
2716
3096
|
const authTag = ciphertextBuffer.subarray(-16);
|
|
2717
3097
|
const encryptedData = ciphertextBuffer.subarray(0, -16);
|
|
2718
|
-
const decipher =
|
|
3098
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, Buffer.from(nonce, "utf-8"));
|
|
2719
3099
|
decipher.setAuthTag(authTag);
|
|
2720
3100
|
decipher.setAAD(Buffer.from(associatedData, "utf-8"));
|
|
2721
3101
|
const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
@@ -3843,13 +4223,327 @@ var SecurityService = class {
|
|
|
3843
4223
|
return this.client.post("/v3/security/echo", request);
|
|
3844
4224
|
}
|
|
3845
4225
|
};
|
|
4226
|
+
|
|
4227
|
+
// src/services/payrollcard.ts
|
|
4228
|
+
var PayrollCardService = class {
|
|
4229
|
+
client;
|
|
4230
|
+
constructor(client) {
|
|
4231
|
+
this.client = client;
|
|
4232
|
+
}
|
|
4233
|
+
/**
|
|
4234
|
+
* 查询授权关系
|
|
4235
|
+
*
|
|
4236
|
+
* 查询商户与用户之间的微工卡授权关系。
|
|
4237
|
+
*
|
|
4238
|
+
* @param params - 查询参数
|
|
4239
|
+
* @returns 授权关系信息
|
|
4240
|
+
*/
|
|
4241
|
+
async queryAuthorization(params) {
|
|
4242
|
+
return this.client.get("/v3/payroll-card/relations", params);
|
|
4243
|
+
}
|
|
4244
|
+
/**
|
|
4245
|
+
* 生成预授权 token
|
|
4246
|
+
*
|
|
4247
|
+
* 生成微工卡预授权 token,用于后续核身操作。
|
|
4248
|
+
*
|
|
4249
|
+
* @param request - 预授权请求参数
|
|
4250
|
+
* @returns 预授权 token 信息
|
|
4251
|
+
*/
|
|
4252
|
+
async createToken(request) {
|
|
4253
|
+
return this.client.post("/v3/payroll-card/tokens", request);
|
|
4254
|
+
}
|
|
4255
|
+
/**
|
|
4256
|
+
* 核身预下单
|
|
4257
|
+
*
|
|
4258
|
+
* 创建核身预下单,获取核身参数。
|
|
4259
|
+
*
|
|
4260
|
+
* @param request - 核身预下单请求参数
|
|
4261
|
+
* @returns 核身参数信息
|
|
4262
|
+
*/
|
|
4263
|
+
async createAuthentication(request) {
|
|
4264
|
+
return this.client.post("/v3/payroll-card/authentications", request);
|
|
4265
|
+
}
|
|
4266
|
+
/**
|
|
4267
|
+
* 查询核身结果
|
|
4268
|
+
*
|
|
4269
|
+
* 根据商户请求号查询核身结果。
|
|
4270
|
+
*
|
|
4271
|
+
* @param outRequestNo - 商户请求号
|
|
4272
|
+
* @param params - 查询参数
|
|
4273
|
+
* @returns 核身结果信息
|
|
4274
|
+
*/
|
|
4275
|
+
async queryAuthentication(outRequestNo, params) {
|
|
4276
|
+
return this.client.get(
|
|
4277
|
+
`/v3/payroll-card/authentications/out-request-no/${outRequestNo}`,
|
|
4278
|
+
params
|
|
4279
|
+
);
|
|
4280
|
+
}
|
|
4281
|
+
/**
|
|
4282
|
+
* 发起批量转账
|
|
4283
|
+
*
|
|
4284
|
+
* 通过工资卡渠道发起批量转账。
|
|
4285
|
+
*
|
|
4286
|
+
* @param request - 批量转账请求参数
|
|
4287
|
+
* @returns 批量转账结果
|
|
4288
|
+
*/
|
|
4289
|
+
async createTransferBatch(request) {
|
|
4290
|
+
return this.client.post("/v3/payroll-card/transfer-batches", request);
|
|
4291
|
+
}
|
|
4292
|
+
};
|
|
4293
|
+
|
|
4294
|
+
// src/services/scanandride.ts
|
|
4295
|
+
var ScanAndRideService = class {
|
|
4296
|
+
client;
|
|
4297
|
+
constructor(client) {
|
|
4298
|
+
this.client = client;
|
|
4299
|
+
}
|
|
4300
|
+
/**
|
|
4301
|
+
* 开通用户服务
|
|
4302
|
+
*
|
|
4303
|
+
* 用户授权开通刷码乘车服务。
|
|
4304
|
+
*
|
|
4305
|
+
* @param request - 开通服务请求参数
|
|
4306
|
+
* @returns 开通结果
|
|
4307
|
+
*/
|
|
4308
|
+
async createUserService(request) {
|
|
4309
|
+
return this.client.post("/v3/qrcode/user-services", request);
|
|
4310
|
+
}
|
|
4311
|
+
/**
|
|
4312
|
+
* 查询用户服务状态
|
|
4313
|
+
*
|
|
4314
|
+
* 查询用户是否已开通刷码乘车服务。
|
|
4315
|
+
*
|
|
4316
|
+
* @param outRequestNo - 商户请求号
|
|
4317
|
+
* @param params - 查询参数
|
|
4318
|
+
* @returns 用户服务状态
|
|
4319
|
+
*/
|
|
4320
|
+
async queryUserService(outRequestNo, params) {
|
|
4321
|
+
return this.client.get(`/v3/qrcode/user-services/out-request-no/${outRequestNo}`, params);
|
|
4322
|
+
}
|
|
4323
|
+
/**
|
|
4324
|
+
* 扣费受理
|
|
4325
|
+
*
|
|
4326
|
+
* 发起刷码乘车扣费请求。
|
|
4327
|
+
*
|
|
4328
|
+
* @param request - 扣费请求参数
|
|
4329
|
+
* @returns 扣费受理结果
|
|
4330
|
+
*/
|
|
4331
|
+
async createTransaction(request) {
|
|
4332
|
+
return this.client.post("/v3/qrcode/transactions", request);
|
|
4333
|
+
}
|
|
4334
|
+
/**
|
|
4335
|
+
* 查询扣费订单
|
|
4336
|
+
*
|
|
4337
|
+
* 根据商户订单号查询扣费订单状态。
|
|
4338
|
+
*
|
|
4339
|
+
* @param outTradeNo - 商户订单号
|
|
4340
|
+
* @param params - 查询参数
|
|
4341
|
+
* @returns 订单信息
|
|
4342
|
+
*/
|
|
4343
|
+
async queryTransaction(outTradeNo, params) {
|
|
4344
|
+
return this.client.get(`/v3/qrcode/transactions/out-trade-no/${outTradeNo}`, params);
|
|
4345
|
+
}
|
|
4346
|
+
};
|
|
4347
|
+
|
|
4348
|
+
// src/services/retailstore.ts
|
|
4349
|
+
var RetailStoreService = class {
|
|
4350
|
+
client;
|
|
4351
|
+
constructor(client) {
|
|
4352
|
+
this.client = client;
|
|
4353
|
+
}
|
|
4354
|
+
/**
|
|
4355
|
+
* 创建门店活动
|
|
4356
|
+
*
|
|
4357
|
+
* @param request - 活动创建请求参数
|
|
4358
|
+
* @returns 创建结果
|
|
4359
|
+
*/
|
|
4360
|
+
async createActivity(request) {
|
|
4361
|
+
return this.client.post("/v3/marketing/goldplan/retailstore/activities", request);
|
|
4362
|
+
}
|
|
4363
|
+
/**
|
|
4364
|
+
* 查询门店活动详情
|
|
4365
|
+
*
|
|
4366
|
+
* @param activityId - 活动ID
|
|
4367
|
+
* @returns 活动详情
|
|
4368
|
+
*/
|
|
4369
|
+
async queryActivity(activityId) {
|
|
4370
|
+
return this.client.get(`/v3/marketing/goldplan/retailstore/activities/${activityId}`);
|
|
4371
|
+
}
|
|
4372
|
+
/**
|
|
4373
|
+
* 更新门店活动
|
|
4374
|
+
*
|
|
4375
|
+
* @param activityId - 活动ID
|
|
4376
|
+
* @param request - 更新请求参数
|
|
4377
|
+
* @returns 更新结果
|
|
4378
|
+
*/
|
|
4379
|
+
async updateActivity(activityId, request) {
|
|
4380
|
+
return this.client.patch(
|
|
4381
|
+
`/v3/marketing/goldplan/retailstore/activities/${activityId}`,
|
|
4382
|
+
request
|
|
4383
|
+
);
|
|
4384
|
+
}
|
|
4385
|
+
/**
|
|
4386
|
+
* 创建门店资质
|
|
4387
|
+
*
|
|
4388
|
+
* @param request - 资质创建请求参数
|
|
4389
|
+
* @returns 创建结果
|
|
4390
|
+
*/
|
|
4391
|
+
async createQualification(request) {
|
|
4392
|
+
return this.client.post("/v3/marketing/goldplan/retailstore/qualifications", request);
|
|
4393
|
+
}
|
|
4394
|
+
/**
|
|
4395
|
+
* 查询门店资质
|
|
4396
|
+
*
|
|
4397
|
+
* @param qualificationId - 资质ID
|
|
4398
|
+
* @returns 资质详情
|
|
4399
|
+
*/
|
|
4400
|
+
async queryQualification(qualificationId) {
|
|
4401
|
+
return this.client.get(`/v3/marketing/goldplan/retailstore/qualifications/${qualificationId}`);
|
|
4402
|
+
}
|
|
4403
|
+
};
|
|
4404
|
+
|
|
4405
|
+
// src/services/goldplan.ts
|
|
4406
|
+
var GoldPlanService = class {
|
|
4407
|
+
client;
|
|
4408
|
+
constructor(client) {
|
|
4409
|
+
this.client = client;
|
|
4410
|
+
}
|
|
4411
|
+
/**
|
|
4412
|
+
* 查询商户零钱余额
|
|
4413
|
+
*
|
|
4414
|
+
* @param mchid - 商户号
|
|
4415
|
+
* @returns 零钱余额信息
|
|
4416
|
+
*/
|
|
4417
|
+
async queryBalance(mchid) {
|
|
4418
|
+
return this.client.get(`/v3/merchant/fund/balance/${mchid}`);
|
|
4419
|
+
}
|
|
4420
|
+
/**
|
|
4421
|
+
* 查询商户零钱流水
|
|
4422
|
+
*
|
|
4423
|
+
* @param mchid - 商户号
|
|
4424
|
+
* @param params - 查询参数
|
|
4425
|
+
* @returns 零钱流水列表
|
|
4426
|
+
*/
|
|
4427
|
+
async queryFlow(mchid, params) {
|
|
4428
|
+
return this.client.get(`/v3/merchant/fund/flow`, { mchid, ...params });
|
|
4429
|
+
}
|
|
4430
|
+
/**
|
|
4431
|
+
* 查询商家零钱状态
|
|
4432
|
+
*
|
|
4433
|
+
* @param mchid - 商户号
|
|
4434
|
+
* @returns 零钱状态信息
|
|
4435
|
+
*/
|
|
4436
|
+
async queryStatus(mchid) {
|
|
4437
|
+
return this.client.get(`/v3/merchant/fund/status/${mchid}`);
|
|
4438
|
+
}
|
|
4439
|
+
};
|
|
4440
|
+
|
|
4441
|
+
// src/services/lovefeast.ts
|
|
4442
|
+
var LoveFeastService = class {
|
|
4443
|
+
client;
|
|
4444
|
+
constructor(client) {
|
|
4445
|
+
this.client = client;
|
|
4446
|
+
}
|
|
4447
|
+
/**
|
|
4448
|
+
* 创建爱心餐品牌
|
|
4449
|
+
*
|
|
4450
|
+
* @param request - 品牌创建请求参数
|
|
4451
|
+
* @returns 创建结果
|
|
4452
|
+
*/
|
|
4453
|
+
async createBrand(request) {
|
|
4454
|
+
return this.client.post("/v3/lovefeast/brands", request);
|
|
4455
|
+
}
|
|
4456
|
+
/**
|
|
4457
|
+
* 查询爱心餐品牌
|
|
4458
|
+
*
|
|
4459
|
+
* @param brandId - 品牌ID
|
|
4460
|
+
* @returns 品牌详情
|
|
4461
|
+
*/
|
|
4462
|
+
async queryBrand(brandId) {
|
|
4463
|
+
return this.client.get(`/v3/lovefeast/brands/${brandId}`);
|
|
4464
|
+
}
|
|
4465
|
+
/**
|
|
4466
|
+
* 创建爱心餐订单
|
|
4467
|
+
*
|
|
4468
|
+
* @param request - 订单创建请求参数
|
|
4469
|
+
* @returns 订单创建结果
|
|
4470
|
+
*/
|
|
4471
|
+
async createOrder(request) {
|
|
4472
|
+
return this.client.post("/v3/lovefeast/orders", request);
|
|
4473
|
+
}
|
|
4474
|
+
/**
|
|
4475
|
+
* 查询爱心餐订单
|
|
4476
|
+
*
|
|
4477
|
+
* @param outTradeNo - 商户订单号
|
|
4478
|
+
* @param params - 查询参数
|
|
4479
|
+
* @returns 订单详情
|
|
4480
|
+
*/
|
|
4481
|
+
async queryOrder(outTradeNo, params) {
|
|
4482
|
+
return this.client.get(`/v3/lovefeast/orders/out-trade-no/${outTradeNo}`, params);
|
|
4483
|
+
}
|
|
4484
|
+
};
|
|
4485
|
+
|
|
4486
|
+
// src/services/merchant-exclusive-coupon.ts
|
|
4487
|
+
var MerchantExclusiveCouponService = class {
|
|
4488
|
+
client;
|
|
4489
|
+
constructor(client) {
|
|
4490
|
+
this.client = client;
|
|
4491
|
+
}
|
|
4492
|
+
/**
|
|
4493
|
+
* 创建优惠券批次
|
|
4494
|
+
*
|
|
4495
|
+
* @param request - 批次创建请求参数
|
|
4496
|
+
* @returns 创建结果
|
|
4497
|
+
*/
|
|
4498
|
+
async createCouponStock(request) {
|
|
4499
|
+
return this.client.post("/v3/marketing/busifavor/stocks", request);
|
|
4500
|
+
}
|
|
4501
|
+
/**
|
|
4502
|
+
* 查询优惠券批次详情
|
|
4503
|
+
*
|
|
4504
|
+
* @param stockId - 批次ID
|
|
4505
|
+
* @returns 批次详情
|
|
4506
|
+
*/
|
|
4507
|
+
async queryCouponStock(stockId) {
|
|
4508
|
+
return this.client.get(`/v3/marketing/busifavor/stocks/${stockId}`);
|
|
4509
|
+
}
|
|
4510
|
+
/**
|
|
4511
|
+
* 发放优惠券
|
|
4512
|
+
*
|
|
4513
|
+
* @param request - 发放请求参数
|
|
4514
|
+
* @returns 发放结果
|
|
4515
|
+
*/
|
|
4516
|
+
async sendCoupon(request) {
|
|
4517
|
+
return this.client.post("/v3/marketing/busifavor/coupons", request);
|
|
4518
|
+
}
|
|
4519
|
+
/**
|
|
4520
|
+
* 查询用户优惠券
|
|
4521
|
+
*
|
|
4522
|
+
* @param openid - 用户标识
|
|
4523
|
+
* @param params - 查询参数
|
|
4524
|
+
* @returns 用户优惠券列表
|
|
4525
|
+
*/
|
|
4526
|
+
async queryUserCoupons(openid, params) {
|
|
4527
|
+
return this.client.get(`/v3/marketing/busifavor/users/${openid}/coupons`, params);
|
|
4528
|
+
}
|
|
4529
|
+
/**
|
|
4530
|
+
* 查询优惠券详情
|
|
4531
|
+
*
|
|
4532
|
+
* @param couponId - 优惠券ID
|
|
4533
|
+
* @param params - 查询参数
|
|
4534
|
+
* @returns 优惠券详情
|
|
4535
|
+
*/
|
|
4536
|
+
async queryCoupon(couponId, params) {
|
|
4537
|
+
return this.client.get(`/v3/marketing/busifavor/users/coupons/${couponId}`, params);
|
|
4538
|
+
}
|
|
4539
|
+
};
|
|
3846
4540
|
function generateAppPaySign(appId, timeStamp, nonceStr, prepayId, privateKey) {
|
|
3847
4541
|
const signString = `${appId}
|
|
3848
4542
|
${timeStamp}
|
|
3849
4543
|
${nonceStr}
|
|
3850
4544
|
prepay_id=${prepayId}
|
|
3851
4545
|
`;
|
|
3852
|
-
const signer =
|
|
4546
|
+
const signer = crypto2.createSign("RSA-SHA256");
|
|
3853
4547
|
signer.update(signString);
|
|
3854
4548
|
signer.end();
|
|
3855
4549
|
return signer.sign(privateKey, "base64");
|
|
@@ -3874,13 +4568,13 @@ ${timeStamp}
|
|
|
3874
4568
|
${nonceStr}
|
|
3875
4569
|
prepay_id=${prepayId}
|
|
3876
4570
|
`;
|
|
3877
|
-
const signer =
|
|
4571
|
+
const signer = crypto2.createSign("RSA-SHA256");
|
|
3878
4572
|
signer.update(signString);
|
|
3879
4573
|
signer.end();
|
|
3880
4574
|
return signer.sign(privateKey, "base64");
|
|
3881
4575
|
}
|
|
3882
4576
|
function generateNonceStr() {
|
|
3883
|
-
return
|
|
4577
|
+
return crypto2.randomUUID().replace(/-/g, "");
|
|
3884
4578
|
}
|
|
3885
4579
|
function buildJsapiBridgeConfig(appId, prepayId, privateKey) {
|
|
3886
4580
|
const timeStamp = String(Math.floor(Date.now() / 1e3));
|
|
@@ -3914,7 +4608,7 @@ ${timeStamp}
|
|
|
3914
4608
|
${nonceStr}
|
|
3915
4609
|
${packageStr}
|
|
3916
4610
|
`;
|
|
3917
|
-
const signer =
|
|
4611
|
+
const signer = crypto2.createSign("RSA-SHA256");
|
|
3918
4612
|
signer.update(signString);
|
|
3919
4613
|
signer.end();
|
|
3920
4614
|
return signer.sign(privateKey, "base64");
|
|
@@ -4064,7 +4758,7 @@ function buildH5CouponUrl(params, signKey) {
|
|
|
4064
4758
|
}
|
|
4065
4759
|
const sortedKeys = Object.keys(signFields).sort();
|
|
4066
4760
|
const signStr = sortedKeys.map((k) => `${k}=${signFields[k]}`).join("&") + `&key=${signKey}`;
|
|
4067
|
-
const sign2 =
|
|
4761
|
+
const sign2 = crypto2.createHmac("sha256", signKey).update(signStr).digest("hex").toUpperCase();
|
|
4068
4762
|
const urlParams = new URLSearchParams({
|
|
4069
4763
|
stock_id: params.stock_id,
|
|
4070
4764
|
out_request_no: params.out_request_no,
|
|
@@ -4166,7 +4860,7 @@ ${timestamp}
|
|
|
4166
4860
|
${nonceStr}
|
|
4167
4861
|
${packageStr}
|
|
4168
4862
|
`;
|
|
4169
|
-
const signer =
|
|
4863
|
+
const signer = crypto2.createSign("RSA-SHA256");
|
|
4170
4864
|
signer.update(signString);
|
|
4171
4865
|
signer.end();
|
|
4172
4866
|
const sign2 = signer.sign(privateKey, "base64");
|
|
@@ -4181,6 +4875,6 @@ ${packageStr}
|
|
|
4181
4875
|
};
|
|
4182
4876
|
}
|
|
4183
4877
|
|
|
4184
|
-
export { AppService, BillService, BusinessCircleService, CallbackHandler, CertificateManager, CombineAppService, CombineH5Service, CombineMiniProgramService, CombineNativeService, CombineService, ComplaintService, CouponService, H5Service, JsapiService, MedInsService, MediaService, MerchantTransferService, NativeService, ParkingService, PartnershipService, PayGiftActivityService, PayScoreService, ProfitSharingService, SecurityService, SmartGuideService, WxPayClient, WxPayError, buildAppBridgeConfig, buildAuthorization, buildH5CouponUrl, buildJsapiBridgeConfig, buildMedInsJsapiBridgeConfig, buildMedInsMiniProgramBridgeConfig, buildMerchantTransferAuthorizationJsapiBridgeConfig, buildMerchantTransferJsapiBridgeConfig, buildMerchantTransferMiniProgramBridgeConfig, buildMiniProgramBridgeConfig, buildParkingAppBridgePath, buildParkingH5BridgeUrl, buildParkingMiniProgramBridgeConfig, buildParkingRepayBridgeConfig, buildPayScoreAppBridgeConfig, buildPayScoreDetailAppBridgeConfig, buildPayScoreDetailJsapiBridgeConfig, buildPayScoreDetailMiniProgramBridgeConfig, buildPayScoreJsapiBridgeConfig, buildPayScoreMiniProgramBridgeConfig, buildSignString, generateAppPaySign, generateNonce, generateNonceStr, generatePayScorePaySign, generatePaySign, oaepEncrypt, sign, verifySignature };
|
|
4878
|
+
export { AppService, BillService, BusinessCircleService, CallbackHandler, CertificateManager, CertificateService, CombineAppService, CombineH5Service, CombineMiniProgramService, CombineNativeService, CombineService, ComplaintService, CouponService, DecryptionException, GoldPlanService, H5Service, HttpException, JsapiService, LoveFeastService, MalformedMessageException, MedInsService, MediaService, MerchantExclusiveCouponService, MerchantTransferService, NativeService, ParkingService, PartnershipService, PayGiftActivityService, PayScoreService, PayrollCardService, ProfitSharingService, RetailStoreService, ScanAndRideService, SecurityService, ServiceException, SmartGuideService, ValidationException, WxPayClient, WxPayError, buildAppBridgeConfig, buildAuthorization, buildH5CouponUrl, buildJsapiBridgeConfig, buildMedInsJsapiBridgeConfig, buildMedInsMiniProgramBridgeConfig, buildMerchantTransferAuthorizationJsapiBridgeConfig, buildMerchantTransferJsapiBridgeConfig, buildMerchantTransferMiniProgramBridgeConfig, buildMiniProgramBridgeConfig, buildParkingAppBridgePath, buildParkingH5BridgeUrl, buildParkingMiniProgramBridgeConfig, buildParkingRepayBridgeConfig, buildPayScoreAppBridgeConfig, buildPayScoreDetailAppBridgeConfig, buildPayScoreDetailJsapiBridgeConfig, buildPayScoreDetailMiniProgramBridgeConfig, buildPayScoreJsapiBridgeConfig, buildPayScoreMiniProgramBridgeConfig, buildSignString, decryptSensitiveFields, decryptSensitiveFieldsInArray, encryptSensitiveFields, encryptSensitiveFieldsInArray, generateAppPaySign, generateNonce, generateNonceStr, generatePayScorePaySign, generatePaySign, isTimestampValid, oaepDecrypt, oaepEncrypt, registerSensitiveFields, sign, verifySignature };
|
|
4185
4879
|
//# sourceMappingURL=index.mjs.map
|
|
4186
4880
|
//# sourceMappingURL=index.mjs.map
|