@sanctum-key/react-native-sdk 1.0.27 → 1.0.29
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 +1 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +45 -20
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +186 -50
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/OverLay/type.d.ts +3 -1
- 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/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 +2 -2
- package/build/src/components/TemplateKYCFlowRefactored.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 +31 -17
- package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +8 -0
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +30 -0
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/package.json +1 -1
- package/src/components/EnhancedCameraView.tsx +60 -27
- package/src/components/KYCElements/IDCardCapture.tsx +236 -80
- package/src/components/OverLay/type.ts +5 -1
- package/src/components/TemplateKYCExample.tsx +7 -3
- package/src/components/TemplateKYCFlowRefactored.tsx +3 -0
- package/src/hooks/useTemplateKYCFlow.tsx +65 -34
- package/src/modules/api/KYCService.ts +41 -0
|
@@ -69,6 +69,64 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
69
69
|
|
|
70
70
|
const scannedBarcodeRef = useRef<string | null>(null);
|
|
71
71
|
|
|
72
|
+
// 🚨 NEW: State for the expiring link code
|
|
73
|
+
const [generatedCode, setGeneratedCode] = useState<string | null>(null);
|
|
74
|
+
const [isGeneratingCode, setIsGeneratingCode] = useState(false);
|
|
75
|
+
const [codeTimeLeft, setCodeTimeLeft] = useState(0);
|
|
76
|
+
|
|
77
|
+
// 🚨 NEW: Countdown Timer Effect
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (codeTimeLeft <= 0) {
|
|
80
|
+
if (generatedCode) setGeneratedCode(null); // Clear the code when timer hits 0
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const timer = setInterval(() => {
|
|
84
|
+
setCodeTimeLeft((prev) => prev - 1);
|
|
85
|
+
}, 1000);
|
|
86
|
+
return () => clearInterval(timer);
|
|
87
|
+
}, [codeTimeLeft, generatedCode]);
|
|
88
|
+
|
|
89
|
+
// 🚨 NEW: Formatting helper
|
|
90
|
+
const formatTime = (seconds: number) => {
|
|
91
|
+
const m = Math.floor(seconds / 60);
|
|
92
|
+
const s = seconds % 60;
|
|
93
|
+
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleGenerateCode = async () => {
|
|
97
|
+
if (isGeneratingCode) return;
|
|
98
|
+
setIsGeneratingCode(true);
|
|
99
|
+
try {
|
|
100
|
+
const apiKey = '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';
|
|
101
|
+
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
showAlert('Error', 'API Key is missing.');
|
|
104
|
+
setIsGeneratingCode(false);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 🚨 Pass the apiKey as the strictly required second parameter
|
|
109
|
+
const res = await kycService.generateReferralCode(
|
|
110
|
+
{ session_id: state.session.session_id },
|
|
111
|
+
apiKey
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Extract the code from the response
|
|
115
|
+
const newCode = res.code || res.referral_code || res?.data?.code || res?.data?.referral_code;
|
|
116
|
+
|
|
117
|
+
if (newCode) {
|
|
118
|
+
setGeneratedCode(newCode);
|
|
119
|
+
setCodeTimeLeft(300); // 300 seconds = 5 minutes
|
|
120
|
+
} else {
|
|
121
|
+
showAlert('Error', 'Failed to parse code from server response.');
|
|
122
|
+
}
|
|
123
|
+
} catch (error: any) {
|
|
124
|
+
showAlert('Error', error.message || 'Failed to generate code');
|
|
125
|
+
} finally {
|
|
126
|
+
setIsGeneratingCode(false);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
72
130
|
useEffect(() => {
|
|
73
131
|
if (typeof window !== 'undefined' && window.location) setCurrentUrl(window.location.href);
|
|
74
132
|
console.log(currentUrl)
|
|
@@ -163,7 +221,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
163
221
|
setTimeout(() => {
|
|
164
222
|
setCameraKey(prev => prev + 1);
|
|
165
223
|
setIsRebootingCamera(false);
|
|
166
|
-
},
|
|
224
|
+
}, 1000);
|
|
167
225
|
};
|
|
168
226
|
|
|
169
227
|
const toggleCameraLens = () => {
|
|
@@ -628,8 +686,43 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
628
686
|
}
|
|
629
687
|
}, [scanPhase, pendingCAImage, silentCaptureResult.barcode]);
|
|
630
688
|
|
|
631
|
-
const handleError = (event: { message: string }) => {
|
|
632
|
-
|
|
689
|
+
const handleError = (event: { message: string, name?: string, code?: string }) => {
|
|
690
|
+
logger.error("Camera Error:", event);
|
|
691
|
+
|
|
692
|
+
// Normalize the error to lowercase for easy checking
|
|
693
|
+
const errString = `${event.message} ${event.name} ${event.code}`.toLowerCase();
|
|
694
|
+
|
|
695
|
+
let friendlyMessage = state.currentLanguage === 'en'
|
|
696
|
+
? 'An unknown camera error occurred.'
|
|
697
|
+
: 'Une erreur de caméra inconnue est survenue.';
|
|
698
|
+
|
|
699
|
+
// 1. Detect Hardware Lock (Camera in use by Zoom, Teams, or another app)
|
|
700
|
+
if (
|
|
701
|
+
errString.includes('notreadableerror') ||
|
|
702
|
+
errString.includes('trackstarterror') ||
|
|
703
|
+
errString.includes('in use') ||
|
|
704
|
+
errString.includes('concurrent') ||
|
|
705
|
+
errString.includes('camera unavailable')
|
|
706
|
+
) {
|
|
707
|
+
friendlyMessage = state.currentLanguage === 'en'
|
|
708
|
+
? 'Your camera is currently being used by another application (like Zoom or Teams). Please close the other app and try again.'
|
|
709
|
+
: 'Votre caméra est actuellement utilisée par une autre application. Veuillez fermer l\'autre application et réessayer.';
|
|
710
|
+
}
|
|
711
|
+
// 2. Detect Permission Denied
|
|
712
|
+
else if (errString.includes('notallowederror') || errString.includes('permission')) {
|
|
713
|
+
friendlyMessage = state.currentLanguage === 'en'
|
|
714
|
+
? 'Camera permission denied. Please allow camera access in your settings to proceed.'
|
|
715
|
+
: 'Autorisation de la caméra refusée. Veuillez autoriser l\'accès dans vos paramètres.';
|
|
716
|
+
}
|
|
717
|
+
// 3. Detect No Camera Found
|
|
718
|
+
else if (errString.includes('notfounderror') || errString.includes('no camera')) {
|
|
719
|
+
friendlyMessage = state.currentLanguage === 'en'
|
|
720
|
+
? 'No camera detected on this device.'
|
|
721
|
+
: 'Aucune caméra détectée sur cet appareil.';
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
showAlert(state.currentLanguage === 'en' ? 'Camera Error' : 'Erreur de Caméra', friendlyMessage);
|
|
725
|
+
|
|
633
726
|
setShowCamera(false);
|
|
634
727
|
setIsProcessingCapture(false);
|
|
635
728
|
};
|
|
@@ -971,88 +1064,104 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
971
1064
|
</View>
|
|
972
1065
|
|
|
973
1066
|
{showQRModal && Platform.OS === 'web' && (
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
<View style={styles.qrToggleContainer}>
|
|
988
|
-
<TouchableOpacity
|
|
989
|
-
style={[styles.qrToggleButton, qrPlatform === 'android' && styles.qrToggleButtonActive]}
|
|
990
|
-
onPress={() => setQrPlatform('android')}
|
|
991
|
-
>
|
|
992
|
-
<Text style={[styles.qrToggleText, qrPlatform === 'android' && styles.qrToggleTextActive]}>
|
|
993
|
-
Android
|
|
994
|
-
</Text>
|
|
995
|
-
</TouchableOpacity>
|
|
996
|
-
<TouchableOpacity
|
|
997
|
-
style={[styles.qrToggleButton, qrPlatform === 'ios' && styles.qrToggleButtonActive]}
|
|
998
|
-
onPress={() => setQrPlatform('ios')}
|
|
999
|
-
>
|
|
1000
|
-
<Text style={[styles.qrToggleText, qrPlatform === 'ios' && styles.qrToggleTextActive]}>
|
|
1001
|
-
iOS (Apple)
|
|
1002
|
-
</Text>
|
|
1003
|
-
</TouchableOpacity>
|
|
1004
|
-
</View>
|
|
1067
|
+
<View style={styles.qrModalOverlay}>
|
|
1068
|
+
<View style={styles.qrModalContainer}>
|
|
1069
|
+
<View style={styles.qrModalHeader}>
|
|
1070
|
+
<Text style={styles.qrModalTitle}>
|
|
1071
|
+
{state.currentLanguage === 'en' ? 'Continue on your phone' : 'Continuer sur votre téléphone'}
|
|
1072
|
+
</Text>
|
|
1073
|
+
<Text style={styles.qrModalText}>
|
|
1074
|
+
{state.currentLanguage === 'en'
|
|
1075
|
+
? "Scan the QR code to download our mobile app and complete the scanning process securely."
|
|
1076
|
+
: "Scannez le code QR pour télécharger notre application et terminer la numérisation en toute sécurité."}
|
|
1077
|
+
</Text>
|
|
1078
|
+
</View>
|
|
1005
1079
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1080
|
+
<View style={styles.qrToggleContainer}>
|
|
1081
|
+
<TouchableOpacity
|
|
1082
|
+
style={[styles.qrToggleButton, qrPlatform === 'android' && styles.qrToggleButtonActive]}
|
|
1083
|
+
onPress={() => setQrPlatform('android')}
|
|
1084
|
+
>
|
|
1085
|
+
<Text style={[styles.qrToggleText, qrPlatform === 'android' && styles.qrToggleTextActive]}>
|
|
1086
|
+
Android
|
|
1087
|
+
</Text>
|
|
1088
|
+
</TouchableOpacity>
|
|
1089
|
+
<TouchableOpacity
|
|
1090
|
+
style={[styles.qrToggleButton, qrPlatform === 'ios' && styles.qrToggleButtonActive]}
|
|
1091
|
+
onPress={() => setQrPlatform('ios')}
|
|
1092
|
+
>
|
|
1093
|
+
<Text style={[styles.qrToggleText, qrPlatform === 'ios' && styles.qrToggleTextActive]}>
|
|
1094
|
+
iOS (Apple)
|
|
1095
|
+
</Text>
|
|
1096
|
+
</TouchableOpacity>
|
|
1097
|
+
</View>
|
|
1018
1098
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
<TouchableOpacity
|
|
1032
|
-
style={styles.storeButton}
|
|
1033
|
-
onPress={() => window.open('https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731', '_blank')}
|
|
1034
|
-
>
|
|
1035
|
-
<Text style={styles.storeButtonText}>App Store</Text>
|
|
1036
|
-
</TouchableOpacity>
|
|
1037
|
-
</View>
|
|
1038
|
-
</View>
|
|
1039
|
-
)}
|
|
1099
|
+
<View style={styles.qrCodeWrapper}>
|
|
1100
|
+
<QRCode
|
|
1101
|
+
value={
|
|
1102
|
+
qrPlatform === 'android'
|
|
1103
|
+
? 'https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US'
|
|
1104
|
+
: 'https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731'
|
|
1105
|
+
}
|
|
1106
|
+
size={180}
|
|
1107
|
+
backgroundColor="#FFFFFF"
|
|
1108
|
+
color="#0F172A"
|
|
1109
|
+
/>
|
|
1110
|
+
</View>
|
|
1040
1111
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1112
|
+
<View style={styles.linkCodeSection}>
|
|
1113
|
+
<Text style={styles.linkCodeHeader}>
|
|
1114
|
+
{state.currentLanguage === 'en' ? 'Use code below to proceed on the Sanctum Key App.' : 'Vous avez déjà l\'application ? Liez-la :'}
|
|
1115
|
+
</Text>
|
|
1116
|
+
|
|
1117
|
+
{generatedCode && codeTimeLeft > 0 ? (
|
|
1118
|
+
<View style={styles.activeCodeContainer}>
|
|
1119
|
+
<Text style={styles.activeCodeText}>{generatedCode}</Text>
|
|
1120
|
+
<Text style={styles.timerText}>
|
|
1121
|
+
{state.currentLanguage === 'en' ? `Expires in ${formatTime(codeTimeLeft)}` : `Expire dans ${formatTime(codeTimeLeft)}`}
|
|
1122
|
+
</Text>
|
|
1123
|
+
</View>
|
|
1124
|
+
) : (
|
|
1125
|
+
<TouchableOpacity
|
|
1126
|
+
style={styles.generateCodeButton}
|
|
1127
|
+
onPress={handleGenerateCode}
|
|
1128
|
+
disabled={isGeneratingCode}
|
|
1129
|
+
>
|
|
1130
|
+
{isGeneratingCode ? (
|
|
1131
|
+
<ActivityIndicator size="small" color="#0F172A" />
|
|
1132
|
+
) : (
|
|
1133
|
+
<Text style={styles.generateCodeButtonText}>
|
|
1134
|
+
{state.currentLanguage === 'en' ? 'Generate 5-Digit Link Code' : 'Générer un code à 5 chiffres'}
|
|
1135
|
+
</Text>
|
|
1136
|
+
)}
|
|
1137
|
+
</TouchableOpacity>
|
|
1138
|
+
)}
|
|
1139
|
+
</View>
|
|
1047
1140
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1141
|
+
{isWebScanningRestricted && (
|
|
1142
|
+
<View style={styles.storeLinksContainer}>
|
|
1143
|
+
<Text style={styles.storeLinksTitle}>
|
|
1144
|
+
{state.currentLanguage === 'en' ? 'Or click to open directly:' : 'Ou cliquez pour ouvrir directement :'}
|
|
1145
|
+
</Text>
|
|
1146
|
+
<View style={styles.storeButtonsRow}>
|
|
1147
|
+
<TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US', '_blank')} >
|
|
1148
|
+
<Text style={styles.storeButtonText}>Google Play</Text>
|
|
1149
|
+
</TouchableOpacity>
|
|
1150
|
+
<TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731', '_blank')} >
|
|
1151
|
+
<Text style={styles.storeButtonText}>App Store</Text>
|
|
1152
|
+
</TouchableOpacity>
|
|
1055
1153
|
</View>
|
|
1154
|
+
</View>
|
|
1155
|
+
)}
|
|
1156
|
+
|
|
1157
|
+
<Button
|
|
1158
|
+
title={state.currentLanguage === 'en' ? "Cancel & Stay on Laptop" : "Annuler et rester sur l'ordinateur"}
|
|
1159
|
+
onPress={() => setShowQRModal(false)}
|
|
1160
|
+
variant="outline"
|
|
1161
|
+
fullWidth
|
|
1162
|
+
/>
|
|
1163
|
+
</View>
|
|
1164
|
+
</View>
|
|
1056
1165
|
)}
|
|
1057
1166
|
</View>
|
|
1058
1167
|
);
|
|
@@ -1172,4 +1281,51 @@ const styles = StyleSheet.create({
|
|
|
1172
1281
|
qrToggleTextActive: {
|
|
1173
1282
|
color: '#0F172A', // Active text color
|
|
1174
1283
|
},
|
|
1284
|
+
linkCodeSection: {
|
|
1285
|
+
width: '100%',
|
|
1286
|
+
alignItems: 'center',
|
|
1287
|
+
backgroundColor: '#F8FAFC', // Very light slate
|
|
1288
|
+
borderRadius: 16,
|
|
1289
|
+
padding: 16,
|
|
1290
|
+
marginBottom: 20,
|
|
1291
|
+
borderWidth: 1,
|
|
1292
|
+
borderColor: '#E2E8F0',
|
|
1293
|
+
},
|
|
1294
|
+
linkCodeHeader: {
|
|
1295
|
+
fontSize: 13,
|
|
1296
|
+
color: '#475569',
|
|
1297
|
+
fontWeight: '600',
|
|
1298
|
+
marginBottom: 12,
|
|
1299
|
+
},
|
|
1300
|
+
generateCodeButton: {
|
|
1301
|
+
backgroundColor: '#E2E8F0',
|
|
1302
|
+
paddingVertical: 12,
|
|
1303
|
+
paddingHorizontal: 20,
|
|
1304
|
+
borderRadius: 10,
|
|
1305
|
+
width: '100%',
|
|
1306
|
+
alignItems: 'center',
|
|
1307
|
+
justifyContent: 'center',
|
|
1308
|
+
height: 44, // Fixed height to prevent jumping during loading state
|
|
1309
|
+
},
|
|
1310
|
+
generateCodeButtonText: {
|
|
1311
|
+
color: '#0F172A',
|
|
1312
|
+
fontWeight: '700',
|
|
1313
|
+
fontSize: 14,
|
|
1314
|
+
},
|
|
1315
|
+
activeCodeContainer: {
|
|
1316
|
+
alignItems: 'center',
|
|
1317
|
+
width: '100%',
|
|
1318
|
+
},
|
|
1319
|
+
activeCodeText: {
|
|
1320
|
+
fontSize: 32,
|
|
1321
|
+
fontWeight: '900',
|
|
1322
|
+
color: '#2DBD60',
|
|
1323
|
+
letterSpacing: 4,
|
|
1324
|
+
marginBottom: 4,
|
|
1325
|
+
},
|
|
1326
|
+
timerText: {
|
|
1327
|
+
fontSize: 12,
|
|
1328
|
+
color: '#EF4444', // Red to indicate urgency
|
|
1329
|
+
fontWeight: '600',
|
|
1330
|
+
},
|
|
1175
1331
|
});
|
|
@@ -57,7 +57,11 @@ export interface EnhancedCameraViewProps {
|
|
|
57
57
|
onCapture?: (result: { success: boolean; path?: string; error?: string }) => void;
|
|
58
58
|
onSilentCapture?: (result: { success: boolean; path?: string; error?: string; barcode:unknown }) => void;
|
|
59
59
|
silentCaptureResult?: ISilentCaptureResult;
|
|
60
|
-
onError?: (
|
|
60
|
+
onError?: (error: {
|
|
61
|
+
message: string;
|
|
62
|
+
name?: string;
|
|
63
|
+
code?: string;
|
|
64
|
+
}) => void;
|
|
61
65
|
onClose?: () => void;
|
|
62
66
|
enableFlash?: boolean;
|
|
63
67
|
enableHdr?: boolean;
|
|
@@ -145,13 +145,14 @@ export interface TemplateKYCExampleProps {
|
|
|
145
145
|
API_KEY?: string;
|
|
146
146
|
templateId?: string;
|
|
147
147
|
template?: KYCTemplate;
|
|
148
|
-
env?: KycEnvironment;
|
|
149
|
-
serverEnv?: BackendEnvironment;
|
|
148
|
+
env?: KycEnvironment;
|
|
149
|
+
serverEnv?: BackendEnvironment;
|
|
150
150
|
existingSessionId?: string;
|
|
151
151
|
initialComponentIndex?: number;
|
|
152
152
|
initialCountryResume?: { code: string; documentType: string; region?: string };
|
|
153
153
|
referenceId?: string;
|
|
154
154
|
onSessionSave?: (sessionId: string) => void;
|
|
155
|
+
kyc_session_id?: string;
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
export function TemplateKYCExample({
|
|
@@ -168,7 +169,8 @@ export function TemplateKYCExample({
|
|
|
168
169
|
initialComponentIndex,
|
|
169
170
|
initialCountryResume,
|
|
170
171
|
referenceId,
|
|
171
|
-
onSessionSave
|
|
172
|
+
onSessionSave,
|
|
173
|
+
kyc_session_id
|
|
172
174
|
}: TemplateKYCExampleProps): JSX.Element { // <--- Explicit Return Type
|
|
173
175
|
|
|
174
176
|
const handleComplete = (data: VerificationState) => {
|
|
@@ -209,6 +211,8 @@ export function TemplateKYCExample({
|
|
|
209
211
|
initialCountryResume={initialCountryResume}
|
|
210
212
|
referenceId={referenceId}
|
|
211
213
|
onSessionSave={onSessionSave}
|
|
214
|
+
kyc_session_id={kyc_session_id}
|
|
215
|
+
|
|
212
216
|
/>
|
|
213
217
|
</View>
|
|
214
218
|
</SafeAreaView>
|
|
@@ -38,6 +38,7 @@ interface TemplateKYCFlowProps {
|
|
|
38
38
|
initialCountryResume?: { code: string; documentType: string; region?: string };
|
|
39
39
|
referenceId?: string;
|
|
40
40
|
onSessionSave?: (sessionId: string) => void;
|
|
41
|
+
kyc_session_id?: string;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
@@ -55,6 +56,7 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
55
56
|
initialCountryResume,
|
|
56
57
|
referenceId,
|
|
57
58
|
onSessionSave,
|
|
59
|
+
kyc_session_id,
|
|
58
60
|
}) => {
|
|
59
61
|
const { t } = useI18n();
|
|
60
62
|
const { template: loadedTemplate, isLoading, error, loadTemplate } = useTemplateLoader();
|
|
@@ -158,6 +160,7 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
158
160
|
initialCountryResume={initialCountryResume}
|
|
159
161
|
referenceId={referenceId}
|
|
160
162
|
onSessionSave={onSessionSave}
|
|
163
|
+
kyc_session_id={kyc_session_id}
|
|
161
164
|
>
|
|
162
165
|
<TemplateKYCFlowContent onCancel={OnCancel} />
|
|
163
166
|
</TemplateKYCFlowProvider>
|
|
@@ -41,6 +41,7 @@ interface TemplateKYCFlowProviderProps {
|
|
|
41
41
|
initialCountryResume?: { code: string; documentType: string; region?: string };
|
|
42
42
|
referenceId?: string;
|
|
43
43
|
onSessionSave?: (sessionId: string) => void;
|
|
44
|
+
kyc_session_id?: string;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export const TemplateKYCFlowProvider: React.FC<TemplateKYCFlowProviderProps> = ({
|
|
@@ -57,8 +58,25 @@ export const TemplateKYCFlowProvider: React.FC<TemplateKYCFlowProviderProps> = (
|
|
|
57
58
|
initialCountryResume,
|
|
58
59
|
referenceId,
|
|
59
60
|
onSessionSave,
|
|
61
|
+
kyc_session_id
|
|
60
62
|
}) => {
|
|
61
|
-
const
|
|
63
|
+
const finalSessionId = existingSessionId || kyc_session_id;
|
|
64
|
+
|
|
65
|
+
const hookResult = useTemplateKYCFlow(
|
|
66
|
+
template,
|
|
67
|
+
onComplete,
|
|
68
|
+
onError,
|
|
69
|
+
onCancel,
|
|
70
|
+
initialLanguage,
|
|
71
|
+
apiKey,
|
|
72
|
+
env,
|
|
73
|
+
finalSessionId,
|
|
74
|
+
initialComponentIndex,
|
|
75
|
+
initialCountryResume,
|
|
76
|
+
referenceId,
|
|
77
|
+
onSessionSave
|
|
78
|
+
);
|
|
79
|
+
|
|
62
80
|
|
|
63
81
|
return (
|
|
64
82
|
<TemplateKYCFlowContext.Provider value={hookResult}>
|
|
@@ -668,10 +686,9 @@ export const useTemplateKYCFlow = (
|
|
|
668
686
|
error: null,
|
|
669
687
|
},
|
|
670
688
|
}));
|
|
671
|
-
logger.log('Initializing session');
|
|
672
689
|
|
|
690
|
+
logger.log('Initializing session');
|
|
673
691
|
const token = apiKey ? undefined : await authentification();
|
|
674
|
-
console.log('token in initializeSession', { token, apiKey });
|
|
675
692
|
|
|
676
693
|
if (!token && !apiKey) {
|
|
677
694
|
throw new Error("Cannot proceed: Authentication token and API key are both missing.");
|
|
@@ -679,15 +696,37 @@ export const useTemplateKYCFlow = (
|
|
|
679
696
|
|
|
680
697
|
let resolvedSessionId = state.session.session_id;
|
|
681
698
|
|
|
699
|
+
// 🚨 THE FIX: If we ALREADY have a session ID from the referral code/props
|
|
682
700
|
if (resolvedSessionId && resolvedSessionId.length > 0) {
|
|
683
|
-
logger.log('Resuming existing session:', resolvedSessionId);
|
|
684
|
-
} else {
|
|
685
|
-
const newSessionRes = await kycService.newSession({ token, apiKey });
|
|
686
|
-
resolvedSessionId = newSessionRes?.session_id;
|
|
701
|
+
logger.log('Resuming existing session from props:', resolvedSessionId);
|
|
687
702
|
|
|
688
|
-
if (!
|
|
689
|
-
|
|
703
|
+
if (onSessionSave && !hasSavedSessionRef.current) {
|
|
704
|
+
hasSavedSessionRef.current = true;
|
|
705
|
+
onSessionSave(resolvedSessionId);
|
|
690
706
|
}
|
|
707
|
+
|
|
708
|
+
// Just mark it as initialized and EXIT.
|
|
709
|
+
// We DO NOT call `newSession` or `initialize_session` to avoid overwriting referral data!
|
|
710
|
+
setState(prev => ({
|
|
711
|
+
...prev,
|
|
712
|
+
session: {
|
|
713
|
+
...prev.session,
|
|
714
|
+
session_id: resolvedSessionId,
|
|
715
|
+
token: token ?? "",
|
|
716
|
+
isInitialized: true,
|
|
717
|
+
isProcessing: false,
|
|
718
|
+
error: null,
|
|
719
|
+
}
|
|
720
|
+
}));
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// --- NORMAL FLOW (No Referral Code / Blank Canvas) ---
|
|
725
|
+
const newSessionRes = await kycService.newSession({ token, apiKey });
|
|
726
|
+
resolvedSessionId = newSessionRes?.session_id;
|
|
727
|
+
|
|
728
|
+
if (!resolvedSessionId) {
|
|
729
|
+
throw new Error("Backend response did not contain a valid session_id.");
|
|
691
730
|
}
|
|
692
731
|
|
|
693
732
|
if (onSessionSave && !hasSavedSessionRef.current && resolvedSessionId) {
|
|
@@ -697,23 +736,22 @@ export const useTemplateKYCFlow = (
|
|
|
697
736
|
|
|
698
737
|
const templateId = chooseTemplateId(templateWithReviewAndVerification);
|
|
699
738
|
|
|
700
|
-
// This is the API call that we know succeeds
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
739
|
+
// This is the API call that we know succeeds for fresh sessions
|
|
740
|
+
await kycService.verificationSession({
|
|
741
|
+
session_id: resolvedSessionId,
|
|
742
|
+
step: 0,
|
|
743
|
+
data: {
|
|
744
|
+
template_id: templateId,
|
|
745
|
+
...(referenceId ? { reference_id: referenceId } : {}),
|
|
746
|
+
metadata: {
|
|
747
|
+
language: initialLanguage,
|
|
748
|
+
},
|
|
709
749
|
},
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
});
|
|
716
|
-
|
|
750
|
+
templateId: templateId,
|
|
751
|
+
token: token ?? "",
|
|
752
|
+
action: 'initialize_session',
|
|
753
|
+
apiKey: apiKey,
|
|
754
|
+
});
|
|
717
755
|
|
|
718
756
|
setState(prev => ({
|
|
719
757
|
...prev,
|
|
@@ -726,15 +764,9 @@ export const useTemplateKYCFlow = (
|
|
|
726
764
|
error: null,
|
|
727
765
|
}
|
|
728
766
|
}));
|
|
729
|
-
|
|
730
767
|
} catch (error: any) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|| error?.message
|
|
734
|
-
|| "Erreur lors de l'initialisation de la session";
|
|
735
|
-
|
|
736
|
-
logger.error('Error initializing sessionnnn:', errorMessage);
|
|
737
|
-
|
|
768
|
+
const errorMessage = error?.response?.data?.message || error?.message || "Erreur lors de l'initialisation de la session";
|
|
769
|
+
logger.error('Error initializing session:', errorMessage);
|
|
738
770
|
setState(prev => ({
|
|
739
771
|
...prev,
|
|
740
772
|
session: {
|
|
@@ -747,7 +779,6 @@ export const useTemplateKYCFlow = (
|
|
|
747
779
|
}
|
|
748
780
|
}, [apiKey, referenceId, chooseTemplateId, templateWithReviewAndVerification, initialLanguage, state.session.session_id, onSessionSave]);
|
|
749
781
|
|
|
750
|
-
// When user switches device: resume with existingSessionId + initialStep — ensure session is initialized so loadSessionData runs
|
|
751
782
|
useEffect(() => {
|
|
752
783
|
if (!existingSessionId || state.session.isInitialized) return;
|
|
753
784
|
if (state.session.session_id !== existingSessionId) return;
|
|
@@ -1037,6 +1037,47 @@ async validateBackTemplate(params: {
|
|
|
1037
1037
|
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Template validation failed');
|
|
1038
1038
|
}
|
|
1039
1039
|
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Generates a new referral code for a specific session.
|
|
1043
|
+
* Endpoint: POST /api/v1/core/referral-code/generate/
|
|
1044
|
+
*/
|
|
1045
|
+
async generateReferralCode(
|
|
1046
|
+
payload: { session_id: string; [key: string]: any },
|
|
1047
|
+
apiKey: string // 🚨 Now strictly required
|
|
1048
|
+
): Promise<any> {
|
|
1049
|
+
try {
|
|
1050
|
+
logger.log(`Generating referral code for session: ${payload.session_id}`);
|
|
1051
|
+
|
|
1052
|
+
if (!apiKey) {
|
|
1053
|
+
throw new Error("Authentication failed: API key is required.");
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const url = `${KYCConfig.getBackendUrl()}/core/referral-code/generate/`;
|
|
1057
|
+
|
|
1058
|
+
const headers: Record<string, string> = {
|
|
1059
|
+
"Content-Type": "application/json",
|
|
1060
|
+
"Authorization": `ApiKey ${apiKey}` // 🚨 Strictly using ApiKey
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
const res = await axios.post(url, payload, {
|
|
1064
|
+
headers,
|
|
1065
|
+
timeout: 8000
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
logger.log("generateReferralCode SUCCESS:", JSON.stringify(res.data, null, 2));
|
|
1069
|
+
return res.data;
|
|
1070
|
+
|
|
1071
|
+
} catch (error: any) {
|
|
1072
|
+
const parsedError = errorMessage(error);
|
|
1073
|
+
const finalErrorMessage = parsedError || error?.message || "Failed to generate referral code";
|
|
1074
|
+
logger.error("Error generating referral code:", typeof finalErrorMessage === 'string' ? finalErrorMessage : JSON.stringify(finalErrorMessage));
|
|
1075
|
+
throw new Error(typeof finalErrorMessage === 'string' ? finalErrorMessage : JSON.stringify(finalErrorMessage));
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
|
|
1040
1081
|
}
|
|
1041
1082
|
|
|
1042
1083
|
const kycService = new KYCService("", "");
|