vesant-sdk 1.6.6 → 2.0.0-dev.03b7c82

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.
Files changed (86) hide show
  1. package/README.md +14 -4
  2. package/dist/client-BJ87_Vv5.d.ts +430 -0
  3. package/dist/{client-ePzhQKp9.d.mts → client-BolQlL5e.d.mts} +1 -1
  4. package/dist/{client-ePzhQKp9.d.ts → client-BolQlL5e.d.ts} +1 -1
  5. package/dist/{client-BlCxjbY2.d.mts → client-Bvp-f05-.d.ts} +7 -7
  6. package/dist/{client-C_A7QLcB.d.ts → client-CIEa7xYG.d.mts} +7 -7
  7. package/dist/client-IAOGCBfm.d.mts +430 -0
  8. package/dist/compliance/index.d.mts +25 -429
  9. package/dist/compliance/index.d.ts +25 -429
  10. package/dist/compliance/index.js +137 -58
  11. package/dist/compliance/index.js.map +1 -1
  12. package/dist/compliance/index.mjs +137 -59
  13. package/dist/compliance/index.mjs.map +1 -1
  14. package/dist/decisions/index.d.mts +2 -2
  15. package/dist/decisions/index.d.ts +2 -2
  16. package/dist/decisions/index.js +1 -1
  17. package/dist/decisions/index.js.map +1 -1
  18. package/dist/decisions/index.mjs +1 -1
  19. package/dist/decisions/index.mjs.map +1 -1
  20. package/dist/geolocation/index.d.mts +4 -4
  21. package/dist/geolocation/index.d.ts +4 -4
  22. package/dist/geolocation/index.js +6 -24
  23. package/dist/geolocation/index.js.map +1 -1
  24. package/dist/geolocation/index.mjs +6 -24
  25. package/dist/geolocation/index.mjs.map +1 -1
  26. package/dist/index.d.mts +14 -71
  27. package/dist/index.d.ts +14 -71
  28. package/dist/index.js +244 -247
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +243 -246
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/kyc/core.d.mts +4 -4
  33. package/dist/kyc/core.d.ts +4 -4
  34. package/dist/kyc/core.js +78 -23
  35. package/dist/kyc/core.js.map +1 -1
  36. package/dist/kyc/core.mjs +78 -24
  37. package/dist/kyc/core.mjs.map +1 -1
  38. package/dist/kyc/index.d.mts +267 -44
  39. package/dist/kyc/index.d.ts +267 -44
  40. package/dist/kyc/index.js +78 -23
  41. package/dist/kyc/index.js.map +1 -1
  42. package/dist/kyc/index.mjs +78 -24
  43. package/dist/kyc/index.mjs.map +1 -1
  44. package/dist/react.d.mts +42 -7
  45. package/dist/react.d.ts +42 -7
  46. package/dist/react.js +621 -277
  47. package/dist/react.js.map +1 -1
  48. package/dist/react.mjs +621 -277
  49. package/dist/react.mjs.map +1 -1
  50. package/dist/risk-profile/index.d.mts +4 -4
  51. package/dist/risk-profile/index.d.ts +4 -4
  52. package/dist/risk-profile/index.js +1 -1
  53. package/dist/risk-profile/index.js.map +1 -1
  54. package/dist/risk-profile/index.mjs +1 -1
  55. package/dist/risk-profile/index.mjs.map +1 -1
  56. package/dist/scores/index.d.mts +2 -2
  57. package/dist/scores/index.d.ts +2 -2
  58. package/dist/scores/index.js +1 -1
  59. package/dist/scores/index.js.map +1 -1
  60. package/dist/scores/index.mjs +1 -1
  61. package/dist/scores/index.mjs.map +1 -1
  62. package/dist/tax/index.d.mts +6 -41
  63. package/dist/tax/index.d.ts +6 -41
  64. package/dist/tax/index.js +1 -36
  65. package/dist/tax/index.js.map +1 -1
  66. package/dist/tax/index.mjs +1 -36
  67. package/dist/tax/index.mjs.map +1 -1
  68. package/dist/{types-X5Md_dD_.d.ts → types-2utj53GK.d.ts} +2 -2
  69. package/dist/{types-1RzYeSal.d.mts → types-C4Zx0d_u.d.mts} +2 -2
  70. package/dist/{types-B4Ezqo7V.d.mts → types-QUCWam16.d.mts} +7 -1
  71. package/dist/{types-B4Ezqo7V.d.ts → types-QUCWam16.d.ts} +7 -1
  72. package/dist/webhooks/index.d.mts +181 -2
  73. package/dist/webhooks/index.d.ts +181 -2
  74. package/dist/webhooks/index.js +49 -7
  75. package/dist/webhooks/index.js.map +1 -1
  76. package/dist/webhooks/index.mjs +49 -7
  77. package/dist/webhooks/index.mjs.map +1 -1
  78. package/package.json +16 -13
  79. package/dist/fraud/index.d.mts +0 -80
  80. package/dist/fraud/index.d.ts +0 -80
  81. package/dist/fraud/index.js +0 -606
  82. package/dist/fraud/index.js.map +0 -1
  83. package/dist/fraud/index.mjs +0 -604
  84. package/dist/fraud/index.mjs.map +0 -1
  85. package/dist/index-B04H4xfJ.d.mts +0 -320
  86. package/dist/index-CItMPmLL.d.ts +0 -320
package/dist/index.js CHANGED
@@ -242,7 +242,7 @@ var noopLogger = {
242
242
  };
243
243
 
244
244
  // src/core/version.ts
245
- var SDK_VERSION = "1.6.6";
245
+ var SDK_VERSION = "2.0.0";
246
246
 
247
247
  // src/shared/browser-utils.ts
