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/dist/index.mjs CHANGED
@@ -181,13 +181,8 @@ var CertificateManager = class {
181
181
  }
182
182
  }
183
183
  };
184
- var SDK_VERSION = "0.2.1";
185
- function getUserAgent() {
186
- const platform = os.platform();
187
- const arch = os.arch();
188
- const nodeVersion = process.version;
189
- return `wxpay-nodejs-sdk/${SDK_VERSION} (${platform} ${arch}) Node.js/${nodeVersion}`;
190
- }
184
+
185
+ // src/utils/exceptions.ts
191
186
  var WxPayError = class extends Error {
192
187
  /** HTTP 状态码 */
193
188
  status;
@@ -210,7 +205,64 @@ var WxPayError = class extends Error {
210
205
  get isServerError() {
211
206
  return this.status >= 500 && this.status < 600;
212
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
+ }
213
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
+ }
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
+ }
214
266
  function buildUrl(base, path, params) {
215
267
  const url = new URL(path, base);
216
268
  if (params) {
@@ -272,7 +324,7 @@ async function parseResponse(response, verify) {
272
324
  message: `HTTP ${response.status}: ${response.statusText}`
273
325
  };
274
326
  }
275
- throw new WxPayError(response.status, headers, errorDetail);
327
+ throw new ServiceException(response.status, headers, errorDetail);
276
328
  }
277
329
  if (verify) {
278
330
  const signature = headers["wechatpay-signature"];
@@ -282,10 +334,7 @@ async function parseResponse(response, verify) {
282
334
  if (signature && timestamp && nonce && serial) {
283
335
  const valid = verify(rawBody, signature, timestamp, nonce, serial);
284
336
  if (!valid) {
285
- throw new WxPayError(response.status, headers, {
286
- code: "SIGN_ERROR",
287
- message: "\u5E94\u7B54\u7B7E\u540D\u9A8C\u8BC1\u5931\u8D25"
288
- });
337
+ throw new ValidationException("\u5E94\u7B54\u7B7E\u540D\u9A8C\u8BC1\u5931\u8D25");
289
338
  }
290
339
  }
291
340
  }
@@ -297,10 +346,9 @@ async function parseResponse(response, verify) {
297
346
  }
298
347
  data = parsed;
299
348
  } catch (error) {
300
- throw new WxPayError(response.status, headers, {
301
- code: "PARSE_ERROR",
302
- message: error instanceof Error ? error.message : "\u54CD\u5E94\u6570\u636E\u89E3\u6790\u5931\u8D25"
303
- });
349
+ throw new MalformedMessageException(
350
+ error instanceof Error ? error.message : "\u54CD\u5E94\u6570\u636E\u89E3\u6790\u5931\u8D25"
351
+ );
304
352
  }
