@sanctum-key/react-native-sdk 1.0.51 → 1.0.52
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/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/xml_file_paths.xml.flat +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +2 -2
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +1 -1
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +1 -1
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +1 -1
- package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +1 -1
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +41 -41
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +52 -52
- package/build/package.json +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +73 -66
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/TemplateKYCExample.d.ts +2 -1
- package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
- package/build/src/components/TemplateKYCExample.js +2 -2
- package/build/src/components/TemplateKYCExample.js.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.d.ts +1 -0
- package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.js +20 -14
- package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
- package/build/src/config/KYCConfig.d.ts.map +1 -1
- package/build/src/config/KYCConfig.js +4 -2
- package/build/src/config/KYCConfig.js.map +1 -1
- package/build/src/hooks/useTemplateKYCFlow.d.ts +1 -0
- package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
- package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts +0 -23
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +46 -83
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +96 -38
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/modules/api/SelfieVerification.d.ts.map +1 -1
- package/build/src/modules/api/SelfieVerification.js +12 -19
- package/build/src/modules/api/SelfieVerification.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/IDCardCapture.tsx +192 -191
- package/src/components/TemplateKYCExample.tsx +3 -0
- package/src/components/TemplateKYCFlowRefactored.tsx +31 -25
- package/src/config/KYCConfig.ts +7 -5
- package/src/hooks/useTemplateKYCFlow.tsx +1 -0
- package/src/modules/api/CardAuthentification.ts +409 -445
- package/src/modules/api/KYCService.ts +599 -449
- package/src/modules/api/SelfieVerification.ts +22 -29
|
@@ -525,217 +525,218 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
525
525
|
}
|
|
526
526
|
};
|
|
527
527
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
528
|
+
const handleSilentCapture = async (result: { success: boolean; path?: string; error?: string; barcode?: unknown }) => {
|
|
529
|
+
if (isProcessingCaptureRef.current) return;
|
|
530
|
+
if (scanPhaseRef.current === 'barcode') return;
|
|
531
|
+
if (isAnalyzingRef.current) return;
|
|
532
|
+
|
|
533
|
+
// 🚨 SYNCHRONOUS LOCK: Block all subsequent camera frames immediately
|
|
534
|
+
isAnalyzingRef.current = true;
|
|
535
|
+
|
|
536
|
+
const side = currentSideRef.current;
|
|
537
|
+
const captured = capturedImagesRef.current;
|
|
538
|
+
const docType = selectedDocTypeRef.current;
|
|
539
|
+
const cData = countryDataRef.current;
|
|
540
|
+
|
|
541
|
+
if (result.success && result.path) {
|
|
542
|
+
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
|
|
543
|
+
const safeCapturePath = Platform.OS === 'ios' && !result.path.startsWith('file://') ? `file://${result.path}` : result.path;
|
|
544
|
+
|
|
545
|
+
let templatePath = captured[side]?.templatePath || '';
|
|
546
|
+
let templateBbox: IBbox | undefined;
|
|
547
|
+
let templateResponse: any;
|
|
538
548
|
|
|
539
|
-
if (
|
|
540
|
-
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing:
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
let templateBbox: IBbox | undefined;
|
|
545
|
-
let templateResponse: any;
|
|
549
|
+
if (!docType) {
|
|
550
|
+
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' }));
|
|
551
|
+
isAnalyzingRef.current = false; // 🚨 UNLOCK
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
546
554
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
555
|
+
const issuingSubdivision = docType.region !== 'root' ? docType.region : undefined;
|
|
556
|
+
|
|
557
|
+
const watchdog = setTimeout(() => {
|
|
558
|
+
if (isAnalyzingRef.current) {
|
|
559
|
+
isAnalyzingRef.current = false; // 🚨 UNLOCK inside watchdog
|
|
560
|
+
setSilentCaptureResult((prev) => ({
|
|
561
|
+
...prev,
|
|
562
|
+
isAnalyzing: false,
|
|
563
|
+
success: false,
|
|
564
|
+
error: state.currentLanguage === 'en' ? 'Taking longer than expected. Hold steady.' : 'Traitement long. Veuillez rester immobile.'
|
|
565
|
+
}));
|
|
550
566
|
}
|
|
567
|
+
}, 20000);
|
|
551
568
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
try { const crop = await cropByObb(safeCapturePath, (templateType as any).card_obb); templateBbox = crop.bbox; } catch {}
|
|
569
|
+
try {
|
|
570
|
+
if (!templatePath) {
|
|
571
|
+
const templateType = await checkTemplateType({
|
|
572
|
+
path: safeCapturePath,
|
|
573
|
+
docType: docType.type as GovernmentDocumentType,
|
|
574
|
+
docRegion: cData?.code || "",
|
|
575
|
+
postfix: side,
|
|
576
|
+
issuingSubdivision: issuingSubdivision
|
|
577
|
+
}, env);
|
|
578
|
+
|
|
579
|
+
templateResponse = templateType;
|
|
580
|
+
if (templateType.template_path) templatePath = templateType.template_path;
|
|
581
|
+
|
|
582
|
+
if (templateType.card_obb) {
|
|
583
|
+
const obbConfidence = getObbConfidence((templateType as any).card_obb);
|
|
584
|
+
if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
|
|
585
|
+
clearTimeout(watchdog);
|
|
586
|
+
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: t('kyc.idCardCapture.cardNotFullyInFrame') }));
|
|
587
|
+
isAnalyzingRef.current = false; // 🚨 UNLOCK
|
|
588
|
+
return;
|
|
573
589
|
}
|
|
590
|
+
try {
|
|
591
|
+
const crop = await cropByObb(safeCapturePath, (templateType as any).card_obb);
|
|
592
|
+
templateBbox = crop.bbox;
|
|
593
|
+
} catch {}
|
|
574
594
|
}
|
|
595
|
+
}
|
|
575
596
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
597
|
+
sdkLogger.info(`Template check passed for ${side}`, { templatePath });
|
|
598
|
+
|
|
599
|
+
const extractedCountryKey = templatePath ? templatePath.split('/')[0] : (ISO_TO_COUNTRY_NAME[cData?.code || ''] || 'root');
|
|
600
|
+
const regionMappings = getCurrentSideVerification(side, extractedCountryKey, cData);
|
|
601
|
+
|
|
602
|
+
let verificationRes: any;
|
|
603
|
+
if (side === 'front') {
|
|
604
|
+
const matchedAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'front');
|
|
605
|
+
const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
|
|
606
|
+
|
|
607
|
+
verificationRes = await frontVerification({
|
|
608
|
+
path: safeCapturePath,
|
|
609
|
+
regionMapping: { authMethod: matchedAuthMethod ? [matchedAuthMethod] : [], mrzTypes: mrzType ? [mrzType] : [] },
|
|
610
|
+
selectedDocumentType: GovernmentDocumentTypeShorted[docType.type as keyof typeof GovernmentDocumentTypeShorted] || '',
|
|
611
|
+
code: cData?.code || '',
|
|
612
|
+
currentSide: side,
|
|
613
|
+
templatePath,
|
|
614
|
+
mrzType,
|
|
615
|
+
issuingSubdivision: issuingSubdivision,
|
|
616
|
+
clientSideBarcodeData: silentCaptureResult.barcode
|
|
617
|
+
}, env);
|
|
618
|
+
} else {
|
|
619
|
+
const matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
|
|
620
|
+
const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
|
|
621
|
+
const frontTemplatePath = captured['front']?.templatePath || '';
|
|
622
|
+
|
|
623
|
+
verificationRes = await backVerification({
|
|
624
|
+
path: safeCapturePath,
|
|
625
|
+
regionMapping: { authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : [], mrzTypes: backMrzType ? [backMrzType] : [] },
|
|
626
|
+
selectedDocumentType: GovernmentDocumentTypeShorted[docType.type as keyof typeof GovernmentDocumentTypeShorted] || '',
|
|
627
|
+
code: cData?.code || '',
|
|
628
|
+
currentSide: side,
|
|
629
|
+
templatePath,
|
|
630
|
+
mrzType: backMrzType,
|
|
631
|
+
templateResponse,
|
|
632
|
+
issuingSubdivision: issuingSubdivision,
|
|
633
|
+
frontTemplatePath: frontTemplatePath
|
|
634
|
+
}, env);
|
|
635
|
+
}
|
|
591
636
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
const verifiedResult: ISilentCaptureResult & { region?: string } = { path: safeCapturePath, templatePath: templatePath, bbox: bbox, success: true, mrz: JSON.stringify(parsedDictionary), barcode: barcodeData, isAnalyzing: false, country: cData?.code, documentType: docType.type, region: docType.region !== 'root' ? docType.region : undefined };
|
|
609
|
-
|
|
610
|
-
clearTimeout(watchdog); setSilentCaptureResult(verifiedResult);
|
|
611
|
-
if (bbox) setBboxBySide(prev => ({ ...prev, ['back']: bbox as IBbox }));
|
|
612
|
-
await autoCapture(safeCapturePath!, verifiedResult);
|
|
613
|
-
return;
|
|
637
|
+
const bbox = verificationRes?.bbox || templateBbox;
|
|
638
|
+
const verifiedMrz = verificationRes?.mrz ? JSON.stringify(verificationRes.mrz) : "";
|
|
639
|
+
|
|
640
|
+
if (isBarcodeRequired(side, cData, docType)) {
|
|
641
|
+
const barcodeData = scannedBarcodeRef.current;
|
|
642
|
+
const isHIC = docType.type === 'health_insurance_card';
|
|
643
|
+
const isValidScan = typeof barcodeData === 'string' && ((isHIC && barcodeData.length > 0) || barcodeData.length > 50);
|
|
644
|
+
|
|
645
|
+
if (isValidScan) {
|
|
646
|
+
let parsedDictionary;
|
|
647
|
+
if (isHIC && barcodeData.length < 50) {
|
|
648
|
+
parsedDictionary = { data: { card_number: barcodeData, barcode_type: "1D" } };
|
|
649
|
+
} else {
|
|
650
|
+
const token = await authentification();
|
|
651
|
+
parsedDictionary = await kycService.formatBarcodeData({ textInput: barcodeData, token }, env);
|
|
614
652
|
}
|
|
615
|
-
|
|
653
|
+
const verifiedResult: ISilentCaptureResult & { region?: string } = {
|
|
654
|
+
path: safeCapturePath, templatePath: templatePath, bbox: bbox, success: true,
|
|
655
|
+
mrz: JSON.stringify(parsedDictionary), barcode: barcodeData, isAnalyzing: false,
|
|
656
|
+
country: cData?.code, documentType: docType.type, region: docType.region !== 'root' ? docType.region : undefined
|
|
657
|
+
};
|
|
658
|
+
|
|
616
659
|
clearTimeout(watchdog);
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
660
|
+
setSilentCaptureResult(verifiedResult);
|
|
661
|
+
if (bbox) setBboxBySide(prev => ({ ...prev, ['back']: bbox as IBbox }));
|
|
662
|
+
|
|
663
|
+
isAnalyzingRef.current = false; // 🚨 UNLOCK on success
|
|
664
|
+
await autoCapture(safeCapturePath!, verifiedResult);
|
|
620
665
|
return;
|
|
621
666
|
}
|
|
622
667
|
|
|
623
|
-
|
|
668
|
+
sdkLogger.info(`Verification passed. Waiting for barcode scan phase.`);
|
|
624
669
|
clearTimeout(watchdog);
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
670
|
+
setPendingCAImage({ path: safeCapturePath!, templateResponse, templatePath, bbox });
|
|
671
|
+
setScanPhase('barcode');
|
|
672
|
+
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
|
|
673
|
+
isAnalyzingRef.current = false; // 🚨 UNLOCK before phase shift
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
628
676
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
rawMessage.includes('token') ||
|
|
641
|
-
rawMessage.includes('unauthenticated')
|
|
642
|
-
) {
|
|
643
|
-
sdkLogger.error(`Secure API Session Expired during AI extraction`);
|
|
644
|
-
setShowCamera(false);
|
|
645
|
-
setIsProcessingCapture(false);
|
|
646
|
-
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: '' }));
|
|
647
|
-
|
|
648
|
-
// Use React Native's native Alert to wait for the user's tap
|
|
649
|
-
Alert.alert(
|
|
650
|
-
state.currentLanguage === 'en' ? 'Session Expired' : 'Session Expirée',
|
|
651
|
-
state.currentLanguage === 'en'
|
|
652
|
-
? 'Your secure session has expired. Please restart the process.'
|
|
653
|
-
: 'Votre session sécurisée a expiré. Veuillez recommencer.',
|
|
654
|
-
[
|
|
655
|
-
{
|
|
656
|
-
text: state.currentLanguage === 'en' ? 'Restart' : 'Recommencer',
|
|
657
|
-
onPress: () => handleExitKYCFlow() // Redirects ONLY when they click the button
|
|
658
|
-
}
|
|
659
|
-
],
|
|
660
|
-
{ cancelable: false } // Forces them to click the button
|
|
661
|
-
);
|
|
662
|
-
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
677
|
+
const verifiedResult: ISilentCaptureResult & { region?: string } = {
|
|
678
|
+
path: safeCapturePath, templatePath, bbox, success: true, mrz: verifiedMrz, isAnalyzing: false,
|
|
679
|
+
country: cData?.code, documentType: docType.type, region: issuingSubdivision
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
clearTimeout(watchdog);
|
|
683
|
+
setSilentCaptureResult(verifiedResult);
|
|
684
|
+
if (bbox) setBboxBySide(prev => ({ ...prev, [side]: bbox }));
|
|
685
|
+
|
|
686
|
+
isAnalyzingRef.current = false; // 🚨 UNLOCK on final success
|
|
687
|
+
await autoCapture(safeCapturePath, verifiedResult);
|
|
665
688
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
)
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
// 2. Document/Country Mismatch
|
|
681
|
-
// Catches: 'pays sélectionné', 'LPIPS'
|
|
682
|
-
else if (rawMessage.includes('pays sélectionné')) {
|
|
683
|
-
hintMessage = state.currentLanguage === 'en'
|
|
684
|
-
? 'This document does not match the selected country.'
|
|
685
|
-
: 'Ce document ne correspond pas au pays sélectionné.';
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// 3. Front/Back Document Mismatch
|
|
689
|
-
// Catches: 'ne correspond pas au recto', 'document différent'
|
|
690
|
-
else if (rawMessage.includes('ne correspond pas au recto') || rawMessage.includes('document différent')) {
|
|
691
|
-
hintMessage = state.currentLanguage === 'en'
|
|
692
|
-
? 'Please scan the back of the exact same ID.'
|
|
693
|
-
: 'Veuillez scanner le verso de la même pièce d\'identité.';
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// 4. Data Extraction Failures (OCR & MRZ)
|
|
697
|
-
// Catches: 'impossible de lire', 'impossible d'extraire', 'mrz illisible', 'lecture mrz invalide'
|
|
698
|
-
else if (
|
|
699
|
-
rawMessage.includes('impossible de lire') ||
|
|
700
|
-
rawMessage.includes('impossible d\'extraire') ||
|
|
701
|
-
rawMessage.includes('mrz') ||
|
|
702
|
-
rawMessage.includes('extraction') ||
|
|
703
|
-
rawMessage.includes('illisible') ||
|
|
704
|
-
rawMessage.includes('invalide')
|
|
705
|
-
) {
|
|
706
|
-
hintMessage = state.currentLanguage === 'en'
|
|
707
|
-
? 'Cannot read document text. Avoid glare, improve lighting, and hold steady.'
|
|
708
|
-
: 'Impossible de lire le texte. Évitez les reflets, améliorez l\'éclairage et restez immobile.';
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// 5. Server Infrastructure & Downtime
|
|
712
|
-
// Catches: Cloudflare tunnel errors, 502/503 Gateway timeouts
|
|
713
|
-
else if (
|
|
714
|
-
rawMessage.includes('cloudflare') ||
|
|
715
|
-
rawMessage.includes('tunnel') ||
|
|
716
|
-
rawMessage.includes('502') ||
|
|
717
|
-
rawMessage.includes('503')
|
|
718
|
-
) {
|
|
719
|
-
hintMessage = state.currentLanguage === 'en'
|
|
720
|
-
? 'Server is temporarily offline. Please try again in a few minutes.'
|
|
721
|
-
: 'Le serveur est temporairement hors ligne. Veuillez réessayer dans quelques minutes.';
|
|
722
|
-
}
|
|
689
|
+
} catch (error: any) {
|
|
690
|
+
clearTimeout(watchdog);
|
|
691
|
+
const rawMessage = (typeof error === 'string' ? error : (error?.message || '')).toLowerCase();
|
|
692
|
+
const statusCode = error?.response?.status || error?.status;
|
|
693
|
+
|
|
694
|
+
if (
|
|
695
|
+
statusCode === 401 || statusCode === 403 ||
|
|
696
|
+
rawMessage.includes('401') || rawMessage.includes('unauthorized') ||
|
|
697
|
+
rawMessage.includes('token') || rawMessage.includes('unauthenticated')
|
|
698
|
+
) {
|
|
699
|
+
sdkLogger.error(`Secure API Session Expired during AI extraction`);
|
|
700
|
+
setShowCamera(false);
|
|
701
|
+
setIsProcessingCapture(false);
|
|
702
|
+
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: '' }));
|
|
723
703
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
704
|
+
Alert.alert(
|
|
705
|
+
state.currentLanguage === 'en' ? 'Session Expired' : 'Session Expirée',
|
|
706
|
+
state.currentLanguage === 'en' ? 'Your secure session has expired. Please restart the process.' : 'Votre session sécurisée a expiré. Veuillez recommencer.',
|
|
707
|
+
[{ text: state.currentLanguage === 'en' ? 'Restart' : 'Recommencer', onPress: () => handleExitKYCFlow() }],
|
|
708
|
+
{ cancelable: false }
|
|
709
|
+
);
|
|
710
|
+
isAnalyzingRef.current = false; // 🚨 UNLOCK on session expiry
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
let hintMessage = '';
|
|
715
|
+
if (rawMessage.includes('card_not_fully_in_frame') || rawMessage.includes('non entièrement visible') || rawMessage.includes('aucun visage détecté') || rawMessage.includes('stabiliser la caméra')) {
|
|
716
|
+
hintMessage = state.currentLanguage === 'en' ? 'Ensure the entire card and your face are clearly inside the frame.' : 'Assurez-vous que toute la carte et le visage sont clairement dans le cadre.';
|
|
717
|
+
} else if (rawMessage.includes('pays sélectionné')) {
|
|
718
|
+
hintMessage = state.currentLanguage === 'en' ? 'This document does not match the selected country.' : 'Ce document ne correspond pas au pays sélectionné.';
|
|
719
|
+
} else if (rawMessage.includes('ne correspond pas au recto') || rawMessage.includes('document différent')) {
|
|
720
|
+
hintMessage = state.currentLanguage === 'en' ? 'Please scan the back of the exact same ID.' : 'Veuillez scanner le verso de la même pièce d\'identité.';
|
|
721
|
+
} else if (rawMessage.includes('impossible de lire') || rawMessage.includes('impossible d\'extraire') || rawMessage.includes('mrz') || rawMessage.includes('extraction') || rawMessage.includes('illisible') || rawMessage.includes('invalide')) {
|
|
722
|
+
hintMessage = state.currentLanguage === 'en' ? 'Cannot read document text. Avoid glare, improve lighting, and hold steady.' : 'Impossible de lire le texte. Évitez les reflets, améliorez l\'éclairage et restez immobile.';
|
|
723
|
+
} else if (rawMessage.includes('cloudflare') || rawMessage.includes('tunnel') || rawMessage.includes('502') || rawMessage.includes('503')) {
|
|
724
|
+
hintMessage = state.currentLanguage === 'en' ? 'Server is temporarily offline. Please try again in a few minutes.' : 'Le serveur est temporairement hors ligne. Veuillez réessayer dans quelques minutes.';
|
|
725
|
+
} else {
|
|
726
|
+
const isNetworkError = error?.isAxiosError || rawMessage === 'network error' || error?.code === 'econnaborted' || rawMessage.includes('network');
|
|
727
|
+
if (isNetworkError) {
|
|
728
|
+
hintMessage = state.currentLanguage === 'en' ? 'Poor connection. Please check your internet.' : 'Connexion faible. Veuillez vérifier votre internet.';
|
|
734
729
|
}
|
|
735
|
-
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: hintMessage }));
|
|
736
730
|
}
|
|
731
|
+
|
|
732
|
+
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: hintMessage }));
|
|
733
|
+
isAnalyzingRef.current = false; // 🚨 UNLOCK at end of catch block
|
|
737
734
|
}
|
|
738
|
-
}
|
|
735
|
+
} else {
|
|
736
|
+
// 🚨 UNLOCK if the camera frame was invalid or empty
|
|
737
|
+
isAnalyzingRef.current = false;
|
|
738
|
+
}
|
|
739
|
+
};
|
|
739
740
|
|
|
740
741
|
useEffect(() => {
|
|
741
742
|
if (scanPhase === 'barcode' && pendingCAImage && silentCaptureResult.barcode) {
|
|
@@ -48,6 +48,7 @@ export interface TemplateKYCExampleProps {
|
|
|
48
48
|
onError: (error: string) => void;
|
|
49
49
|
language: string;
|
|
50
50
|
API_KEY?: string;
|
|
51
|
+
TEST_API_KEY?: string;
|
|
51
52
|
templateId?: string;
|
|
52
53
|
template?: KYCTemplate;
|
|
53
54
|
env?: KycEnvironment;
|
|
@@ -66,6 +67,7 @@ export function TemplateKYCExample({
|
|
|
66
67
|
onError,
|
|
67
68
|
language,
|
|
68
69
|
API_KEY,
|
|
70
|
+
TEST_API_KEY,
|
|
69
71
|
templateId,
|
|
70
72
|
template,
|
|
71
73
|
env = 'PRODUCTION',
|
|
@@ -155,6 +157,7 @@ export function TemplateKYCExample({
|
|
|
155
157
|
onCancel={handleCancel}
|
|
156
158
|
language={language}
|
|
157
159
|
API_KEY={API_KEY}
|
|
160
|
+
TEST_API_KEY={TEST_API_KEY}
|
|
158
161
|
env={env}
|
|
159
162
|
serverEnv={serverEnv}
|
|
160
163
|
existingSessionId={existingSessionId}
|
|
@@ -4,7 +4,6 @@ import { KYCTemplate, VerificationState } from '../types/KYC.types';
|
|
|
4
4
|
import { TemplateKYCFlowProvider, useTemplateKYCFlowContext } from '../hooks/useTemplateKYCFlow';
|
|
5
5
|
import { useI18n } from '../hooks/useI18n';
|
|
6
6
|
import { showAlert } from '../utils/platformAlert';
|
|
7
|
-
|
|
8
7
|
import { IDCardCapture } from './KYCElements/IDCardCapture';
|
|
9
8
|
import { SelfieCaptureTemplate } from './KYCElements/SelfieCaptureTemplate';
|
|
10
9
|
import { FileUploadTemplate } from './KYCElements/FileUploadTemplate';
|
|
@@ -21,18 +20,19 @@ import { PersonalInformationTemplate } from './KYCElements/PersonalInformationTe
|
|
|
21
20
|
import { AdditionalDocumentsTemplate } from './KYCElements/AdditionalDocumentsTemplate';
|
|
22
21
|
import { KycEnvironment, BackendEnvironment } from '../types/env.types';
|
|
23
22
|
import KYCConfig from '../config/KYCConfig';
|
|
24
|
-
import { sdkLogger } from '../utils/logger';
|
|
23
|
+
import { logger, sdkLogger } from '../utils/logger';
|
|
25
24
|
|
|
26
25
|
interface TemplateKYCFlowProps {
|
|
27
|
-
template?: KYCTemplate;
|
|
28
|
-
templateId?: string;
|
|
26
|
+
template?: KYCTemplate;
|
|
27
|
+
templateId?: string;
|
|
29
28
|
onComplete?: (data: VerificationState) => void;
|
|
30
29
|
onError?: (error: string) => void;
|
|
31
30
|
language?: string;
|
|
32
31
|
onCancel?: () => void;
|
|
33
|
-
API_KEY?: string;
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
API_KEY?: string;
|
|
33
|
+
TEST_API_KEY?: string;
|
|
34
|
+
env?: KycEnvironment;
|
|
35
|
+
serverEnv?: BackendEnvironment;
|
|
36
36
|
existingSessionId?: string;
|
|
37
37
|
initialComponentIndex?: number;
|
|
38
38
|
initialCountryResume?: { code: string; documentType: string; region?: string };
|
|
@@ -49,6 +49,7 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
49
49
|
language = 'fr',
|
|
50
50
|
onCancel,
|
|
51
51
|
API_KEY,
|
|
52
|
+
TEST_API_KEY,
|
|
52
53
|
env = 'PRODUCTION',
|
|
53
54
|
serverEnv,
|
|
54
55
|
existingSessionId,
|
|
@@ -61,12 +62,14 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
61
62
|
const { t } = useI18n();
|
|
62
63
|
const { template: loadedTemplate, isLoading, error, loadTemplate } = useTemplateLoader();
|
|
63
64
|
|
|
65
|
+
// 🚨 1. Determine the active API key based on the environment
|
|
66
|
+
const activeApiKey = env === 'PRODUCTION' ? API_KEY : (TEST_API_KEY || API_KEY);
|
|
67
|
+
|
|
64
68
|
useEffect(() => {
|
|
65
|
-
if (
|
|
66
|
-
if (sdkLogger.setApiKey) sdkLogger.setApiKey(
|
|
69
|
+
if (activeApiKey) {
|
|
70
|
+
if (sdkLogger.setApiKey) sdkLogger.setApiKey(activeApiKey);
|
|
67
71
|
}
|
|
68
|
-
|
|
69
|
-
}, [env, serverEnv, existingSessionId, API_KEY]);
|
|
72
|
+
}, [env, serverEnv, existingSessionId, activeApiKey]);
|
|
70
73
|
|
|
71
74
|
useEffect(() => {
|
|
72
75
|
if (serverEnv !== undefined) {
|
|
@@ -77,19 +80,20 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
77
80
|
useEffect(() => {
|
|
78
81
|
if (!providedTemplate && !templateId) {
|
|
79
82
|
const errorMsg = 'Either template or templateId must be provided';
|
|
80
|
-
|
|
83
|
+
logger.error("Template Loading Failed: Missing Props", { errorMsg });
|
|
81
84
|
onError?.(errorMsg);
|
|
82
85
|
return;
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
if (templateId && !
|
|
86
|
-
|
|
88
|
+
if (templateId && !activeApiKey) {
|
|
89
|
+
logger.log(`Fetching template ${templateId} via token authentication`);
|
|
87
90
|
loadTemplate(templateId);
|
|
88
|
-
} else if (templateId &&
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
} else if (templateId && activeApiKey) {
|
|
92
|
+
logger.log(`Fetching template ${templateId} via API Key`);
|
|
93
|
+
// 🚨 2. Use the resolved active key here
|
|
94
|
+
loadTemplate(templateId, activeApiKey);
|
|
91
95
|
}
|
|
92
|
-
}, [templateId,
|
|
96
|
+
}, [templateId, activeApiKey, providedTemplate, loadTemplate, onError]);
|
|
93
97
|
|
|
94
98
|
useEffect(() => {
|
|
95
99
|
if (error) {
|
|
@@ -103,8 +107,8 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
103
107
|
t('kyc.cancelConfirmation.title'),
|
|
104
108
|
t('kyc.cancelConfirmation.message'),
|
|
105
109
|
[
|
|
106
|
-
{
|
|
107
|
-
text: t('kyc.cancelConfirmation.cancelButton'),
|
|
110
|
+
{
|
|
111
|
+
text: t('kyc.cancelConfirmation.cancelButton'),
|
|
108
112
|
style: 'cancel',
|
|
109
113
|
},
|
|
110
114
|
{
|
|
@@ -140,11 +144,11 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
140
144
|
return (
|
|
141
145
|
<View style={styles.errorContainer}>
|
|
142
146
|
<Text style={styles.errorText}>{error}</Text>
|
|
143
|
-
<TouchableOpacity
|
|
144
|
-
style={styles.retryButton}
|
|
147
|
+
<TouchableOpacity
|
|
148
|
+
style={styles.retryButton}
|
|
145
149
|
onPress={() => {
|
|
146
150
|
sdkLogger.info("User retrying template fetch");
|
|
147
|
-
loadTemplate(templateId,
|
|
151
|
+
loadTemplate(templateId, activeApiKey);
|
|
148
152
|
}}
|
|
149
153
|
>
|
|
150
154
|
<Text style={styles.retryButtonText}>{t('common.retry') || 'Retry'}</Text>
|
|
@@ -163,7 +167,6 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
163
167
|
);
|
|
164
168
|
}
|
|
165
169
|
|
|
166
|
-
|
|
167
170
|
return (
|
|
168
171
|
<TemplateKYCFlowProvider
|
|
169
172
|
template={template}
|
|
@@ -179,7 +182,10 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
179
182
|
}}
|
|
180
183
|
onCancel={OnCancel}
|
|
181
184
|
initialLanguage={language}
|
|
182
|
-
|
|
185
|
+
// 🚨 3. Pass the resolved active API key down to the provider!
|
|
186
|
+
// The context/hooks will automatically use this key.
|
|
187
|
+
apiKey={activeApiKey}
|
|
188
|
+
testApiKey={TEST_API_KEY}
|
|
183
189
|
env={env}
|
|
184
190
|
existingSessionId={existingSessionId}
|
|
185
191
|
initialComponentIndex={initialComponentIndex}
|
package/src/config/KYCConfig.ts
CHANGED
|
@@ -4,10 +4,11 @@ class KYCConfig {
|
|
|
4
4
|
private static instance: KYCConfig;
|
|
5
5
|
private backendEnvironment: BackendEnvironment = 'PRODUCTION';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// 🚨 Use a string record to safely handle 'TEST' or 'SANDBOX' aliases
|
|
8
|
+
private backendUrls: Record<string, string> = {
|
|
8
9
|
PRODUCTION: 'https://service.sanctumkey.com/api/v1',
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
TEST: 'https://api.sanctumkey.com/api/v1',
|
|
11
|
+
SANDBOX: 'https://kyc-backend.transfergratis.net/api/v1',
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
private constructor() { }
|
|
@@ -24,7 +25,8 @@ class KYCConfig {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
public getBackendUrl(): string {
|
|
27
|
-
|
|
28
|
+
// Fallback to PRODUCTION if an invalid env string is somehow passed
|
|
29
|
+
return this.backendUrls[this.backendEnvironment] || this.backendUrls['PRODUCTION'];
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
public getBackendEnvironment(): BackendEnvironment {
|
|
@@ -32,4 +34,4 @@ class KYCConfig {
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
export default KYCConfig.getInstance();
|
|
37
|
+
export default KYCConfig.getInstance();
|
|
@@ -33,6 +33,7 @@ interface TemplateKYCFlowProviderProps {
|
|
|
33
33
|
onCancel?: () => void;
|
|
34
34
|
initialLanguage?: string;
|
|
35
35
|
apiKey?: string;
|
|
36
|
+
testApiKey?: string;
|
|
36
37
|
env?: KycEnvironment;
|
|
37
38
|
existingSessionId?: string;
|
|
38
39
|
/** Index in template.components where to resume (0-based). Simple to store in template table. */
|