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