vesant-sdk 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -0
- package/dist/client-CA9Wr_qb.d.ts +1108 -0
- package/dist/client-DMNkESa0.d.mts +1108 -0
- package/dist/compliance/index.d.mts +543 -0
- package/dist/compliance/index.d.ts +543 -0
- package/dist/compliance/index.js +2133 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/compliance/index.mjs +2130 -0
- package/dist/compliance/index.mjs.map +1 -0
- package/dist/geolocation/index.d.mts +73 -0
- package/dist/geolocation/index.d.ts +73 -0
- package/dist/geolocation/index.js +1100 -0
- package/dist/geolocation/index.js.map +1 -0
- package/dist/geolocation/index.mjs +1094 -0
- package/dist/geolocation/index.mjs.map +1 -0
- package/dist/index.d.mts +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +2968 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2948 -0
- package/dist/index.mjs.map +1 -0
- package/dist/kyc/core.d.mts +3 -0
- package/dist/kyc/core.d.ts +3 -0
- package/dist/kyc/core.js +773 -0
- package/dist/kyc/core.js.map +1 -0
- package/dist/kyc/core.mjs +771 -0
- package/dist/kyc/core.mjs.map +1 -0
- package/dist/kyc/index.d.mts +734 -0
- package/dist/kyc/index.d.ts +734 -0
- package/dist/kyc/index.js +773 -0
- package/dist/kyc/index.js.map +1 -0
- package/dist/kyc/index.mjs +771 -0
- package/dist/kyc/index.mjs.map +1 -0
- package/dist/react.d.mts +487 -0
- package/dist/react.d.ts +487 -0
- package/dist/react.js +1122 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +1102 -0
- package/dist/react.mjs.map +1 -0
- package/dist/risk-profile/index.d.mts +228 -0
- package/dist/risk-profile/index.d.ts +228 -0
- package/dist/risk-profile/index.js +548 -0
- package/dist/risk-profile/index.js.map +1 -0
- package/dist/risk-profile/index.mjs +546 -0
- package/dist/risk-profile/index.mjs.map +1 -0
- package/dist/types-Bnsnejor.d.mts +150 -0
- package/dist/types-Bnsnejor.d.ts +150 -0
- package/dist/types-CFupjwi8.d.ts +213 -0
- package/dist/types-CqOLbaXk.d.mts +213 -0
- package/package.json +94 -0
|
@@ -0,0 +1,2133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/errors.ts
|
|
4
|
+
var CGSError = class _CGSError extends Error {
|
|
5
|
+
constructor(message, code, statusCode, details) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.statusCode = statusCode;
|
|
9
|
+
this.details = details;
|
|
10
|
+
this.name = "CGSError";
|
|
11
|
+
Object.setPrototypeOf(this, _CGSError.prototype);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var NetworkError = class _NetworkError extends CGSError {
|
|
15
|
+
constructor(message, originalError) {
|
|
16
|
+
super(message, "NETWORK_ERROR", void 0, { originalError });
|
|
17
|
+
this.originalError = originalError;
|
|
18
|
+
this.name = "NetworkError";
|
|
19
|
+
Object.setPrototypeOf(this, _NetworkError.prototype);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var ValidationError = class _ValidationError extends CGSError {
|
|
23
|
+
constructor(message, fields) {
|
|
24
|
+
super(message, "VALIDATION_ERROR", 400, { fields });
|
|
25
|
+
this.name = "ValidationError";
|
|
26
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var ServiceUnavailableError = class _ServiceUnavailableError extends CGSError {
|
|
30
|
+
constructor(service) {
|
|
31
|
+
super(`${service} is unavailable`, "SERVICE_UNAVAILABLE", 503, { service });
|
|
32
|
+
this.name = "ServiceUnavailableError";
|
|
33
|
+
Object.setPrototypeOf(this, _ServiceUnavailableError.prototype);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var AuthenticationError = class _AuthenticationError extends CGSError {
|
|
37
|
+
constructor(message = "Authentication failed") {
|
|
38
|
+
super(message, "AUTHENTICATION_ERROR", 401);
|
|
39
|
+
this.name = "AuthenticationError";
|
|
40
|
+
Object.setPrototypeOf(this, _AuthenticationError.prototype);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var RateLimitError = class _RateLimitError extends CGSError {
|
|
44
|
+
constructor(retryAfter) {
|
|
45
|
+
super("Rate limit exceeded", "RATE_LIMIT_EXCEEDED", 429, { retryAfter });
|
|
46
|
+
this.retryAfter = retryAfter;
|
|
47
|
+
this.name = "RateLimitError";
|
|
48
|
+
Object.setPrototypeOf(this, _RateLimitError.prototype);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var TimeoutError = class _TimeoutError extends CGSError {
|
|
52
|
+
constructor(timeout) {
|
|
53
|
+
super(`Request timeout after ${timeout}ms`, "TIMEOUT", 408, { timeout });
|
|
54
|
+
this.timeout = timeout;
|
|
55
|
+
this.name = "TimeoutError";
|
|
56
|
+
Object.setPrototypeOf(this, _TimeoutError.prototype);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var ComplianceError = class _ComplianceError extends CGSError {
|
|
60
|
+
constructor(message, originalError, code = "COMPLIANCE_ERROR") {
|
|
61
|
+
super(message, code, void 0, { originalError });
|
|
62
|
+
this.originalError = originalError;
|
|
63
|
+
this.name = "ComplianceError";
|
|
64
|
+
Object.setPrototypeOf(this, _ComplianceError.prototype);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/core/client.ts
|
|
69
|
+
var BaseClient = class {
|
|
70
|
+
constructor(config) {
|
|
71
|
+
this.config = {
|
|
72
|
+
baseURL: config.baseURL,
|
|
73
|
+
tenantId: config.tenantId,
|
|
74
|
+
apiKey: config.apiKey || "",
|
|
75
|
+
headers: config.headers || {},
|
|
76
|
+
timeout: config.timeout || 1e4,
|
|
77
|
+
retries: config.retries || 3,
|
|
78
|
+
debug: config.debug || false
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Make an HTTP request with timeout and error handling
|
|
83
|
+
*/
|
|
84
|
+
async request(endpoint, options = {}, serviceURL) {
|
|
85
|
+
const url = `${serviceURL || this.config.baseURL}${endpoint}`;
|
|
86
|
+
const headers = {
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
"X-Tenant-ID": this.config.tenantId,
|
|
89
|
+
...this.config.headers,
|
|
90
|
+
...options.headers || {}
|
|
91
|
+
};
|
|
92
|
+
if (this.config.apiKey) {
|
|
93
|
+
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
94
|
+
}
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
97
|
+
try {
|
|
98
|
+
if (this.config.debug) {
|
|
99
|
+
console.log(`[CGS SDK] ${options.method || "GET"} ${url}`, {
|
|
100
|
+
headers,
|
|
101
|
+
body: options.body
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const response = await fetch(url, {
|
|
105
|
+
...options,
|
|
106
|
+
headers,
|
|
107
|
+
signal: controller.signal
|
|
108
|
+
});
|
|
109
|
+
clearTimeout(timeoutId);
|
|
110
|
+
const data = await response.json();
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
this.handleErrorResponse(response.status, data);
|
|
113
|
+
}
|
|
114
|
+
if (this.config.debug) {
|
|
115
|
+
console.log(`[CGS SDK] Response:`, data);
|
|
116
|
+
}
|
|
117
|
+
return data;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
clearTimeout(timeoutId);
|
|
120
|
+
if (error instanceof Error) {
|
|
121
|
+
if (error.name === "AbortError") {
|
|
122
|
+
throw new TimeoutError(this.config.timeout);
|
|
123
|
+
}
|
|
124
|
+
if (error instanceof CGSError) {
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
throw new NetworkError("Network request failed", error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Make an HTTP request with retry logic
|
|
133
|
+
*/
|
|
134
|
+
async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries) {
|
|
135
|
+
let lastError;
|
|
136
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
137
|
+
try {
|
|
138
|
+
return await this.request(endpoint, options, serviceURL);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
141
|
+
if (lastError instanceof CGSError && lastError.statusCode && lastError.statusCode >= 400 && lastError.statusCode < 500) {
|
|
142
|
+
throw lastError;
|
|
143
|
+
}
|
|
144
|
+
if (attempt === retries) {
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
const delay = Math.min(1e3 * Math.pow(2, attempt) + Math.random() * 1e3, 1e4);
|
|
148
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
149
|
+
if (this.config.debug) {
|
|
150
|
+
console.log(`[CGS SDK] Retry attempt ${attempt + 1}/${retries} after ${delay.toFixed(0)}ms`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw new NetworkError(`Request failed after ${retries} retries`, lastError);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Handle error responses from API
|
|
158
|
+
*/
|
|
159
|
+
handleErrorResponse(status, data) {
|
|
160
|
+
const message = data.error || data.message || `HTTP ${status}`;
|
|
161
|
+
switch (status) {
|
|
162
|
+
case 400:
|
|
163
|
+
throw new CGSError(message, "BAD_REQUEST", 400, data);
|
|
164
|
+
case 401:
|
|
165
|
+
throw new AuthenticationError(message);
|
|
166
|
+
case 403:
|
|
167
|
+
throw new CGSError(message, "FORBIDDEN", 403, data);
|
|
168
|
+
case 404:
|
|
169
|
+
throw new CGSError(message, "NOT_FOUND", 404, data);
|
|
170
|
+
case 429:
|
|
171
|
+
const retryAfter = data.retry_after || data.retryAfter;
|
|
172
|
+
throw new RateLimitError(retryAfter);
|
|
173
|
+
case 500:
|
|
174
|
+
case 502:
|
|
175
|
+
case 503:
|
|
176
|
+
case 504:
|
|
177
|
+
throw new ServiceUnavailableError(message);
|
|
178
|
+
default:
|
|
179
|
+
throw new CGSError(message, "UNKNOWN_ERROR", status, data);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Build query string from parameters
|
|
184
|
+
*/
|
|
185
|
+
buildQueryString(params) {
|
|
186
|
+
const query = new URLSearchParams();
|
|
187
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
188
|
+
if (value !== void 0 && value !== null) {
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
value.forEach((item) => query.append(key, String(item)));
|
|
191
|
+
} else {
|
|
192
|
+
query.append(key, String(value));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
const queryString = query.toString();
|
|
197
|
+
return queryString ? `?${queryString}` : "";
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Update client configuration
|
|
201
|
+
*/
|
|
202
|
+
updateConfig(config) {
|
|
203
|
+
this.config = {
|
|
204
|
+
...this.config,
|
|
205
|
+
...config,
|
|
206
|
+
headers: {
|
|
207
|
+
...this.config.headers,
|
|
208
|
+
...config.headers || {}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get current configuration (readonly)
|
|
214
|
+
*/
|
|
215
|
+
getConfig() {
|
|
216
|
+
return { ...this.config };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Health check endpoint
|
|
220
|
+
*/
|
|
221
|
+
async healthCheck() {
|
|
222
|
+
return this.request("/api/v1/health");
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// src/geolocation/client.ts
|
|
227
|
+
var GeolocationClient = class extends BaseClient {
|
|
228
|
+
constructor(config) {
|
|
229
|
+
const baseConfig = {
|
|
230
|
+
baseURL: config.baseURL,
|
|
231
|
+
tenantId: config.tenantId,
|
|
232
|
+
apiKey: config.apiKey,
|
|
233
|
+
headers: config.headers,
|
|
234
|
+
timeout: config.timeout,
|
|
235
|
+
retries: 3,
|
|
236
|
+
// Default retry count for geolocation calls
|
|
237
|
+
debug: config.debug
|
|
238
|
+
};
|
|
239
|
+
super(baseConfig);
|
|
240
|
+
}
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// IP Verification & Compliance
|
|
243
|
+
// ============================================================================
|
|
244
|
+
/**
|
|
245
|
+
* Verify an IP address and check compliance
|
|
246
|
+
*
|
|
247
|
+
* Uses requestWithRetry() for automatic retry on transient failures.
|
|
248
|
+
*
|
|
249
|
+
* @param request - Verification request with IP, user ID, event type, and optional device fingerprint
|
|
250
|
+
* @returns Location verification result with risk assessment
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```typescript
|
|
254
|
+
* const result = await client.verifyIP({
|
|
255
|
+
* ip_address: "8.8.8.8",
|
|
256
|
+
* user_id: "user_123",
|
|
257
|
+
* event_type: "login",
|
|
258
|
+
* device_fingerprint: {
|
|
259
|
+
* device_id: "device_abc",
|
|
260
|
+
* user_agent: navigator.userAgent,
|
|
261
|
+
* platform: "web"
|
|
262
|
+
* }
|
|
263
|
+
* });
|
|
264
|
+
*
|
|
265
|
+
* if (result.is_blocked) {
|
|
266
|
+
* console.log("Access blocked:", result.risk_reasons);
|
|
267
|
+
* }
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
async verifyIP(request) {
|
|
271
|
+
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
272
|
+
method: "POST",
|
|
273
|
+
body: JSON.stringify(request)
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Check compliance for a specific country
|
|
278
|
+
*
|
|
279
|
+
* @param countryISO - ISO 3166-1 alpha-2 country code (e.g., "US", "GB")
|
|
280
|
+
* @returns Jurisdiction configuration and compliance status
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```typescript
|
|
284
|
+
* const compliance = await client.checkCompliance("KP"); // North Korea
|
|
285
|
+
* if (!compliance.is_compliant) {
|
|
286
|
+
* console.log("Country is not allowed:", compliance.jurisdiction?.status);
|
|
287
|
+
* }
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
async checkCompliance(countryISO) {
|
|
291
|
+
return this.requestWithRetry(
|
|
292
|
+
`/api/v1/geo/check-compliance`,
|
|
293
|
+
{
|
|
294
|
+
method: "POST",
|
|
295
|
+
body: JSON.stringify({ country_iso: countryISO })
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
// ============================================================================
|
|
300
|
+
// CipherText Validation
|
|
301
|
+
// ============================================================================
|
|
302
|
+
/**
|
|
303
|
+
* Validate a cipherText generated by the frontend SDK
|
|
304
|
+
*
|
|
305
|
+
* The cipherText contains encrypted device fingerprint and optional location data.
|
|
306
|
+
* This method decrypts and validates the data, returning device info, location,
|
|
307
|
+
* and risk assessment.
|
|
308
|
+
*
|
|
309
|
+
* @param cipherText - The encrypted cipherText string from generateCipherText()
|
|
310
|
+
* @param userId - User ID associated with this verification
|
|
311
|
+
* @param eventType - Reason for verification (login, registration, etc.)
|
|
312
|
+
* @param expectedIP - Optional expected IP address for additional validation
|
|
313
|
+
* @returns Validation result with device info, location, and risk assessment
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* // Validate cipherText during login
|
|
318
|
+
* const result = await client.validateCipherText(
|
|
319
|
+
* cipherText,
|
|
320
|
+
* "user_123",
|
|
321
|
+
* "login"
|
|
322
|
+
* );
|
|
323
|
+
*
|
|
324
|
+
* if (!result.valid) {
|
|
325
|
+
* console.log("CipherText validation failed:", result.errors);
|
|
326
|
+
* return;
|
|
327
|
+
* }
|
|
328
|
+
*
|
|
329
|
+
* console.log("Device UUID:", result.device_uuid);
|
|
330
|
+
* console.log("IP Address:", result.ip_address);
|
|
331
|
+
* console.log("Risk Level:", result.risk.level);
|
|
332
|
+
*
|
|
333
|
+
* if (result.risk.is_vpn) {
|
|
334
|
+
* console.log("VPN detected");
|
|
335
|
+
* }
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
async validateCipherText(cipherText, userId, eventType, expectedIP) {
|
|
339
|
+
const request = {
|
|
340
|
+
cipher_text: cipherText,
|
|
341
|
+
user_id: userId,
|
|
342
|
+
event_type: eventType,
|
|
343
|
+
expected_ip: expectedIP
|
|
344
|
+
};
|
|
345
|
+
return this.requestWithRetry(
|
|
346
|
+
"/api/v1/geo/validate-ciphertext",
|
|
347
|
+
{
|
|
348
|
+
method: "POST",
|
|
349
|
+
body: JSON.stringify(request)
|
|
350
|
+
}
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Validate cipherText and verify IP in a single call
|
|
355
|
+
*
|
|
356
|
+
* Combines cipherText validation with IP verification for complete
|
|
357
|
+
* location and device verification in one request.
|
|
358
|
+
*
|
|
359
|
+
* @param cipherText - The encrypted cipherText string
|
|
360
|
+
* @param ipAddress - Client IP address
|
|
361
|
+
* @param userId - User ID
|
|
362
|
+
* @param eventType - Event type (login, registration, etc.)
|
|
363
|
+
* @returns Combined validation and verification result
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* ```typescript
|
|
367
|
+
* const result = await client.validateAndVerify(
|
|
368
|
+
* cipherText,
|
|
369
|
+
* clientIP,
|
|
370
|
+
* "user_123",
|
|
371
|
+
* "registration"
|
|
372
|
+
* );
|
|
373
|
+
*
|
|
374
|
+
* if (!result.ciphertext_valid) {
|
|
375
|
+
* throw new Error("Device verification failed");
|
|
376
|
+
* }
|
|
377
|
+
*
|
|
378
|
+
* if (result.location.is_blocked) {
|
|
379
|
+
* throw new Error("Location not allowed");
|
|
380
|
+
* }
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
async validateAndVerify(cipherText, ipAddress, userId, eventType) {
|
|
384
|
+
const cipherTextResult = await this.validateCipherText(
|
|
385
|
+
cipherText,
|
|
386
|
+
userId,
|
|
387
|
+
eventType,
|
|
388
|
+
ipAddress
|
|
389
|
+
);
|
|
390
|
+
const locationResult = await this.verifyIP({
|
|
391
|
+
ip_address: ipAddress,
|
|
392
|
+
user_id: userId,
|
|
393
|
+
event_type: eventType,
|
|
394
|
+
device_fingerprint: cipherTextResult.valid && cipherTextResult.device ? {
|
|
395
|
+
device_id: cipherTextResult.device_uuid,
|
|
396
|
+
user_agent: "",
|
|
397
|
+
platform: cipherTextResult.device.platform,
|
|
398
|
+
browser: cipherTextResult.device.browser,
|
|
399
|
+
os: cipherTextResult.device.os
|
|
400
|
+
} : void 0
|
|
401
|
+
});
|
|
402
|
+
return {
|
|
403
|
+
ciphertext_valid: cipherTextResult.valid,
|
|
404
|
+
ciphertext_result: cipherTextResult,
|
|
405
|
+
location: locationResult
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Get location history for a user
|
|
410
|
+
*
|
|
411
|
+
* @param userId - User ID to retrieve history for
|
|
412
|
+
* @param limit - Maximum number of records to return (default: 100)
|
|
413
|
+
* @returns Array of geolocation records
|
|
414
|
+
*/
|
|
415
|
+
async getUserLocationHistory(userId, limit = 100) {
|
|
416
|
+
return this.request(
|
|
417
|
+
`/api/v1/geo/user/?user_id=${userId}&limit=${limit}`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
// ============================================================================
|
|
421
|
+
// Alert Management
|
|
422
|
+
// ============================================================================
|
|
423
|
+
/**
|
|
424
|
+
* Get a specific alert by ID
|
|
425
|
+
*
|
|
426
|
+
* @param alertId - Alert ID
|
|
427
|
+
* @returns Alert details
|
|
428
|
+
*/
|
|
429
|
+
async getAlert(alertId) {
|
|
430
|
+
return this.request(`/api/v1/alerts/${alertId}`);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* List alerts with filters and pagination
|
|
434
|
+
*
|
|
435
|
+
* @param filters - Optional filters (status, severity, type, user, dates)
|
|
436
|
+
* @param pagination - Optional pagination (page, page_size)
|
|
437
|
+
* @returns Paginated list of alerts
|
|
438
|
+
*
|
|
439
|
+
* @example
|
|
440
|
+
* ```typescript
|
|
441
|
+
* const alerts = await client.listAlerts(
|
|
442
|
+
* { status: "active", severity: "critical" },
|
|
443
|
+
* { page: 1, page_size: 20 }
|
|
444
|
+
* );
|
|
445
|
+
* console.log(`Found ${alerts.total} critical alerts`);
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
async listAlerts(filters, pagination) {
|
|
449
|
+
const params = {
|
|
450
|
+
...filters,
|
|
451
|
+
...pagination
|
|
452
|
+
};
|
|
453
|
+
return this.request(`/api/v1/alerts${this.buildQueryString(params)}`);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Update alert status
|
|
457
|
+
*
|
|
458
|
+
* @param alertId - Alert ID
|
|
459
|
+
* @param status - New status
|
|
460
|
+
*/
|
|
461
|
+
async updateAlertStatus(alertId, status) {
|
|
462
|
+
await this.request(`/api/v1/alerts/${alertId}/status`, {
|
|
463
|
+
method: "PUT",
|
|
464
|
+
body: JSON.stringify({ status })
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Assign an alert to an analyst
|
|
469
|
+
*
|
|
470
|
+
* @param alertId - Alert ID
|
|
471
|
+
* @param assignedTo - User ID to assign to
|
|
472
|
+
*/
|
|
473
|
+
async assignAlert(alertId, assignedTo) {
|
|
474
|
+
await this.request(`/api/v1/alerts/${alertId}/assign`, {
|
|
475
|
+
method: "POST",
|
|
476
|
+
body: JSON.stringify({ assigned_to: assignedTo })
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Resolve an alert
|
|
481
|
+
*
|
|
482
|
+
* @param alertId - Alert ID
|
|
483
|
+
* @param resolution - Resolution type
|
|
484
|
+
* @param notes - Optional notes
|
|
485
|
+
*/
|
|
486
|
+
async resolveAlert(alertId, resolution, notes) {
|
|
487
|
+
await this.request(`/api/v1/alerts/${alertId}/resolve`, {
|
|
488
|
+
method: "POST",
|
|
489
|
+
body: JSON.stringify({ resolution, notes })
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Dismiss an alert as false positive
|
|
494
|
+
*
|
|
495
|
+
* @param alertId - Alert ID
|
|
496
|
+
* @param notes - Optional notes explaining why it's a false positive
|
|
497
|
+
*/
|
|
498
|
+
async dismissAlert(alertId, notes) {
|
|
499
|
+
await this.request(`/api/v1/alerts/${alertId}/dismiss`, {
|
|
500
|
+
method: "POST",
|
|
501
|
+
body: JSON.stringify({ notes })
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Escalate an alert to higher severity or different assignee
|
|
506
|
+
*
|
|
507
|
+
* @param alertId - Alert ID
|
|
508
|
+
* @param assignedTo - New assignee user ID
|
|
509
|
+
* @param severity - New severity level
|
|
510
|
+
* @param notes - Escalation notes
|
|
511
|
+
*/
|
|
512
|
+
async escalateAlert(alertId, assignedTo, severity, notes) {
|
|
513
|
+
await this.request(`/api/v1/alerts/${alertId}/escalate`, {
|
|
514
|
+
method: "POST",
|
|
515
|
+
body: JSON.stringify({ assigned_to: assignedTo, severity, notes })
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Block an alert (mark as blocked)
|
|
520
|
+
*
|
|
521
|
+
* @param alertId - Alert ID
|
|
522
|
+
* @param notes - Optional notes
|
|
523
|
+
*/
|
|
524
|
+
async blockAlert(alertId, notes) {
|
|
525
|
+
await this.request(`/api/v1/alerts/${alertId}/block`, {
|
|
526
|
+
method: "POST",
|
|
527
|
+
body: JSON.stringify({ notes })
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Get dashboard metrics
|
|
532
|
+
*
|
|
533
|
+
* @param timeRangeHours - Time range in hours (default: 24)
|
|
534
|
+
* @returns Dashboard metrics and statistics
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* ```typescript
|
|
538
|
+
* const metrics = await client.getDashboardMetrics(168); // Last 7 days
|
|
539
|
+
* console.log(`${metrics.critical_alerts} critical alerts in last week`);
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
async getDashboardMetrics(timeRangeHours = 24) {
|
|
543
|
+
return this.request(
|
|
544
|
+
`/api/v1/alerts/metrics?time_range_hours=${timeRangeHours}`
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
// ============================================================================
|
|
548
|
+
// Jurisdiction Configuration
|
|
549
|
+
// ============================================================================
|
|
550
|
+
/**
|
|
551
|
+
* List all jurisdiction configurations
|
|
552
|
+
*
|
|
553
|
+
* @returns Array of jurisdiction configurations
|
|
554
|
+
*/
|
|
555
|
+
async listJurisdictions() {
|
|
556
|
+
return this.request("/api/v1/jurisdictions");
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Get a jurisdiction configuration by ID
|
|
560
|
+
*
|
|
561
|
+
* @param jurisdictionId - Jurisdiction ID
|
|
562
|
+
* @returns Jurisdiction configuration
|
|
563
|
+
*/
|
|
564
|
+
async getJurisdiction(jurisdictionId) {
|
|
565
|
+
return this.request(`/api/v1/jurisdictions/${jurisdictionId}`);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Create a new jurisdiction configuration
|
|
569
|
+
*
|
|
570
|
+
* @param request - Jurisdiction configuration data
|
|
571
|
+
* @returns Created jurisdiction
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* ```typescript
|
|
575
|
+
* const jurisdiction = await client.createJurisdiction({
|
|
576
|
+
* country_iso: "US",
|
|
577
|
+
* country_name: "United States",
|
|
578
|
+
* status: "allowed",
|
|
579
|
+
* risk_level: "low",
|
|
580
|
+
* allow_login: true,
|
|
581
|
+
* allow_registration: true,
|
|
582
|
+
* allow_transactions: true,
|
|
583
|
+
* require_kyc: false,
|
|
584
|
+
* require_enhanced_verification: false
|
|
585
|
+
* });
|
|
586
|
+
* ```
|
|
587
|
+
*/
|
|
588
|
+
async createJurisdiction(request) {
|
|
589
|
+
return this.request("/api/v1/jurisdictions", {
|
|
590
|
+
method: "POST",
|
|
591
|
+
body: JSON.stringify(request)
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Update a jurisdiction configuration
|
|
596
|
+
*
|
|
597
|
+
* @param jurisdictionId - Jurisdiction ID
|
|
598
|
+
* @param request - Updated fields
|
|
599
|
+
* @returns Updated jurisdiction
|
|
600
|
+
*/
|
|
601
|
+
async updateJurisdiction(jurisdictionId, request) {
|
|
602
|
+
return this.request(`/api/v1/jurisdictions/${jurisdictionId}`, {
|
|
603
|
+
method: "PUT",
|
|
604
|
+
body: JSON.stringify(request)
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Delete a jurisdiction configuration
|
|
609
|
+
*
|
|
610
|
+
* @param jurisdictionId - Jurisdiction ID
|
|
611
|
+
*/
|
|
612
|
+
async deleteJurisdiction(jurisdictionId) {
|
|
613
|
+
await this.request(`/api/v1/jurisdictions/${jurisdictionId}`, {
|
|
614
|
+
method: "DELETE"
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
// ============================================================================
|
|
618
|
+
// Geofence Rules
|
|
619
|
+
// ============================================================================
|
|
620
|
+
/**
|
|
621
|
+
* List all geofence rules
|
|
622
|
+
*
|
|
623
|
+
* @returns Array of geofence rules
|
|
624
|
+
*/
|
|
625
|
+
async listGeofenceRules() {
|
|
626
|
+
return this.request("/api/v1/geofence-rules");
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Get a geofence rule by ID
|
|
630
|
+
*
|
|
631
|
+
* @param ruleId - Geofence rule ID
|
|
632
|
+
* @returns Geofence rule
|
|
633
|
+
*/
|
|
634
|
+
async getGeofenceRule(ruleId) {
|
|
635
|
+
return this.request(`/api/v1/geofence-rules/${ruleId}`);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Create a new geofence rule
|
|
639
|
+
*
|
|
640
|
+
* @param request - Geofence rule data
|
|
641
|
+
* @returns Created geofence rule
|
|
642
|
+
*
|
|
643
|
+
* @example
|
|
644
|
+
* ```typescript
|
|
645
|
+
* const rule = await client.createGeofenceRule({
|
|
646
|
+
* name: "Block High Risk Countries",
|
|
647
|
+
* rule_type: "block_list",
|
|
648
|
+
* action: "block",
|
|
649
|
+
* countries: ["KP", "IR", "SY"],
|
|
650
|
+
* event_types: ["login", "transaction"],
|
|
651
|
+
* priority: 100
|
|
652
|
+
* });
|
|
653
|
+
* ```
|
|
654
|
+
*/
|
|
655
|
+
async createGeofenceRule(request) {
|
|
656
|
+
return this.request("/api/v1/geofence-rules", {
|
|
657
|
+
method: "POST",
|
|
658
|
+
body: JSON.stringify(request)
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Update a geofence rule
|
|
663
|
+
*
|
|
664
|
+
* @param ruleId - Geofence rule ID
|
|
665
|
+
* @param request - Updated fields
|
|
666
|
+
* @returns Updated geofence rule
|
|
667
|
+
*/
|
|
668
|
+
async updateGeofenceRule(ruleId, request) {
|
|
669
|
+
return this.request(`/api/v1/geofence-rules/${ruleId}`, {
|
|
670
|
+
method: "PUT",
|
|
671
|
+
body: JSON.stringify(request)
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Delete a geofence rule
|
|
676
|
+
*
|
|
677
|
+
* @param ruleId - Geofence rule ID
|
|
678
|
+
*/
|
|
679
|
+
async deleteGeofenceRule(ruleId) {
|
|
680
|
+
await this.request(`/api/v1/geofence-rules/${ruleId}`, {
|
|
681
|
+
method: "DELETE"
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
// ============================================================================
|
|
685
|
+
// Device Fingerprinting
|
|
686
|
+
// ============================================================================
|
|
687
|
+
/**
|
|
688
|
+
* Get all devices for a user
|
|
689
|
+
*
|
|
690
|
+
* @param userId - User ID
|
|
691
|
+
* @returns Array of device fingerprints
|
|
692
|
+
*/
|
|
693
|
+
async getUserDevices(userId) {
|
|
694
|
+
return this.request(`/api/v1/devices/user/?user_id=${userId}`);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Get a specific device by device ID
|
|
698
|
+
*
|
|
699
|
+
* @param deviceId - Device ID
|
|
700
|
+
* @returns Device fingerprint
|
|
701
|
+
*/
|
|
702
|
+
async getDevice(deviceId) {
|
|
703
|
+
return this.request(`/api/v1/devices/?device_id=${deviceId}`);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Update device trust status
|
|
707
|
+
*
|
|
708
|
+
* @param deviceId - Device ID
|
|
709
|
+
* @param action - "trust" or "untrust"
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```typescript
|
|
713
|
+
* // Mark a device as trusted
|
|
714
|
+
* await client.updateDeviceTrust("device_abc", "trust");
|
|
715
|
+
*
|
|
716
|
+
* // Mark a device as untrusted
|
|
717
|
+
* await client.updateDeviceTrust("device_xyz", "untrust");
|
|
718
|
+
* ```
|
|
719
|
+
*/
|
|
720
|
+
async updateDeviceTrust(deviceId, action) {
|
|
721
|
+
await this.request(`/api/v1/devices/?device_id=${deviceId}&action=${action}`, {
|
|
722
|
+
method: "POST"
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
// ============================================================================
|
|
726
|
+
// Location Request (Live Location Tracking)
|
|
727
|
+
// ============================================================================
|
|
728
|
+
/**
|
|
729
|
+
* Create a location request to get customer's live location
|
|
730
|
+
*
|
|
731
|
+
* @param request - Location request details
|
|
732
|
+
* @returns Location request result with share link
|
|
733
|
+
*
|
|
734
|
+
* @example
|
|
735
|
+
* ```typescript
|
|
736
|
+
* const result = await client.createLocationRequest({
|
|
737
|
+
* user_id: "customer_123",
|
|
738
|
+
* channel: "sms",
|
|
739
|
+
* phone: "+1234567890",
|
|
740
|
+
* reason: "Verification for high-value transaction"
|
|
741
|
+
* });
|
|
742
|
+
*
|
|
743
|
+
* console.log(`Share link sent: ${result.share_link}`);
|
|
744
|
+
* console.log(`Expires at: ${result.token_expiry}`);
|
|
745
|
+
* ```
|
|
746
|
+
*/
|
|
747
|
+
async createLocationRequest(request) {
|
|
748
|
+
return this.request("/api/v1/location-requests", {
|
|
749
|
+
method: "POST",
|
|
750
|
+
body: JSON.stringify(request)
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Get a location request by ID
|
|
755
|
+
*
|
|
756
|
+
* @param requestId - Location request ID
|
|
757
|
+
* @returns Location request details
|
|
758
|
+
*/
|
|
759
|
+
async getLocationRequest(requestId) {
|
|
760
|
+
return this.request(`/api/v1/location-requests/${requestId}`);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* List location requests with filters and pagination
|
|
764
|
+
*
|
|
765
|
+
* @param filters - Optional filters
|
|
766
|
+
* @param pagination - Optional pagination
|
|
767
|
+
* @returns Paginated list of location requests
|
|
768
|
+
*
|
|
769
|
+
* @example
|
|
770
|
+
* ```typescript
|
|
771
|
+
* const requests = await client.listLocationRequests(
|
|
772
|
+
* { status: "completed", user_id: "customer_123" },
|
|
773
|
+
* { page: 1, limit: 20 }
|
|
774
|
+
* );
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
777
|
+
async listLocationRequests(filters, pagination) {
|
|
778
|
+
const params = {
|
|
779
|
+
...filters,
|
|
780
|
+
...pagination
|
|
781
|
+
};
|
|
782
|
+
return this.request(
|
|
783
|
+
`/api/v1/location-requests${this.buildQueryString(params)}`
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Cancel a pending location request
|
|
788
|
+
*
|
|
789
|
+
* @param requestId - Location request ID
|
|
790
|
+
*/
|
|
791
|
+
async cancelLocationRequest(requestId) {
|
|
792
|
+
await this.request(`/api/v1/location-requests/${requestId}/cancel`, {
|
|
793
|
+
method: "POST"
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Resend notification for a location request
|
|
798
|
+
*
|
|
799
|
+
* @param requestId - Location request ID
|
|
800
|
+
* @param contact - Email or phone to send to
|
|
801
|
+
*/
|
|
802
|
+
async resendLocationRequest(requestId, contact) {
|
|
803
|
+
await this.request(`/api/v1/location-requests/${requestId}/resend`, {
|
|
804
|
+
method: "POST",
|
|
805
|
+
body: JSON.stringify(contact)
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
// ============================================================================
|
|
809
|
+
// Location Share (Customer-facing - for SDK integration in customer apps)
|
|
810
|
+
// ============================================================================
|
|
811
|
+
/**
|
|
812
|
+
* Get location share info (customer-facing)
|
|
813
|
+
* This is called from the customer's device to get request details
|
|
814
|
+
*
|
|
815
|
+
* @param token - Secure token from share link
|
|
816
|
+
* @returns Location share info
|
|
817
|
+
*/
|
|
818
|
+
async getLocationShareInfo(token) {
|
|
819
|
+
return this.request(`/api/v1/location/share/${token}`);
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Submit location capture (customer-facing)
|
|
823
|
+
* This is called from the customer's device to submit their location
|
|
824
|
+
*
|
|
825
|
+
* @param token - Secure token from share link
|
|
826
|
+
* @param capture - Location capture data (GPS or WiFi)
|
|
827
|
+
* @returns Capture response
|
|
828
|
+
*
|
|
829
|
+
* @example
|
|
830
|
+
* ```typescript
|
|
831
|
+
* // Using GPS (preferred)
|
|
832
|
+
* const result = await client.captureLocation(token, {
|
|
833
|
+
* latitude: 37.7749,
|
|
834
|
+
* longitude: -122.4194,
|
|
835
|
+
* accuracy: 10
|
|
836
|
+
* });
|
|
837
|
+
*
|
|
838
|
+
* // Using WiFi positioning (fallback)
|
|
839
|
+
* const result = await client.captureLocation(token, {
|
|
840
|
+
* wifi_networks: [
|
|
841
|
+
* { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
|
|
842
|
+
* { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
|
|
843
|
+
* ]
|
|
844
|
+
* });
|
|
845
|
+
* ```
|
|
846
|
+
*/
|
|
847
|
+
async captureLocation(token, capture) {
|
|
848
|
+
return this.request(`/api/v1/location/share/${token}`, {
|
|
849
|
+
method: "POST",
|
|
850
|
+
body: JSON.stringify(capture)
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
// ============================================================================
|
|
854
|
+
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|
|
855
|
+
// ============================================================================
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// src/risk-profile/client.ts
|
|
859
|
+
var RiskProfileClient = class extends BaseClient {
|
|
860
|
+
constructor(config) {
|
|
861
|
+
super(config);
|
|
862
|
+
}
|
|
863
|
+
// ============================================================================
|
|
864
|
+
// Profile Management
|
|
865
|
+
// ============================================================================
|
|
866
|
+
/**
|
|
867
|
+
* Create a new customer risk profile
|
|
868
|
+
*
|
|
869
|
+
* @param request - Profile creation data
|
|
870
|
+
* @returns Created customer profile
|
|
871
|
+
*
|
|
872
|
+
* @example
|
|
873
|
+
* ```typescript
|
|
874
|
+
* const profile = await client.createProfile({
|
|
875
|
+
* customer_id: 'CUST-12345',
|
|
876
|
+
* entity_type: 'individual',
|
|
877
|
+
* customer_status: 'active',
|
|
878
|
+
* full_name: 'John Doe',
|
|
879
|
+
* email_address: 'john@example.com',
|
|
880
|
+
* date_of_birth: '1990-01-15',
|
|
881
|
+
* country_of_residence: 'US'
|
|
882
|
+
* });
|
|
883
|
+
* ```
|
|
884
|
+
*/
|
|
885
|
+
async createProfile(request) {
|
|
886
|
+
return this.request("/api/v1/profiles", {
|
|
887
|
+
method: "POST",
|
|
888
|
+
body: JSON.stringify(request)
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Get customer profile by customer ID
|
|
893
|
+
*
|
|
894
|
+
* Note: This searches for the profile using the customer_id field.
|
|
895
|
+
* Returns the first matching profile for the tenant.
|
|
896
|
+
*
|
|
897
|
+
* @param customerId - Customer ID to search for
|
|
898
|
+
* @returns Customer profile
|
|
899
|
+
*
|
|
900
|
+
* @example
|
|
901
|
+
* ```typescript
|
|
902
|
+
* const profile = await client.getProfile('CUST-12345');
|
|
903
|
+
* console.log('Risk score:', profile.risk_score);
|
|
904
|
+
* ```
|
|
905
|
+
*/
|
|
906
|
+
async getProfile(customerId) {
|
|
907
|
+
const response = await this.queryProfiles({
|
|
908
|
+
search: customerId,
|
|
909
|
+
page: 1,
|
|
910
|
+
page_size: 1
|
|
911
|
+
});
|
|
912
|
+
if (response.data.length === 0) {
|
|
913
|
+
throw new Error(`Profile not found for customer ID: ${customerId}`);
|
|
914
|
+
}
|
|
915
|
+
return response.data[0];
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Get profile by UUID
|
|
919
|
+
*
|
|
920
|
+
* @param profileId - Profile UUID
|
|
921
|
+
* @returns Customer profile
|
|
922
|
+
*/
|
|
923
|
+
async getProfileById(profileId) {
|
|
924
|
+
const details = await this.getProfileDetails(profileId);
|
|
925
|
+
return details.profile;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Get detailed profile information including risk factors and history
|
|
929
|
+
*
|
|
930
|
+
* @param profileId - Profile UUID
|
|
931
|
+
* @returns Detailed profile response with risk factors and history
|
|
932
|
+
*
|
|
933
|
+
* @example
|
|
934
|
+
* ```typescript
|
|
935
|
+
* const details = await client.getProfileDetails(profileId);
|
|
936
|
+
* console.log('Risk factors:', details.risk_factors);
|
|
937
|
+
* console.log('Risk history:', details.risk_history);
|
|
938
|
+
* console.log('Alert counts:', {
|
|
939
|
+
* watchlist: details.watchlist_alert_count,
|
|
940
|
+
* fraud: details.fraud_alert_count,
|
|
941
|
+
* geo: details.geolocation_alert_count
|
|
942
|
+
* });
|
|
943
|
+
* ```
|
|
944
|
+
*/
|
|
945
|
+
async getProfileDetails(profileId) {
|
|
946
|
+
return this.request(
|
|
947
|
+
`/api/v1/risk-dashboard/profiles/${profileId}`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Update customer profile
|
|
952
|
+
*
|
|
953
|
+
* @param profileId - Profile UUID
|
|
954
|
+
* @param updates - Fields to update
|
|
955
|
+
* @returns Updated customer profile
|
|
956
|
+
*
|
|
957
|
+
* @example
|
|
958
|
+
* ```typescript
|
|
959
|
+
* const updated = await client.updateProfile(profileId, {
|
|
960
|
+
* location: 'New York, USA',
|
|
961
|
+
* location_compliance: 'compliant',
|
|
962
|
+
* last_recorded_activity: new Date().toISOString()
|
|
963
|
+
* });
|
|
964
|
+
* ```
|
|
965
|
+
*/
|
|
966
|
+
async updateProfile(profileId, updates) {
|
|
967
|
+
return this.request(`/api/v1/profiles/${profileId}`, {
|
|
968
|
+
method: "PUT",
|
|
969
|
+
body: JSON.stringify(updates)
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Manually recalculate risk score for a profile
|
|
974
|
+
*
|
|
975
|
+
* Triggers immediate risk score recalculation based on current risk factors.
|
|
976
|
+
*
|
|
977
|
+
* @param profileId - Profile UUID
|
|
978
|
+
* @param reason - Optional reason for recalculation
|
|
979
|
+
*
|
|
980
|
+
* @example
|
|
981
|
+
* ```typescript
|
|
982
|
+
* await client.recalculateRiskScore(profileId, 'Manual review completed');
|
|
983
|
+
* ```
|
|
984
|
+
*/
|
|
985
|
+
async recalculateRiskScore(profileId, reason) {
|
|
986
|
+
const body = reason ? JSON.stringify({ reason }) : void 0;
|
|
987
|
+
await this.request(`/api/v1/profiles/recalculate/${profileId}`, {
|
|
988
|
+
method: "POST",
|
|
989
|
+
body
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
// ============================================================================
|
|
993
|
+
// Query & Search
|
|
994
|
+
// ============================================================================
|
|
995
|
+
/**
|
|
996
|
+
* Query profiles with advanced filters
|
|
997
|
+
*
|
|
998
|
+
* @param filters - Filter criteria and pagination
|
|
999
|
+
* @returns Paginated list of profiles
|
|
1000
|
+
*
|
|
1001
|
+
* @example
|
|
1002
|
+
* ```typescript
|
|
1003
|
+
* const results = await client.queryProfiles({
|
|
1004
|
+
* risk_category: ['high', 'critical'],
|
|
1005
|
+
* kyc_status: ['pending'],
|
|
1006
|
+
* is_pep: true,
|
|
1007
|
+
* page: 1,
|
|
1008
|
+
* page_size: 20,
|
|
1009
|
+
* sort_by: 'risk_score',
|
|
1010
|
+
* sort_order: 'desc'
|
|
1011
|
+
* });
|
|
1012
|
+
*
|
|
1013
|
+
* console.log(`Found ${results.total} high-risk profiles`);
|
|
1014
|
+
* results.data.forEach(profile => {
|
|
1015
|
+
* console.log(`${profile.full_name}: ${profile.risk_score}`);
|
|
1016
|
+
* });
|
|
1017
|
+
* ```
|
|
1018
|
+
*/
|
|
1019
|
+
async queryProfiles(filters = {}) {
|
|
1020
|
+
const queryString = this.buildQueryString(filters);
|
|
1021
|
+
return this.request(
|
|
1022
|
+
`/api/v1/risk-dashboard/profiles${queryString}`
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Search profiles by text (searches name, email, customer_id, account_number)
|
|
1027
|
+
*
|
|
1028
|
+
* @param searchText - Text to search for
|
|
1029
|
+
* @param limit - Maximum results to return (default: 10)
|
|
1030
|
+
* @returns Array of matching profiles
|
|
1031
|
+
*/
|
|
1032
|
+
async searchProfiles(searchText, limit = 10) {
|
|
1033
|
+
const response = await this.queryProfiles({
|
|
1034
|
+
search: searchText,
|
|
1035
|
+
page: 1,
|
|
1036
|
+
page_size: limit
|
|
1037
|
+
});
|
|
1038
|
+
return response.data;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Get all high-risk profiles (high + critical)
|
|
1042
|
+
*
|
|
1043
|
+
* @param limit - Maximum results to return (default: 50)
|
|
1044
|
+
* @returns Array of high-risk profiles
|
|
1045
|
+
*/
|
|
1046
|
+
async getHighRiskProfiles(limit = 50) {
|
|
1047
|
+
const response = await this.queryProfiles({
|
|
1048
|
+
risk_category: ["high", "critical"],
|
|
1049
|
+
page: 1,
|
|
1050
|
+
page_size: limit,
|
|
1051
|
+
sort_by: "risk_score",
|
|
1052
|
+
sort_order: "desc"
|
|
1053
|
+
});
|
|
1054
|
+
return response.data;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Get profiles requiring PEP review
|
|
1058
|
+
*
|
|
1059
|
+
* @param limit - Maximum results to return (default: 50)
|
|
1060
|
+
* @returns Array of PEP profiles
|
|
1061
|
+
*/
|
|
1062
|
+
async getPEPProfiles(limit = 50) {
|
|
1063
|
+
const response = await this.queryProfiles({
|
|
1064
|
+
is_pep: true,
|
|
1065
|
+
page: 1,
|
|
1066
|
+
page_size: limit
|
|
1067
|
+
});
|
|
1068
|
+
return response.data;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Get profiles with sanctions matches
|
|
1072
|
+
*
|
|
1073
|
+
* @param limit - Maximum results to return (default: 50)
|
|
1074
|
+
* @returns Array of sanctioned profiles
|
|
1075
|
+
*/
|
|
1076
|
+
async getSanctionedProfiles(limit = 50) {
|
|
1077
|
+
const response = await this.queryProfiles({
|
|
1078
|
+
has_sanctions: true,
|
|
1079
|
+
page: 1,
|
|
1080
|
+
page_size: limit
|
|
1081
|
+
});
|
|
1082
|
+
return response.data;
|
|
1083
|
+
}
|
|
1084
|
+
// ============================================================================
|
|
1085
|
+
// Dashboard & Metrics
|
|
1086
|
+
// ============================================================================
|
|
1087
|
+
/**
|
|
1088
|
+
* Get risk dashboard metrics and statistics
|
|
1089
|
+
*
|
|
1090
|
+
* @param startDate - Optional start date for metrics (ISO format)
|
|
1091
|
+
* @param endDate - Optional end date for metrics (ISO format)
|
|
1092
|
+
* @returns Dashboard metrics
|
|
1093
|
+
*
|
|
1094
|
+
* @example
|
|
1095
|
+
* ```typescript
|
|
1096
|
+
* const metrics = await client.getDashboardMetrics();
|
|
1097
|
+
* console.log('Total risky profiles:', metrics.total_risky_profiles);
|
|
1098
|
+
* console.log('Critical profiles:', metrics.total_critical_profiles);
|
|
1099
|
+
* console.log('Average risk score:', metrics.average_risk_score);
|
|
1100
|
+
*
|
|
1101
|
+
* metrics.risk_distribution.forEach(item => {
|
|
1102
|
+
* console.log(`${item.category}: ${item.count} (${item.percentage}%)`);
|
|
1103
|
+
* });
|
|
1104
|
+
* ```
|
|
1105
|
+
*/
|
|
1106
|
+
async getDashboardMetrics(startDate, endDate) {
|
|
1107
|
+
const params = {};
|
|
1108
|
+
if (startDate) params.start_date = startDate;
|
|
1109
|
+
if (endDate) params.end_date = endDate;
|
|
1110
|
+
const queryString = this.buildQueryString(params);
|
|
1111
|
+
return this.request(
|
|
1112
|
+
`/api/v1/risk-dashboard/metrics${queryString}`
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
// ============================================================================
|
|
1116
|
+
// Bulk Operations
|
|
1117
|
+
// ============================================================================
|
|
1118
|
+
/**
|
|
1119
|
+
* Get or create profile (idempotent operation)
|
|
1120
|
+
*
|
|
1121
|
+
* Attempts to get existing profile by customer_id, creates if not found.
|
|
1122
|
+
*
|
|
1123
|
+
* @param customerId - Customer ID
|
|
1124
|
+
* @param createRequest - Profile data to use if creating new profile
|
|
1125
|
+
* @returns Existing or newly created profile
|
|
1126
|
+
*
|
|
1127
|
+
* @example
|
|
1128
|
+
* ```typescript
|
|
1129
|
+
* const profile = await client.getOrCreateProfile('CUST-123', {
|
|
1130
|
+
* customer_id: 'CUST-123',
|
|
1131
|
+
* entity_type: 'individual',
|
|
1132
|
+
* full_name: 'John Doe',
|
|
1133
|
+
* email_address: 'john@example.com'
|
|
1134
|
+
* });
|
|
1135
|
+
* ```
|
|
1136
|
+
*/
|
|
1137
|
+
async getOrCreateProfile(customerId, createRequest) {
|
|
1138
|
+
try {
|
|
1139
|
+
return await this.getProfile(customerId);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
return await this.createProfile(createRequest);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Batch get profiles by customer IDs
|
|
1146
|
+
*
|
|
1147
|
+
* @param customerIds - Array of customer IDs
|
|
1148
|
+
* @returns Array of profiles (may be less than input if some not found)
|
|
1149
|
+
*/
|
|
1150
|
+
async batchGetProfiles(customerIds) {
|
|
1151
|
+
const profiles = [];
|
|
1152
|
+
for (const customerId of customerIds) {
|
|
1153
|
+
try {
|
|
1154
|
+
const profile = await this.getProfile(customerId);
|
|
1155
|
+
profiles.push(profile);
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
if (this.config.debug) {
|
|
1158
|
+
console.warn(`[RiskProfileClient] Profile not found for: ${customerId}`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return profiles;
|
|
1163
|
+
}
|
|
1164
|
+
// ============================================================================
|
|
1165
|
+
// Configuration
|
|
1166
|
+
// ============================================================================
|
|
1167
|
+
/**
|
|
1168
|
+
* Get risk configuration for tenant
|
|
1169
|
+
*
|
|
1170
|
+
* Returns the risk scoring weights and thresholds configured for the tenant.
|
|
1171
|
+
*
|
|
1172
|
+
* @returns Risk configuration
|
|
1173
|
+
*/
|
|
1174
|
+
async getRiskConfiguration() {
|
|
1175
|
+
return this.request("/api/v1/risk-config");
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Update risk configuration for tenant
|
|
1179
|
+
*
|
|
1180
|
+
* Updates risk scoring weights and/or thresholds.
|
|
1181
|
+
*
|
|
1182
|
+
* @param config - Configuration updates
|
|
1183
|
+
* @returns Updated risk configuration
|
|
1184
|
+
*/
|
|
1185
|
+
async updateRiskConfiguration(config) {
|
|
1186
|
+
return this.request("/api/v1/risk-config", {
|
|
1187
|
+
method: "PUT",
|
|
1188
|
+
body: JSON.stringify(config)
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
// src/compliance/types.ts
|
|
1194
|
+
var DEFAULT_CURRENCY_RATES = {
|
|
1195
|
+
USD: 1,
|
|
1196
|
+
EUR: 1.1,
|
|
1197
|
+
GBP: 1.27,
|
|
1198
|
+
CAD: 0.74,
|
|
1199
|
+
AUD: 0.66,
|
|
1200
|
+
JPY: 67e-4,
|
|
1201
|
+
CHF: 1.13,
|
|
1202
|
+
CNY: 0.14,
|
|
1203
|
+
INR: 0.012,
|
|
1204
|
+
BRL: 0.2,
|
|
1205
|
+
MXN: 0.058,
|
|
1206
|
+
ZAR: 0.055
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
// src/compliance/client.ts
|
|
1210
|
+
var ComplianceClient = class {
|
|
1211
|
+
constructor(config) {
|
|
1212
|
+
this.config = {
|
|
1213
|
+
baseURL: config.baseURL,
|
|
1214
|
+
tenantId: config.tenantId,
|
|
1215
|
+
apiKey: config.apiKey || "",
|
|
1216
|
+
headers: config.headers || {},
|
|
1217
|
+
timeout: config.timeout || 1e4,
|
|
1218
|
+
retries: config.retries || 3,
|
|
1219
|
+
debug: config.debug || false,
|
|
1220
|
+
autoCreateProfiles: config.autoCreateProfiles !== false,
|
|
1221
|
+
// default true
|
|
1222
|
+
syncMode: config.syncMode || "sync"
|
|
1223
|
+
};
|
|
1224
|
+
this.geoClient = new GeolocationClient({
|
|
1225
|
+
baseURL: this.config.baseURL,
|
|
1226
|
+
tenantId: this.config.tenantId,
|
|
1227
|
+
apiKey: this.config.apiKey,
|
|
1228
|
+
headers: this.config.headers,
|
|
1229
|
+
timeout: this.config.timeout,
|
|
1230
|
+
debug: this.config.debug
|
|
1231
|
+
});
|
|
1232
|
+
this.riskClient = new RiskProfileClient({
|
|
1233
|
+
baseURL: this.config.baseURL,
|
|
1234
|
+
tenantId: this.config.tenantId,
|
|
1235
|
+
apiKey: this.config.apiKey,
|
|
1236
|
+
headers: this.config.headers,
|
|
1237
|
+
timeout: this.config.timeout,
|
|
1238
|
+
debug: this.config.debug
|
|
1239
|
+
});
|
|
1240
|
+
this.currencyRates = DEFAULT_CURRENCY_RATES;
|
|
1241
|
+
}
|
|
1242
|
+
// ============================================================================
|
|
1243
|
+
// Main Verification Methods
|
|
1244
|
+
// ============================================================================
|
|
1245
|
+
/**
|
|
1246
|
+
* Verify customer registration with automatic profile creation
|
|
1247
|
+
*
|
|
1248
|
+
* This is the primary integration point for new customer sign-ups.
|
|
1249
|
+
* Combines geolocation verification with customer risk profile creation.
|
|
1250
|
+
*
|
|
1251
|
+
* Important: Profile is only created if geolocation verification passes.
|
|
1252
|
+
* This prevents orphaned geolocation records when registration is blocked.
|
|
1253
|
+
*
|
|
1254
|
+
* @param request - Registration verification request
|
|
1255
|
+
* @returns Verification response with profile and compliance status
|
|
1256
|
+
*
|
|
1257
|
+
* @example
|
|
1258
|
+
* ```typescript
|
|
1259
|
+
* const result = await sdk.verifyAtRegistration({
|
|
1260
|
+
* customerId: 'CUST-12345',
|
|
1261
|
+
* fullName: 'John Doe',
|
|
1262
|
+
* emailAddress: 'john@example.com',
|
|
1263
|
+
* ipAddress: req.ip,
|
|
1264
|
+
* deviceFingerprint: getDeviceFingerprint()
|
|
1265
|
+
* });
|
|
1266
|
+
*
|
|
1267
|
+
* if (result.allowed) {
|
|
1268
|
+
* // Create account
|
|
1269
|
+
* console.log('Profile created:', result.profile);
|
|
1270
|
+
* if (result.requiresKYC) {
|
|
1271
|
+
* // Redirect to KYC flow
|
|
1272
|
+
* }
|
|
1273
|
+
* } else {
|
|
1274
|
+
* // Block registration
|
|
1275
|
+
* console.log('Blocked:', result.blockReasons);
|
|
1276
|
+
* }
|
|
1277
|
+
* ```
|
|
1278
|
+
*/
|
|
1279
|
+
async verifyAtRegistration(request) {
|
|
1280
|
+
const startTime = Date.now();
|
|
1281
|
+
this.validateRegistrationRequest(request);
|
|
1282
|
+
let geoVerification = null;
|
|
1283
|
+
try {
|
|
1284
|
+
if (this.config.debug) {
|
|
1285
|
+
console.log("[ComplianceSDK] Starting registration verification for:", request.customerId);
|
|
1286
|
+
}
|
|
1287
|
+
geoVerification = await this.geoClient.verifyIP({
|
|
1288
|
+
ip_address: request.ipAddress,
|
|
1289
|
+
user_id: request.customerId,
|
|
1290
|
+
event_type: "registration",
|
|
1291
|
+
device_fingerprint: request.deviceFingerprint
|
|
1292
|
+
});
|
|
1293
|
+
const blockReasons = this.evaluateRegistrationBlock(geoVerification);
|
|
1294
|
+
if (blockReasons.length > 0) {
|
|
1295
|
+
if (this.config.debug) {
|
|
1296
|
+
console.log("[ComplianceSDK] Registration blocked at geo stage:", blockReasons);
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
allowed: false,
|
|
1300
|
+
geolocation: geoVerification,
|
|
1301
|
+
profile: null,
|
|
1302
|
+
// Type assertion for blocked case
|
|
1303
|
+
requiresKYC: false,
|
|
1304
|
+
requiresEDD: false,
|
|
1305
|
+
blockReasons,
|
|
1306
|
+
processingTime: Date.now() - startTime
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
const profile = await this.riskClient.createProfile({
|
|
1310
|
+
customer_id: request.customerId,
|
|
1311
|
+
entity_type: request.entityType || "individual",
|
|
1312
|
+
customer_status: "active",
|
|
1313
|
+
full_name: request.fullName,
|
|
1314
|
+
email_address: request.emailAddress,
|
|
1315
|
+
primary_phone_number: request.phoneNumber,
|
|
1316
|
+
date_of_birth: request.dateOfBirth,
|
|
1317
|
+
residential_address: request.address,
|
|
1318
|
+
country_of_residence: geoVerification.location.country_iso,
|
|
1319
|
+
// Geolocation data
|
|
1320
|
+
location: `${geoVerification.location.city}, ${geoVerification.location.country}`,
|
|
1321
|
+
location_compliance: geoVerification.is_compliant ? "compliant" : "non-compliant",
|
|
1322
|
+
// Initial KYC status
|
|
1323
|
+
kyc_status: "pending"
|
|
1324
|
+
});
|
|
1325
|
+
const requiresKYC = geoVerification.jurisdiction?.require_kyc || false;
|
|
1326
|
+
const requiresEDD = geoVerification.jurisdiction?.require_enhanced_verification || false;
|
|
1327
|
+
if (this.config.debug) {
|
|
1328
|
+
console.log("[ComplianceSDK] Registration verification complete:", {
|
|
1329
|
+
allowed: true,
|
|
1330
|
+
riskScore: geoVerification.risk_score,
|
|
1331
|
+
profileId: profile.id,
|
|
1332
|
+
requiresKYC,
|
|
1333
|
+
requiresEDD
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
return {
|
|
1337
|
+
allowed: true,
|
|
1338
|
+
geolocation: geoVerification,
|
|
1339
|
+
profile,
|
|
1340
|
+
requiresKYC,
|
|
1341
|
+
requiresEDD,
|
|
1342
|
+
blockReasons: [],
|
|
1343
|
+
processingTime: Date.now() - startTime
|
|
1344
|
+
};
|
|
1345
|
+
} catch (error) {
|
|
1346
|
+
if (this.config.debug) {
|
|
1347
|
+
console.error("[ComplianceSDK] Registration verification failed:", error);
|
|
1348
|
+
}
|
|
1349
|
+
throw new ComplianceError(
|
|
1350
|
+
"Registration verification failed",
|
|
1351
|
+
{
|
|
1352
|
+
stage: geoVerification ? "profile_creation" : "geo_verification",
|
|
1353
|
+
geoRecordId: geoVerification?.record_id,
|
|
1354
|
+
customerId: request.customerId,
|
|
1355
|
+
cause: error
|
|
1356
|
+
},
|
|
1357
|
+
geoVerification ? "PROFILE_CREATION_FAILED" : "GEO_VERIFICATION_FAILED"
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Evaluate if registration should be blocked based on geolocation verification
|
|
1363
|
+
*
|
|
1364
|
+
* Checks:
|
|
1365
|
+
* 1. General compliance status (is_compliant)
|
|
1366
|
+
* 2. Explicit block flag (is_blocked)
|
|
1367
|
+
* 3. Jurisdiction-specific registration allowance (allow_registration)
|
|
1368
|
+
* 4. Risk level thresholds
|
|
1369
|
+
*
|
|
1370
|
+
* @param geoVerification - Geolocation verification result
|
|
1371
|
+
* @returns Array of block reasons (empty if allowed)
|
|
1372
|
+
*/
|
|
1373
|
+
evaluateRegistrationBlock(geoVerification) {
|
|
1374
|
+
const blockReasons = [];
|
|
1375
|
+
if (geoVerification.is_blocked) {
|
|
1376
|
+
blockReasons.push(...geoVerification.risk_reasons);
|
|
1377
|
+
}
|
|
1378
|
+
if (!geoVerification.is_compliant) {
|
|
1379
|
+
if (!blockReasons.includes("non_compliant_jurisdiction")) {
|
|
1380
|
+
blockReasons.push("non_compliant_jurisdiction");
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
const jurisdiction = geoVerification.jurisdiction;
|
|
1384
|
+
if (jurisdiction) {
|
|
1385
|
+
if (jurisdiction.allow_registration === false) {
|
|
1386
|
+
blockReasons.push("registration_not_allowed_in_jurisdiction");
|
|
1387
|
+
}
|
|
1388
|
+
if (jurisdiction.status === "blocked" || jurisdiction.status === "sanctioned") {
|
|
1389
|
+
if (!blockReasons.includes("blocked_jurisdiction")) {
|
|
1390
|
+
blockReasons.push("blocked_jurisdiction");
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (jurisdiction.status === "restricted" && jurisdiction.allow_registration === false) {
|
|
1394
|
+
if (!blockReasons.includes("restricted_jurisdiction_no_registration")) {
|
|
1395
|
+
blockReasons.push("restricted_jurisdiction_no_registration");
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
if (geoVerification.geofence_evaluation?.blocked) {
|
|
1400
|
+
blockReasons.push(...geoVerification.geofence_evaluation.reasons);
|
|
1401
|
+
}
|
|
1402
|
+
if (geoVerification.risk_level === "critical") {
|
|
1403
|
+
if (!blockReasons.includes("critical_risk_level")) {
|
|
1404
|
+
blockReasons.push("critical_risk_level");
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return [...new Set(blockReasons)];
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Validate registration request has all required fields
|
|
1411
|
+
*
|
|
1412
|
+
* @param request - Registration request to validate
|
|
1413
|
+
* @throws ValidationError if required fields are missing or invalid
|
|
1414
|
+
*/
|
|
1415
|
+
validateRegistrationRequest(request) {
|
|
1416
|
+
const errors = [];
|
|
1417
|
+
if (!request.customerId?.trim()) {
|
|
1418
|
+
errors.push("customerId is required");
|
|
1419
|
+
}
|
|
1420
|
+
if (!request.ipAddress?.trim()) {
|
|
1421
|
+
errors.push("ipAddress is required");
|
|
1422
|
+
} else if (!this.isValidIP(request.ipAddress)) {
|
|
1423
|
+
errors.push("ipAddress format is invalid");
|
|
1424
|
+
}
|
|
1425
|
+
if (!request.emailAddress?.trim()) {
|
|
1426
|
+
errors.push("emailAddress is required");
|
|
1427
|
+
} else if (!this.isValidEmail(request.emailAddress)) {
|
|
1428
|
+
errors.push("emailAddress format is invalid");
|
|
1429
|
+
}
|
|
1430
|
+
if (!request.fullName?.trim()) {
|
|
1431
|
+
errors.push("fullName is required");
|
|
1432
|
+
}
|
|
1433
|
+
if (errors.length > 0) {
|
|
1434
|
+
throw new ValidationError(`Invalid registration request: ${errors.join(", ")}`, errors);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Validate IP address format (IPv4 or IPv6)
|
|
1439
|
+
*/
|
|
1440
|
+
isValidIP(ip) {
|
|
1441
|
+
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
1442
|
+
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,7}:$|^(?:[0-9a-fA-F]{1,4}:){0,6}::(?:[0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}$/;
|
|
1443
|
+
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Validate email address format
|
|
1447
|
+
*/
|
|
1448
|
+
isValidEmail(email) {
|
|
1449
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1450
|
+
return emailRegex.test(email);
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Verify customer login with profile activity update
|
|
1454
|
+
*
|
|
1455
|
+
* Verifies geolocation and updates customer profile with latest activity.
|
|
1456
|
+
*
|
|
1457
|
+
* @param request - Login verification request
|
|
1458
|
+
* @returns Verification response with compliance status
|
|
1459
|
+
*
|
|
1460
|
+
* @example
|
|
1461
|
+
* ```typescript
|
|
1462
|
+
* const result = await sdk.verifyAtLogin({
|
|
1463
|
+
* customerId: 'CUST-12345',
|
|
1464
|
+
* ipAddress: req.ip,
|
|
1465
|
+
* deviceFingerprint: getDeviceFingerprint()
|
|
1466
|
+
* });
|
|
1467
|
+
*
|
|
1468
|
+
* if (result.allowed) {
|
|
1469
|
+
* // Allow login
|
|
1470
|
+
* if (result.requiresStepUp) {
|
|
1471
|
+
* // Trigger MFA or additional verification
|
|
1472
|
+
* }
|
|
1473
|
+
* } else {
|
|
1474
|
+
* // Block login
|
|
1475
|
+
* console.log('Blocked:', result.blockReasons);
|
|
1476
|
+
* }
|
|
1477
|
+
* ```
|
|
1478
|
+
*/
|
|
1479
|
+
async verifyAtLogin(request) {
|
|
1480
|
+
const startTime = Date.now();
|
|
1481
|
+
try {
|
|
1482
|
+
if (this.config.debug) {
|
|
1483
|
+
console.log("[ComplianceSDK] Starting login verification for:", request.customerId);
|
|
1484
|
+
}
|
|
1485
|
+
const geoVerification = await this.geoClient.verifyIP({
|
|
1486
|
+
ip_address: request.ipAddress,
|
|
1487
|
+
user_id: request.customerId,
|
|
1488
|
+
event_type: "login",
|
|
1489
|
+
device_fingerprint: request.deviceFingerprint
|
|
1490
|
+
});
|
|
1491
|
+
let profile;
|
|
1492
|
+
try {
|
|
1493
|
+
profile = await this.riskClient.getProfile(request.customerId);
|
|
1494
|
+
if (this.shouldUpdateProfile(profile, geoVerification.location.city)) {
|
|
1495
|
+
await this.riskClient.updateProfile(profile.id, {
|
|
1496
|
+
last_recorded_activity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1497
|
+
location: `${geoVerification.location.city}, ${geoVerification.location.country}`,
|
|
1498
|
+
location_compliance: geoVerification.is_compliant ? "compliant" : "non-compliant"
|
|
1499
|
+
});
|
|
1500
|
+
profile = await this.riskClient.getProfile(request.customerId);
|
|
1501
|
+
}
|
|
1502
|
+
} catch (error) {
|
|
1503
|
+
if (this.config.autoCreateProfiles && this.config.debug) {
|
|
1504
|
+
console.warn("[ComplianceSDK] Profile not found for login, creating...");
|
|
1505
|
+
}
|
|
1506
|
+
profile = await this.createProfileFromGeo(request.customerId, geoVerification);
|
|
1507
|
+
}
|
|
1508
|
+
const isAllowed = geoVerification.is_compliant && !geoVerification.is_blocked && profile.customer_status !== "suspended";
|
|
1509
|
+
const requiresStepUp = geoVerification.risk_level === "high" || geoVerification.risk_level === "critical";
|
|
1510
|
+
return {
|
|
1511
|
+
allowed: isAllowed,
|
|
1512
|
+
geolocation: geoVerification,
|
|
1513
|
+
profile,
|
|
1514
|
+
requiresStepUp,
|
|
1515
|
+
blockReasons: this.getBlockReasons(geoVerification, profile),
|
|
1516
|
+
processingTime: Date.now() - startTime
|
|
1517
|
+
};
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
throw new ComplianceError("Login verification failed", error);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Verify transaction with amount-based risk assessment
|
|
1524
|
+
*
|
|
1525
|
+
* Combines geolocation verification with transaction amount analysis
|
|
1526
|
+
* and customer risk profile for comprehensive transaction screening.
|
|
1527
|
+
*
|
|
1528
|
+
* @param request - Transaction verification request
|
|
1529
|
+
* @returns Verification response with transaction risk assessment
|
|
1530
|
+
*
|
|
1531
|
+
* @example
|
|
1532
|
+
* ```typescript
|
|
1533
|
+
* const result = await sdk.verifyAtTransaction({
|
|
1534
|
+
* customerId: 'CUST-12345',
|
|
1535
|
+
* ipAddress: req.ip,
|
|
1536
|
+
* amount: 5000,
|
|
1537
|
+
* currency: 'USD',
|
|
1538
|
+
* transactionType: 'withdrawal',
|
|
1539
|
+
* deviceFingerprint: getDeviceFingerprint()
|
|
1540
|
+
* });
|
|
1541
|
+
*
|
|
1542
|
+
* if (result.allowed) {
|
|
1543
|
+
* // Process transaction
|
|
1544
|
+
* } else if (result.requiresApproval) {
|
|
1545
|
+
* // Queue for manual review
|
|
1546
|
+
* } else {
|
|
1547
|
+
* // Block transaction
|
|
1548
|
+
* console.log('Blocked:', result.blockReasons);
|
|
1549
|
+
* }
|
|
1550
|
+
* ```
|
|
1551
|
+
*/
|
|
1552
|
+
async verifyAtTransaction(request) {
|
|
1553
|
+
const startTime = Date.now();
|
|
1554
|
+
try {
|
|
1555
|
+
if (this.config.debug) {
|
|
1556
|
+
console.log("[ComplianceSDK] Starting transaction verification:", {
|
|
1557
|
+
customerId: request.customerId,
|
|
1558
|
+
amount: request.amount,
|
|
1559
|
+
currency: request.currency
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
const geoVerification = await this.geoClient.verifyIP({
|
|
1563
|
+
ip_address: request.ipAddress,
|
|
1564
|
+
user_id: request.customerId,
|
|
1565
|
+
event_type: "transaction",
|
|
1566
|
+
device_fingerprint: request.deviceFingerprint
|
|
1567
|
+
});
|
|
1568
|
+
const profile = await this.riskClient.getProfile(request.customerId);
|
|
1569
|
+
const transactionRisk = this.calculateTransactionRisk(
|
|
1570
|
+
request.amount,
|
|
1571
|
+
request.currency,
|
|
1572
|
+
geoVerification,
|
|
1573
|
+
profile
|
|
1574
|
+
);
|
|
1575
|
+
const jurisdictionAllowed = this.checkJurisdictionLimits(
|
|
1576
|
+
request.amount,
|
|
1577
|
+
request.currency,
|
|
1578
|
+
geoVerification.jurisdiction
|
|
1579
|
+
);
|
|
1580
|
+
const isAllowed = geoVerification.is_compliant && !geoVerification.is_blocked && jurisdictionAllowed && transactionRisk.allowed;
|
|
1581
|
+
return {
|
|
1582
|
+
allowed: isAllowed,
|
|
1583
|
+
geolocation: geoVerification,
|
|
1584
|
+
profile,
|
|
1585
|
+
transactionRisk,
|
|
1586
|
+
requiresApproval: transactionRisk.requiresManualReview,
|
|
1587
|
+
blockReasons: this.getTransactionBlockReasons(
|
|
1588
|
+
geoVerification,
|
|
1589
|
+
transactionRisk,
|
|
1590
|
+
jurisdictionAllowed
|
|
1591
|
+
),
|
|
1592
|
+
processingTime: Date.now() - startTime
|
|
1593
|
+
};
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
throw new ComplianceError("Transaction verification failed", error);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Generic event verification (for other touchpoints)
|
|
1600
|
+
*
|
|
1601
|
+
* @param request - Event verification request
|
|
1602
|
+
* @returns Verification response
|
|
1603
|
+
*/
|
|
1604
|
+
async verifyEvent(request) {
|
|
1605
|
+
const startTime = Date.now();
|
|
1606
|
+
const geoVerification = await this.geoClient.verifyIP({
|
|
1607
|
+
ip_address: request.ipAddress,
|
|
1608
|
+
user_id: request.customerId,
|
|
1609
|
+
event_type: request.eventType,
|
|
1610
|
+
device_fingerprint: request.deviceFingerprint
|
|
1611
|
+
});
|
|
1612
|
+
if (this.config.autoCreateProfiles) {
|
|
1613
|
+
try {
|
|
1614
|
+
const profile = await this.riskClient.getProfile(request.customerId);
|
|
1615
|
+
await this.riskClient.updateProfile(profile.id, {
|
|
1616
|
+
last_recorded_activity: (/* @__PURE__ */ new Date()).toISOString()
|
|
1617
|
+
});
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
if (this.config.debug) {
|
|
1620
|
+
console.warn("[ComplianceSDK] Profile not found for event");
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
return {
|
|
1625
|
+
allowed: geoVerification.is_compliant && !geoVerification.is_blocked,
|
|
1626
|
+
geolocation: geoVerification,
|
|
1627
|
+
blockReasons: geoVerification.risk_reasons,
|
|
1628
|
+
processingTime: Date.now() - startTime
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
// ============================================================================
|
|
1632
|
+
// Helper Methods
|
|
1633
|
+
// ============================================================================
|
|
1634
|
+
shouldUpdateProfile(profile, newCity) {
|
|
1635
|
+
return !profile.location || !profile.location.includes(newCity);
|
|
1636
|
+
}
|
|
1637
|
+
async createProfileFromGeo(customerId, geoVerification) {
|
|
1638
|
+
return this.riskClient.createProfile({
|
|
1639
|
+
customer_id: customerId,
|
|
1640
|
+
entity_type: "individual",
|
|
1641
|
+
customer_status: "active",
|
|
1642
|
+
email_address: `${customerId}@placeholder.local`,
|
|
1643
|
+
// Temporary email
|
|
1644
|
+
location: `${geoVerification.location.city}, ${geoVerification.location.country}`,
|
|
1645
|
+
location_compliance: geoVerification.is_compliant ? "compliant" : "non-compliant",
|
|
1646
|
+
country_of_residence: geoVerification.location.country_iso,
|
|
1647
|
+
kyc_status: "pending"
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
calculateTransactionRisk(amount, currency, geoVerification, profile) {
|
|
1651
|
+
let riskScore = 0;
|
|
1652
|
+
const factors = [];
|
|
1653
|
+
const normalizedAmount = this.normalizeToUSD(amount, currency);
|
|
1654
|
+
if (normalizedAmount > 1e4) {
|
|
1655
|
+
riskScore += 30;
|
|
1656
|
+
factors.push("high_transaction_amount");
|
|
1657
|
+
} else if (normalizedAmount > 5e3) {
|
|
1658
|
+
riskScore += 15;
|
|
1659
|
+
factors.push("elevated_transaction_amount");
|
|
1660
|
+
}
|
|
1661
|
+
riskScore += geoVerification.risk_score * 0.4;
|
|
1662
|
+
if (geoVerification.risk_level === "high" || geoVerification.risk_level === "critical") {
|
|
1663
|
+
factors.push("high_risk_location");
|
|
1664
|
+
}
|
|
1665
|
+
riskScore += profile.risk_score * 0.3;
|
|
1666
|
+
if (profile.risk_category === "high" || profile.risk_category === "critical") {
|
|
1667
|
+
factors.push("high_risk_customer");
|
|
1668
|
+
}
|
|
1669
|
+
if (geoVerification.location.is_vpn || geoVerification.location.is_proxy) {
|
|
1670
|
+
riskScore += 20;
|
|
1671
|
+
factors.push("anonymization_detected");
|
|
1672
|
+
}
|
|
1673
|
+
if (profile.customer_status === "suspended") {
|
|
1674
|
+
riskScore += 50;
|
|
1675
|
+
factors.push("account_suspended");
|
|
1676
|
+
}
|
|
1677
|
+
return {
|
|
1678
|
+
score: Math.min(riskScore, 100),
|
|
1679
|
+
level: this.getRiskLevel(riskScore),
|
|
1680
|
+
factors,
|
|
1681
|
+
allowed: riskScore < 70,
|
|
1682
|
+
requiresManualReview: riskScore >= 60 && riskScore < 70
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
checkJurisdictionLimits(amount, currency, jurisdiction) {
|
|
1686
|
+
if (!jurisdiction || !jurisdiction.max_transaction_amount) {
|
|
1687
|
+
return true;
|
|
1688
|
+
}
|
|
1689
|
+
const normalizedAmount = this.normalizeToUSD(amount, currency);
|
|
1690
|
+
return normalizedAmount <= jurisdiction.max_transaction_amount;
|
|
1691
|
+
}
|
|
1692
|
+
normalizeToUSD(amount, currency) {
|
|
1693
|
+
const rate = this.currencyRates[currency.toUpperCase()] || 1;
|
|
1694
|
+
return amount * rate;
|
|
1695
|
+
}
|
|
1696
|
+
getRiskLevel(score) {
|
|
1697
|
+
if (score >= 80) return "critical";
|
|
1698
|
+
if (score >= 60) return "high";
|
|
1699
|
+
if (score >= 40) return "medium";
|
|
1700
|
+
return "low";
|
|
1701
|
+
}
|
|
1702
|
+
getBlockReasons(geoVerification, profile) {
|
|
1703
|
+
const reasons = [];
|
|
1704
|
+
if (geoVerification.is_blocked) {
|
|
1705
|
+
reasons.push(...geoVerification.risk_reasons);
|
|
1706
|
+
}
|
|
1707
|
+
if (profile.customer_status === "suspended") {
|
|
1708
|
+
reasons.push("account_suspended");
|
|
1709
|
+
}
|
|
1710
|
+
if (profile.has_sanctions) {
|
|
1711
|
+
reasons.push("sanctions_match");
|
|
1712
|
+
}
|
|
1713
|
+
return reasons;
|
|
1714
|
+
}
|
|
1715
|
+
getTransactionBlockReasons(geoVerification, transactionRisk, jurisdictionAllowed) {
|
|
1716
|
+
const reasons = [];
|
|
1717
|
+
if (!geoVerification.is_compliant) {
|
|
1718
|
+
reasons.push("non_compliant_jurisdiction");
|
|
1719
|
+
}
|
|
1720
|
+
if (!jurisdictionAllowed) {
|
|
1721
|
+
reasons.push("exceeds_jurisdiction_limit");
|
|
1722
|
+
}
|
|
1723
|
+
if (!transactionRisk.allowed) {
|
|
1724
|
+
reasons.push(...transactionRisk.factors);
|
|
1725
|
+
}
|
|
1726
|
+
return reasons;
|
|
1727
|
+
}
|
|
1728
|
+
// ============================================================================
|
|
1729
|
+
// Advanced Registration Methods
|
|
1730
|
+
// ============================================================================
|
|
1731
|
+
/**
|
|
1732
|
+
* Verify registration with synchronized risk calculation
|
|
1733
|
+
*
|
|
1734
|
+
* This method extends verifyAtRegistration by waiting for the geolocation
|
|
1735
|
+
* risk factor to be processed by the backend. Use this when you need
|
|
1736
|
+
* the accurate risk score immediately after registration.
|
|
1737
|
+
*
|
|
1738
|
+
* Note: This adds latency (up to `maxWaitMs`) but provides accurate risk data.
|
|
1739
|
+
*
|
|
1740
|
+
* @param request - Registration verification request
|
|
1741
|
+
* @param options - Sync options
|
|
1742
|
+
* @returns Verification response with updated risk score
|
|
1743
|
+
*
|
|
1744
|
+
* @example
|
|
1745
|
+
* ```typescript
|
|
1746
|
+
* const result = await sdk.verifyAtRegistrationWithSync(
|
|
1747
|
+
* {
|
|
1748
|
+
* customerId: 'CUST-12345',
|
|
1749
|
+
* fullName: 'John Doe',
|
|
1750
|
+
* emailAddress: 'john@example.com',
|
|
1751
|
+
* ipAddress: req.ip,
|
|
1752
|
+
* },
|
|
1753
|
+
* { maxWaitMs: 5000, pollIntervalMs: 500 }
|
|
1754
|
+
* );
|
|
1755
|
+
*
|
|
1756
|
+
* // profile.risk_score now reflects geolocation risk factor
|
|
1757
|
+
* console.log('Synced risk score:', result.profile?.risk_score);
|
|
1758
|
+
* ```
|
|
1759
|
+
*/
|
|
1760
|
+
async verifyAtRegistrationWithSync(request, options = {}) {
|
|
1761
|
+
const { maxWaitMs = 5e3, pollIntervalMs = 500 } = options;
|
|
1762
|
+
const result = await this.verifyAtRegistration(request);
|
|
1763
|
+
if (!result.allowed || !result.profile) {
|
|
1764
|
+
return { ...result, riskSynced: false };
|
|
1765
|
+
}
|
|
1766
|
+
const syncedProfile = await this.waitForRiskCalculation(
|
|
1767
|
+
result.profile.id,
|
|
1768
|
+
maxWaitMs,
|
|
1769
|
+
pollIntervalMs
|
|
1770
|
+
);
|
|
1771
|
+
if (syncedProfile) {
|
|
1772
|
+
return {
|
|
1773
|
+
...result,
|
|
1774
|
+
profile: syncedProfile,
|
|
1775
|
+
riskSynced: true
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
if (this.config.debug) {
|
|
1779
|
+
console.warn("[ComplianceSDK] Risk sync timed out, returning unsynchronized profile");
|
|
1780
|
+
}
|
|
1781
|
+
return { ...result, riskSynced: false };
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Wait for risk score to be calculated
|
|
1785
|
+
*
|
|
1786
|
+
* Polls the profile until risk_score > 0 or risk_last_calculated is set,
|
|
1787
|
+
* indicating that risk factors have been processed.
|
|
1788
|
+
*
|
|
1789
|
+
* @param profileId - Profile UUID to poll
|
|
1790
|
+
* @param maxWaitMs - Maximum wait time in milliseconds
|
|
1791
|
+
* @param pollIntervalMs - Polling interval in milliseconds
|
|
1792
|
+
* @returns Updated profile or null if timeout
|
|
1793
|
+
*/
|
|
1794
|
+
async waitForRiskCalculation(profileId, maxWaitMs, pollIntervalMs) {
|
|
1795
|
+
const startTime = Date.now();
|
|
1796
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1797
|
+
try {
|
|
1798
|
+
const profile = await this.riskClient.getProfileById(profileId);
|
|
1799
|
+
if (profile.risk_last_calculated || profile.risk_score > 0) {
|
|
1800
|
+
return profile;
|
|
1801
|
+
}
|
|
1802
|
+
} catch (error) {
|
|
1803
|
+
if (this.config.debug) {
|
|
1804
|
+
console.warn("[ComplianceSDK] Error polling profile:", error);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
await this.sleep(pollIntervalMs);
|
|
1808
|
+
}
|
|
1809
|
+
return null;
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Pre-check if registration would be allowed from an IP address
|
|
1813
|
+
*
|
|
1814
|
+
* Use this before showing the registration form to provide early feedback
|
|
1815
|
+
* to users in blocked jurisdictions.
|
|
1816
|
+
*
|
|
1817
|
+
* @param ipAddress - IP address to check
|
|
1818
|
+
* @returns Pre-check result with jurisdiction info
|
|
1819
|
+
*
|
|
1820
|
+
* @example
|
|
1821
|
+
* ```typescript
|
|
1822
|
+
* const preCheck = await sdk.preCheckRegistration(req.ip);
|
|
1823
|
+
*
|
|
1824
|
+
* if (!preCheck.canRegister) {
|
|
1825
|
+
* return res.render('blocked', { reasons: preCheck.blockReasons });
|
|
1826
|
+
* }
|
|
1827
|
+
*
|
|
1828
|
+
* if (preCheck.requiresKYC) {
|
|
1829
|
+
* // Prepare KYC flow
|
|
1830
|
+
* }
|
|
1831
|
+
* ```
|
|
1832
|
+
*/
|
|
1833
|
+
async preCheckRegistration(ipAddress) {
|
|
1834
|
+
const geoCheck = await this.geoClient.verifyIP({
|
|
1835
|
+
ip_address: ipAddress,
|
|
1836
|
+
user_id: `_precheck_${Date.now()}`,
|
|
1837
|
+
// Temporary ID for pre-check
|
|
1838
|
+
event_type: "registration"
|
|
1839
|
+
});
|
|
1840
|
+
const blockReasons = this.evaluateRegistrationBlock(geoCheck);
|
|
1841
|
+
const jurisdiction = geoCheck.jurisdiction || null;
|
|
1842
|
+
return {
|
|
1843
|
+
canRegister: blockReasons.length === 0,
|
|
1844
|
+
jurisdiction,
|
|
1845
|
+
blockReasons,
|
|
1846
|
+
riskLevel: geoCheck.risk_level,
|
|
1847
|
+
requiresKYC: jurisdiction?.require_kyc || false,
|
|
1848
|
+
requiresEDD: jurisdiction?.require_enhanced_verification || false,
|
|
1849
|
+
location: {
|
|
1850
|
+
country: geoCheck.location.country,
|
|
1851
|
+
countryISO: geoCheck.location.country_iso,
|
|
1852
|
+
city: geoCheck.location.city
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
// ============================================================================
|
|
1857
|
+
// Location Request Methods
|
|
1858
|
+
// ============================================================================
|
|
1859
|
+
/**
|
|
1860
|
+
* Request live location from a customer
|
|
1861
|
+
*
|
|
1862
|
+
* Creates a location request and sends a notification to the customer
|
|
1863
|
+
* via the specified channel (SMS, email, or push). The customer receives
|
|
1864
|
+
* a link to share their location.
|
|
1865
|
+
*
|
|
1866
|
+
* @param input - Location request details
|
|
1867
|
+
* @returns Location request result with share link
|
|
1868
|
+
*
|
|
1869
|
+
* @example
|
|
1870
|
+
* ```typescript
|
|
1871
|
+
* // Request location via SMS for transaction verification
|
|
1872
|
+
* const result = await sdk.requestCustomerLocation({
|
|
1873
|
+
* customerId: 'CUST-12345',
|
|
1874
|
+
* channel: 'sms',
|
|
1875
|
+
* phone: '+1234567890',
|
|
1876
|
+
* reason: 'Verification required for high-value transaction'
|
|
1877
|
+
* });
|
|
1878
|
+
*
|
|
1879
|
+
* console.log('Share link sent:', result.shareLink);
|
|
1880
|
+
* console.log('Expires at:', result.tokenExpiry);
|
|
1881
|
+
*
|
|
1882
|
+
* // Request location via email
|
|
1883
|
+
* const emailResult = await sdk.requestCustomerLocation({
|
|
1884
|
+
* customerId: 'CUST-12345',
|
|
1885
|
+
* channel: 'email',
|
|
1886
|
+
* email: 'customer@example.com',
|
|
1887
|
+
* reason: 'Account verification',
|
|
1888
|
+
* expiryHours: 48
|
|
1889
|
+
* });
|
|
1890
|
+
* ```
|
|
1891
|
+
*/
|
|
1892
|
+
async requestCustomerLocation(input) {
|
|
1893
|
+
if (this.config.debug) {
|
|
1894
|
+
console.log("[ComplianceSDK] Creating location request for customer:", input.customerId);
|
|
1895
|
+
}
|
|
1896
|
+
this.validateLocationRequestInput(input);
|
|
1897
|
+
let profile;
|
|
1898
|
+
try {
|
|
1899
|
+
profile = await this.riskClient.getProfile(input.customerId);
|
|
1900
|
+
} catch (error) {
|
|
1901
|
+
if (this.config.debug) {
|
|
1902
|
+
console.warn("[ComplianceSDK] Customer profile not found:", input.customerId);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
const phone = input.phone || profile?.primary_phone_number;
|
|
1906
|
+
const email = input.email || profile?.email_address;
|
|
1907
|
+
const result = await this.geoClient.createLocationRequest({
|
|
1908
|
+
user_id: input.customerId,
|
|
1909
|
+
channel: input.channel,
|
|
1910
|
+
reason: input.reason,
|
|
1911
|
+
phone,
|
|
1912
|
+
email,
|
|
1913
|
+
expiry_hours: input.expiryHours
|
|
1914
|
+
});
|
|
1915
|
+
if (this.config.debug) {
|
|
1916
|
+
console.log("[ComplianceSDK] Location request created:", {
|
|
1917
|
+
requestId: result.request.id,
|
|
1918
|
+
channel: input.channel,
|
|
1919
|
+
expiresAt: result.token_expiry
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
return {
|
|
1923
|
+
request: result.request,
|
|
1924
|
+
shareLink: result.share_link,
|
|
1925
|
+
tokenExpiry: result.token_expiry,
|
|
1926
|
+
profile
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Get a specific location request by ID
|
|
1931
|
+
*
|
|
1932
|
+
* @param requestId - Location request ID
|
|
1933
|
+
* @returns Location request details
|
|
1934
|
+
*
|
|
1935
|
+
* @example
|
|
1936
|
+
* ```typescript
|
|
1937
|
+
* const request = await sdk.getLocationRequest('req_abc123');
|
|
1938
|
+
* console.log('Status:', request.status);
|
|
1939
|
+
* if (request.latitude && request.longitude) {
|
|
1940
|
+
* console.log('Location captured:', request.latitude, request.longitude);
|
|
1941
|
+
* }
|
|
1942
|
+
* ```
|
|
1943
|
+
*/
|
|
1944
|
+
async getLocationRequest(requestId) {
|
|
1945
|
+
return this.geoClient.getLocationRequest(requestId);
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* List location requests with filters and pagination
|
|
1949
|
+
*
|
|
1950
|
+
* @param filters - Optional filters
|
|
1951
|
+
* @param pagination - Optional pagination
|
|
1952
|
+
* @returns Paginated list of location requests
|
|
1953
|
+
*
|
|
1954
|
+
* @example
|
|
1955
|
+
* ```typescript
|
|
1956
|
+
* // Get pending requests for a customer
|
|
1957
|
+
* const pending = await sdk.listLocationRequests(
|
|
1958
|
+
* { status: 'pending', user_id: 'CUST-12345' },
|
|
1959
|
+
* { page: 1, limit: 20 }
|
|
1960
|
+
* );
|
|
1961
|
+
*
|
|
1962
|
+
* // Get all completed requests from last week
|
|
1963
|
+
* const completed = await sdk.listLocationRequests({
|
|
1964
|
+
* status: 'completed',
|
|
1965
|
+
* date_from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
|
|
1966
|
+
* });
|
|
1967
|
+
* ```
|
|
1968
|
+
*/
|
|
1969
|
+
async listLocationRequests(filters, pagination) {
|
|
1970
|
+
return this.geoClient.listLocationRequests(filters, pagination);
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Cancel a pending location request
|
|
1974
|
+
*
|
|
1975
|
+
* @param requestId - Location request ID
|
|
1976
|
+
*
|
|
1977
|
+
* @example
|
|
1978
|
+
* ```typescript
|
|
1979
|
+
* await sdk.cancelLocationRequest('req_abc123');
|
|
1980
|
+
* ```
|
|
1981
|
+
*/
|
|
1982
|
+
async cancelLocationRequest(requestId) {
|
|
1983
|
+
if (this.config.debug) {
|
|
1984
|
+
console.log("[ComplianceSDK] Cancelling location request:", requestId);
|
|
1985
|
+
}
|
|
1986
|
+
await this.geoClient.cancelLocationRequest(requestId);
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Resend notification for a location request
|
|
1990
|
+
*
|
|
1991
|
+
* @param requestId - Location request ID
|
|
1992
|
+
* @param contact - Contact details (email or phone)
|
|
1993
|
+
*
|
|
1994
|
+
* @example
|
|
1995
|
+
* ```typescript
|
|
1996
|
+
* // Resend to a different phone number
|
|
1997
|
+
* await sdk.resendLocationRequest('req_abc123', { phone: '+0987654321' });
|
|
1998
|
+
*
|
|
1999
|
+
* // Resend to email
|
|
2000
|
+
* await sdk.resendLocationRequest('req_abc123', { email: 'new@example.com' });
|
|
2001
|
+
* ```
|
|
2002
|
+
*/
|
|
2003
|
+
async resendLocationRequest(requestId, contact) {
|
|
2004
|
+
if (this.config.debug) {
|
|
2005
|
+
console.log("[ComplianceSDK] Resending location request:", requestId);
|
|
2006
|
+
}
|
|
2007
|
+
await this.geoClient.resendLocationRequest(requestId, contact);
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Validate location request input
|
|
2011
|
+
*/
|
|
2012
|
+
validateLocationRequestInput(input) {
|
|
2013
|
+
const errors = [];
|
|
2014
|
+
if (!input.customerId?.trim()) {
|
|
2015
|
+
errors.push("customerId is required");
|
|
2016
|
+
}
|
|
2017
|
+
if (!input.channel) {
|
|
2018
|
+
errors.push("channel is required");
|
|
2019
|
+
}
|
|
2020
|
+
if (!input.reason?.trim()) {
|
|
2021
|
+
errors.push("reason is required for compliance/audit purposes");
|
|
2022
|
+
}
|
|
2023
|
+
if (input.channel === "sms" && !input.phone) {
|
|
2024
|
+
errors.push("phone is required for SMS channel");
|
|
2025
|
+
}
|
|
2026
|
+
if (input.channel === "email" && !input.email) {
|
|
2027
|
+
errors.push("email is required for email channel");
|
|
2028
|
+
}
|
|
2029
|
+
if (errors.length > 0) {
|
|
2030
|
+
throw new ValidationError(`Invalid location request: ${errors.join(", ")}`, errors);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
// ============================================================================
|
|
2034
|
+
// Configuration
|
|
2035
|
+
// ============================================================================
|
|
2036
|
+
/**
|
|
2037
|
+
* Update currency exchange rates
|
|
2038
|
+
*
|
|
2039
|
+
* Use this to keep currency rates current for accurate transaction risk
|
|
2040
|
+
* calculation. Rates should be updated regularly (e.g., daily).
|
|
2041
|
+
*
|
|
2042
|
+
* @param rates - Currency to USD exchange rates
|
|
2043
|
+
*
|
|
2044
|
+
* @example
|
|
2045
|
+
* ```typescript
|
|
2046
|
+
* // Update specific rates
|
|
2047
|
+
* sdk.updateCurrencyRates({
|
|
2048
|
+
* EUR: 1.08,
|
|
2049
|
+
* GBP: 1.25,
|
|
2050
|
+
* BTC: 42000, // For crypto support
|
|
2051
|
+
* });
|
|
2052
|
+
*
|
|
2053
|
+
* // Or fetch from external service
|
|
2054
|
+
* const rates = await fetchExchangeRates();
|
|
2055
|
+
* sdk.updateCurrencyRates(rates);
|
|
2056
|
+
* ```
|
|
2057
|
+
*/
|
|
2058
|
+
updateCurrencyRates(rates) {
|
|
2059
|
+
this.currencyRates = { ...this.currencyRates, ...rates };
|
|
2060
|
+
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Get current currency rates
|
|
2063
|
+
*
|
|
2064
|
+
* @returns Current currency to USD exchange rates
|
|
2065
|
+
*/
|
|
2066
|
+
getCurrencyRates() {
|
|
2067
|
+
return { ...this.currencyRates };
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Fetch and update currency rates from an external API
|
|
2071
|
+
*
|
|
2072
|
+
* This method allows integration with external exchange rate providers.
|
|
2073
|
+
* Pass a fetcher function that returns the new rates.
|
|
2074
|
+
*
|
|
2075
|
+
* @param fetcher - Async function that returns currency rates
|
|
2076
|
+
* @returns The fetched rates
|
|
2077
|
+
*
|
|
2078
|
+
* @example
|
|
2079
|
+
* ```typescript
|
|
2080
|
+
* // Using a rate provider
|
|
2081
|
+
* const rates = await sdk.fetchCurrencyRates(async () => {
|
|
2082
|
+
* const response = await fetch('https://api.exchangerate.host/latest?base=USD');
|
|
2083
|
+
* const data = await response.json();
|
|
2084
|
+
* return data.rates;
|
|
2085
|
+
* });
|
|
2086
|
+
*
|
|
2087
|
+
* console.log('Updated rates:', rates);
|
|
2088
|
+
* ```
|
|
2089
|
+
*/
|
|
2090
|
+
async fetchCurrencyRates(fetcher) {
|
|
2091
|
+
try {
|
|
2092
|
+
const rates = await fetcher();
|
|
2093
|
+
this.updateCurrencyRates(rates);
|
|
2094
|
+
return rates;
|
|
2095
|
+
} catch (error) {
|
|
2096
|
+
if (this.config.debug) {
|
|
2097
|
+
console.error("[ComplianceSDK] Failed to fetch currency rates:", error);
|
|
2098
|
+
}
|
|
2099
|
+
throw new ComplianceError("Failed to fetch currency rates", error);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Get underlying geolocation client
|
|
2104
|
+
*/
|
|
2105
|
+
getGeolocationClient() {
|
|
2106
|
+
return this.geoClient;
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Get underlying risk profile client
|
|
2110
|
+
*/
|
|
2111
|
+
getRiskProfileClient() {
|
|
2112
|
+
return this.riskClient;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Update configuration
|
|
2116
|
+
*/
|
|
2117
|
+
updateConfig(config) {
|
|
2118
|
+
this.config = { ...this.config, ...config };
|
|
2119
|
+
this.geoClient.updateConfig(config);
|
|
2120
|
+
this.riskClient.updateConfig(config);
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Helper to create a delay
|
|
2124
|
+
*/
|
|
2125
|
+
sleep(ms) {
|
|
2126
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2129
|
+
|
|
2130
|
+
exports.ComplianceClient = ComplianceClient;
|
|
2131
|
+
exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
|
|
2132
|
+
//# sourceMappingURL=index.js.map
|
|
2133
|
+
//# sourceMappingURL=index.js.map
|