wxpay-nodejs-sdk 0.2.2 → 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/dist/index.js CHANGED
@@ -189,13 +189,8 @@ var CertificateManager = class {
189
189
  }
190
190
  }
191
191
  };
192
- var SDK_VERSION = "0.2.1";
193
- function getUserAgent() {
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 WxPayError(response.status, headers, errorDetail);
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 WxPayError(response.status, headers, {
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 WxPayError(response.status, headers, {
309
- code: "PARSE_ERROR",
310
- message: error instanceof Error ? error.message : "\u54CD\u5E94\u6570\u636E\u89E3\u6790\u5931\u8D25"
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
- const signatureType = headers["wechatpay-signature-type"];
2667
- const serialNo = headers["wechatpay-serial"];
2668
- if (signatureType && !SUPPORTED_SIGNATURE_TYPES.has(signatureType)) {
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