248
248
  function generateUUID() {
@@ -652,8 +652,9 @@ async function computeHmacSha256(message, secret) {
652
652
  const { createHmac } = await import('crypto');
653
653
  return createHmac("sha256", secret).update(message).digest("hex");
654
654
  } catch {
655
- throw new Error(
656
- "No crypto implementation available. Requires Web Crypto API or Node.js crypto module."
655
+ throw new VesantError(
656
+ "No crypto implementation available. Requires Web Crypto API or Node.js crypto module.",
657
+ "CRYPTO_UNAVAILABLE"
657
658
  );
658
659
  }
659
660
  }
@@ -787,7 +788,7 @@ function encodePayload(payload) {
787
788
  } else if (typeof Buffer !== "undefined") {
788
789
  return Buffer.from(json, "utf-8").toString("base64");
789
790
  }
790
- throw new Error("No base64 encoding method available");
791
+ throw new VesantError("No base64 encoding method available", "BASE64_UNAVAILABLE");
791
792
  }
792
793
  async function generateCipherText(options, config) {
793
794
  const warnings = [];
@@ -812,8 +813,9 @@ async function generateCipherText(options, config) {
812
813
  if (location) {
813
814
  locationData = location;
814
815
  } else if (gpsRequiredByConfig) {
815
- throw new Error(
816
- `GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`
816
+ throw new VesantError(
817
+ `GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
818
+ "GPS_REQUIRED"
817
819
  );
818
820
  } else {
819
821
  warnings.push("GPS location not available or permission denied");
@@ -932,10 +934,9 @@ var GeolocationClient = class extends BaseClient {
932
934
  if (!request.ip_address?.trim()) {
933
935
  throw new ValidationError("ip_address is required and must be a non-empty string", ["ip_address"]);
934
936
  }
935
- const enrichedRequest = request.device_fingerprint ? request : { ...request, device_fingerprint: collectDeviceFingerprint() };
936
937
  return this.requestWithRetry("/api/v1/geo/verify", {
937
938
  method: "POST",
938
- body: JSON.stringify(enrichedRequest)
939
+ body: JSON.stringify(request)
939
940
  }, void 0, void 0, requestOptions);
940
941
  }
941
942
  /**
@@ -1339,24 +1340,6 @@ var GeolocationClient = class extends BaseClient {
1339
1340
  // Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
1340
1341
  // ============================================================================
1341
1342
  };
1342
- function collectDeviceFingerprint() {
1343
- const deviceId = generateDeviceId();
1344
- const browserInfo = getBrowserInfo();
1345
- const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "server";
1346
- const platform = typeof navigator !== "undefined" ? navigator.platform || "unknown" : "server";
1347
- return {
1348
- device_id: deviceId,
1349
- user_agent: userAgent,
1350
- platform,
1351
- browser: browserInfo.browser,
1352
- browser_version: browserInfo.browser_version,
1353
- os: browserInfo.os,
1354
- os_version: browserInfo.os_version,
1355
- screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
1356
- language: typeof navigator !== "undefined" ? navigator.language : void 0,
1357
- timezone: Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone
1358
- };
1359
- }
1360
1343
 
1361
1344
  // src/risk-profile/client.ts
1362
1345
  var RiskProfileClient = class extends BaseClient {
@@ -1473,6 +1456,81 @@ var RiskProfileClient = class extends BaseClient {
1473
1456
  }
1474
1457
  };
1475
1458
 
1459
+ // src/compliance/block-reasons.ts
1460
+ var sdkReasons = {
1461
+ // Jurisdiction
1462
+ jurisdictionBlocked: (country, countryISO) => ({
1463
+ code: "JURISDICTION_BLOCKED",
1464
+ message: "Access from a blocked jurisdiction",
1465
+ metadata: { country, country_iso: countryISO }
1466
+ }),
1467
+ jurisdictionNonCompliant: () => ({
1468
+ code: "JURISDICTION_NON_COMPLIANT",
1469
+ message: "Location does not meet compliance requirements"
1470
+ }),
1471
+ jurisdictionRegistrationDenied: () => ({
1472
+ code: "JURISDICTION_REGISTRATION_DENIED",
1473
+ message: "Registration is not permitted in this jurisdiction"
1474
+ }),
1475
+ jurisdictionRestricted: () => ({
1476
+ code: "JURISDICTION_RESTRICTED",
1477
+ message: "Access from a restricted jurisdiction"
1478
+ }),
1479
+ // Risk
1480
+ riskCriticalLevel: () => ({
1481
+ code: "RISK_CRITICAL_LEVEL",
1482
+ message: "Risk assessment reached critical threshold"
1483
+ }),
1484
+ accountSuspended: () => ({
1485
+ code: "RISK_ACCOUNT_SUSPENDED",
1486
+ message: "Customer account is suspended"
1487
+ }),
1488
+ sanctionsMatch: () => ({
1489
+ code: "RISK_SANCTIONS_MATCH",
1490
+ message: "Customer profile matched against sanctions list"
1491
+ }),
1492
+ riskHighLocation: () => ({
1493
+ code: "RISK_HIGH_LOCATION",
1494
+ message: "High-risk geographic location"
1495
+ }),
1496
+ riskHighCustomer: () => ({
1497
+ code: "RISK_HIGH_CUSTOMER",
1498
+ message: "Customer flagged as high risk"
1499
+ }),
1500
+ // Device
1501
+ ciphertextInvalid: () => ({
1502
+ code: "DEVICE_CIPHERTEXT_INVALID",
1503
+ message: "Device verification payload failed validation"
1504
+ }),
1505
+ gpsIPMismatch: () => ({
1506
+ code: "DEVICE_GPS_IP_MISMATCH",
1507
+ message: "GPS location does not match IP-derived location"
1508
+ }),
1509
+ gpsRequired: () => ({
1510
+ code: "DEVICE_GPS_REQUIRED",
1511
+ message: "GPS verification is required but was not provided"
1512
+ }),
1513
+ // Transaction
1514
+ transactionHighAmount: (amount, currency, threshold) => ({
1515
+ code: "TRANSACTION_HIGH_AMOUNT",
1516
+ message: "Transaction amount exceeds high-value threshold",
1517
+ metadata: { amount, currency, threshold }
1518
+ }),
1519
+ transactionElevatedAmount: (amount, currency, threshold) => ({
1520
+ code: "TRANSACTION_ELEVATED_AMOUNT",
1521
+ message: "Transaction amount exceeds elevated-value threshold",
1522
+ metadata: { amount, currency, threshold }
1523
+ }),
1524
+ transactionJurisdictionLimit: () => ({
1525
+ code: "TRANSACTION_JURISDICTION_LIMIT",
1526
+ message: "Transaction amount exceeds jurisdiction limit"
1527
+ }),
1528
+ anonymizationDetected: () => ({
1529
+ code: "NETWORK_ANONYMIZER_DETECTED",
1530
+ message: "Anonymization tool detected"
1531
+ })
1532
+ };
1533
+
1476
1534
  // src/compliance/types.ts
1477
1535
  var DEFAULT_CURRENCY_RATES = {
1478
1536
  USD: 1,
@@ -1673,7 +1731,7 @@ var ComplianceClient = class {
1673
1731
  } catch (error) {
1674
1732
  if (this.config.debug) {
1675
1733
  this.logger.error("Registration verification failed", {
1676
- code: error instanceof Error ? error.code : void 0,
1734
+ code: error instanceof VesantError ? error.code : void 0,
1677
1735
  message: error instanceof Error ? error.message : "Unknown error"
1678
1736
  });
1679
1737
  }
@@ -1705,23 +1763,23 @@ var ComplianceClient = class {
1705
1763
  blockReasons.push(...geoVerification.risk_reasons ?? []);
1706
1764
  }
1707
1765
  if (!geoVerification.is_compliant) {
1708
- if (!blockReasons.includes("non_compliant_jurisdiction")) {
1709
- blockReasons.push("non_compliant_jurisdiction");
1766
+ if (!blockReasons.some((r) => r.code === "JURISDICTION_NON_COMPLIANT")) {
1767
+ blockReasons.push(sdkReasons.jurisdictionNonCompliant());
1710
1768
  }
1711
1769
  }
1712
1770
  const jurisdiction = geoVerification.jurisdiction;
1713
1771
  if (jurisdiction) {
1714
1772
  if (jurisdiction.allow_registration === false) {
1715
- blockReasons.push("registration_not_allowed_in_jurisdiction");
1773
+ blockReasons.push(sdkReasons.jurisdictionRegistrationDenied());
1716
1774
  }
1717
1775
  if (jurisdiction.status === "blocked" || jurisdiction.status === "sanctioned") {
1718
- if (!blockReasons.includes("blocked_jurisdiction")) {
1719
- blockReasons.push("blocked_jurisdiction");
1776
+ if (!blockReasons.some((r) => r.code === "JURISDICTION_BLOCKED")) {
1777
+ blockReasons.push(sdkReasons.jurisdictionBlocked(jurisdiction.country_name ?? "", jurisdiction.country_iso ?? ""));
1720
1778
  }
1721
1779
  }
1722
1780
  if (jurisdiction.status === "restricted" && jurisdiction.allow_registration === false) {
1723
- if (!blockReasons.includes("restricted_jurisdiction_no_registration")) {
1724
- blockReasons.push("restricted_jurisdiction_no_registration");
1781
+ if (!blockReasons.some((r) => r.code === "JURISDICTION_RESTRICTED")) {
1782
+ blockReasons.push(sdkReasons.jurisdictionRestricted());
1725
1783
  }
1726
1784
  }
1727
1785
  }
@@ -1729,25 +1787,30 @@ var ComplianceClient = class {
1729
1787
  blockReasons.push(...geoVerification.geofence_evaluation.reasons ?? []);
1730
1788
  }
1731
1789
  if (geoVerification.risk_level === "critical") {
1732
- if (!blockReasons.includes("critical_risk_level")) {
1733
- blockReasons.push("critical_risk_level");
1790
+ if (!blockReasons.some((r) => r.code === "RISK_CRITICAL_LEVEL")) {
1791
+ blockReasons.push(sdkReasons.riskCriticalLevel());
1734
1792
  }
1735
1793
  }
1736
1794
  if (cipherTextResult) {
1737
1795
  if (!cipherTextResult.valid) {
1738
- blockReasons.push("ciphertext_validation_failed");
1796
+ blockReasons.push(sdkReasons.ciphertextInvalid());
1739
1797
  }
1740
1798
  if (cipherTextResult.risk?.is_blocked) {
1741
1799
  blockReasons.push(...cipherTextResult.risk.block_reasons || []);
1742
1800
  }
1743
1801
  if (cipherTextResult.risk?.location_mismatch) {
1744
- blockReasons.push("gps_ip_location_mismatch");
1802
+ blockReasons.push(sdkReasons.gpsIPMismatch());
1745
1803
  }
1746
1804
  }
1747
1805
  if (geoVerification.gps_required && !cipherTextResult) {
1748
- blockReasons.push("gps_verification_required");
1806
+ blockReasons.push(sdkReasons.gpsRequired());
1749
1807
  }
1750
- return [...new Set(blockReasons)];
1808
+ const seen = /* @__PURE__ */ new Set();
1809
+ return blockReasons.filter((r) => {
1810
+ if (seen.has(r.code)) return false;
1811
+ seen.add(r.code);
1812
+ return true;
1813
+ });
1751
1814
  }
1752
1815
  /**
1753
1816
  * Validate registration request has all required fields
@@ -2131,7 +2194,7 @@ var ComplianceClient = class {
2131
2194
  const blockReasons = [...geoVerification.risk_reasons ?? []];
2132
2195
  if (cipherTextResult) {
2133
2196
  if (!cipherTextResult.valid) {
2134
- blockReasons.push("ciphertext_validation_failed");
2197
+ blockReasons.push(sdkReasons.ciphertextInvalid());
2135
2198
  }
2136
2199
  if (cipherTextResult.risk?.is_blocked) {
2137
2200
  blockReasons.push(...cipherTextResult.risk.block_reasons || []);
@@ -2139,12 +2202,18 @@ var ComplianceClient = class {
2139
2202
  }
2140
2203
  const gpsBlocked = geoVerification.gps_required && !cipherTextResult;
2141
2204
  if (gpsBlocked) {
2142
- blockReasons.push("gps_verification_required");
2205
+ blockReasons.push(sdkReasons.gpsRequired());
2143
2206
  }
2207
+ const seen = /* @__PURE__ */ new Set();
2208
+ const dedupedBlockReasons = blockReasons.filter((r) => {
2209
+ if (seen.has(r.code)) return false;
2210
+ seen.add(r.code);
2211
+ return true;
2212
+ });
2144
2213
  return {
2145
2214
  allowed: geoVerification.is_compliant && !geoVerification.is_blocked && !cipherTextBlocked && !gpsBlocked,
2146
2215
  geolocation: geoVerification,
2147
- blockReasons: [...new Set(blockReasons)],
2216
+ blockReasons: dedupedBlockReasons,
2148
2217
  processingTime: Date.now() - startTime,
2149
2218
  cipherTextValidation: cipherTextResult
2150
2219
  };
@@ -2247,31 +2316,31 @@ var ComplianceClient = class {
2247
2316
  const normalizedAmount = this.normalizeToUSD(amount, currency);
2248
2317
  if (normalizedAmount > 1e4) {
2249
2318
  riskScore += 30;
2250
- factors.push("high_transaction_amount");
2319
+ factors.push(sdkReasons.transactionHighAmount(normalizedAmount, currency, 1e4));
2251
2320
  } else if (normalizedAmount > 5e3) {
2252
2321
  riskScore += 15;
2253
- factors.push("elevated_transaction_amount");
2322
+ factors.push(sdkReasons.transactionElevatedAmount(normalizedAmount, currency, 5e3));
2254
2323
  }
2255
2324
  riskScore += geoVerification.risk_score * 0.4;
2256
2325
  if (geoVerification.risk_level === "high" || geoVerification.risk_level === "critical") {
2257
- factors.push("high_risk_location");
2326
+ factors.push(sdkReasons.riskHighLocation());
2258
2327
  }
2259
2328
  riskScore += profile.risk_score * 0.3;
2260
2329
  if (profile.risk_category === "high" || profile.risk_category === "critical") {
2261
- factors.push("high_risk_customer");
2330
+ factors.push(sdkReasons.riskHighCustomer());
2262
2331
  }
2263
2332
  if (geoVerification.location.is_vpn || geoVerification.location.is_proxy) {
2264
2333
  riskScore += 20;
2265
- factors.push("anonymization_detected");
2334
+ factors.push(sdkReasons.anonymizationDetected());
2266
2335
  }
2267
2336
  if (profile.customer_status === "suspended") {
2268
2337
  riskScore += 50;
2269
- factors.push("account_suspended");
2338
+ factors.push(sdkReasons.accountSuspended());
2270
2339
  }
2271
2340
  if (cipherTextResult) {
2272
2341
  if (cipherTextResult.risk?.location_mismatch) {
2273
2342
  riskScore += 20;
2274
- factors.push("gps_ip_location_mismatch");
2343
+ factors.push(sdkReasons.gpsIPMismatch());
2275
2344
  }
2276
2345
  if (cipherTextResult.risk?.score) {
2277
2346
  riskScore += cipherTextResult.risk.score * 0.2;
@@ -2309,48 +2378,58 @@ var ComplianceClient = class {
2309
2378
  }
2310
2379
  if (profile) {
2311
2380
  if (profile.customer_status === "suspended") {
2312
- reasons.push("account_suspended");
2381
+ reasons.push(sdkReasons.accountSuspended());
2313
2382
  }
2314
2383
  if (profile.has_sanctions) {
2315
- reasons.push("sanctions_match");
2384
+ reasons.push(sdkReasons.sanctionsMatch());
2316
2385
  }
2317
2386
  }
2318
2387
  if (cipherTextResult) {
2319
2388
  if (!cipherTextResult.valid) {
2320
- reasons.push("ciphertext_validation_failed");
2389
+ reasons.push(sdkReasons.ciphertextInvalid());
2321
2390
  }
2322
2391
  if (cipherTextResult.risk?.is_blocked) {
2323
2392
  reasons.push(...cipherTextResult.risk.block_reasons || []);
2324
2393
  }
2325
2394
  }
2326
2395
  if (geoVerification.gps_required && !cipherTextResult) {
2327
- reasons.push("gps_verification_required");
2396
+ reasons.push(sdkReasons.gpsRequired());
2328
2397
  }
2329
- return [...new Set(reasons)];
2398
+ const seen = /* @__PURE__ */ new Set();
2399
+ return reasons.filter((r) => {
2400
+ if (seen.has(r.code)) return false;
2401
+ seen.add(r.code);
2402
+ return true;
2403
+ });
2330
2404
  }
2331
2405
  getTransactionBlockReasons(geoVerification, transactionRisk, jurisdictionAllowed, cipherTextResult) {
2332
2406
  const reasons = [];
2333
2407
  if (!geoVerification.is_compliant) {
2334
- reasons.push("non_compliant_jurisdiction");
2408
+ reasons.push(sdkReasons.jurisdictionNonCompliant());
2335
2409
  }
2336
2410
  if (!jurisdictionAllowed) {
2337
- reasons.push("exceeds_jurisdiction_limit");
2411
+ reasons.push(sdkReasons.transactionJurisdictionLimit());
2338
2412
  }
2339
2413
  if (!transactionRisk.allowed) {
2340
2414
  reasons.push(...transactionRisk.factors);
2341
2415
  }
2342
2416
  if (cipherTextResult) {
2343
2417
  if (!cipherTextResult.valid) {
2344
- reasons.push("ciphertext_validation_failed");
2418
+ reasons.push(sdkReasons.ciphertextInvalid());
2345
2419
  }
2346
2420
  if (cipherTextResult.risk?.is_blocked) {
2347
2421
  reasons.push(...cipherTextResult.risk.block_reasons || []);
2348
2422
  }
2349
2423
  }
2350
2424
  if (geoVerification.gps_required && !cipherTextResult) {
2351
- reasons.push("gps_verification_required");
2425
+ reasons.push(sdkReasons.gpsRequired());
2352
2426
  }
2353
- return [...new Set(reasons)];
2427
+ const seen = /* @__PURE__ */ new Set();
2428
+ return reasons.filter((r) => {
2429
+ if (seen.has(r.code)) return false;
2430
+ seen.add(r.code);
2431
+ return true;
2432
+ });
2354
2433
  }
2355
2434
  // ============================================================================
2356
2435
  // Location Request Methods
@@ -2628,23 +2707,19 @@ var KycClient = class extends BaseClient {
2628
2707
  *
2629
2708
  * Generates a link that the user can visit to submit their KYC documents.
2630
2709
  *
2631
- * @param request - Request containing the user ID, redirect URL, callback URL, and trigger event
2632
- * @returns Response containing kyc_required, can_skip, and optionally the redirect link and KYC ID
2710
+ * @param request - Request containing the user ID, optional redirect URL, and optional callback URL (receives POST requests)
2711
+ * @returns Response containing the redirect link and KYC ID
2633
2712
  *
2634
2713
  * @example
2635
2714
  * ```typescript
2636
2715
  * const result = await client.requestKycSubmitLink({
2637
2716
  * user_id: "user_123",
2638
- * redirect_url: "https://merchant.com/kyc-complete",
2639
- * callback_url: "https://merchant.com/api/kyc-webhook",
2640
- * trigger_event: "onboarding"
2717
+ * redirect_url: "https://merchant.com/kyc-complete", // optional
2718
+ * callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
2641
2719
  * });
2642
2720
  *
2643
- * if (result.kyc_required && result.link) {
2644
- * console.log(`Redirect user to: ${result.link}`);
2645
- * } else if (result.can_skip) {
2646
- * console.log("KYC not required, user can proceed");
2647
- * }
2721
+ * console.log(`Redirect user to: ${result.link}`);
2722
+ * console.log(`KYC ID: ${result.kyc_id}`);
2648
2723
  * ```
2649
2724
  */
2650
2725
  async requestKycSubmitLink(request) {
@@ -2655,40 +2730,84 @@ var KycClient = class extends BaseClient {
2655
2730
  });
2656
2731
  }
2657
2732
  /**
2658
- * Create a reuse KYC session for validate a user with existing KYC verification
2733
+ * Create a Re-Use KYC session.
2734
+ *
2735
+ * Inspect the response before showing UI:
2736
+ * - `is_required === false` → skip face capture; `reason` explains why.
2737
+ * - `device_type === 'desktop'` → render `qr_payload` as a QR; the
2738
+ * mobile device picks up the session via the connect endpoint.
2739
+ * - `device_type === 'mobile'` → open the face capture modal directly.
2659
2740
  *
2660
- * @param request - Request containing the reference, customer_id, optional redirect URL, and callback URL (receives POST requests)
2741
+ * @param request - Reference, customer_id, event, amount (for threshold events), optional URLs.
2661
2742
  */
2662
2743
  async createReuseKycSession(request) {
2663
- return this.requestWithRetry("/api/v1/kyc/face/session", {
2744
+ return this.request("/api/v1/kyc/face/session", {
2664
2745
  method: "POST",
2665
2746
  body: JSON.stringify(request),
2666
2747
  headers: this.getUserHeaders()
2667
2748
  });
2668
2749
  }
2669
2750
  /**
2670
- * Submit a reuse KYC session for validate a user with existing KYC verification
2751
+ * Submit a real-time face capture for an active Re-Use KYC session.
2671
2752
  *
2672
- * @param request - Request containing the reference, token and proof (receives POST requests)
2753
+ * **Mobile-only.** The server rejects desktop User-Agents with HTTP
2754
+ * 400 (`face capture must be completed on a mobile device`). Use the
2755
+ * QR handoff from `createReuseKycSession` for desktop callers.
2756
+ *
2757
+ * The `data` field on the response carries `retries_remaining`,
2758
+ * `retry_limit_exceeded`, and the reaction flags (`enforce_logout`,
2759
+ * `freeze_account`, etc.) so the tenant app can act on a final failure.
2760
+ *
2761
+ * @param request - Token from `createReuseKycSession`, plus the base64 selfie.
2673
2762
  */
2674
2763
  async submitReuseKycSession(request) {
2675
- return this.requestWithRetry("/api/v1/kyc/face/submit", {
2764
+ return this.request("/api/v1/kyc/face/submit", {
2676
2765
  method: "POST",
2677
2766
  body: JSON.stringify(request),
2678
2767
  headers: this.getUserHeaders()
2679
2768
  });
2680
2769
  }
2681
2770
  /**
2682
- * Check reuse KYC session status for a reference
2683
- * @param reference - The unique reference used for the reuse KYC session (e.g., customer ID or transaction ID)
2684
- * @returns Response with kyc_status and message (reason)
2685
- * **/
2771
+ * Look up the current state of a Re-Use KYC session by its forward
2772
+ * reference. Useful for desktop pollers waiting on the mobile handoff.
2773
+ *
2774
+ * @param reference - The reference used when the session was created.
2775
+ */
2686
2776
  async getReuseKycSessionStatus(reference) {
2687
2777
  return this.requestWithRetry(`/api/v1/kyc/face/verify/${encodeURIComponent(reference)}`, {
2688
2778
  method: "GET",
2689
2779
  headers: this.getUserHeaders()
2690
2780
  });
2691
2781
  }
2782
+ /**
2783
+ * Fetch the Redis-backed handoff session for a token. Same backing
2784
+ * store as normal KYC (`kyc:session:<token>`, 15-minute TTL). Desktop
2785
+ * callers poll `mobile_connected` to detect when a mobile device has
2786
+ * scanned the QR and attached.
2787
+ *
2788
+ * @param token - The session token returned by `createReuseKycSession`.
2789
+ */
2790
+ async getHandoffSession(token) {
2791
+ return this.request(
2792
+ `/api/v1/kyc/session${this.buildQueryString({ token })}`,
2793
+ { headers: this.getUserHeaders() }
2794
+ );
2795
+ }
2796
+ /**
2797
+ * Attach a mobile device to a desktop-initiated session. The mobile
2798
+ * client calls this after scanning the QR code. The desktop poller
2799
+ * sees `mobile_connected: true` on the next `getHandoffSession`.
2800
+ *
2801
+ * @param token - The session token transferred via the QR payload.
2802
+ * @param isDisconnect - Pass true to release the session (default false).
2803
+ */
2804
+ async connectMobileSession(token, isDisconnect = false) {
2805
+ return this.request("/api/v1/kyc/session/connect", {
2806
+ method: "PUT",
2807
+ body: JSON.stringify({ token, is_disconnect: isDisconnect }),
2808
+ headers: this.getUserHeaders()
2809
+ });
2810
+ }
2692
2811
  /**
2693
2812
  * Check KYC status for a user
2694
2813
  *
@@ -3047,8 +3166,9 @@ var KycClient = class extends BaseClient {
3047
3166
  */
3048
3167
  async riskProfileRequest(path, options = {}) {
3049
3168
  if (!this.riskProfileBaseURL) {
3050
- throw new Error(
3051
- "Risk Profile Service URL not configured. Please provide riskProfileBaseURL in KycClientConfig."
3169
+ throw new ValidationError(
3170
+ "Risk Profile Service URL not configured. Please provide riskProfileBaseURL in KycClientConfig.",
3171
+ ["riskProfileBaseURL"]
3052
3172
  );
3053
3173
  }
3054
3174
  return this.request(path, {
@@ -3160,6 +3280,19 @@ var KycClient = class extends BaseClient {
3160
3280
  // ============================================================================
3161
3281
  };
3162
3282
 
3283
+ // src/kyc/types.ts
3284
+ var KYC_DECLINED_DESCRIPTIONS = {
3285
+ KYC_DOCUMENT_EXPIRED: "The submitted document has expired",
3286
+ KYC_DOCUMENT_INVALID: "The submitted document could not be verified",
3287
+ KYC_FACE_MISMATCH: "Face verification did not match the identity document",
3288
+ KYC_AGE_REQUIREMENT: "Age requirement not met",
3289
+ KYC_DUPLICATE_IDENTITY: "This identity has already been verified under another account",
3290
+ KYC_ADDRESS_MISMATCH: "Address verification failed",
3291
+ KYC_NAME_MISMATCH: "Name on document does not match the registered name",
3292
+ KYC_PROVIDER_REJECTED: "Identity verification was rejected by the verification provider",
3293
+ KYC_DECLINED: "Identity verification was declined"
3294
+ };
3295
+
3163
3296
  // src/tax/client.ts
3164
3297
  var TaxClient = class extends BaseClient {
3165
3298
  constructor(config) {
@@ -3226,41 +3359,6 @@ var TaxClient = class extends BaseClient {
3226
3359
  { ...requestOptions, responseType: "arraybuffer" }
3227
3360
  );
3228
3361
  }
3229
- async runReminders() {
3230
- return this.request("/api/v1/tax/reminders/run", {
3231
- method: "POST"
3232
- });
3233
- }
3234
- /**
3235
- * Update only the reminder configuration (frequency + max count) without
3236
- * touching any other tax rule settings. Fetches current rules first and
3237
- * merges the reminder fields before sending the update.
3238
- *
3239
- * Requires the caller to be authenticated as a Tenant Super Admin.
3240
- * Throws VesantError with status 403 if the role requirement is not met.
3241
- */
3242
- async updateReminderConfig(input) {
3243
- const current = await this.getTaxRules();
3244
- return this.updateTaxRules({
3245
- trigger_account_creation: current.trigger_account_creation,
3246
- trigger_first_withdrawal: current.trigger_first_withdrawal,
3247
- trigger_threshold: current.trigger_threshold,
3248
- trigger_manual: current.trigger_manual,
3249
- trigger_tin_invalid: current.trigger_tin_invalid,
3250
- trigger_w8ben_expiry: current.trigger_w8ben_expiry,
3251
- trigger_tin_expired: current.trigger_tin_expired,
3252
- us_withholding_enabled: current.us_withholding_enabled,
3253
- non_us_withholding_enabled: current.non_us_withholding_enabled,
3254
- transaction_action: current.transaction_action,
3255
- w8ben_expiry_warning_days: current.w8ben_expiry_warning_days,
3256
- tin_verification_due_date: current.tin_verification_due_date,
3257
- email_sending_method: current.email_sending_method,
3258
- display_tin_links_on_platform: current.display_tin_links_on_platform,
3259
- tax_treaty_support: current.tax_treaty_support,
3260
- reminder_frequency_days: input.reminder_frequency_days,
3261
- reminder_max_count: input.reminder_max_count
3262
- });
3263
- }
3264
3362
  // ==========================================================================
3265
3363
  // P2 — Tenant Tax Rules
3266
3364
  // ==========================================================================
@@ -3304,136 +3402,15 @@ var TaxClient = class extends BaseClient {
3304
3402
  }
3305
3403
  };
3306
3404
 
3307
- // src/transaction/client.ts
3308
- var TransactionClient = class extends BaseClient {
3309
- constructor(config) {
3310
- super(config);
3311
- }
3312
- // ==========================================================================
3313
- // Transaction creation
3314
- // ==========================================================================
3315
- /**
3316
- * Submit a new transaction record to the monitoring service.
3317
- *
3318
- * The gateway endpoint is `POST /api/v1/tm/transactions` and returns 201
3319
- * with no body on success. The SDK method resolves to `void` to reflect
3320
- * that behaviour.
3321
- *
3322
- * @param request - Data required to create the transaction
3323
- * @param requestOptions - Optional request options (e.g. AbortSignal)
3324
- *
3325
- * @example
3326
- * ```ts
3327
- * const client = new TransactionClient({
3328
- * baseURL: process.env.API_GATEWAY_URL!,
3329
- * tenantId: 'tenant-123',
3330
- * apiKey: 'pk_test_...',
3331
- * });
3332
- *
3333
- * await client.createTransaction({
3334
- * tx_id: 'tx-1',
3335
- * reference: 'ref-1',
3336
- * tenant_id: 'tenant-123',
3337
- * customer_id: 'cust-1',
3338
- * transaction_type: 'deposit',
3339
- * transaction_mode: 'ach',
3340
- * amount: '100.00',
3341
- * currency: 'USD',
3342
- * status: 'pending',
3343
- * source_account: 'acct-1',
3344
- * destination_account: 'acct-2',
3345
- * country: 'US',
3346
- * ip_address: '10.0.0.1',
3347
- * metadata: {},
3348
- * benificiary_comment: '',
3349
- * transaction_date: new Date().toISOString(),
3350
- * });
3351
- * ```
3352
- */
3353
- async createTransaction(request, requestOptions) {
3354
- const data = await this.requestWithRetry("/api/v1/tm/transactions", {
3355
- method: "POST",
3356
- body: JSON.stringify(request)
3357
- }, void 0, void 0, requestOptions);
3358
- return data;
3359
- }
3360
- /**
3361
- *
3362
- * @param transactionId tx_id of the transaction
3363
- * @param requestOptions optional request options (e.g. AbortSignal)
3364
- */
3365
- async getTransaction(transactionId, requestOptions) {
3366
- await this.requestWithRetry(`/api/v1/tm/transactions/status/${transactionId}`, {
3367
- method: "GET"
3368
- }, void 0, void 0, requestOptions);
3369
- }
3370
- };
3371
-
3372
- // src/fraud/client.ts
3373
- var FraudClient = class extends BaseClient {
3374
- /**
3375
- * Submit a single fraud event for scoring.
3376
- */
3377
- async scoreEvent(request, requestOptions) {
3378
- this.validateScoreRequest(request);
3379
- return this.requestWithRetry(
3380
- "/api/v1/fraud/score",
3381
- {
3382
- method: "POST",
3383
- body: JSON.stringify(request)
3384
- },
3385
- void 0,
3386
- void 0,
3387
- requestOptions
3388
- );
3389
- }
3390
- /**
3391
- * Submit multiple fraud events for scoring.
3392
- */
3393
- async scoreEventsBulk(requests, requestOptions) {
3394
- if (!Array.isArray(requests) || requests.length === 0) {
3395
- throw new ValidationError(
3396
- "Invalid bulk score request: at least one score request is required",
3397
- ["requests"]
3398
- );
3399
- }
3400
- requests.forEach((request, index) => this.validateScoreRequest(request, index));
3401
- return this.requestWithRetry(
3402
- "/api/v1/fraud/score/bulk",
3403
- {
3404
- method: "POST",
3405
- body: JSON.stringify(requests)
3406
- },
3407
- void 0,
3408
- void 0,
3409
- requestOptions
3410
- );
3411
- }
3412
- validateScoreRequest(request, index) {
3413
- const errors = [];
3414
- const prefix = index === void 0 ? "" : `requests[${index}].`;
3415
- if (!request.customer_id?.trim()) {
3416
- errors.push(`${prefix}customer_id is required`);
3417
- }
3418
- if (!request.sift_user_id?.trim()) {
3419
- errors.push(`${prefix}sift_user_id is required`);
3420
- }
3421
- if (!request.event_type?.trim()) {
3422
- errors.push(`${prefix}event_type is required`);
3423
- }
3424
- if (errors.length > 0) {
3425
- throw new ValidationError(`Invalid fraud score request: ${errors.join(", ")}`, errors);
3426
- }
3427
- }
3428
- };
3429
-
3430
3405
  // src/webhooks/handler.ts
3431
3406
  var WebhookHandler = class {
3432
3407
  constructor(config) {
3433
3408
  this.handlers = /* @__PURE__ */ new Map();
3434
3409
  this.anyHandlers = [];
3410
+ this.seenEventIds = /* @__PURE__ */ new Map();
3435
3411
  this.secret = config.secret;
3436
3412
  this.tolerance = config.tolerance ?? 3e5;
3413
+ this.replayProtection = config.replayProtection ?? true;
3437
3414
  }
3438
3415
  /**
3439
3416
  * Register a handler for a specific event type.
@@ -3457,7 +3434,7 @@ var WebhookHandler = class {
3457
3434
  async verifyAndParse(body, signature) {
3458
3435
  const isValid = await verifyWebhookSignature(body, signature, this.secret);
3459
3436
  if (!isValid) {
3460
- throw new Error("Invalid webhook signature");
3437
+ throw new ValidationError("Invalid webhook signature", ["signature"]);
3461
3438
  }
3462
3439
  return this.parseAndValidate(body);
3463
3440
  }
@@ -3477,17 +3454,35 @@ var WebhookHandler = class {
3477
3454
  parseAndValidate(body) {
3478
3455
  const event = JSON.parse(body);
3479
3456
  if (!event.type || !event.id || !event.timestamp) {
3480
- throw new Error("Invalid webhook event: missing required fields (type, id, timestamp)");
3457
+ throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
3481
3458
  }
3482
3459
  if (this.tolerance > 0) {
3483
3460
  const eventTime = new Date(event.timestamp).getTime();
3484
3461
  const now = Date.now();
3485
3462
  if (Math.abs(now - eventTime) > this.tolerance) {
3486
- throw new Error(
3487
- `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`
3463
+ throw new ValidationError(
3464
+ `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
3465
+ ["timestamp"]
3488
3466
  );
3489
3467
  }
3490
3468
  }
3469
+ if (this.replayProtection) {
3470
+ if (this.seenEventIds.has(event.id)) {
3471
+ throw new ValidationError(
3472
+ `Duplicate webhook event: ${event.id} has already been processed`,
3473
+ ["id"]
3474
+ );
3475
+ }
3476
+ const now = Date.now();
3477
+ this.seenEventIds.set(event.id, now);
3478
+ if (this.seenEventIds.size > 1e3) {
3479
+ for (const [id, seenAt] of this.seenEventIds) {
3480
+ if (now - seenAt > this.tolerance) {
3481
+ this.seenEventIds.delete(id);
3482
+ }
3483
+ }
3484
+ }
3485
+ }
3491
3486
  return event;
3492
3487
  }
3493
3488
  async dispatch(event) {
@@ -3519,6 +3514,8 @@ function createWebhookMiddleware(options) {
3519
3514
  res.status(401).json({ error: message });
3520
3515
  } else if (message.includes("tolerance") || message.includes("timestamp")) {
3521
3516
  res.status(400).json({ error: message });
3517
+ } else if (message.includes("Duplicate")) {
3518
+ res.status(409).json({ error: message });
3522
3519
  } else if (next) {
3523
3520
  next(error);
3524
3521
  } else {
@@ -3547,7 +3544,7 @@ function createNextWebhookHandler(options) {
3547
3544
  });
3548
3545
  } catch (error) {
3549
3546
  const message = error instanceof Error ? error.message : "Webhook processing failed";
3550
- const status = message.includes("signature") ? 401 : message.includes("tolerance") || message.includes("timestamp") ? 400 : 500;
3547
+ const status = message.includes("signature") ? 401 : message.includes("tolerance") || message.includes("timestamp") ? 400 : message.includes("Duplicate") ? 409 : 500;
3551
3548
  return new Response(JSON.stringify({ error: message }), {
3552
3549
  status,
3553
3550
  headers: { "Content-Type": "application/json" }
@@ -3579,8 +3576,8 @@ exports.ComplianceBlockedError = ComplianceBlockedError;
3579
3576
  exports.ComplianceClient = ComplianceClient;
3580
3577
  exports.ComplianceError = ComplianceError;
3581
3578
  exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
3582
- exports.FraudClient = FraudClient;
3583
3579
  exports.GeolocationClient = GeolocationClient;
3580
+ exports.KYC_DECLINED_DESCRIPTIONS = KYC_DECLINED_DESCRIPTIONS;
3584
3581
  exports.KycClient = KycClient;
3585
3582
  exports.NetworkError = NetworkError;
3586
3583
  exports.RateLimitError = RateLimitError;
@@ -3590,7 +3587,6 @@ exports.SDK_VERSION = SDK_VERSION;
3590
3587
  exports.ServiceUnavailableError = ServiceUnavailableError;
3591
3588
  exports.TaxClient = TaxClient;
3592
3589
  exports.TimeoutError = TimeoutError;
3593
- exports.TransactionClient = TransactionClient;
3594
3590
  exports.ValidationError = ValidationError;
3595
3591
  exports.VesantError = VesantError;
3596
3592
  exports.WebhookHandler = WebhookHandler;
@@ -3601,6 +3597,7 @@ exports.decodeCipherText = decodeCipherText;
3601
3597
  exports.generateCipherText = generateCipherText;
3602
3598
  exports.isCipherTextExpired = isCipherTextExpired;
3603
3599
  exports.noopLogger = noopLogger;
3600
+ exports.sdkReasons = sdkReasons;
3604
3601
  exports.verifyWebhookSignature = verifyWebhookSignature;
3605
3602
  //# sourceMappingURL=index.js.map
3606
3603
  //# sourceMappingURL=index.js.map