@sanctum-key/react-native-sdk 1.0.23 → 1.0.25
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/build/package.json +3 -1
- package/build/src/components/EnhancedCameraView.d.ts +7 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +88 -30
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/EnhancedCameraView.web.d.ts +5 -1
- package/build/src/components/EnhancedCameraView.web.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.web.js +80 -295
- package/build/src/components/EnhancedCameraView.web.js.map +1 -1
- package/build/src/components/KYCElements/CameraCapture.js +1 -1
- package/build/src/components/KYCElements/CameraCapture.js.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.js +38 -218
- package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.js +119 -26
- package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +512 -87
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.js +93 -51
- package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
- package/build/src/components/OverLay/type.d.ts +1 -0
- package/build/src/components/OverLay/type.d.ts.map +1 -1
- package/build/src/components/OverLay/type.js.map +1 -1
- package/build/src/components/WebBarcodeScanner.d.ts +9 -0
- package/build/src/components/WebBarcodeScanner.d.ts.map +1 -0
- package/build/src/components/WebBarcodeScanner.js +152 -0
- package/build/src/components/WebBarcodeScanner.js.map +1 -0
- package/build/src/config/KYCConfigs.js +2 -2
- package/build/src/config/KYCConfigs.js.map +1 -1
- package/build/src/config/region_mapping.js +14 -14
- package/build/src/config/region_mapping.js.map +1 -1
- package/build/src/config/region_mapping.json +0 -14
- package/build/src/modules/api/CardAuthentification.d.ts +2 -0
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +23 -40
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +7 -2
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +163 -57
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/types/KYC.types.d.ts +1 -0
- package/build/src/types/KYC.types.d.ts.map +1 -1
- package/build/src/types/KYC.types.js.map +1 -1
- package/build/src/utils/aamvaParser.d.ts +62 -0
- package/build/src/utils/aamvaParser.d.ts.map +1 -0
- package/build/src/utils/aamvaParser.js +91 -0
- package/build/src/utils/aamvaParser.js.map +1 -0
- package/build/src/utils/cropByObb.d.ts.map +1 -1
- package/build/src/utils/cropByObb.js +4 -3
- package/build/src/utils/cropByObb.js.map +1 -1
- package/build/src/web/WebKYCEntry.d.ts +1 -1
- package/build/src/web/WebKYCEntry.d.ts.map +1 -1
- package/build/src/web/WebKYCEntry.js +105 -239
- package/build/src/web/WebKYCEntry.js.map +1 -1
- package/package.json +3 -1
- package/src/components/EnhancedCameraView.tsx +116 -42
- package/src/components/EnhancedCameraView.web.tsx +98 -338
- package/src/components/KYCElements/CameraCapture.tsx +1 -1
- package/src/components/KYCElements/CountrySelection.tsx +60 -240
- package/src/components/KYCElements/CountrySelectionTemplate.tsx +148 -29
- package/src/components/KYCElements/IDCardCapture.tsx +675 -142
- package/src/components/KYCElements/VerificationProgressTemplate.tsx +112 -65
- package/src/components/OverLay/type.ts +1 -1
- package/src/components/WebBarcodeScanner.tsx +273 -0
- package/src/config/KYCConfigs.ts +2 -2
- package/src/config/region_mapping.json +0 -14
- package/src/config/region_mapping.ts +14 -14
- package/src/modules/api/CardAuthentification.ts +44 -58
- package/src/modules/api/KYCService.ts +236 -82
- package/src/types/KYC.types.ts +1 -0
- package/src/utils/aamvaParser.ts +102 -0
- package/src/utils/cropByObb.ts +5 -3
- package/src/web/WebKYCEntry.tsx +125 -301
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { GovernmentDocumentType, GovernmentDocumentTypeShorted, OrientationVideoResponse } from '../../types/KYC.types';
|
|
3
3
|
import { CheckTemplateTypeResponse } from '../../components/OverLay/type';
|
|
4
|
-
import { SessionResponse,
|
|
4
|
+
import { SessionResponse, VerificationSessionRequest } from './types';
|
|
5
5
|
import { KycEnvironment } from '../../types/env.types';
|
|
6
6
|
import { logger } from '../../utils/logger';
|
|
7
7
|
import { Platform } from 'react-native';
|
|
@@ -17,6 +17,22 @@ const appendFileToFormData = async (formData: FormData, key: string, uri: string
|
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
const documentTypeMap: Record<string, string> = {
|
|
21
|
+
passport: "passport",
|
|
22
|
+
driversLicense: "driving_licence",
|
|
23
|
+
healthInsuranceCard: "health_insurance_card",
|
|
24
|
+
residencePermit: "permanent_residence_card",
|
|
25
|
+
identityCard: "identity_card",
|
|
26
|
+
|
|
27
|
+
nationalId: "identity_card", // We saw this in your earlier logs!
|
|
28
|
+
national_id: "identity_card",
|
|
29
|
+
|
|
30
|
+
drivers_licence: "driving_licence", // Plural 'drivers' to singular 'driving'
|
|
31
|
+
drivers_license: "driving_licence", // American spelling 'license'
|
|
32
|
+
driving_license: "driving_licence", // American spelling 'license'
|
|
33
|
+
permanent_residence: "permanent_residence_card", // Missing the word 'card'
|
|
34
|
+
};
|
|
35
|
+
|
|
20
36
|
let cachedToken: string | null = null;
|
|
21
37
|
let tokenExpiryTime: number | null = null;
|
|
22
38
|
let activeAuthRequest: Promise<string> | null = null;
|
|
@@ -63,15 +79,9 @@ export class KYCService {
|
|
|
63
79
|
private faceServiceURL = 'https://face-infera.sanctumkey.com';
|
|
64
80
|
private textExtractionServiceURL = 'https://text-infera.sanctumkey.com';
|
|
65
81
|
private mrzServiceURL = 'https://mrz-infera.sanctumkey.com';
|
|
66
|
-
private barcodeServiceURL = 'https://
|
|
82
|
+
private barcodeServiceURL = 'https://face-infera.sanctumkey.com';
|
|
67
83
|
private orientationServiceURL = 'http://18.188.180.154:8080';
|
|
68
84
|
|
|
69
|
-
// private faceServiceURL = 'https://kyc-engine.transfergratis.net:8000';
|
|
70
|
-
// private textExtractionServiceURL = 'https://kyc-engine.transfergratis.net:8006';
|
|
71
|
-
// private mrzServiceURL = 'https://kyc-engine.transfergratis.net:8002';
|
|
72
|
-
// private barcodeServiceURL = 'https://kyc-engine.transfergratis.net:8000';
|
|
73
|
-
// private orientationServiceURL = 'http://18.188.180.154:8080';
|
|
74
|
-
|
|
75
85
|
constructor(baseURL: string, apiKey: string) {
|
|
76
86
|
this.baseURL = baseURL;
|
|
77
87
|
this.apiKey = apiKey;
|
|
@@ -288,7 +298,7 @@ export class KYCService {
|
|
|
288
298
|
docRegion: string;
|
|
289
299
|
token: string;
|
|
290
300
|
postfix: string;
|
|
291
|
-
issuingSubdivision?: string;
|
|
301
|
+
issuingSubdivision?: string;
|
|
292
302
|
},
|
|
293
303
|
env: KycEnvironment = 'PRODUCTION'
|
|
294
304
|
): Promise<any> {
|
|
@@ -304,7 +314,6 @@ export class KYCService {
|
|
|
304
314
|
};
|
|
305
315
|
}
|
|
306
316
|
|
|
307
|
-
// 🚨 Destructure issuingSubdivision
|
|
308
317
|
const { fileUri, docType, docRegion, token, postfix, issuingSubdivision } = params;
|
|
309
318
|
const formData = new FormData();
|
|
310
319
|
|
|
@@ -315,7 +324,6 @@ export class KYCService {
|
|
|
315
324
|
// Build the base URL
|
|
316
325
|
let url = `${this.mrzServiceURL}/get_template_version/?doc_type=${encodeURIComponent(docTypeShorted)}&doc_region=${encodeURIComponent(docRegion)}&postfix=${postfix}`;
|
|
317
326
|
|
|
318
|
-
// 🚨 Append the subdivision strictly for Canada/regional docs
|
|
319
327
|
if (issuingSubdivision) {
|
|
320
328
|
url += `&issuing_subdivision=${encodeURIComponent(issuingSubdivision)}`;
|
|
321
329
|
}
|
|
@@ -339,8 +347,8 @@ export class KYCService {
|
|
|
339
347
|
fileUri: string;
|
|
340
348
|
docType: string;
|
|
341
349
|
docRegion: string;
|
|
342
|
-
templatePath: string;
|
|
343
|
-
issuingSubdivision?: string;
|
|
350
|
+
templatePath: string;
|
|
351
|
+
issuingSubdivision?: string;
|
|
344
352
|
token: string;
|
|
345
353
|
postfix?: string
|
|
346
354
|
}): Promise<any> {
|
|
@@ -348,19 +356,18 @@ export class KYCService {
|
|
|
348
356
|
|
|
349
357
|
// 1. Prepare Form Data
|
|
350
358
|
const formData = new FormData();
|
|
351
|
-
// Prefixing with 'front' or 'back' as strictly required by the docs
|
|
352
359
|
await appendFileToFormData(formData, 'file', fileUri, `id_card_${postfix}.jpg`, 'image/jpeg');
|
|
353
360
|
|
|
354
|
-
|
|
355
|
-
|
|
361
|
+
let queryParams = `doc_type=${encodeURIComponent(docType)}&doc_region=${encodeURIComponent(docRegion)}&template_path=${encodeURIComponent(templatePath)}&postfix=${postfix}&cleaned=true`;
|
|
362
|
+
|
|
356
363
|
if (issuingSubdivision) {
|
|
357
364
|
queryParams += `&issuing_subdivision=${encodeURIComponent(issuingSubdivision)}`;
|
|
358
365
|
}
|
|
366
|
+
logger.log(`URL PARAMS: ${queryParams}`)
|
|
359
367
|
|
|
360
368
|
const url = `${this.textExtractionServiceURL}/extract_doc_information/?${queryParams}`;
|
|
361
369
|
|
|
362
370
|
try {
|
|
363
|
-
// 3. Start the Job
|
|
364
371
|
const startRes = await axios.post(url, formData, {
|
|
365
372
|
headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}` },
|
|
366
373
|
timeout: 60000,
|
|
@@ -388,14 +395,13 @@ export class KYCService {
|
|
|
388
395
|
|
|
389
396
|
if (statusData.status === 'completed') {
|
|
390
397
|
logger.log('Extraction completed successfully', JSON.stringify(truncateFields(statusData.result)));
|
|
391
|
-
return statusData.result;
|
|
398
|
+
return statusData.result;
|
|
392
399
|
}
|
|
393
400
|
|
|
394
401
|
if (statusData.status === 'failed') {
|
|
395
402
|
throw new Error(statusData.error || "Document extraction failed during processing.");
|
|
396
403
|
}
|
|
397
404
|
|
|
398
|
-
// If 'processing', loop continues...
|
|
399
405
|
}
|
|
400
406
|
|
|
401
407
|
throw new Error("Document extraction timed out waiting for results.");
|
|
@@ -407,8 +413,7 @@ export class KYCService {
|
|
|
407
413
|
}
|
|
408
414
|
}
|
|
409
415
|
|
|
410
|
-
|
|
411
|
-
async extractBarcode(params: { fileUri: string; token: string; postfix?: string }, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
|
|
416
|
+
async extractBarcode(params: { fileUri: string; token: string; docType: string; postfix?: string }, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
|
|
412
417
|
// SANDBOX mode
|
|
413
418
|
if (env === 'SANDBOX') {
|
|
414
419
|
console.log("SANDBOX mode: Skipping AI barcode extraction");
|
|
@@ -419,35 +424,85 @@ export class KYCService {
|
|
|
419
424
|
};
|
|
420
425
|
}
|
|
421
426
|
|
|
422
|
-
const { fileUri, token, postfix = 'back' } = params;
|
|
427
|
+
const { fileUri, token, docType, postfix = 'back' } = params;
|
|
428
|
+
logger.log(docType)
|
|
423
429
|
const formData = new FormData();
|
|
424
430
|
|
|
425
|
-
|
|
426
|
-
|
|
431
|
+
await appendFileToFormData(formData, 'file', fileUri, `id_${postfix}_photo.jpg`, 'image/jpeg');
|
|
432
|
+
|
|
433
|
+
const url = `${this.barcodeServiceURL}/decode_barcode/?doc_type=${encodeURIComponent(docType)}`;
|
|
427
434
|
|
|
428
|
-
const url = `${this.barcodeServiceURL}/decode_barcode/`;
|
|
429
435
|
const attempt = async () => {
|
|
430
436
|
try {
|
|
431
437
|
const res = await axios.post(url, formData, {
|
|
432
|
-
headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}
|
|
438
|
+
headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}` },
|
|
433
439
|
timeout: 60000,
|
|
434
440
|
});
|
|
435
|
-
|
|
436
|
-
|
|
441
|
+
|
|
442
|
+
logger.log('📦 Raw extractBarcode response:', JSON.stringify(res.data, null, 2));
|
|
443
|
+
|
|
444
|
+
|
|
437
445
|
return res.data;
|
|
438
446
|
} catch (e: any) {
|
|
439
|
-
throw new Error(e?.message || 'Erreur de détection du
|
|
447
|
+
throw new Error(e?.message || 'Erreur de détection du code-barres');
|
|
440
448
|
}
|
|
441
449
|
};
|
|
450
|
+
|
|
442
451
|
try {
|
|
443
452
|
return await attempt();
|
|
444
453
|
} catch (e) {
|
|
445
|
-
logger.error('Error extracting Barcode text:', JSON.stringify(e));
|
|
454
|
+
logger.error('❌ Error extracting Barcode text:', JSON.stringify(e));
|
|
446
455
|
throw e;
|
|
447
456
|
}
|
|
448
457
|
}
|
|
449
458
|
|
|
450
|
-
|
|
459
|
+
async formatBarcodeData(params: { textInput: string; token: string }, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
|
|
460
|
+
// SANDBOX mode
|
|
461
|
+
if (env === 'SANDBOX') {
|
|
462
|
+
console.log("SANDBOX mode: Skipping AI barcode formatting");
|
|
463
|
+
logger.log("SANDBOX mode: Returning mock formatted barcode response");
|
|
464
|
+
return {
|
|
465
|
+
barcode_type: "SANDBOX_MOCK",
|
|
466
|
+
first_name: "JOHN",
|
|
467
|
+
last_name: "DOE",
|
|
468
|
+
card_number: "000000000000"
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const { textInput, token } = params;
|
|
473
|
+
|
|
474
|
+
const formData = new URLSearchParams();
|
|
475
|
+
formData.append('text_input', textInput);
|
|
476
|
+
|
|
477
|
+
const url = `${this.barcodeServiceURL}/format_barcode_data/`;
|
|
478
|
+
|
|
479
|
+
const attempt = async () => {
|
|
480
|
+
try {
|
|
481
|
+
const res = await axios.post(url, formData.toString(), {
|
|
482
|
+
headers: {
|
|
483
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
484
|
+
'Authorization': `Bearer ${token}`
|
|
485
|
+
},
|
|
486
|
+
timeout: 60000,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
logger.log('📦 Raw formatBarcodeData response:', JSON.stringify(res.data, null, 2));
|
|
490
|
+
|
|
491
|
+
return res.data;
|
|
492
|
+
} catch (e: any) {
|
|
493
|
+
throw new Error(e?.message || 'Erreur de formatage du code-barres');
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
return await attempt();
|
|
499
|
+
} catch (e) {
|
|
500
|
+
logger.error('❌ Error formatting Barcode text:', JSON.stringify(e));
|
|
501
|
+
throw e;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
|
|
451
506
|
async extractMrzText(
|
|
452
507
|
params: {
|
|
453
508
|
fileUri: string;
|
|
@@ -500,8 +555,6 @@ export class KYCService {
|
|
|
500
555
|
let response;
|
|
501
556
|
|
|
502
557
|
if (Platform.OS === 'web') {
|
|
503
|
-
// 🚨 THE FIX: Completely bypass React Native Web's networking polyfills.
|
|
504
|
-
// We explicitly grab the browser's native APIs so the bundler cannot interfere.
|
|
505
558
|
const nativeFetch = window.fetch;
|
|
506
559
|
const NativeFormData = window.FormData;
|
|
507
560
|
|
|
@@ -642,57 +695,127 @@ export class KYCService {
|
|
|
642
695
|
}
|
|
643
696
|
}
|
|
644
697
|
|
|
698
|
+
|
|
645
699
|
async verificationSession(payload: VerificationSessionRequest): Promise<any> {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
throw new Error("Authentication failed: No API key or Token available.");
|
|
654
|
-
}
|
|
700
|
+
try {
|
|
701
|
+
const { session_id, step, data, templateId, action, apiKey } = payload;
|
|
702
|
+
const token = apiKey ? undefined : await authentification();
|
|
703
|
+
|
|
704
|
+
if (!apiKey && !token) {
|
|
705
|
+
throw new Error("Authentication failed: No API key or Token available.");
|
|
706
|
+
}
|
|
655
707
|
|
|
708
|
+
// 1. Original Shallow Clone (Preserves file paths perfectly)
|
|
709
|
+
const normalizedData = { ...data };
|
|
656
710
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
711
|
+
logger.log("🔍 [DocType Check] Raw incoming data.metadata.document_type:", normalizedData?.metadata?.document_type);
|
|
712
|
+
logger.log("🔍 [DocType Check] Raw incoming data.documentType:", normalizedData?.documentType);
|
|
713
|
+
|
|
714
|
+
// Enhanced fallback to guarantee we find the document type
|
|
715
|
+
const rawDocType =
|
|
716
|
+
normalizedData?.metadata?.document_type ||
|
|
717
|
+
normalizedData?.documentType ||
|
|
718
|
+
normalizedData?.documents?.documentType;
|
|
719
|
+
|
|
720
|
+
if (rawDocType) {
|
|
721
|
+
const originalDocType = rawDocType;
|
|
722
|
+
const mappedDocType = documentTypeMap[originalDocType] ?? originalDocType;
|
|
723
|
+
|
|
724
|
+
normalizedData.metadata = {
|
|
725
|
+
...normalizedData.metadata,
|
|
726
|
+
document_type: mappedDocType,
|
|
667
727
|
};
|
|
728
|
+
|
|
729
|
+
normalizedData.documentType = mappedDocType;
|
|
668
730
|
|
|
669
|
-
|
|
731
|
+
if (normalizedData.documents) {
|
|
732
|
+
normalizedData.documents = {
|
|
733
|
+
...normalizedData.documents,
|
|
734
|
+
documentType: mappedDocType
|
|
735
|
+
};
|
|
670
736
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
apiKey: apiKey ? '***HIDDEN***' : 'None', // Don't log the raw API key!
|
|
676
|
-
token: token ? '***HIDDEN***' : 'None',
|
|
677
|
-
payload: logPayload
|
|
678
|
-
}, null, 2));
|
|
737
|
+
const isSingleSided = ['passport', 'visa'].includes(mappedDocType.toLowerCase());
|
|
738
|
+
if (isSingleSided && !normalizedData.documents.back) {
|
|
739
|
+
normalizedData.documents.back = {};
|
|
740
|
+
}
|
|
679
741
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
742
|
+
if (normalizedData.documents.front?.mrz && typeof normalizedData.documents.front.mrz === 'string') {
|
|
743
|
+
try {
|
|
744
|
+
normalizedData.documents.front = {
|
|
745
|
+
...normalizedData.documents.front,
|
|
746
|
+
mrz: JSON.parse(normalizedData.documents.front.mrz)
|
|
747
|
+
};
|
|
748
|
+
} catch (e) {
|
|
749
|
+
logger.log("Warning: Could not parse front MRZ JSON");
|
|
685
750
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
logger.log('verificationSession res', JSON.stringify(truncateFields(res.data), null, 2));
|
|
689
|
-
return res.data;
|
|
751
|
+
}
|
|
690
752
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
753
|
+
if (normalizedData.documents.back?.mrz && typeof normalizedData.documents.back.mrz === 'string') {
|
|
754
|
+
try {
|
|
755
|
+
normalizedData.documents.back = {
|
|
756
|
+
...normalizedData.documents.back,
|
|
757
|
+
mrz: JSON.parse(normalizedData.documents.back.mrz)
|
|
758
|
+
};
|
|
759
|
+
} catch (e) {
|
|
760
|
+
logger.log("Warning: Could not parse back MRZ JSON");
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
694
764
|
}
|
|
765
|
+
|
|
766
|
+
const payloadData = {
|
|
767
|
+
action,
|
|
768
|
+
data: {
|
|
769
|
+
...normalizedData,
|
|
770
|
+
step,
|
|
771
|
+
templateId,
|
|
772
|
+
},
|
|
773
|
+
session_id,
|
|
774
|
+
timestamp: new Date().toISOString(),
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
const url = `${KYCConfig.getBackendUrl()}/verification/api/kyc/sessions/${session_id}/steps/${step}/`;
|
|
778
|
+
const logPayload = truncateFields({ payloadData, session_id, step });
|
|
779
|
+
|
|
780
|
+
logger.log(
|
|
781
|
+
"verificationSession payload",
|
|
782
|
+
JSON.stringify(
|
|
783
|
+
{
|
|
784
|
+
url,
|
|
785
|
+
apiKey: apiKey ? "***HIDDEN***" : "None",
|
|
786
|
+
token: token ? "***HIDDEN***" : "None",
|
|
787
|
+
payload: logPayload,
|
|
788
|
+
},
|
|
789
|
+
null,
|
|
790
|
+
2
|
|
791
|
+
)
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
const headers: Record<string, string> = {
|
|
795
|
+
"Content-Type": "application/json",
|
|
796
|
+
...(apiKey ? { Authorization: `ApiKey ${apiKey}` } : {}),
|
|
797
|
+
...(token && !apiKey ? { Authorization: `Bearer ${token}` } : {})
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
const res = await axios.post<SessionResponse>(url, payloadData, { headers });
|
|
801
|
+
|
|
802
|
+
logger.log(
|
|
803
|
+
"verificationSession res",
|
|
804
|
+
JSON.stringify(truncateFields(res.data), null, 2)
|
|
805
|
+
);
|
|
806
|
+
return res.data;
|
|
807
|
+
|
|
808
|
+
} catch (error: any) {
|
|
809
|
+
const parsedError = errorMessage(error);
|
|
810
|
+
const finalErrorMessage = parsedError || error?.message || "Unknown validation error";
|
|
811
|
+
|
|
812
|
+
logger.error(
|
|
813
|
+
"Error validating component:",
|
|
814
|
+
JSON.stringify(finalErrorMessage, null, 2)
|
|
815
|
+
);
|
|
816
|
+
throw new Error(typeof finalErrorMessage === 'string' ? finalErrorMessage : JSON.stringify(finalErrorMessage));
|
|
695
817
|
}
|
|
818
|
+
}
|
|
696
819
|
|
|
697
820
|
/** Send email verification code. POST /api/v1/accounts/email/send/ */
|
|
698
821
|
async sendEmailVerificationCode(
|
|
@@ -734,16 +857,49 @@ export class KYCService {
|
|
|
734
857
|
return res.data;
|
|
735
858
|
}
|
|
736
859
|
|
|
737
|
-
async getVerificationResult(session_id: string): Promise<
|
|
860
|
+
async getVerificationResult(session_id: string, apiKey?: string): Promise<any> {
|
|
738
861
|
try {
|
|
739
|
-
|
|
862
|
+
|
|
863
|
+
const token = apiKey ? undefined : await authentification();
|
|
864
|
+
|
|
740
865
|
const url = `${KYCConfig.getBackendUrl()}/verification/api/kyc/result/?session_id=${session_id}`;
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
866
|
+
|
|
867
|
+
const controller = new AbortController();
|
|
868
|
+
const timeoutId = setTimeout(() => {
|
|
869
|
+
logger.log("⏳ 8 seconds passed. Force-killing hanging Axios request...");
|
|
870
|
+
controller.abort();
|
|
871
|
+
}, 8000);
|
|
872
|
+
|
|
873
|
+
const headers: Record<string, string> = {
|
|
874
|
+
'Content-Type': 'application/json',
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
if (apiKey) {
|
|
878
|
+
headers["Authorization"] = `ApiKey ${apiKey}`;
|
|
879
|
+
} else if (token) {
|
|
880
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
logger.log("3. Firing Axios GET to:", url);
|
|
884
|
+
|
|
885
|
+
const res = await axios.get(url, {
|
|
886
|
+
headers,
|
|
887
|
+
signal: controller.signal
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
// Clear the timeout if the request succeeds!
|
|
891
|
+
clearTimeout(timeoutId);
|
|
892
|
+
|
|
744
893
|
return res.data;
|
|
745
|
-
|
|
746
|
-
|
|
894
|
+
|
|
895
|
+
} catch (error: any) {
|
|
896
|
+
// Catch the AbortController kill signal and format it as a timeout
|
|
897
|
+
if (axios.isCancel(error) || error.name === 'AbortError' || error.name === 'CanceledError') {
|
|
898
|
+
logger.error('Axios request was forcefully aborted due to timeout');
|
|
899
|
+
throw new Error('Timeout exceeded');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
logger.error('Error getting verification result:', JSON.stringify(errorMessage(error) || error.message, null, 2));
|
|
747
903
|
throw error;
|
|
748
904
|
}
|
|
749
905
|
}
|
|
@@ -852,7 +1008,6 @@ async validateBackTemplate(params: {
|
|
|
852
1008
|
|
|
853
1009
|
const { region, docType, frontTemplateName, backTemplateName, subRegion, token } = params;
|
|
854
1010
|
|
|
855
|
-
// 🚨 THE FIX: Format the payload as URL Query Parameters
|
|
856
1011
|
const queryParams = new URLSearchParams({
|
|
857
1012
|
region: region,
|
|
858
1013
|
doc_type: docType,
|
|
@@ -957,7 +1112,6 @@ export const authentification = async (): Promise<string> => {
|
|
|
957
1112
|
} catch (error: any) {
|
|
958
1113
|
cachedToken = null; // Clear bad cache
|
|
959
1114
|
|
|
960
|
-
// 🚨 FIX: Extract Keycloak's exact error message so it doesn't log as {}
|
|
961
1115
|
const safeErrorMessage =
|
|
962
1116
|
error?.response?.data?.error_description ||
|
|
963
1117
|
error?.response?.data?.error ||
|
package/src/types/KYC.types.ts
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export const parseAAMVABarcode = (rawBarcode: string) => {
|
|
2
|
+
// If it's a short 1D barcode, just return the number
|
|
3
|
+
if (!rawBarcode || rawBarcode.length < 50) {
|
|
4
|
+
return { card_number: rawBarcode, barcode_type: "1D" };
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const fields: Record<string, string> = {};
|
|
8
|
+
const additionalInfo: string[] = [];
|
|
9
|
+
|
|
10
|
+
// 1. Sanitize literal escaped newlines (just in case the SDK stringified them)
|
|
11
|
+
let normalized = rawBarcode.replace(/\\n/g, '\n').replace(/\\r/g, '\r');
|
|
12
|
+
|
|
13
|
+
// 2. Split by standard linebreaks AND AAMVA hidden control characters (FS, GS, RS)
|
|
14
|
+
const segments = normalized.split(/[\n\r\u001c\u001d\u001e]+/);
|
|
15
|
+
|
|
16
|
+
segments.forEach(segment => {
|
|
17
|
+
let clean = segment.trim();
|
|
18
|
+
|
|
19
|
+
// 3. Fix the Canadian/BC quirk: Subfile identifiers attached directly to the first data element
|
|
20
|
+
// e.g., "IDDAQ123456..." or "DLDAQ123456..."
|
|
21
|
+
if (clean.startsWith('DL') && clean.length > 5 && /^[A-Z]{3}/.test(clean.substring(2))) {
|
|
22
|
+
clean = clean.substring(2);
|
|
23
|
+
} else if (clean.startsWith('ID') && clean.length > 5 && /^[A-Z]{3}/.test(clean.substring(2))) {
|
|
24
|
+
clean = clean.substring(2);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 4. Forcefully extract the 3-letter code and its data
|
|
28
|
+
const match = clean.match(/^([A-Z]{3})(.+)/);
|
|
29
|
+
if (match) {
|
|
30
|
+
fields[match[1]] = match[2].trim();
|
|
31
|
+
} else if (clean.length > 0 && !clean.startsWith('@')) {
|
|
32
|
+
additionalInfo.push(clean);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Helper to format AAMVA dates (MMDDYYYY or YYYYMMDD) into YYYY-MM-DD
|
|
37
|
+
const formatDate = (rawDate: string | undefined) => {
|
|
38
|
+
if (!rawDate || rawDate.length !== 8) return "NONE";
|
|
39
|
+
|
|
40
|
+
const firstFour = parseInt(rawDate.substring(0, 4), 10);
|
|
41
|
+
// If the first 4 digits are a valid year, it's YYYYMMDD
|
|
42
|
+
if (firstFour > 1900 && firstFour < 2100) {
|
|
43
|
+
return `${rawDate.substring(0, 4)}-${rawDate.substring(4, 6)}-${rawDate.substring(6, 8)}`;
|
|
44
|
+
}
|
|
45
|
+
// Otherwise, it's MMDDYYYY
|
|
46
|
+
return `${rawDate.substring(4, 8)}-${rawDate.substring(0, 2)}-${rawDate.substring(2, 4)}`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Helper to safely fetch a field or its fallback code
|
|
50
|
+
const getVal = (code: string, fallbackCode?: string) => {
|
|
51
|
+
const val = fields[code] || (fallbackCode ? fields[fallbackCode] : undefined);
|
|
52
|
+
return (val && val.trim().length > 0) ? val.trim() : "NONE";
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Standardize gender codes
|
|
56
|
+
let gender = getVal("DBC");
|
|
57
|
+
if (gender === "1") gender = "M";
|
|
58
|
+
if (gender === "2") gender = "F";
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
barcode_type: normalized.includes("ANSI") ? "ANSI" : "UNKNOWN",
|
|
62
|
+
restriction_codes: getVal("DAR", "DCD"),
|
|
63
|
+
compliance_type: getVal("DDA"),
|
|
64
|
+
|
|
65
|
+
// --- Document Dates ---
|
|
66
|
+
issue_date: formatDate(fields["DBD"]),
|
|
67
|
+
expiry_date: formatDate(fields["DBA"]),
|
|
68
|
+
birth_date: formatDate(fields["DBB"]),
|
|
69
|
+
under_18_until: formatDate(fields["DBH"]),
|
|
70
|
+
under_21_until: formatDate(fields["DBI"]),
|
|
71
|
+
|
|
72
|
+
// --- Demographics ---
|
|
73
|
+
last_name: getVal("DCS", "DCA"),
|
|
74
|
+
first_name: getVal("DAC", "DCT"),
|
|
75
|
+
middle_name: getVal("DAD", "DCU"),
|
|
76
|
+
name_suffix: getVal("DCU"),
|
|
77
|
+
customer_full_name: getVal("DAA"),
|
|
78
|
+
gender: gender,
|
|
79
|
+
eye_color: getVal("DAY"),
|
|
80
|
+
hair_color: getVal("DAZ"),
|
|
81
|
+
height: getVal("DAU"),
|
|
82
|
+
weight: getVal("DAW", "DAX"),
|
|
83
|
+
|
|
84
|
+
// --- Location ---
|
|
85
|
+
street_address: getVal("DAG"),
|
|
86
|
+
street_address_2: getVal("DAH"),
|
|
87
|
+
city: getVal("DAI"),
|
|
88
|
+
province: getVal("DAJ"),
|
|
89
|
+
postal_code: getVal("DAK"),
|
|
90
|
+
country: getVal("DCG") !== "NONE" ? getVal("DCG") : "CAN",
|
|
91
|
+
|
|
92
|
+
// --- ID Numbers & Security ---
|
|
93
|
+
card_number: getVal("DAQ"),
|
|
94
|
+
document_discriminator: getVal("DCF"),
|
|
95
|
+
inventory_control_number: getVal("DCK", "DCB"),
|
|
96
|
+
|
|
97
|
+
// The raw dump in case you ever need an obscure field
|
|
98
|
+
raw_fields: fields,
|
|
99
|
+
|
|
100
|
+
additional_info: additionalInfo.length > 0 ? additionalInfo : ["NONE"]
|
|
101
|
+
};
|
|
102
|
+
};
|
package/src/utils/cropByObb.ts
CHANGED
|
@@ -147,11 +147,13 @@ async function normalizeOrientationAndGetDimensions(
|
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
// Native: re-encode to bake EXIF rotation into actual pixels
|
|
151
150
|
const result = await ImageManipulator.manipulateAsync(
|
|
152
151
|
uri,
|
|
153
|
-
[],
|
|
154
|
-
{
|
|
152
|
+
[],
|
|
153
|
+
{
|
|
154
|
+
compress: 1,
|
|
155
|
+
format: ImageManipulator.SaveFormat.JPEG
|
|
156
|
+
}
|
|
155
157
|
);
|
|
156
158
|
return { normalizedUri: result.uri, width: result.width, height: result.height };
|
|
157
159
|
}
|