vesant-sdk 1.6.6 → 1.7.0-dev.7d59b3e
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-ePzhQKp9.d.mts → client-BolQlL5e.d.mts} +1 -1
- package/dist/{client-ePzhQKp9.d.ts → client-BolQlL5e.d.ts} +1 -1
- package/dist/client-C3DCmGe9.d.ts +436 -0
- package/dist/{client-C_A7QLcB.d.ts → client-DMIRx7Tu.d.mts} +5 -3
- package/dist/{client-BlCxjbY2.d.mts → client-DoMSYMMR.d.ts} +5 -3
- package/dist/client-ZNdnpWe7.d.mts +436 -0
- package/dist/compliance/index.d.mts +25 -429
- package/dist/compliance/index.d.ts +25 -429
- package/dist/compliance/index.js +187 -103
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +187 -104
- 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 +7 -24
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +7 -24
- package/dist/geolocation/index.mjs.map +1 -1
- package/dist/index.d.mts +12 -70
- package/dist/index.d.ts +12 -70
- package/dist/index.js +302 -296
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +301 -295
- 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 +86 -27
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +86 -28
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +280 -50
- package/dist/kyc/index.d.ts +280 -50
- package/dist/kyc/index.js +86 -27
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +86 -28
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +46 -9
- package/dist/react.d.ts +46 -9
- package/dist/react.js +891 -276
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +890 -275
- 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-1RzYeSal.d.mts → types-BOFaMQxI.d.mts} +2 -2
- package/dist/{types-B4Ezqo7V.d.mts → types-CBQRNL-l.d.mts} +14 -1
- package/dist/{types-B4Ezqo7V.d.ts → types-CBQRNL-l.d.ts} +14 -1
- package/dist/{types-X5Md_dD_.d.ts → types-UGyDl1fd.d.ts} +2 -2
- package/dist/webhooks/index.d.mts +189 -2
- package/dist/webhooks/index.d.ts +189 -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 = "1.
|
|
243
|
+
var SDK_VERSION = "1.7.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
|
/**
|
|
@@ -1158,6 +1159,7 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1158
1159
|
risk_level: risk.level ?? "low",
|
|
1159
1160
|
risk_score: risk.score ?? 0,
|
|
1160
1161
|
risk_reasons: risk.is_blocked && risk.block_reasons ? risk.block_reasons : risk.factors ?? [],
|
|
1162
|
+
risk_reasons_structured: risk.block_reasons_structured ?? [],
|
|
1161
1163
|
jurisdiction: cipherTextResult.jurisdiction,
|
|
1162
1164
|
geofence_evaluation: cipherTextResult.geofence_evaluation,
|
|
1163
1165
|
record_id: cipherTextResult.record_id ?? "",
|
|
@@ -1337,24 +1339,6 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1337
1339
|
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|
|
1338
1340
|
// ============================================================================
|
|
1339
1341
|
};
|
|
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
1342
|
|
|
1359
1343
|
// src/risk-profile/client.ts
|
|
1360
1344
|
var RiskProfileClient = class extends BaseClient {
|
|
@@ -1471,6 +1455,81 @@ var RiskProfileClient = class extends BaseClient {
|
|
|
1471
1455
|
}
|
|
1472
1456
|
};
|
|
1473
1457
|
|
|
1458
|
+
// src/compliance/block-reasons.ts
|
|
1459
|
+
var sdkReasons = {
|
|
1460
|
+
// Jurisdiction
|
|
1461
|
+
jurisdictionBlocked: (country, countryISO) => ({
|
|
1462
|
+
code: "JURISDICTION_BLOCKED",
|
|
1463
|
+
message: "Access from a blocked jurisdiction",
|
|
1464
|
+
metadata: { country, country_iso: countryISO }
|
|
1465
|
+
}),
|
|
1466
|
+
jurisdictionNonCompliant: () => ({
|
|
1467
|
+
code: "JURISDICTION_NON_COMPLIANT",
|
|
1468
|
+
message: "Location does not meet compliance requirements"
|
|
1469
|
+
}),
|
|
1470
|
+
jurisdictionRegistrationDenied: () => ({
|
|
1471
|
+
code: "JURISDICTION_REGISTRATION_DENIED",
|
|
1472
|
+
message: "Registration is not permitted in this jurisdiction"
|
|
1473
|
+
}),
|
|
1474
|
+
jurisdictionRestricted: () => ({
|
|
1475
|
+
code: "JURISDICTION_RESTRICTED",
|
|
1476
|
+
message: "Access from a restricted jurisdiction"
|
|
1477
|
+
}),
|
|
1478
|
+
// Risk
|
|
1479
|
+
riskCriticalLevel: () => ({
|
|
1480
|
+
code: "RISK_CRITICAL_LEVEL",
|
|
1481
|
+
message: "Risk assessment reached critical threshold"
|
|
1482
|
+
}),
|
|
1483
|
+
accountSuspended: () => ({
|
|
1484
|
+
code: "RISK_ACCOUNT_SUSPENDED",
|
|
1485
|
+
message: "Customer account is suspended"
|
|
1486
|
+
}),
|
|
1487
|
+
sanctionsMatch: () => ({
|
|
1488
|
+
code: "RISK_SANCTIONS_MATCH",
|
|
1489
|
+
message: "Customer profile matched against sanctions list"
|
|
1490
|
+
}),
|
|
1491
|
+
riskHighLocation: () => ({
|
|
1492
|
+
code: "RISK_HIGH_LOCATION",
|
|
1493
|
+
message: "High-risk geographic location"
|
|
1494
|
+
}),
|
|
1495
|
+
riskHighCustomer: () => ({
|
|
1496
|
+
code: "RISK_HIGH_CUSTOMER",
|
|
1497
|
+
message: "Customer flagged as high risk"
|
|
1498
|
+
}),
|
|
1499
|
+
// Device
|
|
1500
|
+
ciphertextInvalid: () => ({
|
|
1501
|
+
code: "DEVICE_CIPHERTEXT_INVALID",
|
|
1502
|
+
message: "Device verification payload failed validation"
|
|
1503
|
+
}),
|
|
1504
|
+
gpsIPMismatch: () => ({
|
|
1505
|
+
code: "DEVICE_GPS_IP_MISMATCH",
|
|
1506
|
+
message: "GPS location does not match IP-derived location"
|
|
1507
|
+
}),
|
|
1508
|
+
gpsRequired: () => ({
|
|
1509
|
+
code: "DEVICE_GPS_REQUIRED",
|
|
1510
|
+
message: "GPS verification is required but was not provided"
|
|
1511
|
+
}),
|
|
1512
|
+
// Transaction
|
|
1513
|
+
transactionHighAmount: (amount, currency, threshold) => ({
|
|
1514
|
+
code: "TRANSACTION_HIGH_AMOUNT",
|
|
1515
|
+
message: "Transaction amount exceeds high-value threshold",
|
|
1516
|
+
metadata: { amount, currency, threshold }
|
|
1517
|
+
}),
|
|
1518
|
+
transactionElevatedAmount: (amount, currency, threshold) => ({
|
|
1519
|
+
code: "TRANSACTION_ELEVATED_AMOUNT",
|
|
1520
|
+
message: "Transaction amount exceeds elevated-value threshold",
|
|
1521
|
+
metadata: { amount, currency, threshold }
|
|
1522
|
+
}),
|
|
1523
|
+
transactionJurisdictionLimit: () => ({
|
|
1524
|
+
code: "TRANSACTION_JURISDICTION_LIMIT",
|
|
1525
|
+
message: "Transaction amount exceeds jurisdiction limit"
|
|
1526
|
+
}),
|
|
1527
|
+
anonymizationDetected: () => ({
|
|
1528
|
+
code: "NETWORK_ANONYMIZER_DETECTED",
|
|
1529
|
+
message: "Anonymization tool detected"
|
|
1530
|
+
})
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1474
1533
|
// src/compliance/types.ts
|
|
1475
1534
|
var DEFAULT_CURRENCY_RATES = {
|
|
1476
1535
|
USD: 1,
|
|
@@ -1617,10 +1676,10 @@ var ComplianceClient = class {
|
|
|
1617
1676
|
device_fingerprint: request.deviceFingerprint
|
|
1618
1677
|
}, requestOptions);
|
|
1619
1678
|
}
|
|
1620
|
-
const
|
|
1621
|
-
if (
|
|
1679
|
+
const structuredBlockReasons = this.evaluateRegistrationBlock(geoVerification, cipherTextResult);
|
|
1680
|
+
if (structuredBlockReasons.length > 0) {
|
|
1622
1681
|
if (this.config.debug) {
|
|
1623
|
-
this.logger.debug("Registration blocked at geo stage", { blockReasons });
|
|
1682
|
+
this.logger.debug("Registration blocked at geo stage", { blockReasons: structuredBlockReasons });
|
|
1624
1683
|
}
|
|
1625
1684
|
return {
|
|
1626
1685
|
allowed: false,
|
|
@@ -1628,9 +1687,7 @@ var ComplianceClient = class {
|
|
|
1628
1687
|
profile: null,
|
|
1629
1688
|
requiresKYC: false,
|
|
1630
1689
|
requiresEDD: false,
|
|
1631
|
-
|
|
1632
|
-
processingTime: Date.now() - startTime,
|
|
1633
|
-
cipherTextValidation: cipherTextResult
|
|
1690
|
+
...this.buildReasonTail(structuredBlockReasons, startTime, cipherTextResult)
|
|
1634
1691
|
};
|
|
1635
1692
|
}
|
|
1636
1693
|
const profile = await this.riskClient.createProfile({
|
|
@@ -1664,14 +1721,12 @@ var ComplianceClient = class {
|
|
|
1664
1721
|
profile,
|
|
1665
1722
|
requiresKYC,
|
|
1666
1723
|
requiresEDD,
|
|
1667
|
-
|
|
1668
|
-
processingTime: Date.now() - startTime,
|
|
1669
|
-
cipherTextValidation: cipherTextResult
|
|
1724
|
+
...this.buildReasonTail([], startTime, cipherTextResult)
|
|
1670
1725
|
};
|
|
1671
1726
|
} catch (error) {
|
|
1672
1727
|
if (this.config.debug) {
|
|
1673
1728
|
this.logger.error("Registration verification failed", {
|
|
1674
|
-
code: error instanceof
|
|
1729
|
+
code: error instanceof VesantError ? error.code : void 0,
|
|
1675
1730
|
message: error instanceof Error ? error.message : "Unknown error"
|
|
1676
1731
|
});
|
|
1677
1732
|
}
|
|
@@ -1700,52 +1755,62 @@ var ComplianceClient = class {
|
|
|
1700
1755
|
evaluateRegistrationBlock(geoVerification, cipherTextResult) {
|
|
1701
1756
|
const blockReasons = [];
|
|
1702
1757
|
if (geoVerification.is_blocked) {
|
|
1703
|
-
blockReasons.push(...geoVerification.
|
|
1758
|
+
blockReasons.push(...geoVerification.risk_reasons_structured ?? []);
|
|
1704
1759
|
}
|
|
1705
1760
|
if (!geoVerification.is_compliant) {
|
|
1706
|
-
if (!blockReasons.
|
|
1707
|
-
blockReasons.push(
|
|
1761
|
+
if (!blockReasons.some((r) => r.code === "JURISDICTION_NON_COMPLIANT")) {
|
|
1762
|
+
blockReasons.push(sdkReasons.jurisdictionNonCompliant());
|
|
1708
1763
|
}
|
|
1709
1764
|
}
|
|
1710
1765
|
const jurisdiction = geoVerification.jurisdiction;
|
|
1711
1766
|
if (jurisdiction) {
|
|
1712
1767
|
if (jurisdiction.allow_registration === false) {
|
|
1713
|
-
blockReasons.push(
|
|
1768
|
+
blockReasons.push(sdkReasons.jurisdictionRegistrationDenied());
|
|
1714
1769
|
}
|
|
1715
1770
|
if (jurisdiction.status === "blocked" || jurisdiction.status === "sanctioned") {
|
|
1716
|
-
if (!blockReasons.
|
|
1717
|
-
blockReasons.push("
|
|
1771
|
+
if (!blockReasons.some((r) => r.code === "JURISDICTION_BLOCKED")) {
|
|
1772
|
+
blockReasons.push(sdkReasons.jurisdictionBlocked(jurisdiction.country_name ?? "", jurisdiction.country_iso ?? ""));
|
|
1718
1773
|
}
|
|
1719
1774
|
}
|
|
1720
1775
|
if (jurisdiction.status === "restricted" && jurisdiction.allow_registration === false) {
|
|
1721
|
-
if (!blockReasons.
|
|
1722
|
-
blockReasons.push(
|
|
1776
|
+
if (!blockReasons.some((r) => r.code === "JURISDICTION_RESTRICTED")) {
|
|
1777
|
+
blockReasons.push(sdkReasons.jurisdictionRestricted());
|
|
1723
1778
|
}
|
|
1724
1779
|
}
|
|
1725
1780
|
}
|
|
1726
1781
|
if (geoVerification.geofence_evaluation?.blocked) {
|
|
1727
|
-
blockReasons.push(
|
|
1782
|
+
blockReasons.push(
|
|
1783
|
+
...(geoVerification.geofence_evaluation.reasons ?? []).map((m) => ({
|
|
1784
|
+
code: "JURISDICTION_GEOFENCE_VIOLATION",
|
|
1785
|
+
message: m
|
|
1786
|
+
}))
|
|
1787
|
+
);
|
|
1728
1788
|
}
|
|
1729
1789
|
if (geoVerification.risk_level === "critical") {
|
|
1730
|
-
if (!blockReasons.
|
|
1731
|
-
blockReasons.push(
|
|
1790
|
+
if (!blockReasons.some((r) => r.code === "RISK_CRITICAL_LEVEL")) {
|
|
1791
|
+
blockReasons.push(sdkReasons.riskCriticalLevel());
|
|
1732
1792
|
}
|
|
1733
1793
|
}
|
|
1734
1794
|
if (cipherTextResult) {
|
|
1735
1795
|
if (!cipherTextResult.valid) {
|
|
1736
|
-
blockReasons.push(
|
|
1796
|
+
blockReasons.push(sdkReasons.ciphertextInvalid());
|
|
1737
1797
|
}
|
|
1738
1798
|
if (cipherTextResult.risk?.is_blocked) {
|
|
1739
|
-
blockReasons.push(...cipherTextResult.risk.
|
|
1799
|
+
blockReasons.push(...cipherTextResult.risk.block_reasons_structured ?? []);
|
|
1740
1800
|
}
|
|
1741
1801
|
if (cipherTextResult.risk?.location_mismatch) {
|
|
1742
|
-
blockReasons.push(
|
|
1802
|
+
blockReasons.push(sdkReasons.gpsIPMismatch());
|
|
1743
1803
|
}
|
|
1744
1804
|
}
|
|
1745
1805
|
if (geoVerification.gps_required && !cipherTextResult) {
|
|
1746
|
-
blockReasons.push(
|
|
1806
|
+
blockReasons.push(sdkReasons.gpsRequired());
|
|
1747
1807
|
}
|
|
1748
|
-
|
|
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
|
+
});
|
|
1749
1814
|
}
|
|
1750
1815
|
/**
|
|
1751
1816
|
* Validate registration request has all required fields
|
|
@@ -1916,9 +1981,7 @@ var ComplianceClient = class {
|
|
|
1916
1981
|
geolocation: geoVerification,
|
|
1917
1982
|
profile: null,
|
|
1918
1983
|
requiresStepUp: false,
|
|
1919
|
-
|
|
1920
|
-
processingTime: Date.now() - startTime,
|
|
1921
|
-
cipherTextValidation: cipherTextResult
|
|
1984
|
+
...this.buildReasonTail(loginBlockReasons, startTime, cipherTextResult)
|
|
1922
1985
|
};
|
|
1923
1986
|
}
|
|
1924
1987
|
let profile;
|
|
@@ -1938,14 +2001,13 @@ var ComplianceClient = class {
|
|
|
1938
2001
|
profile = await this.createProfileFromGeo(request.customerId, geoVerification);
|
|
1939
2002
|
}
|
|
1940
2003
|
const requiresStepUp = geoVerification.risk_level === "high" || geoVerification.risk_level === "critical" || cipherTextResult?.risk?.location_mismatch === true;
|
|
2004
|
+
const loginFinalStructured = isBlocked || profile.customer_status === "suspended" ? this.getBlockReasons(geoVerification, profile, cipherTextResult) : [];
|
|
1941
2005
|
return {
|
|
1942
2006
|
allowed: !isBlocked && profile.customer_status !== "suspended",
|
|
1943
2007
|
geolocation: geoVerification,
|
|
1944
2008
|
profile,
|
|
1945
2009
|
requiresStepUp,
|
|
1946
|
-
|
|
1947
|
-
processingTime: Date.now() - startTime,
|
|
1948
|
-
cipherTextValidation: cipherTextResult
|
|
2010
|
+
...this.buildReasonTail(loginFinalStructured, startTime, cipherTextResult)
|
|
1949
2011
|
};
|
|
1950
2012
|
} catch (error) {
|
|
1951
2013
|
throw new ComplianceError("Login verification failed", error instanceof Error ? error.message : void 0);
|
|
@@ -2027,20 +2089,19 @@ var ComplianceClient = class {
|
|
|
2027
2089
|
}
|
|
2028
2090
|
const geoBlocked = !geoVerification.is_compliant || geoVerification.is_blocked || !!cipherTextResult?.risk?.is_blocked || cipherTextResult?.valid === false || geoVerification.gps_required && !cipherTextResult;
|
|
2029
2091
|
if (geoBlocked && !profileResult) {
|
|
2092
|
+
const earlyStructured = this.getTransactionBlockReasons(
|
|
2093
|
+
geoVerification,
|
|
2094
|
+
{ score: 0, level: "low", factors: [], allowed: false, requiresManualReview: false },
|
|
2095
|
+
true,
|
|
2096
|
+
cipherTextResult
|
|
2097
|
+
);
|
|
2030
2098
|
return {
|
|
2031
2099
|
allowed: false,
|
|
2032
2100
|
geolocation: geoVerification,
|
|
2033
2101
|
profile: null,
|
|
2034
2102
|
transactionRisk: { score: 0, level: "low", factors: [], allowed: false, requiresManualReview: false },
|
|
2035
2103
|
requiresApproval: false,
|
|
2036
|
-
|
|
2037
|
-
geoVerification,
|
|
2038
|
-
{ score: 0, level: "low", factors: [], allowed: false, requiresManualReview: false },
|
|
2039
|
-
true,
|
|
2040
|
-
cipherTextResult
|
|
2041
|
-
),
|
|
2042
|
-
processingTime: Date.now() - startTime,
|
|
2043
|
-
cipherTextValidation: cipherTextResult
|
|
2104
|
+
...this.buildReasonTail(earlyStructured, startTime, cipherTextResult)
|
|
2044
2105
|
};
|
|
2045
2106
|
}
|
|
2046
2107
|
if (!profileResult) {
|
|
@@ -2060,20 +2121,19 @@ var ComplianceClient = class {
|
|
|
2060
2121
|
geoVerification.jurisdiction
|
|
2061
2122
|
);
|
|
2062
2123
|
const isAllowed = !geoBlocked && jurisdictionAllowed && transactionRisk.allowed && profile.customer_status !== "suspended";
|
|
2124
|
+
const txStructured = this.getTransactionBlockReasons(
|
|
2125
|
+
geoVerification,
|
|
2126
|
+
transactionRisk,
|
|
2127
|
+
jurisdictionAllowed,
|
|
2128
|
+
cipherTextResult
|
|
2129
|
+
);
|
|
2063
2130
|
return {
|
|
2064
2131
|
allowed: isAllowed,
|
|
2065
2132
|
geolocation: geoVerification,
|
|
2066
2133
|
profile,
|
|
2067
2134
|
transactionRisk,
|
|
2068
2135
|
requiresApproval: transactionRisk.requiresManualReview,
|
|
2069
|
-
|
|
2070
|
-
geoVerification,
|
|
2071
|
-
transactionRisk,
|
|
2072
|
-
jurisdictionAllowed,
|
|
2073
|
-
cipherTextResult
|
|
2074
|
-
),
|
|
2075
|
-
processingTime: Date.now() - startTime,
|
|
2076
|
-
cipherTextValidation: cipherTextResult
|
|
2136
|
+
...this.buildReasonTail(txStructured, startTime, cipherTextResult)
|
|
2077
2137
|
};
|
|
2078
2138
|
} catch (error) {
|
|
2079
2139
|
throw new ComplianceError("Transaction verification failed", error instanceof Error ? error.message : void 0);
|
|
@@ -2126,30 +2186,42 @@ var ComplianceClient = class {
|
|
|
2126
2186
|
}
|
|
2127
2187
|
}
|
|
2128
2188
|
const cipherTextBlocked = cipherTextResult ? !cipherTextResult.valid || cipherTextResult.risk?.is_blocked === true : false;
|
|
2129
|
-
const
|
|
2189
|
+
const structuredEventReasons = [...geoVerification.risk_reasons_structured ?? []];
|
|
2130
2190
|
if (cipherTextResult) {
|
|
2131
2191
|
if (!cipherTextResult.valid) {
|
|
2132
|
-
|
|
2192
|
+
structuredEventReasons.push(sdkReasons.ciphertextInvalid());
|
|
2133
2193
|
}
|
|
2134
2194
|
if (cipherTextResult.risk?.is_blocked) {
|
|
2135
|
-
|
|
2195
|
+
structuredEventReasons.push(...cipherTextResult.risk.block_reasons_structured ?? []);
|
|
2136
2196
|
}
|
|
2137
2197
|
}
|
|
2138
2198
|
const gpsBlocked = geoVerification.gps_required && !cipherTextResult;
|
|
2139
2199
|
if (gpsBlocked) {
|
|
2140
|
-
|
|
2200
|
+
structuredEventReasons.push(sdkReasons.gpsRequired());
|
|
2141
2201
|
}
|
|
2202
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2203
|
+
const dedupedStructured = structuredEventReasons.filter((r) => {
|
|
2204
|
+
if (seen.has(r.code)) return false;
|
|
2205
|
+
seen.add(r.code);
|
|
2206
|
+
return true;
|
|
2207
|
+
});
|
|
2142
2208
|
return {
|
|
2143
2209
|
allowed: geoVerification.is_compliant && !geoVerification.is_blocked && !cipherTextBlocked && !gpsBlocked,
|
|
2144
2210
|
geolocation: geoVerification,
|
|
2145
|
-
|
|
2146
|
-
processingTime: Date.now() - startTime,
|
|
2147
|
-
cipherTextValidation: cipherTextResult
|
|
2211
|
+
...this.buildReasonTail(dedupedStructured, startTime, cipherTextResult)
|
|
2148
2212
|
};
|
|
2149
2213
|
}
|
|
2150
2214
|
// ============================================================================
|
|
2151
2215
|
// Helper Methods
|
|
2152
2216
|
// ============================================================================
|
|
2217
|
+
buildReasonTail(structured, startTime, cipherTextResult) {
|
|
2218
|
+
return {
|
|
2219
|
+
blockReasons: structured.map((r) => r.message),
|
|
2220
|
+
blockReasonsStructured: structured,
|
|
2221
|
+
processingTime: Date.now() - startTime,
|
|
2222
|
+
cipherTextValidation: cipherTextResult
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2153
2225
|
shouldUpdateProfile(profile, newCity) {
|
|
2154
2226
|
return !profile.location || !profile.location.includes(newCity);
|
|
2155
2227
|
}
|
|
@@ -2226,7 +2298,8 @@ var ComplianceClient = class {
|
|
|
2226
2298
|
is_blocked: risk.is_blocked,
|
|
2227
2299
|
risk_level: risk.level,
|
|
2228
2300
|
risk_score: risk.score,
|
|
2229
|
-
risk_reasons:
|
|
2301
|
+
risk_reasons: [],
|
|
2302
|
+
risk_reasons_structured: risk.is_blocked && risk.block_reasons_structured ? risk.block_reasons_structured : [],
|
|
2230
2303
|
jurisdiction: ct.jurisdiction,
|
|
2231
2304
|
geofence_evaluation: ct.geofence_evaluation,
|
|
2232
2305
|
record_id: ct.record_id ?? "",
|
|
@@ -2241,35 +2314,35 @@ var ComplianceClient = class {
|
|
|
2241
2314
|
);
|
|
2242
2315
|
}
|
|
2243
2316
|
let riskScore = 0;
|
|
2244
|
-
const
|
|
2317
|
+
const structuredFactors = [];
|
|
2245
2318
|
const normalizedAmount = this.normalizeToUSD(amount, currency);
|
|
2246
2319
|
if (normalizedAmount > 1e4) {
|
|
2247
2320
|
riskScore += 30;
|
|
2248
|
-
|
|
2321
|
+
structuredFactors.push(sdkReasons.transactionHighAmount(normalizedAmount, currency, 1e4));
|
|
2249
2322
|
} else if (normalizedAmount > 5e3) {
|
|
2250
2323
|
riskScore += 15;
|
|
2251
|
-
|
|
2324
|
+
structuredFactors.push(sdkReasons.transactionElevatedAmount(normalizedAmount, currency, 5e3));
|
|
2252
2325
|
}
|
|
2253
2326
|
riskScore += geoVerification.risk_score * 0.4;
|
|
2254
2327
|
if (geoVerification.risk_level === "high" || geoVerification.risk_level === "critical") {
|
|
2255
|
-
|
|
2328
|
+
structuredFactors.push(sdkReasons.riskHighLocation());
|
|
2256
2329
|
}
|
|
2257
2330
|
riskScore += profile.risk_score * 0.3;
|
|
2258
2331
|
if (profile.risk_category === "high" || profile.risk_category === "critical") {
|
|
2259
|
-
|
|
2332
|
+
structuredFactors.push(sdkReasons.riskHighCustomer());
|
|
2260
2333
|
}
|
|
2261
2334
|
if (geoVerification.location.is_vpn || geoVerification.location.is_proxy) {
|
|
2262
2335
|
riskScore += 20;
|
|
2263
|
-
|
|
2336
|
+
structuredFactors.push(sdkReasons.anonymizationDetected());
|
|
2264
2337
|
}
|
|
2265
2338
|
if (profile.customer_status === "suspended") {
|
|
2266
2339
|
riskScore += 50;
|
|
2267
|
-
|
|
2340
|
+
structuredFactors.push(sdkReasons.accountSuspended());
|
|
2268
2341
|
}
|
|
2269
2342
|
if (cipherTextResult) {
|
|
2270
2343
|
if (cipherTextResult.risk?.location_mismatch) {
|
|
2271
2344
|
riskScore += 20;
|
|
2272
|
-
|
|
2345
|
+
structuredFactors.push(sdkReasons.gpsIPMismatch());
|
|
2273
2346
|
}
|
|
2274
2347
|
if (cipherTextResult.risk?.score) {
|
|
2275
2348
|
riskScore += cipherTextResult.risk.score * 0.2;
|
|
@@ -2278,7 +2351,8 @@ var ComplianceClient = class {
|
|
|
2278
2351
|
return {
|
|
2279
2352
|
score: Math.min(riskScore, 100),
|
|
2280
2353
|
level: this.getRiskLevel(riskScore),
|
|
2281
|
-
factors,
|
|
2354
|
+
factors: structuredFactors.map((r) => r.message),
|
|
2355
|
+
factorsStructured: structuredFactors,
|
|
2282
2356
|
allowed: riskScore < 70,
|
|
2283
2357
|
requiresManualReview: riskScore >= 60 && riskScore < 70
|
|
2284
2358
|
};
|
|
@@ -2303,52 +2377,62 @@ var ComplianceClient = class {
|
|
|
2303
2377
|
getBlockReasons(geoVerification, profile, cipherTextResult) {
|
|
2304
2378
|
const reasons = [];
|
|
2305
2379
|
if (geoVerification.is_blocked) {
|
|
2306
|
-
reasons.push(...geoVerification.
|
|
2380
|
+
reasons.push(...geoVerification.risk_reasons_structured ?? []);
|
|
2307
2381
|
}
|
|
2308
2382
|
if (profile) {
|
|
2309
2383
|
if (profile.customer_status === "suspended") {
|
|
2310
|
-
reasons.push(
|
|
2384
|
+
reasons.push(sdkReasons.accountSuspended());
|
|
2311
2385
|
}
|
|
2312
2386
|
if (profile.has_sanctions) {
|
|
2313
|
-
reasons.push(
|
|
2387
|
+
reasons.push(sdkReasons.sanctionsMatch());
|
|
2314
2388
|
}
|
|
2315
2389
|
}
|
|
2316
2390
|
if (cipherTextResult) {
|
|
2317
2391
|
if (!cipherTextResult.valid) {
|
|
2318
|
-
reasons.push(
|
|
2392
|
+
reasons.push(sdkReasons.ciphertextInvalid());
|
|
2319
2393
|
}
|
|
2320
2394
|
if (cipherTextResult.risk?.is_blocked) {
|
|
2321
|
-
reasons.push(...cipherTextResult.risk.
|
|
2395
|
+
reasons.push(...cipherTextResult.risk.block_reasons_structured ?? []);
|
|
2322
2396
|
}
|
|
2323
2397
|
}
|
|
2324
2398
|
if (geoVerification.gps_required && !cipherTextResult) {
|
|
2325
|
-
reasons.push(
|
|
2399
|
+
reasons.push(sdkReasons.gpsRequired());
|
|
2326
2400
|
}
|
|
2327
|
-
|
|
2401
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2402
|
+
return reasons.filter((r) => {
|
|
2403
|
+
if (seen.has(r.code)) return false;
|
|
2404
|
+
seen.add(r.code);
|
|
2405
|
+
return true;
|
|
2406
|
+
});
|
|
2328
2407
|
}
|
|
2329
2408
|
getTransactionBlockReasons(geoVerification, transactionRisk, jurisdictionAllowed, cipherTextResult) {
|
|
2330
2409
|
const reasons = [];
|
|
2331
2410
|
if (!geoVerification.is_compliant) {
|
|
2332
|
-
reasons.push(
|
|
2411
|
+
reasons.push(sdkReasons.jurisdictionNonCompliant());
|
|
2333
2412
|
}
|
|
2334
2413
|
if (!jurisdictionAllowed) {
|
|
2335
|
-
reasons.push(
|
|
2414
|
+
reasons.push(sdkReasons.transactionJurisdictionLimit());
|
|
2336
2415
|
}
|
|
2337
2416
|
if (!transactionRisk.allowed) {
|
|
2338
|
-
reasons.push(...transactionRisk.
|
|
2417
|
+
reasons.push(...transactionRisk.factorsStructured ?? []);
|
|
2339
2418
|
}
|
|
2340
2419
|
if (cipherTextResult) {
|
|
2341
2420
|
if (!cipherTextResult.valid) {
|
|
2342
|
-
reasons.push(
|
|
2421
|
+
reasons.push(sdkReasons.ciphertextInvalid());
|
|
2343
2422
|
}
|
|
2344
2423
|
if (cipherTextResult.risk?.is_blocked) {
|
|
2345
|
-
reasons.push(...cipherTextResult.risk.
|
|
2424
|
+
reasons.push(...cipherTextResult.risk.block_reasons_structured ?? []);
|
|
2346
2425
|
}
|
|
2347
2426
|
}
|
|
2348
2427
|
if (geoVerification.gps_required && !cipherTextResult) {
|
|
2349
|
-
reasons.push(
|
|
2428
|
+
reasons.push(sdkReasons.gpsRequired());
|
|
2350
2429
|
}
|
|
2351
|
-
|
|
2430
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2431
|
+
return reasons.filter((r) => {
|
|
2432
|
+
if (seen.has(r.code)) return false;
|
|
2433
|
+
seen.add(r.code);
|
|
2434
|
+
return true;
|
|
2435
|
+
});
|
|
2352
2436
|
}
|
|
2353
2437
|
// ============================================================================
|
|
2354
2438
|
// Location Request Methods
|
|
@@ -2626,23 +2710,19 @@ var KycClient = class extends BaseClient {
|
|
|
2626
2710
|
*
|
|
2627
2711
|
* Generates a link that the user can visit to submit their KYC documents.
|
|
2628
2712
|
*
|
|
2629
|
-
* @param request - Request containing the user ID, redirect URL, callback URL
|
|
2630
|
-
* @returns Response containing
|
|
2713
|
+
* @param request - Request containing the user ID, optional redirect URL, and optional callback URL (receives POST requests)
|
|
2714
|
+
* @returns Response containing the redirect link and KYC ID
|
|
2631
2715
|
*
|
|
2632
2716
|
* @example
|
|
2633
2717
|
* ```typescript
|
|
2634
2718
|
* const result = await client.requestKycSubmitLink({
|
|
2635
2719
|
* 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"
|
|
2720
|
+
* redirect_url: "https://merchant.com/kyc-complete", // optional
|
|
2721
|
+
* callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
|
|
2639
2722
|
* });
|
|
2640
2723
|
*
|
|
2641
|
-
*
|
|
2642
|
-
*
|
|
2643
|
-
* } else if (result.can_skip) {
|
|
2644
|
-
* console.log("KYC not required, user can proceed");
|
|
2645
|
-
* }
|
|
2724
|
+
* console.log(`Redirect user to: ${result.link}`);
|
|
2725
|
+
* console.log(`KYC ID: ${result.kyc_id}`);
|
|
2646
2726
|
* ```
|
|
2647
2727
|
*/
|
|
2648
2728
|
async requestKycSubmitLink(request) {
|
|
@@ -2653,40 +2733,88 @@ var KycClient = class extends BaseClient {
|
|
|
2653
2733
|
});
|
|
2654
2734
|
}
|
|
2655
2735
|
/**
|
|
2656
|
-
* Create a
|
|
2736
|
+
* Create a Event-Based Face Verification session.
|
|
2737
|
+
*
|
|
2738
|
+
* Inspect the response before showing UI:
|
|
2739
|
+
* - `is_required === false` → skip face capture; `reason` explains why.
|
|
2740
|
+
* - `device_type === 'desktop'` → render `qr_payload` as a QR; the
|
|
2741
|
+
* mobile device picks up the session via the connect endpoint.
|
|
2742
|
+
* - `device_type === 'mobile'` → open the face capture modal directly.
|
|
2657
2743
|
*
|
|
2658
|
-
* @param request -
|
|
2744
|
+
* @param request - Reference, customer_id, event, amount (for threshold events), optional URLs.
|
|
2659
2745
|
*/
|
|
2660
|
-
async
|
|
2661
|
-
return this.
|
|
2746
|
+
async createEventBasedFaceVerificationSession(request) {
|
|
2747
|
+
return this.request("/api/v1/kyc/face/session", {
|
|
2662
2748
|
method: "POST",
|
|
2663
2749
|
body: JSON.stringify(request),
|
|
2664
2750
|
headers: this.getUserHeaders()
|
|
2665
2751
|
});
|
|
2666
2752
|
}
|
|
2667
2753
|
/**
|
|
2668
|
-
* Submit a
|
|
2754
|
+
* Submit a real-time face capture for an active Event-Based Face Verification session.
|
|
2755
|
+
*
|
|
2756
|
+
* **Mobile-only.** The server rejects desktop User-Agents with HTTP
|
|
2757
|
+
* 400 (`face capture must be completed on a mobile device`). Use the
|
|
2758
|
+
* QR handoff from `createEventBasedFaceVerificationSession` for desktop callers.
|
|
2759
|
+
*
|
|
2760
|
+
* The `data` field on the response carries `retries_remaining`,
|
|
2761
|
+
* `retry_limit_exceeded`, and the reaction flags (`enforce_logout`,
|
|
2762
|
+
* `freeze_account`, etc.) so the tenant app can act on a final failure.
|
|
2669
2763
|
*
|
|
2670
|
-
* @param request -
|
|
2764
|
+
* @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
|
|
2671
2765
|
*/
|
|
2672
|
-
async
|
|
2673
|
-
return this.
|
|
2766
|
+
async submitEventBasedFaceVerificationSession(request) {
|
|
2767
|
+
return this.request("/api/v1/kyc/face/submit", {
|
|
2674
2768
|
method: "POST",
|
|
2675
2769
|
body: JSON.stringify(request),
|
|
2676
2770
|
headers: this.getUserHeaders()
|
|
2677
2771
|
});
|
|
2678
2772
|
}
|
|
2679
2773
|
/**
|
|
2680
|
-
*
|
|
2681
|
-
*
|
|
2682
|
-
*
|
|
2683
|
-
*
|
|
2684
|
-
|
|
2685
|
-
|
|
2774
|
+
* Look up the current state of a Event-Based Face Verification session by its
|
|
2775
|
+
* session token. Useful for desktop pollers waiting on the mobile handoff.
|
|
2776
|
+
*
|
|
2777
|
+
* The lookup is scoped by the unguessable, server-issued session token (not
|
|
2778
|
+
* the enumerable forward reference): the endpoint is public/unauthenticated,
|
|
2779
|
+
* and scoping by the token is what prevents cross-tenant status reads.
|
|
2780
|
+
*
|
|
2781
|
+
* @param token - The session token returned by `createEventBasedFaceVerificationSession`.
|
|
2782
|
+
*/
|
|
2783
|
+
async getEventBasedFaceVerificationSessionStatus(token) {
|
|
2784
|
+
return this.requestWithRetry(`/api/v1/kyc/face/verify/${encodeURIComponent(token)}`, {
|
|
2686
2785
|
method: "GET",
|
|
2687
2786
|
headers: this.getUserHeaders()
|
|
2688
2787
|
});
|
|
2689
2788
|
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Fetch the Redis-backed handoff session for a token. Same backing
|
|
2791
|
+
* store as normal KYC (`kyc:session:<token>`, 15-minute TTL). Desktop
|
|
2792
|
+
* callers poll `mobile_connected` to detect when a mobile device has
|
|
2793
|
+
* scanned the QR and attached.
|
|
2794
|
+
*
|
|
2795
|
+
* @param token - The session token returned by `createEventBasedFaceVerificationSession`.
|
|
2796
|
+
*/
|
|
2797
|
+
async getHandoffSession(token) {
|
|
2798
|
+
return this.request(
|
|
2799
|
+
`/api/v1/kyc/session${this.buildQueryString({ token })}`,
|
|
2800
|
+
{ headers: this.getUserHeaders() }
|
|
2801
|
+
);
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Attach a mobile device to a desktop-initiated session. The mobile
|
|
2805
|
+
* client calls this after scanning the QR code. The desktop poller
|
|
2806
|
+
* sees `mobile_connected: true` on the next `getHandoffSession`.
|
|
2807
|
+
*
|
|
2808
|
+
* @param token - The session token transferred via the QR payload.
|
|
2809
|
+
* @param isDisconnect - Pass true to release the session (default false).
|
|
2810
|
+
*/
|
|
2811
|
+
async connectMobileSession(token, isDisconnect = false) {
|
|
2812
|
+
return this.request("/api/v1/kyc/session/connect", {
|
|
2813
|
+
method: "PUT",
|
|
2814
|
+
body: JSON.stringify({ token, is_disconnect: isDisconnect }),
|
|
2815
|
+
headers: this.getUserHeaders()
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2690
2818
|
/**
|
|
2691
2819
|
* Check KYC status for a user
|
|
2692
2820
|
*
|
|
@@ -3045,8 +3173,9 @@ var KycClient = class extends BaseClient {
|
|
|
3045
3173
|
*/
|
|
3046
3174
|
async riskProfileRequest(path, options = {}) {
|
|
3047
3175
|
if (!this.riskProfileBaseURL) {
|
|
3048
|
-
throw new
|
|
3049
|
-
"Risk Profile Service URL not configured. Please provide riskProfileBaseURL in KycClientConfig."
|
|
3176
|
+
throw new ValidationError(
|
|
3177
|
+
"Risk Profile Service URL not configured. Please provide riskProfileBaseURL in KycClientConfig.",
|
|
3178
|
+
["riskProfileBaseURL"]
|
|
3050
3179
|
);
|
|
3051
3180
|
}
|
|
3052
3181
|
return this.request(path, {
|
|
@@ -3158,6 +3287,19 @@ var KycClient = class extends BaseClient {
|
|
|
3158
3287
|
// ============================================================================
|
|
3159
3288
|
};
|
|
3160
3289
|
|
|
3290
|
+
// src/kyc/types.ts
|
|
3291
|
+
var KYC_DECLINED_DESCRIPTIONS = {
|
|
3292
|
+
KYC_DOCUMENT_EXPIRED: "The submitted document has expired",
|
|
3293
|
+
KYC_DOCUMENT_INVALID: "The submitted document could not be verified",
|
|
3294
|
+
KYC_FACE_MISMATCH: "Face verification did not match the identity document",
|
|
3295
|
+
KYC_AGE_REQUIREMENT: "Age requirement not met",
|
|
3296
|
+
KYC_DUPLICATE_IDENTITY: "This identity has already been verified under another account",
|
|
3297
|
+
KYC_ADDRESS_MISMATCH: "Address verification failed",
|
|
3298
|
+
KYC_NAME_MISMATCH: "Name on document does not match the registered name",
|
|
3299
|
+
KYC_PROVIDER_REJECTED: "Identity verification was rejected by the verification provider",
|
|
3300
|
+
KYC_DECLINED: "Identity verification was declined"
|
|
3301
|
+
};
|
|
3302
|
+
|
|
3161
3303
|
// src/tax/client.ts
|
|
3162
3304
|
var TaxClient = class extends BaseClient {
|
|
3163
3305
|
constructor(config) {
|
|
@@ -3224,41 +3366,6 @@ var TaxClient = class extends BaseClient {
|
|
|
3224
3366
|
{ ...requestOptions, responseType: "arraybuffer" }
|
|
3225
3367
|
);
|
|
3226
3368
|
}
|
|
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
3369
|
// ==========================================================================
|
|
3263
3370
|
// P2 — Tenant Tax Rules
|
|
3264
3371
|
// ==========================================================================
|
|
@@ -3302,136 +3409,15 @@ var TaxClient = class extends BaseClient {
|
|
|
3302
3409
|
}
|
|
3303
3410
|
};
|
|
3304
3411
|
|
|
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
3412
|
// src/webhooks/handler.ts
|
|
3429
3413
|
var WebhookHandler = class {
|
|
3430
3414
|
constructor(config) {
|
|
3431
3415
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3432
3416
|
this.anyHandlers = [];
|
|
3417
|
+
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3433
3418
|
this.secret = config.secret;
|
|
3434
3419
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3420
|
+
this.replayProtection = config.replayProtection ?? true;
|
|
3435
3421
|
}
|
|
3436
3422
|
/**
|
|
3437
3423
|
* Register a handler for a specific event type.
|
|
@@ -3455,7 +3441,7 @@ var WebhookHandler = class {
|
|
|
3455
3441
|
async verifyAndParse(body, signature) {
|
|
3456
3442
|
const isValid = await verifyWebhookSignature(body, signature, this.secret);
|
|
3457
3443
|
if (!isValid) {
|
|
3458
|
-
throw new
|
|
3444
|
+
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3459
3445
|
}
|
|
3460
3446
|
return this.parseAndValidate(body);
|
|
3461
3447
|
}
|
|
@@ -3475,16 +3461,34 @@ var WebhookHandler = class {
|
|
|
3475
3461
|
parseAndValidate(body) {
|
|
3476
3462
|
const event = JSON.parse(body);
|
|
3477
3463
|
if (!event.type || !event.id || !event.timestamp) {
|
|
3478
|
-
throw new
|
|
3464
|
+
throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
|
|
3479
3465
|
}
|
|
3480
3466
|
if (this.tolerance > 0) {
|
|
3481
3467
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3482
3468
|
const now = Date.now();
|
|
3483
3469
|
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3484
|
-
throw new
|
|
3485
|
-
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)
|
|
3470
|
+
throw new ValidationError(
|
|
3471
|
+
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3472
|
+
["timestamp"]
|
|
3473
|
+
);
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
if (this.replayProtection) {
|
|
3477
|
+
if (this.seenEventIds.has(event.id)) {
|
|
3478
|
+
throw new ValidationError(
|
|
3479
|
+
`Duplicate webhook event: ${event.id} has already been processed`,
|
|
3480
|
+
["id"]
|
|
3486
3481
|
);
|
|
3487
3482
|
}
|
|
3483
|
+
const now = Date.now();
|
|
3484
|
+
this.seenEventIds.set(event.id, now);
|
|
3485
|
+
if (this.seenEventIds.size > 1e3) {
|
|
3486
|
+
for (const [id, seenAt] of this.seenEventIds) {
|
|
3487
|
+
if (now - seenAt > this.tolerance) {
|
|
3488
|
+
this.seenEventIds.delete(id);
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3488
3492
|
}
|
|
3489
3493
|
return event;
|
|
3490
3494
|
}
|
|
@@ -3517,6 +3521,8 @@ function createWebhookMiddleware(options) {
|
|
|
3517
3521
|
res.status(401).json({ error: message });
|
|
3518
3522
|
} else if (message.includes("tolerance") || message.includes("timestamp")) {
|
|
3519
3523
|
res.status(400).json({ error: message });
|
|
3524
|
+
} else if (message.includes("Duplicate")) {
|
|
3525
|
+
res.status(409).json({ error: message });
|
|
3520
3526
|
} else if (next) {
|
|
3521
3527
|
next(error);
|
|
3522
3528
|
} else {
|
|
@@ -3545,7 +3551,7 @@ function createNextWebhookHandler(options) {
|
|
|
3545
3551
|
});
|
|
3546
3552
|
} catch (error) {
|
|
3547
3553
|
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;
|
|
3554
|
+
const status = message.includes("signature") ? 401 : message.includes("tolerance") || message.includes("timestamp") ? 400 : message.includes("Duplicate") ? 409 : 500;
|
|
3549
3555
|
return new Response(JSON.stringify({ error: message }), {
|
|
3550
3556
|
status,
|
|
3551
3557
|
headers: { "Content-Type": "application/json" }
|
|
@@ -3568,6 +3574,6 @@ function buildHandler(options) {
|
|
|
3568
3574
|
return handler;
|
|
3569
3575
|
}
|
|
3570
3576
|
|
|
3571
|
-
export { AuthenticationError, BaseClient, CGSError, CircuitBreaker, CircuitBreakerOpenError, ComplianceBlockedError, ComplianceClient, ComplianceError, DEFAULT_CURRENCY_RATES,
|
|
3577
|
+
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
3578
|
//# sourceMappingURL=index.mjs.map
|
|
3573
3579
|
//# sourceMappingURL=index.mjs.map
|