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.
Files changed (50) hide show
  1. package/README.md +247 -0
  2. package/dist/client-CA9Wr_qb.d.ts +1108 -0
  3. package/dist/client-DMNkESa0.d.mts +1108 -0
  4. package/dist/compliance/index.d.mts +543 -0
  5. package/dist/compliance/index.d.ts +543 -0
  6. package/dist/compliance/index.js +2133 -0
  7. package/dist/compliance/index.js.map +1 -0
  8. package/dist/compliance/index.mjs +2130 -0
  9. package/dist/compliance/index.mjs.map +1 -0
  10. package/dist/geolocation/index.d.mts +73 -0
  11. package/dist/geolocation/index.d.ts +73 -0
  12. package/dist/geolocation/index.js +1100 -0
  13. package/dist/geolocation/index.js.map +1 -0
  14. package/dist/geolocation/index.mjs +1094 -0
  15. package/dist/geolocation/index.mjs.map +1 -0
  16. package/dist/index.d.mts +50 -0
  17. package/dist/index.d.ts +50 -0
  18. package/dist/index.js +2968 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/index.mjs +2948 -0
  21. package/dist/index.mjs.map +1 -0
  22. package/dist/kyc/core.d.mts +3 -0
  23. package/dist/kyc/core.d.ts +3 -0
  24. package/dist/kyc/core.js +773 -0
  25. package/dist/kyc/core.js.map +1 -0
  26. package/dist/kyc/core.mjs +771 -0
  27. package/dist/kyc/core.mjs.map +1 -0
  28. package/dist/kyc/index.d.mts +734 -0
  29. package/dist/kyc/index.d.ts +734 -0
  30. package/dist/kyc/index.js +773 -0
  31. package/dist/kyc/index.js.map +1 -0
  32. package/dist/kyc/index.mjs +771 -0
  33. package/dist/kyc/index.mjs.map +1 -0
  34. package/dist/react.d.mts +487 -0
  35. package/dist/react.d.ts +487 -0
  36. package/dist/react.js +1122 -0
  37. package/dist/react.js.map +1 -0
  38. package/dist/react.mjs +1102 -0
  39. package/dist/react.mjs.map +1 -0
  40. package/dist/risk-profile/index.d.mts +228 -0
  41. package/dist/risk-profile/index.d.ts +228 -0
  42. package/dist/risk-profile/index.js +548 -0
  43. package/dist/risk-profile/index.js.map +1 -0
  44. package/dist/risk-profile/index.mjs +546 -0
  45. package/dist/risk-profile/index.mjs.map +1 -0
  46. package/dist/types-Bnsnejor.d.mts +150 -0
  47. package/dist/types-Bnsnejor.d.ts +150 -0
  48. package/dist/types-CFupjwi8.d.ts +213 -0
  49. package/dist/types-CqOLbaXk.d.mts +213 -0
  50. 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