wxpay-nodejs-sdk 0.2.2 → 0.2.4
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 +4 -0
- package/dist/index.d.mts +153 -6
- package/dist/index.d.ts +153 -6
- package/dist/index.js +190 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +181 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -189,13 +189,8 @@ var CertificateManager = class {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
};
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const platform = os__default.default.platform();
|
|
195
|
-
const arch = os__default.default.arch();
|
|
196
|
-
const nodeVersion = process.version;
|
|
197
|
-
return `wxpay-nodejs-sdk/${SDK_VERSION} (${platform} ${arch}) Node.js/${nodeVersion}`;
|
|
198
|
-
}
|
|
192
|
+
|
|
193
|
+
// src/utils/exceptions.ts
|
|
199
194
|
var WxPayError = class extends Error {
|
|
200
195
|
/** HTTP 状态码 */
|
|
201
196
|
status;
|
|
@@ -218,7 +213,64 @@ var WxPayError = class extends Error {
|
|
|
218
213
|
get isServerError() {
|
|
219
214
|
return this.status >= 500 && this.status < 600;
|
|
220
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* 判断是否为特定错误码
|
|
218
|
+
*
|
|
219
|
+
* @param code - 错误码
|
|
220
|
+
* @returns 是否匹配
|
|
221
|
+
*/
|
|
222
|
+
isApiError(code) {
|
|
223
|
+
return this.detail.code === code;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var ServiceException = class extends WxPayError {
|
|
227
|
+
/** 微信支付业务错误码 */
|
|
228
|
+
errorCode;
|
|
229
|
+
/** 微信支付业务错误信息 */
|
|
230
|
+
errorMessage;
|
|
231
|
+
constructor(status, headers, detail) {
|
|
232
|
+
super(status, headers, detail);
|
|
233
|
+
this.name = "ServiceException";
|
|
234
|
+
this.errorCode = detail.code;
|
|
235
|
+
this.errorMessage = detail.message;
|
|
236
|
+
}
|
|
221
237
|
};
|
|
238
|
+
var HttpException = class extends WxPayError {
|
|
239
|
+
constructor(message, cause) {
|
|
240
|
+
super(0, {}, { code: "NETWORK_ERROR", message });
|
|
241
|
+
this.name = "HttpException";
|
|
242
|
+
if (cause) {
|
|
243
|
+
this.cause = cause;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
var ValidationException = class extends WxPayError {
|
|
248
|
+
constructor(message) {
|
|
249
|
+
super(0, {}, { code: "SIGN_ERROR", message });
|
|
250
|
+
this.name = "ValidationException";
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
var DecryptionException = class extends WxPayError {
|
|
254
|
+
constructor(message) {
|
|
255
|
+
super(0, {}, { code: "DECRYPT_ERROR", message });
|
|
256
|
+
this.name = "DecryptionException";
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
var MalformedMessageException = class extends WxPayError {
|
|
260
|
+
constructor(message) {
|
|
261
|
+
super(0, {}, { code: "PARSE_ERROR", message });
|
|
262
|
+
this.name = "MalformedMessageException";
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/utils/http.ts
|
|
267
|
+
var SDK_VERSION = "0.2.1";
|
|
268
|
+
function getUserAgent() {
|
|
269
|
+
const platform = os__default.default.platform();
|
|
270
|
+
const arch = os__default.default.arch();
|
|
271
|
+
const nodeVersion = process.version;
|
|
272
|
+
return `wxpay-nodejs-sdk/${SDK_VERSION} (${platform} ${arch}) Node.js/${nodeVersion}`;
|
|
273
|
+
}
|
|
222
274
|
function buildUrl(base, path, params) {
|
|
223
275
|
const url = new URL(path, base);
|
|
224
276
|
if (params) {
|
|
@@ -280,7 +332,7 @@ async function parseResponse(response, verify) {
|
|
|
280
332
|
message: `HTTP ${response.status}: ${response.statusText}`
|
|
281
333
|
};
|
|
282
334
|
}
|
|
283
|
-
throw new
|
|
335
|
+
throw new ServiceException(response.status, headers, errorDetail);
|
|
284
336
|
}
|
|
285
337
|
if (verify) {
|
|
286
338
|
const signature = headers["wechatpay-signature"];
|
|
@@ -290,10 +342,7 @@ async function parseResponse(response, verify) {
|
|
|
290
342
|
if (signature && timestamp && nonce && serial) {
|
|
291
343
|
const valid = verify(rawBody, signature, timestamp, nonce, serial);
|
|
292
344
|
if (!valid) {
|
|
293
|
-
throw new
|
|
294
|
-
code: "SIGN_ERROR",
|
|
295
|
-
message: "\u5E94\u7B54\u7B7E\u540D\u9A8C\u8BC1\u5931\u8D25"
|
|
296
|
-
});
|
|
345
|
+
throw new ValidationException("\u5E94\u7B54\u7B7E\u540D\u9A8C\u8BC1\u5931\u8D25");
|
|
297
346
|
}
|
|
298
347
|
}
|
|
299
348
|
}
|
|
@@ -305,10 +354,9 @@ async function parseResponse(response, verify) {
|
|
|
305
354
|
}
|
|
306
355
|
data = parsed;
|
|
307
356
|
} catch (error) {
|
|
308
|
-
throw new
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
});
|
|
357
|
+
throw new MalformedMessageException(
|
|
358
|
+
error instanceof Error ? error.message : "\u54CD\u5E94\u6570\u636E\u89E3\u6790\u5931\u8D25"
|
|
359
|
+
);
|
|
312
360
|
}
|
|
313
361
|
return {
|
|
314
362
|
status: response.status,
|
|
@@ -326,6 +374,12 @@ ${body}
|
|
|
326
374
|
`;
|
|
327
375
|
}
|
|
328
376
|
function sign(signString, privateKey) {
|
|
377
|
+
if (!signString) {
|
|
378
|
+
throw new Error("\u7B7E\u540D\u4E32\u4E0D\u80FD\u4E3A\u7A7A");
|
|
379
|
+
}
|
|
380
|
+
if (!privateKey) {
|
|
381
|
+
throw new Error("\u5546\u6237\u79C1\u94A5\u4E0D\u80FD\u4E3A\u7A7A");
|
|
382
|
+
}
|
|
329
383
|
const signer = crypto2__default.default.createSign("RSA-SHA256");
|
|
330
384
|
signer.update(signString);
|
|
331
385
|
signer.end();
|
|
@@ -345,6 +399,18 @@ function isTimestampValid(timestamp) {
|
|
|
345
399
|
return Math.abs(now - responseTime) < RESPONSE_EXPIRED_SECONDS;
|
|
346
400
|
}
|
|
347
401
|
function verifySignature(body, signature, timestamp, nonce, publicKey) {
|
|
402
|
+
if (!signature) {
|
|
403
|
+
throw new Error("\u7B7E\u540D\u503C(signature)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
404
|
+
}
|
|
405
|
+
if (!timestamp) {
|
|
406
|
+
throw new Error("\u65F6\u95F4\u6233(timestamp)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
407
|
+
}
|
|
408
|
+
if (!nonce) {
|
|
409
|
+
throw new Error("\u968F\u673A\u4E32(nonce)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
410
|
+
}
|
|
411
|
+
if (!publicKey) {
|
|
412
|
+
throw new Error("\u516C\u94A5(publicKey)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
413
|
+
}
|
|
348
414
|
if (!isTimestampValid(timestamp)) {
|
|
349
415
|
return false;
|
|
350
416
|
}
|
|
@@ -358,6 +424,12 @@ ${body}
|
|
|
358
424
|
return verifier.verify(publicKey, signature, "base64");
|
|
359
425
|
}
|
|
360
426
|
function oaepEncrypt(plaintext, publicKey) {
|
|
427
|
+
if (!publicKey) {
|
|
428
|
+
throw new Error("\u52A0\u5BC6\u516C\u94A5\u4E0D\u80FD\u4E3A\u7A7A");
|
|
429
|
+
}
|
|
430
|
+
if (!plaintext) {
|
|
431
|
+
return "";
|
|
432
|
+
}
|
|
361
433
|
const encrypted = crypto2__default.default.publicEncrypt(
|
|
362
434
|
{
|
|
363
435
|
key: publicKey,
|
|
@@ -369,6 +441,12 @@ function oaepEncrypt(plaintext, publicKey) {
|
|
|
369
441
|
return encrypted.toString("base64");
|
|
370
442
|
}
|
|
371
443
|
function oaepDecrypt(ciphertext, privateKey) {
|
|
444
|
+
if (!privateKey) {
|
|
445
|
+
throw new Error("\u89E3\u5BC6\u79C1\u94A5\u4E0D\u80FD\u4E3A\u7A7A");
|
|
446
|
+
}
|
|
447
|
+
if (!ciphertext) {
|
|
448
|
+
return "";
|
|
449
|
+
}
|
|
372
450
|
const decrypted = crypto2__default.default.privateDecrypt(
|
|
373
451
|
{
|
|
374
452
|
key: privateKey,
|
|
@@ -783,6 +861,48 @@ var WxPayClient = class _WxPayClient {
|
|
|
783
861
|
};
|
|
784
862
|
}
|
|
785
863
|
};
|
|
864
|
+
|
|
865
|
+
// src/utils/sensitive.ts
|
|
866
|
+
var sensitiveFields = /* @__PURE__ */ new Map();
|
|
867
|
+
function registerSensitiveFields(typeName, fields) {
|
|
868
|
+
sensitiveFields.set(typeName, new Set(fields));
|
|
869
|
+
}
|
|
870
|
+
function getSensitiveFields(typeName) {
|
|
871
|
+
return sensitiveFields.get(typeName) ?? /* @__PURE__ */ new Set();
|
|
872
|
+
}
|
|
873
|
+
function encryptSensitiveFields(obj, typeName, publicKey) {
|
|
874
|
+
const fields = getSensitiveFields(typeName);
|
|
875
|
+
if (fields.size === 0) return obj;
|
|
876
|
+
const result = { ...obj };
|
|
877
|
+
for (const field of fields) {
|
|
878
|
+
const value = result[field];
|
|
879
|
+
if (typeof value === "string" && value.length > 0) {
|
|
880
|
+
result[field] = oaepEncrypt(value, publicKey);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return result;
|
|
884
|
+
}
|
|
885
|
+
function decryptSensitiveFields(obj, typeName, privateKey) {
|
|
886
|
+
const fields = getSensitiveFields(typeName);
|
|
887
|
+
if (fields.size === 0) return obj;
|
|
888
|
+
const result = { ...obj };
|
|
889
|
+
for (const field of fields) {
|
|
890
|
+
const value = result[field];
|
|
891
|
+
if (typeof value === "string" && value.length > 0) {
|
|
892
|
+
try {
|
|
893
|
+
result[field] = oaepDecrypt(value, privateKey);
|
|
894
|
+
} catch {
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return result;
|
|
899
|
+
}
|
|
900
|
+
function encryptSensitiveFieldsInArray(arr, typeName, publicKey) {
|
|
901
|
+
return arr.map((item) => encryptSensitiveFields(item, typeName, publicKey));
|
|
902
|
+
}
|
|
903
|
+
function decryptSensitiveFieldsInArray(arr, typeName, privateKey) {
|
|
904
|
+
return arr.map((item) => decryptSensitiveFields(item, typeName, privateKey));
|
|
905
|
+
}
|
|
786
906
|
var CertificateService = class {
|
|
787
907
|
client;
|
|
788
908
|
apiV3Key;
|
|
@@ -2644,6 +2764,8 @@ var BillService = class {
|
|
|
2644
2764
|
}
|
|
2645
2765
|
};
|
|
2646
2766
|
var SUPPORTED_SIGNATURE_TYPES = /* @__PURE__ */ new Set(["WECHATPAY2-SHA256-RSA2048"]);
|
|
2767
|
+
var DEFAULT_SIGNATURE_TYPE = "WECHATPAY2-SHA256-RSA2048";
|
|
2768
|
+
var SUPPORTED_ALGORITHMS = /* @__PURE__ */ new Set(["AEAD_AES_256_GCM"]);
|
|
2647
2769
|
var CallbackHandler = class {
|
|
2648
2770
|
apiV3Key;
|
|
2649
2771
|
certificates;
|
|
@@ -2660,14 +2782,26 @@ var CallbackHandler = class {
|
|
|
2660
2782
|
* @param headers - 回调请求头
|
|
2661
2783
|
* @param body - 回调请求体(原始 JSON 字符串)
|
|
2662
2784
|
* @returns 签名验证是否通过
|
|
2663
|
-
* @throws
|
|
2785
|
+
* @throws 如果必填参数缺失、签名类型不支持或找不到对应的证书
|
|
2664
2786
|
*/
|
|
2665
2787
|
verifySignature(headers, body) {
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2788
|
+
if (!headers["wechatpay-serial"]) {
|
|
2789
|
+
throw new Error("\u56DE\u8C03\u5934 wechatpay-serial \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2790
|
+
}
|
|
2791
|
+
if (!headers["wechatpay-signature"]) {
|
|
2792
|
+
throw new Error("\u56DE\u8C03\u5934 wechatpay-signature \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2793
|
+
}
|
|
2794
|
+
if (!headers["wechatpay-timestamp"]) {
|
|
2795
|
+
throw new Error("\u56DE\u8C03\u5934 wechatpay-timestamp \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2796
|
+
}
|
|
2797
|
+
if (!headers["wechatpay-nonce"]) {
|
|
2798
|
+
throw new Error("\u56DE\u8C03\u5934 wechatpay-nonce \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2799
|
+
}
|
|
2800
|
+
const signatureType = headers["wechatpay-signature-type"] ?? DEFAULT_SIGNATURE_TYPE;
|
|
2801
|
+
if (!SUPPORTED_SIGNATURE_TYPES.has(signatureType)) {
|
|
2669
2802
|
throw new Error(`\u4E0D\u652F\u6301\u7684\u7B7E\u540D\u7C7B\u578B: ${signatureType}`);
|
|
2670
2803
|
}
|
|
2804
|
+
const serialNo = headers["wechatpay-serial"];
|
|
2671
2805
|
const publicKey = this.certificates.getPublicKey(serialNo);
|
|
2672
2806
|
if (!publicKey) {
|
|
2673
2807
|
throw new Error(`\u672A\u627E\u5230\u5E8F\u5217\u53F7\u4E3A ${serialNo} \u7684\u5E73\u53F0\u8BC1\u4E66\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u914D\u7F6E\u5E73\u53F0\u8BC1\u4E66`);
|
|
@@ -2687,10 +2821,22 @@ var CallbackHandler = class {
|
|
|
2687
2821
|
*
|
|
2688
2822
|
* @param notification - 回调通知 JSON 对象
|
|
2689
2823
|
* @returns 解密后的业务数据
|
|
2690
|
-
* @throws
|
|
2824
|
+
* @throws 如果通知结构无效、算法不匹配或解密失败
|
|
2691
2825
|
*/
|
|
2692
2826
|
decryptNotification(notification) {
|
|
2693
2827
|
const { resource } = notification;
|
|
2828
|
+
if (!resource.algorithm) {
|
|
2829
|
+
throw new Error("\u56DE\u8C03\u901A\u77E5 resource \u7F3A\u5C11 algorithm \u5B57\u6BB5");
|
|
2830
|
+
}
|
|
2831
|
+
if (!resource.ciphertext) {
|
|
2832
|
+
throw new Error("\u56DE\u8C03\u901A\u77E5 resource \u7F3A\u5C11 ciphertext \u5B57\u6BB5");
|
|
2833
|
+
}
|
|
2834
|
+
if (!resource.nonce) {
|
|
2835
|
+
throw new Error("\u56DE\u8C03\u901A\u77E5 resource \u7F3A\u5C11 nonce \u5B57\u6BB5");
|
|
2836
|
+
}
|
|
2837
|
+
if (!SUPPORTED_ALGORITHMS.has(resource.algorithm)) {
|
|
2838
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u52A0\u5BC6\u7B97\u6CD5: ${resource.algorithm}`);
|
|
2839
|
+
}
|
|
2694
2840
|
const plaintext = this.aesGcmDecrypt(
|
|
2695
2841
|
resource.ciphertext,
|
|
2696
2842
|
resource.associated_data,
|
|
@@ -2938,10 +3084,23 @@ var CallbackHandler = class {
|
|
|
2938
3084
|
* @param associatedData - 附加数据(用于 AEAD 认证)
|
|
2939
3085
|
* @param nonce - 随机串
|
|
2940
3086
|
* @returns 解密后的明文字符串
|
|
3087
|
+
* @throws 如果密文格式无效或解密失败
|
|
2941
3088
|
*/
|
|
2942
3089
|
aesGcmDecrypt(ciphertext, associatedData, nonce) {
|
|
3090
|
+
if (!ciphertext) {
|
|
3091
|
+
throw new Error("\u5BC6\u6587(ciphertext)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3092
|
+
}
|
|
3093
|
+
if (!nonce) {
|
|
3094
|
+
throw new Error("\u968F\u673A\u4E32(nonce)\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3095
|
+
}
|
|
3096
|
+
if (this.apiV3Key.length !== 32) {
|
|
3097
|
+
throw new Error("APIv3 \u5BC6\u94A5\u65E0\u6548\uFF0C\u957F\u5EA6\u5FC5\u987B\u4E3A 32 \u4E2A\u5B57\u8282");
|
|
3098
|
+
}
|
|
2943
3099
|
const key = Buffer.from(this.apiV3Key, "utf-8");
|
|
2944
3100
|
const ciphertextBuffer = Buffer.from(ciphertext, "base64");
|
|
3101
|
+
if (ciphertextBuffer.length <= 16) {
|
|
3102
|
+
throw new Error("\u5BC6\u6587\u957F\u5EA6\u65E0\u6548\uFF0C\u5FC5\u987B\u5927\u4E8E 16 \u5B57\u8282\uFF08\u542B AuthTag\uFF09");
|
|
3103
|
+
}
|
|
2945
3104
|
const authTag = ciphertextBuffer.subarray(-16);
|
|
2946
3105
|
const encryptedData = ciphertextBuffer.subarray(0, -16);
|
|
2947
3106
|
const decipher = crypto2__default.default.createDecipheriv("aes-256-gcm", key, Buffer.from(nonce, "utf-8"));
|
|
@@ -4737,10 +4896,13 @@ exports.CombineNativeService = CombineNativeService;
|
|
|
4737
4896
|
exports.CombineService = CombineService;
|
|
4738
4897
|
exports.ComplaintService = ComplaintService;
|
|
4739
4898
|
exports.CouponService = CouponService;
|
|
4899
|
+
exports.DecryptionException = DecryptionException;
|
|
4740
4900
|
exports.GoldPlanService = GoldPlanService;
|
|
4741
4901
|
exports.H5Service = H5Service;
|
|
4902
|
+
exports.HttpException = HttpException;
|
|
4742
4903
|
exports.JsapiService = JsapiService;
|
|
4743
4904
|
exports.LoveFeastService = LoveFeastService;
|
|
4905
|
+
exports.MalformedMessageException = MalformedMessageException;
|
|
4744
4906
|
exports.MedInsService = MedInsService;
|
|
4745
4907
|
exports.MediaService = MediaService;
|
|
4746
4908
|
exports.MerchantExclusiveCouponService = MerchantExclusiveCouponService;
|
|
@@ -4755,7 +4917,9 @@ exports.ProfitSharingService = ProfitSharingService;
|
|
|
4755
4917
|
exports.RetailStoreService = RetailStoreService;
|
|
4756
4918
|
exports.ScanAndRideService = ScanAndRideService;
|
|
4757
4919
|
exports.SecurityService = SecurityService;
|
|
4920
|
+
exports.ServiceException = ServiceException;
|
|
4758
4921
|
exports.SmartGuideService = SmartGuideService;
|
|
4922
|
+
exports.ValidationException = ValidationException;
|
|
4759
4923
|
exports.WxPayClient = WxPayClient;
|
|
4760
4924
|
exports.WxPayError = WxPayError;
|
|
4761
4925
|
exports.buildAppBridgeConfig = buildAppBridgeConfig;
|
|
@@ -4779,6 +4943,10 @@ exports.buildPayScoreDetailMiniProgramBridgeConfig = buildPayScoreDetailMiniProg
|
|
|
4779
4943
|
exports.buildPayScoreJsapiBridgeConfig = buildPayScoreJsapiBridgeConfig;
|
|
4780
4944
|
exports.buildPayScoreMiniProgramBridgeConfig = buildPayScoreMiniProgramBridgeConfig;
|
|
4781
4945
|
exports.buildSignString = buildSignString;
|
|
4946
|
+
exports.decryptSensitiveFields = decryptSensitiveFields;
|
|
4947
|
+
exports.decryptSensitiveFieldsInArray = decryptSensitiveFieldsInArray;
|
|
4948
|
+
exports.encryptSensitiveFields = encryptSensitiveFields;
|
|
4949
|
+
exports.encryptSensitiveFieldsInArray = encryptSensitiveFieldsInArray;
|
|
4782
4950
|
exports.generateAppPaySign = generateAppPaySign;
|
|
4783
4951
|
exports.generateNonce = generateNonce;
|
|
4784
4952
|
exports.generateNonceStr = generateNonceStr;
|
|
@@ -4787,6 +4955,7 @@ exports.generatePaySign = generatePaySign;
|
|
|
4787
4955
|
exports.isTimestampValid = isTimestampValid;
|
|
4788
4956
|
exports.oaepDecrypt = oaepDecrypt;
|
|
4789
4957
|
exports.oaepEncrypt = oaepEncrypt;
|
|
4958
|
+
exports.registerSensitiveFields = registerSensitiveFields;
|
|
4790
4959
|
exports.sign = sign;
|
|
4791
4960
|
exports.verifySignature = verifySignature;
|
|
4792
4961
|
//# sourceMappingURL=index.js.map
|