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