305
353
  return {
306
354
  status: response.status,
@@ -318,6 +366,12 @@ ${body}
318
366
  `;
319
367
  }
320
368
  function sign(signString, privateKey) {
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
+ }
321
375
  const signer = crypto2.createSign("RSA-SHA256");
322
376
  signer.update(signString);
323
377
  signer.end();
@@ -337,6 +391,18 @@ function isTimestampValid(timestamp) {
337
391
  return Math.abs(now - responseTime) < RESPONSE_EXPIRED_SECONDS;
338
392
  }
339
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
+ }
340
406
  if (!isTimestampValid(timestamp)) {
341
407
  return false;
342
408
  }
@@ -350,6 +416,12 @@ ${body}
350
416
  return verifier.verify(publicKey, signature, "base64");
351
417
  }
352
418
  function oaepEncrypt(plaintext, publicKey) {
419
+ if (!publicKey) {
420
+ throw new Error("\u52A0\u5BC6\u516C\u94A5\u4E0D\u80FD\u4E3A\u7A7A");
421
+ }
422
+ if (!plaintext) {
423
+ return "";
424
+ }
353
425
  const encrypted = crypto2.publicEncrypt(
354
426
  {
355
427
  key: publicKey,
@@ -361,6 +433,12 @@ function oaepEncrypt(plaintext, publicKey) {
361
433
  return encrypted.toString("base64");
362
434
  }
363
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
+ }
364
442
  const decrypted = crypto2.privateDecrypt(
365
443
  {
366
444
  key: privateKey,
@@ -775,6 +853,48 @@ var WxPayClient = class _WxPayClient {
775
853
  };
776
854
  }
777
855
  };
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
+ }
778
898
  var CertificateService = class {
779
899
  client;
780
900
  apiV3Key;
@@ -2636,6 +2756,8 @@ var BillService = class {
2636
2756
  }
2637
2757
  };
2638
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"]);
2639
2761
  var CallbackHandler = class {
2640
2762
  apiV3Key;
2641
2763
  certificates;
@@ -2652,14 +2774,26 @@ var CallbackHandler = class {
2652
2774
  * @param headers - 回调请求头
2653
2775
  * @param body - 回调请求体(原始 JSON 字符串)
2654
2776
  * @returns 签名验证是否通过
2655
- * @throws 如果签名类型不支持或找不到对应的证书
2777
+ * @throws 如果必填参数缺失、签名类型不支持或找不到对应的证书
2656
2778
  */
2657
2779
  verifySignature(headers, body) {
2658
- const signatureType = headers["wechatpay-signature-type"];
2659
- const serialNo = headers["wechatpay-serial"];
2660
- if (signatureType && !SUPPORTED_SIGNATURE_TYPES.has(signatureType)) {
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)) {
2661
2794
  throw new Error(`\u4E0D\u652F\u6301\u7684\u7B7E\u540D\u7C7B\u578B: ${signatureType}`);
2662
2795
  }
2796
+ const serialNo = headers["wechatpay-serial"];
2663
2797
  const publicKey = this.certificates.getPublicKey(serialNo);
2664
2798
  if (!publicKey) {
2665
2799
  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`);
@@ -2679,10 +2813,22 @@ var CallbackHandler = class {
2679
2813
  *
2680
2814
  * @param notification - 回调通知 JSON 对象
2681
2815
  * @returns 解密后的业务数据
2682
- * @throws 如果解密后的数据为空或格式无效
2816
+ * @throws 如果通知结构无效、算法不匹配或解密失败
2683
2817
  */
2684
2818
  decryptNotification(notification) {
2685
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
+ }
2686
2832
  const plaintext = this.aesGcmDecrypt(
2687
2833
  resource.ciphertext,
2688
2834
  resource.associated_data,
@@ -2930,10 +3076,23 @@ var CallbackHandler = class {
2930
3076
  * @param associatedData - 附加数据(用于 AEAD 认证)
2931
3077
  * @param nonce - 随机串
2932
3078
  * @returns 解密后的明文字符串
3079
+ * @throws 如果密文格式无效或解密失败
2933
3080
  */
2934
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
+ }
2935
3091
  const key = Buffer.from(this.apiV3Key, "utf-8");
2936
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
+ }
2937
3096
  const authTag = ciphertextBuffer.subarray(-16);
2938
3097
  const encryptedData = ciphertextBuffer.subarray(0, -16);
2939
3098
  const decipher = crypto2.createDecipheriv("aes-256-gcm", key, Buffer.from(nonce, "utf-8"));
@@ -4716,6 +4875,6 @@ ${packageStr}
4716
4875
  };
4717
4876
  }
4718
4877
 
4719
- export { AppService, BillService, BusinessCircleService, CallbackHandler, CertificateManager, CertificateService, CombineAppService, CombineH5Service, CombineMiniProgramService, CombineNativeService, CombineService, ComplaintService, CouponService, GoldPlanService, H5Service, JsapiService, LoveFeastService, MedInsService, MediaService, MerchantExclusiveCouponService, MerchantTransferService, NativeService, ParkingService, PartnershipService, PayGiftActivityService, PayScoreService, PayrollCardService, ProfitSharingService, RetailStoreService, ScanAndRideService, 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, isTimestampValid, oaepDecrypt, 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 };
4720
4879
  //# sourceMappingURL=index.mjs.map
4721
4880
  //# sourceMappingURL=index.mjs.map