@sanctum-key/react-native-sdk 1.0.26 → 1.0.28
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 -2
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +156 -48
- 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 +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/index.d.ts +4 -4
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +1 -6
- package/build/src/index.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/build/src/modules/camera/NativeCameraModule.web.d.ts +30 -0
- package/build/src/modules/camera/NativeCameraModule.web.d.ts.map +1 -0
- package/build/src/modules/camera/NativeCameraModule.web.js +40 -0
- package/build/src/modules/camera/NativeCameraModule.web.js.map +1 -0
- package/package.json +1 -2
- package/src/components/KYCElements/IDCardCapture.tsx +198 -77
- package/src/components/TemplateKYCExample.tsx +7 -3
- package/src/components/TemplateKYCFlowRefactored.tsx +3 -0
- package/src/hooks/useTemplateKYCFlow.tsx +65 -34
- package/src/index.ts +5 -5
- package/src/modules/api/KYCService.ts +41 -0
- package/src/modules/camera/NativeCameraModule.web.ts +61 -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)
|
|
@@ -971,88 +1029,104 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
971
1029
|
</View>
|
|
972
1030
|
|
|
973
1031
|
{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>
|
|
1032
|
+
<View style={styles.qrModalOverlay}>
|
|
1033
|
+
<View style={styles.qrModalContainer}>
|
|
1034
|
+
<View style={styles.qrModalHeader}>
|
|
1035
|
+
<Text style={styles.qrModalTitle}>
|
|
1036
|
+
{state.currentLanguage === 'en' ? 'Continue on your phone' : 'Continuer sur votre téléphone'}
|
|
1037
|
+
</Text>
|
|
1038
|
+
<Text style={styles.qrModalText}>
|
|
1039
|
+
{state.currentLanguage === 'en'
|
|
1040
|
+
? "Scan the QR code to download our mobile app and complete the scanning process securely."
|
|
1041
|
+
: "Scannez le code QR pour télécharger notre application et terminer la numérisation en toute sécurité."}
|
|
1042
|
+
</Text>
|
|
1043
|
+
</View>
|
|
1005
1044
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1045
|
+
<View style={styles.qrToggleContainer}>
|
|
1046
|
+
<TouchableOpacity
|
|
1047
|
+
style={[styles.qrToggleButton, qrPlatform === 'android' && styles.qrToggleButtonActive]}
|
|
1048
|
+
onPress={() => setQrPlatform('android')}
|
|
1049
|
+
>
|
|
1050
|
+
<Text style={[styles.qrToggleText, qrPlatform === 'android' && styles.qrToggleTextActive]}>
|
|
1051
|
+
Android
|
|
1052
|
+
</Text>
|
|
1053
|
+
</TouchableOpacity>
|
|
1054
|
+
<TouchableOpacity
|
|
1055
|
+
style={[styles.qrToggleButton, qrPlatform === 'ios' && styles.qrToggleButtonActive]}
|
|
1056
|
+
onPress={() => setQrPlatform('ios')}
|
|
1057
|
+
>
|
|
1058
|
+
<Text style={[styles.qrToggleText, qrPlatform === 'ios' && styles.qrToggleTextActive]}>
|
|
1059
|
+
iOS (Apple)
|
|
1060
|
+
</Text>
|
|
1061
|
+
</TouchableOpacity>
|
|
1062
|
+
</View>
|
|
1018
1063
|
|
|
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
|
-
)}
|
|
1064
|
+
<View style={styles.qrCodeWrapper}>
|
|
1065
|
+
<QRCode
|
|
1066
|
+
value={
|
|
1067
|
+
qrPlatform === 'android'
|
|
1068
|
+
? 'https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US'
|
|
1069
|
+
: 'https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731'
|
|
1070
|
+
}
|
|
1071
|
+
size={180}
|
|
1072
|
+
backgroundColor="#FFFFFF"
|
|
1073
|
+
color="#0F172A"
|
|
1074
|
+
/>
|
|
1075
|
+
</View>
|
|
1040
1076
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1077
|
+
<View style={styles.linkCodeSection}>
|
|
1078
|
+
<Text style={styles.linkCodeHeader}>
|
|
1079
|
+
{state.currentLanguage === 'en' ? 'Use code below to proceed on the Sanctum Key App.' : 'Vous avez déjà l\'application ? Liez-la :'}
|
|
1080
|
+
</Text>
|
|
1081
|
+
|
|
1082
|
+
{generatedCode && codeTimeLeft > 0 ? (
|
|
1083
|
+
<View style={styles.activeCodeContainer}>
|
|
1084
|
+
<Text style={styles.activeCodeText}>{generatedCode}</Text>
|
|
1085
|
+
<Text style={styles.timerText}>
|
|
1086
|
+
{state.currentLanguage === 'en' ? `Expires in ${formatTime(codeTimeLeft)}` : `Expire dans ${formatTime(codeTimeLeft)}`}
|
|
1087
|
+
</Text>
|
|
1088
|
+
</View>
|
|
1089
|
+
) : (
|
|
1090
|
+
<TouchableOpacity
|
|
1091
|
+
style={styles.generateCodeButton}
|
|
1092
|
+
onPress={handleGenerateCode}
|
|
1093
|
+
disabled={isGeneratingCode}
|
|
1094
|
+
>
|
|
1095
|
+
{isGeneratingCode ? (
|
|
1096
|
+
<ActivityIndicator size="small" color="#0F172A" />
|
|
1097
|
+
) : (
|
|
1098
|
+
<Text style={styles.generateCodeButtonText}>
|
|
1099
|
+
{state.currentLanguage === 'en' ? 'Generate 5-Digit Link Code' : 'Générer un code à 5 chiffres'}
|
|
1100
|
+
</Text>
|
|
1101
|
+
)}
|
|
1102
|
+
</TouchableOpacity>
|
|
1103
|
+
)}
|
|
1104
|
+
</View>
|
|
1047
1105
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1106
|
+
{isWebScanningRestricted && (
|
|
1107
|
+
<View style={styles.storeLinksContainer}>
|
|
1108
|
+
<Text style={styles.storeLinksTitle}>
|
|
1109
|
+
{state.currentLanguage === 'en' ? 'Or click to open directly:' : 'Ou cliquez pour ouvrir directement :'}
|
|
1110
|
+
</Text>
|
|
1111
|
+
<View style={styles.storeButtonsRow}>
|
|
1112
|
+
<TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US', '_blank')} >
|
|
1113
|
+
<Text style={styles.storeButtonText}>Google Play</Text>
|
|
1114
|
+
</TouchableOpacity>
|
|
1115
|
+
<TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731', '_blank')} >
|
|
1116
|
+
<Text style={styles.storeButtonText}>App Store</Text>
|
|
1117
|
+
</TouchableOpacity>
|
|
1055
1118
|
</View>
|
|
1119
|
+
</View>
|
|
1120
|
+
)}
|
|
1121
|
+
|
|
1122
|
+
<Button
|
|
1123
|
+
title={state.currentLanguage === 'en' ? "Cancel & Stay on Laptop" : "Annuler et rester sur l'ordinateur"}
|
|
1124
|
+
onPress={() => setShowQRModal(false)}
|
|
1125
|
+
variant="outline"
|
|
1126
|
+
fullWidth
|
|
1127
|
+
/>
|
|
1128
|
+
</View>
|
|
1129
|
+
</View>
|
|
1056
1130
|
)}
|
|
1057
1131
|
</View>
|
|
1058
1132
|
);
|
|
@@ -1172,4 +1246,51 @@ const styles = StyleSheet.create({
|
|
|
1172
1246
|
qrToggleTextActive: {
|
|
1173
1247
|
color: '#0F172A', // Active text color
|
|
1174
1248
|
},
|
|
1249
|
+
linkCodeSection: {
|
|
1250
|
+
width: '100%',
|
|
1251
|
+
alignItems: 'center',
|
|
1252
|
+
backgroundColor: '#F8FAFC', // Very light slate
|
|
1253
|
+
borderRadius: 16,
|
|
1254
|
+
padding: 16,
|
|
1255
|
+
marginBottom: 20,
|
|
1256
|
+
borderWidth: 1,
|
|
1257
|
+
borderColor: '#E2E8F0',
|
|
1258
|
+
},
|
|
1259
|
+
linkCodeHeader: {
|
|
1260
|
+
fontSize: 13,
|
|
1261
|
+
color: '#475569',
|
|
1262
|
+
fontWeight: '600',
|
|
1263
|
+
marginBottom: 12,
|
|
1264
|
+
},
|
|
1265
|
+
generateCodeButton: {
|
|
1266
|
+
backgroundColor: '#E2E8F0',
|
|
1267
|
+
paddingVertical: 12,
|
|
1268
|
+
paddingHorizontal: 20,
|
|
1269
|
+
borderRadius: 10,
|
|
1270
|
+
width: '100%',
|
|
1271
|
+
alignItems: 'center',
|
|
1272
|
+
justifyContent: 'center',
|
|
1273
|
+
height: 44, // Fixed height to prevent jumping during loading state
|
|
1274
|
+
},
|
|
1275
|
+
generateCodeButtonText: {
|
|
1276
|
+
color: '#0F172A',
|
|
1277
|
+
fontWeight: '700',
|
|
1278
|
+
fontSize: 14,
|
|
1279
|
+
},
|
|
1280
|
+
activeCodeContainer: {
|
|
1281
|
+
alignItems: 'center',
|
|
1282
|
+
width: '100%',
|
|
1283
|
+
},
|
|
1284
|
+
activeCodeText: {
|
|
1285
|
+
fontSize: 32,
|
|
1286
|
+
fontWeight: '900',
|
|
1287
|
+
color: '#2DBD60',
|
|
1288
|
+
letterSpacing: 4,
|
|
1289
|
+
marginBottom: 4,
|
|
1290
|
+
},
|
|
1291
|
+
timerText: {
|
|
1292
|
+
fontSize: 12,
|
|
1293
|
+
color: '#EF4444', // Red to indicate urgency
|
|
1294
|
+
fontWeight: '600',
|
|
1295
|
+
},
|
|
1175
1296
|
});
|
|
@@ -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;
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Explicitly mark these as type-only exports to prevent Web Bundler crashes
|
|
2
|
+
export type * from './SanctumKeySdk.types';
|
|
3
3
|
|
|
4
4
|
// Export KYC types
|
|
5
|
-
export * from './types/KYC.types';
|
|
5
|
+
export type * from './types/KYC.types';
|
|
6
6
|
|
|
7
7
|
// Export Environment types
|
|
8
|
-
export * from './types/env.types';
|
|
8
|
+
export type * from './types/env.types';
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
export { TemplateKYCExample as LaunchSanctumKeyKYC } from './components/TemplateKYCExample';
|
|
@@ -26,4 +26,4 @@ export { EnhancedCameraView } from './components/EnhancedCameraView';
|
|
|
26
26
|
export { UnifiedCameraModule } from './modules/camera';
|
|
27
27
|
|
|
28
28
|
// Export Web KYC Components
|
|
29
|
-
export { WebKYCEntry } from './web';
|
|
29
|
+
export { default as WebKYCEntry } from './web/WebKYCEntry';
|
|
@@ -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("", "");
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
|
|
3
|
+
export interface CameraCaptureResult {
|
|
4
|
+
success: boolean;
|
|
5
|
+
path?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface FilePickerResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
uri?: string;
|
|
12
|
+
path?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class NativeCameraModule {
|
|
17
|
+
constructor() {
|
|
18
|
+
console.log('NativeCameraModule (WEB) constructor called');
|
|
19
|
+
console.log('Platform:', Platform.OS);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async testModule(): Promise<string> {
|
|
23
|
+
return 'Web Module Mock Successful';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async requestCameraPermission(): Promise<boolean> {
|
|
27
|
+
console.warn('Camera permission mock on Web');
|
|
28
|
+
// For a real web implementation, you would use navigator.mediaDevices.getUserMedia
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async openCameraWithInstructions(instructions: string): Promise<CameraCaptureResult> {
|
|
33
|
+
console.warn('Native camera cannot be opened on the web.');
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: 'Camera capture is not supported in the web mock yet.'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async openFilePicker(allowedTypes: string[] = ['*/*']): Promise<FilePickerResult> {
|
|
41
|
+
console.warn('Native file picker cannot be opened on the web.');
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: 'File picking is not supported in the web mock yet.'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
onCameraCapture(callback: (event: any) => void) {
|
|
49
|
+
return { remove: () => {} };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
onFileSelected(callback: (event: any) => void) {
|
|
53
|
+
return { remove: () => {} };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onError(callback: (event: any) => void) {
|
|
57
|
+
return { remove: () => {} };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default new NativeCameraModule();
|