@sanctum-key/react-native-sdk 1.0.48 → 1.0.50
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/KYCElements/CountrySelection.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.js +33 -108
- 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 +50 -164
- package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
- package/build/src/components/KYCElements/DocumentTypeSelection.d.ts.map +1 -1
- package/build/src/components/KYCElements/DocumentTypeSelection.js +32 -109
- package/build/src/components/KYCElements/DocumentTypeSelection.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +58 -25
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/InitializationStep.d.ts.map +1 -1
- package/build/src/components/KYCElements/InitializationStep.js +13 -1
- package/build/src/components/KYCElements/InitializationStep.js.map +1 -1
- package/build/src/components/KYCElements/LocationCaptureTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/LocationCaptureTemplate.js +46 -157
- package/build/src/components/KYCElements/LocationCaptureTemplate.js.map +1 -1
- package/build/src/components/KYCElements/SelfieCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/SelfieCapture.js +28 -24
- package/build/src/components/KYCElements/SelfieCapture.js.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.js +31 -40
- package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
- package/build/src/components/SelfieCapture.d.ts.map +1 -1
- package/build/src/components/SelfieCapture.js +33 -90
- package/build/src/components/SelfieCapture.js.map +1 -1
- package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
- package/build/src/components/TemplateKYCExample.js +69 -120
- package/build/src/components/TemplateKYCExample.js.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.d.ts +0 -2
- package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.js +95 -210
- package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
- package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
- package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
- 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/logger.d.ts +12 -4
- package/build/src/utils/logger.d.ts.map +1 -1
- package/build/src/utils/logger.js +156 -28
- package/build/src/utils/logger.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/CountrySelection.tsx +36 -109
- package/src/components/KYCElements/CountrySelectionTemplate.tsx +67 -266
- package/src/components/KYCElements/DocumentTypeSelection.tsx +34 -110
- package/src/components/KYCElements/IDCardCapture.tsx +93 -39
- package/src/components/KYCElements/InitializationStep.tsx +17 -2
- package/src/components/KYCElements/LocationCaptureTemplate.tsx +53 -170
- package/src/components/KYCElements/SelfieCapture.tsx +50 -58
- package/src/components/KYCElements/VerificationProgressTemplate.tsx +38 -45
- package/src/components/SelfieCapture.tsx +44 -100
- package/src/components/TemplateKYCExample.tsx +94 -145
- package/src/components/TemplateKYCFlowRefactored.tsx +105 -239
- package/src/hooks/useTemplateKYCFlow.tsx +1 -0
- package/src/types/KYC.types.ts +0 -1
- package/src/utils/logger.ts +182 -29
|
@@ -37,8 +37,10 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
37
37
|
logger.log(`Starting verification check (attempt ${currentRetryCount + 1}/${MAX_RETRIES})...`);
|
|
38
38
|
const result: any = await kycService.getVerificationResult(state.session.session_id);
|
|
39
39
|
|
|
40
|
-
const rootStatus = (result?.status || '').toLowerCase();
|
|
41
|
-
|
|
40
|
+
const rootStatus = (result?.status || '').toLowerCase().trim().replace(/\.$/, '');
|
|
41
|
+
|
|
42
|
+
// FAILED CONDITION
|
|
43
|
+
if (rootStatus === 'failed' || rootStatus === 'rejected' || rootStatus === 'error') {
|
|
42
44
|
actions.setVerificationState({
|
|
43
45
|
status: 'failed',
|
|
44
46
|
result: {
|
|
@@ -68,50 +70,15 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
68
70
|
throw new Error('Verification payload missing or invalid for current session');
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
const vStatus = (vData.verification_status || vData.status || vData.state || '').toLowerCase();
|
|
73
|
+
const vStatus = (vData.verification_status || vData.status || vData.state || '').toLowerCase().replace(/\.$/, '');
|
|
72
74
|
const vDecision = (vData.decision || vData.outcome || vData.result || '').toLowerCase();
|
|
73
75
|
|
|
74
|
-
logger.log(`Parsed Status: [${vStatus}], Decision: [${vDecision}]`);
|
|
75
|
-
|
|
76
|
-
// SUCCESS CONDITION
|
|
77
|
-
if (
|
|
78
|
-
vStatus === 'approved' ||
|
|
79
|
-
vStatus === 'verified' ||
|
|
80
|
-
vStatus === 'success' ||
|
|
81
|
-
vStatus === 'completed' ||
|
|
82
|
-
vDecision === 'approved' ||
|
|
83
|
-
vDecision === 'passed' ||
|
|
84
|
-
vDecision === 'clear'
|
|
85
|
-
) {
|
|
86
|
-
const hasActualData = vData && Object.keys(vData).length > 0;
|
|
87
|
-
|
|
88
|
-
if (!hasActualData) {
|
|
89
|
-
actions.setVerificationState({
|
|
90
|
-
status: 'failed',
|
|
91
|
-
result: {
|
|
92
|
-
referenceId: state.session.session_id,
|
|
93
|
-
issues: ['Document text could not be read clearly. Please retake the photo in better lighting.']
|
|
94
|
-
} as any
|
|
95
|
-
});
|
|
96
|
-
isPollingRef.current = false;
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
actions.setVerificationState({
|
|
101
|
-
status: 'success',
|
|
102
|
-
result: { ...result, referenceId: vData.verification_id || vData.session_id || state.session.session_id } as any
|
|
103
|
-
});
|
|
104
|
-
isPollingRef.current = false;
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
76
|
+
logger.log(`Parsed Root Status: [${rootStatus}], Inner Status: [${vStatus}], Decision: [${vDecision}]`);
|
|
107
77
|
|
|
108
|
-
|
|
78
|
+
|
|
109
79
|
if (
|
|
110
|
-
|
|
111
|
-
vStatus === 'pending' ||
|
|
112
|
-
vStatus === 'in_progress' ||
|
|
113
|
-
vStatus === 'initiated' ||
|
|
114
|
-
!vStatus
|
|
80
|
+
rootStatus === 'pending' || rootStatus === 'processing' || rootStatus === 'in_progress' ||
|
|
81
|
+
vStatus === 'processing' || vStatus === 'pending' || vStatus === 'in_progress' || vStatus === 'initiated' || !vStatus
|
|
115
82
|
) {
|
|
116
83
|
if (currentRetryCount < MAX_RETRIES) {
|
|
117
84
|
const nextCount = currentRetryCount + 1;
|
|
@@ -139,8 +106,36 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
139
106
|
return;
|
|
140
107
|
}
|
|
141
108
|
|
|
109
|
+
// SUCCESS CONDITION (Only runs if we are absolutely sure it is no longer pending)
|
|
110
|
+
if (
|
|
111
|
+
rootStatus === 'success' || rootStatus === 'approved' || rootStatus === 'verified' ||
|
|
112
|
+
vStatus === 'approved' || vStatus === 'verified' || vStatus === 'success' || vStatus === 'completed' ||
|
|
113
|
+
vDecision === 'approved' || vDecision === 'passed' || vDecision === 'clear'
|
|
114
|
+
) {
|
|
115
|
+
const hasActualData = vData && Object.keys(vData).length > 0;
|
|
116
|
+
|
|
117
|
+
if (!hasActualData) {
|
|
118
|
+
actions.setVerificationState({
|
|
119
|
+
status: 'failed',
|
|
120
|
+
result: {
|
|
121
|
+
referenceId: state.session.session_id,
|
|
122
|
+
issues: ['Document text could not be read clearly. Please retake the photo in better lighting.']
|
|
123
|
+
} as any
|
|
124
|
+
});
|
|
125
|
+
isPollingRef.current = false;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
actions.setVerificationState({
|
|
130
|
+
status: 'success',
|
|
131
|
+
result: { ...result, referenceId: vData.verification_id || vData.session_id || state.session.session_id } as any
|
|
132
|
+
});
|
|
133
|
+
isPollingRef.current = false;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
142
137
|
// UNKNOWN FALLBACK
|
|
143
|
-
logger.warn(`Unknown verification status received: ${vStatus}`);
|
|
138
|
+
logger.warn(`Unknown verification status received: Root: ${rootStatus}, Inner: ${vStatus}`);
|
|
144
139
|
actions.setVerificationState({
|
|
145
140
|
status: 'failed',
|
|
146
141
|
result: {
|
|
@@ -153,7 +148,6 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
153
148
|
} catch (error: any) {
|
|
154
149
|
logger.error('getVerificationResult error', JSON.stringify(errorMessage(error) || { message: error?.message || 'Unknown error' }, null, 2));
|
|
155
150
|
|
|
156
|
-
// If network fails temporarily, we should also retry instead of instantly failing
|
|
157
151
|
if (currentRetryCount < MAX_RETRIES) {
|
|
158
152
|
const nextCount = currentRetryCount + 1;
|
|
159
153
|
if (isMountedRef.current) setRetryCount(nextCount);
|
|
@@ -204,7 +198,6 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
204
198
|
};
|
|
205
199
|
}, [isSuccess, actions]);
|
|
206
200
|
|
|
207
|
-
// --- RENDER LOGIC ---
|
|
208
201
|
|
|
209
202
|
if (isSuccess) {
|
|
210
203
|
return (
|
|
@@ -3,6 +3,7 @@ import { View, Text, TouchableOpacity, Image, Alert, StyleSheet } from 'react-na
|
|
|
3
3
|
import { useKYCStore } from '../stores/kycStore';
|
|
4
4
|
import VisionCameraModule from '../modules/camera/VisionCameraModule';
|
|
5
5
|
import { EnhancedCameraView } from './EnhancedCameraView';
|
|
6
|
+
import { sdkLogger } from '../utils/logger';
|
|
6
7
|
|
|
7
8
|
export const SelfieCapture: React.FC = () => {
|
|
8
9
|
const [capturedSelfie, setCapturedSelfie] = useState<string | null>(null);
|
|
@@ -13,10 +14,12 @@ export const SelfieCapture: React.FC = () => {
|
|
|
13
14
|
const instructions = "Placez votre visage dans le cadre et souriez naturellement. Assurez-vous que votre visage est bien éclairé et centré.";
|
|
14
15
|
|
|
15
16
|
useEffect(() => {
|
|
16
|
-
|
|
17
|
+
sdkLogger.info("User started Selfie Capture step");
|
|
18
|
+
|
|
17
19
|
const checkCameraAvailability = async () => {
|
|
18
20
|
const isAvailable = await VisionCameraModule.isCameraAvailable();
|
|
19
21
|
if (!isAvailable) {
|
|
22
|
+
sdkLogger.error("Selfie camera check failed: Camera not available on this platform");
|
|
20
23
|
Alert.alert('Erreur', 'Caméra non disponible sur cette plateforme');
|
|
21
24
|
}
|
|
22
25
|
};
|
|
@@ -26,19 +29,23 @@ export const SelfieCapture: React.FC = () => {
|
|
|
26
29
|
|
|
27
30
|
const handleCapture = async () => {
|
|
28
31
|
try {
|
|
32
|
+
sdkLogger.info("Requesting permissions to start selfie camera");
|
|
29
33
|
setIsCapturing(true);
|
|
30
34
|
setProcessing(true);
|
|
31
|
-
|
|
35
|
+
|
|
32
36
|
const hasPermissions = await VisionCameraModule.requestAllPermissions();
|
|
33
37
|
if (!hasPermissions) {
|
|
38
|
+
sdkLogger.warn("User denied camera/microphone permissions for selfie");
|
|
34
39
|
Alert.alert('Permission refusée', 'Nous avons besoin de l\'accès à la caméra et au microphone');
|
|
35
40
|
setIsCapturing(false);
|
|
36
41
|
setProcessing(false);
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
39
|
-
|
|
44
|
+
|
|
45
|
+
sdkLogger.info("Selfie camera permissions granted, mounting camera");
|
|
40
46
|
setShowCamera(true);
|
|
41
|
-
} catch (error) {
|
|
47
|
+
} catch (error: any) {
|
|
48
|
+
sdkLogger.error("Failed to open selfie camera", { error: error?.message });
|
|
42
49
|
Alert.alert('Erreur', 'Impossible d\'ouvrir la caméra');
|
|
43
50
|
setIsCapturing(false);
|
|
44
51
|
setProcessing(false);
|
|
@@ -46,6 +53,7 @@ export const SelfieCapture: React.FC = () => {
|
|
|
46
53
|
};
|
|
47
54
|
|
|
48
55
|
const handleCaptureSuccess = (imagePath: string) => {
|
|
56
|
+
sdkLogger.info("User successfully captured selfie image");
|
|
49
57
|
setCapturedSelfie(imagePath);
|
|
50
58
|
setSelfie(imagePath);
|
|
51
59
|
setShowCamera(false);
|
|
@@ -54,6 +62,7 @@ export const SelfieCapture: React.FC = () => {
|
|
|
54
62
|
};
|
|
55
63
|
|
|
56
64
|
const handleRetake = () => {
|
|
65
|
+
sdkLogger.info("User chose to retake selfie");
|
|
57
66
|
setCapturedSelfie(null);
|
|
58
67
|
setShowCamera(false);
|
|
59
68
|
};
|
|
@@ -62,6 +71,7 @@ export const SelfieCapture: React.FC = () => {
|
|
|
62
71
|
if (result.success && result.path) {
|
|
63
72
|
handleCaptureSuccess(result.path);
|
|
64
73
|
} else {
|
|
74
|
+
sdkLogger.warn("Selfie capture action failed", { error: result.error }); // 🚨 LOG
|
|
65
75
|
Alert.alert('Erreur', result.error || 'Échec de la capture');
|
|
66
76
|
setIsCapturing(false);
|
|
67
77
|
setProcessing(false);
|
|
@@ -69,6 +79,7 @@ export const SelfieCapture: React.FC = () => {
|
|
|
69
79
|
};
|
|
70
80
|
|
|
71
81
|
const handleCameraError = (event: { message: string }) => {
|
|
82
|
+
sdkLogger.error("Selfie camera hardware/software error", { error: event.message }); // 🚨 LOG
|
|
72
83
|
Alert.alert('Erreur', event.message || 'Une erreur est survenue');
|
|
73
84
|
setShowCamera(false);
|
|
74
85
|
setIsCapturing(false);
|
|
@@ -76,6 +87,7 @@ export const SelfieCapture: React.FC = () => {
|
|
|
76
87
|
};
|
|
77
88
|
|
|
78
89
|
const handleCloseCamera = () => {
|
|
90
|
+
sdkLogger.info("User manually closed the selfie camera without capturing"); // 🚨 LOG
|
|
79
91
|
setShowCamera(false);
|
|
80
92
|
setIsCapturing(false);
|
|
81
93
|
setProcessing(false);
|
|
@@ -115,18 +127,21 @@ export const SelfieCapture: React.FC = () => {
|
|
|
115
127
|
resizeMode="cover"
|
|
116
128
|
/>
|
|
117
129
|
</View>
|
|
118
|
-
|
|
130
|
+
|
|
119
131
|
<View style={styles.buttonContainer}>
|
|
120
|
-
<TouchableOpacity
|
|
121
|
-
style={[styles.button, styles.retakeButton]}
|
|
132
|
+
<TouchableOpacity
|
|
133
|
+
style={[styles.button, styles.retakeButton]}
|
|
122
134
|
onPress={handleRetake}
|
|
123
135
|
>
|
|
124
136
|
<Text style={styles.buttonText}>Reprendre</Text>
|
|
125
137
|
</TouchableOpacity>
|
|
126
|
-
|
|
127
|
-
<TouchableOpacity
|
|
128
|
-
style={[styles.button, styles.validateButton]}
|
|
129
|
-
onPress={() => {
|
|
138
|
+
|
|
139
|
+
<TouchableOpacity
|
|
140
|
+
style={[styles.button, styles.validateButton]}
|
|
141
|
+
onPress={() => {
|
|
142
|
+
sdkLogger.info("User validated selfie capture, proceeding to next step"); // 🚨 LOG
|
|
143
|
+
/* Navigation vers l'étape suivante */
|
|
144
|
+
}}
|
|
130
145
|
>
|
|
131
146
|
<Text style={styles.buttonText}>Valider</Text>
|
|
132
147
|
</TouchableOpacity>
|
|
@@ -139,8 +154,8 @@ export const SelfieCapture: React.FC = () => {
|
|
|
139
154
|
{instructions}
|
|
140
155
|
</Text>
|
|
141
156
|
|
|
142
|
-
<TouchableOpacity
|
|
143
|
-
style={[styles.button, styles.captureButton]}
|
|
157
|
+
<TouchableOpacity
|
|
158
|
+
style={[styles.button, styles.captureButton]}
|
|
144
159
|
onPress={handleCapture}
|
|
145
160
|
disabled={isCapturing}
|
|
146
161
|
>
|
|
@@ -155,90 +170,19 @@ export const SelfieCapture: React.FC = () => {
|
|
|
155
170
|
};
|
|
156
171
|
|
|
157
172
|
const styles = StyleSheet.create({
|
|
158
|
-
container: {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
},
|
|
175
|
-
instructions: {
|
|
176
|
-
fontSize: 16,
|
|
177
|
-
color: '#666',
|
|
178
|
-
textAlign: 'center',
|
|
179
|
-
marginBottom: 40,
|
|
180
|
-
lineHeight: 24,
|
|
181
|
-
},
|
|
182
|
-
previewContainer: {
|
|
183
|
-
flex: 1,
|
|
184
|
-
padding: 20,
|
|
185
|
-
},
|
|
186
|
-
imageContainer: {
|
|
187
|
-
flex: 1,
|
|
188
|
-
justifyContent: 'center',
|
|
189
|
-
alignItems: 'center',
|
|
190
|
-
marginBottom: 20,
|
|
191
|
-
},
|
|
192
|
-
previewImage: {
|
|
193
|
-
width: 250,
|
|
194
|
-
height: 250,
|
|
195
|
-
borderRadius: 125,
|
|
196
|
-
borderWidth: 3,
|
|
197
|
-
borderColor: '#4CAF50',
|
|
198
|
-
},
|
|
199
|
-
buttonContainer: {
|
|
200
|
-
flexDirection: 'row',
|
|
201
|
-
justifyContent: 'space-between',
|
|
202
|
-
gap: 15,
|
|
203
|
-
},
|
|
204
|
-
button: {
|
|
205
|
-
flex: 1,
|
|
206
|
-
paddingVertical: 15,
|
|
207
|
-
paddingHorizontal: 20,
|
|
208
|
-
borderRadius: 10,
|
|
209
|
-
alignItems: 'center',
|
|
210
|
-
justifyContent: 'center',
|
|
211
|
-
},
|
|
212
|
-
captureButton: {
|
|
213
|
-
backgroundColor: '#4CAF50',
|
|
214
|
-
},
|
|
215
|
-
retakeButton: {
|
|
216
|
-
backgroundColor: '#FF9800',
|
|
217
|
-
},
|
|
218
|
-
validateButton: {
|
|
219
|
-
backgroundColor: '#2196F3',
|
|
220
|
-
},
|
|
221
|
-
buttonText: {
|
|
222
|
-
color: 'white',
|
|
223
|
-
fontSize: 16,
|
|
224
|
-
fontWeight: '600',
|
|
225
|
-
},
|
|
226
|
-
selfieOverlay: {
|
|
227
|
-
position: 'absolute',
|
|
228
|
-
top: 0,
|
|
229
|
-
left: 0,
|
|
230
|
-
right: 0,
|
|
231
|
-
bottom: 0,
|
|
232
|
-
justifyContent: 'center',
|
|
233
|
-
alignItems: 'center',
|
|
234
|
-
pointerEvents: 'none',
|
|
235
|
-
},
|
|
236
|
-
faceFrame: {
|
|
237
|
-
width: 250,
|
|
238
|
-
height: 300,
|
|
239
|
-
borderWidth: 3,
|
|
240
|
-
borderColor: '#4CAF50',
|
|
241
|
-
borderRadius: 125,
|
|
242
|
-
backgroundColor: 'transparent',
|
|
243
|
-
},
|
|
244
|
-
});
|
|
173
|
+
container: { flex: 1, backgroundColor: '#f5f5f5', },
|
|
174
|
+
captureContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, },
|
|
175
|
+
title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 20, textAlign: 'center', },
|
|
176
|
+
instructions: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 40, lineHeight: 24, },
|
|
177
|
+
previewContainer: { flex: 1, padding: 20, },
|
|
178
|
+
imageContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', marginBottom: 20, },
|
|
179
|
+
previewImage: { width: 250, height: 250, borderRadius: 125, borderWidth: 3, borderColor: '#4CAF50', },
|
|
180
|
+
buttonContainer: { flexDirection: 'row', justifyContent: 'space-between', gap: 15, },
|
|
181
|
+
button: { flex: 1, paddingVertical: 15, paddingHorizontal: 20, borderRadius: 10, alignItems: 'center', justifyContent: 'center', },
|
|
182
|
+
captureButton: { backgroundColor: '#4CAF50', },
|
|
183
|
+
retakeButton: { backgroundColor: '#FF9800', },
|
|
184
|
+
validateButton: { backgroundColor: '#2196F3', },
|
|
185
|
+
buttonText: { color: 'white', fontSize: 16, fontWeight: '600', },
|
|
186
|
+
selfieOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', pointerEvents: 'none', },
|
|
187
|
+
faceFrame: { width: 250, height: 300, borderWidth: 3, borderColor: '#4CAF50', borderRadius: 125, backgroundColor: 'transparent', },
|
|
188
|
+
});
|
|
@@ -3,140 +3,45 @@ import { View, SafeAreaView } from 'react-native';
|
|
|
3
3
|
import { TemplateKYCFlow } from './TemplateKYCFlowRefactored';
|
|
4
4
|
import { KYCTemplate, VerificationState } from '../types/KYC.types';
|
|
5
5
|
import { KycEnvironment, BackendEnvironment } from '../types/env.types';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { sdkLogger } from '../utils/logger'; // 🚨 IMPORT SDK LOGGER
|
|
9
7
|
|
|
10
8
|
const advancedVerificationTemplate: KYCTemplate = {
|
|
11
9
|
id: 1,
|
|
12
|
-
name: {
|
|
13
|
-
|
|
14
|
-
fr: "Vérification Avancée"
|
|
15
|
-
},
|
|
16
|
-
description: {
|
|
17
|
-
en: "Extended identity verification flow",
|
|
18
|
-
fr: "Processus de vérification d'identité étendu"
|
|
19
|
-
},
|
|
10
|
+
name: { en: "Advanced Verification", fr: "Vérification Avancée" },
|
|
11
|
+
description: { en: "Extended identity verification flow", fr: "Processus de vérification d'identité étendu" },
|
|
20
12
|
version: "1.1.0",
|
|
21
13
|
components: [
|
|
22
14
|
{
|
|
23
|
-
id: 5,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
fr: "Activez votre localisation"
|
|
29
|
-
},
|
|
30
|
-
instructions: {
|
|
31
|
-
en: "We need your GPS location for compliance purposes.",
|
|
32
|
-
fr: "Nous avons besoin de votre localisation GPS pour des raisons réglementaires."
|
|
33
|
-
},
|
|
34
|
-
ui: {
|
|
35
|
-
icon: "location-icon.png",
|
|
36
|
-
themeColor: "#2DBD60",
|
|
37
|
-
buttonText: { en: "Enable Location", fr: "Activer la localisation" }
|
|
38
|
-
},
|
|
39
|
-
config: {
|
|
40
|
-
accuracy: "high",
|
|
41
|
-
required: true
|
|
42
|
-
} as const
|
|
15
|
+
id: 5, type: "location" as const, order: 1,
|
|
16
|
+
labels: { en: "Enable your location", fr: "Activez votre localisation" },
|
|
17
|
+
instructions: { en: "We need your GPS location for compliance purposes.", fr: "Nous avons besoin de votre localisation GPS pour des raisons réglementaires." },
|
|
18
|
+
ui: { icon: "location-icon.png", themeColor: "#2DBD60", buttonText: { en: "Enable Location", fr: "Activer la localisation" } },
|
|
19
|
+
config: { accuracy: "high", required: true } as const
|
|
43
20
|
},
|
|
44
21
|
{
|
|
45
|
-
id: 6,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
fr: "Sélectionnez votre pays"
|
|
51
|
-
},
|
|
52
|
-
instructions: {
|
|
53
|
-
en: "Choose your country of residence.",
|
|
54
|
-
fr: "Choisissez votre pays de résidence."
|
|
55
|
-
},
|
|
56
|
-
ui: {
|
|
57
|
-
icon: "country-icon.png",
|
|
58
|
-
themeColor: "#2DBD60",
|
|
59
|
-
buttonText: { en: "Confirm", fr: "Confirmer" }
|
|
60
|
-
},
|
|
61
|
-
config: {
|
|
62
|
-
allowed_countries: ["CM", "NG", "CI", "KE", "GH"],
|
|
63
|
-
default_country: "CM",
|
|
64
|
-
required: true
|
|
65
|
-
} as const
|
|
22
|
+
id: 6, type: "country_selection" as const, order: 2,
|
|
23
|
+
labels: { en: "Select your country", fr: "Sélectionnez votre pays" },
|
|
24
|
+
instructions: { en: "Choose your country of residence.", fr: "Choisissez votre pays de résidence." },
|
|
25
|
+
ui: { icon: "country-icon.png", themeColor: "#2DBD60", buttonText: { en: "Confirm", fr: "Confirmer" } },
|
|
26
|
+
config: { allowed_countries: ["CM", "NG", "CI", "KE", "GH"], default_country: "CM", required: true } as const
|
|
66
27
|
},
|
|
67
28
|
{
|
|
68
|
-
id: 1,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
front: {
|
|
74
|
-
en: "Upload front of ID card",
|
|
75
|
-
fr: "Téléversez le recto de votre carte d'identité"
|
|
76
|
-
},
|
|
77
|
-
back: {
|
|
78
|
-
en: "Upload back of ID card",
|
|
79
|
-
fr: "Téléversez le verso de votre carte d'identité"
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
instructions: {
|
|
83
|
-
front: {
|
|
84
|
-
en: "Take a clear photo of the front of your ID.",
|
|
85
|
-
fr: "Prenez une photo nette du recto de votre pièce."
|
|
86
|
-
},
|
|
87
|
-
back: {
|
|
88
|
-
en: "Take a clear photo of the back of your ID.",
|
|
89
|
-
fr: "Prenez une photo nette du verso de votre pièce."
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
ui: {
|
|
93
|
-
front: {
|
|
94
|
-
icon: "id-card-icon.png",
|
|
95
|
-
themeColor: "#2DBD60",
|
|
96
|
-
buttonText: { en: "Next", fr: "Suivant" }
|
|
97
|
-
},
|
|
98
|
-
back: {
|
|
99
|
-
icon: "id-card-icon.png",
|
|
100
|
-
themeColor: "#2DBD60",
|
|
101
|
-
buttonText: { en: "Next", fr: "Suivant" }
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
config: {
|
|
105
|
-
sides: ["front", "back"],
|
|
106
|
-
allowed_formats: ["jpg", "png"],
|
|
107
|
-
max_size_mb: 5,
|
|
108
|
-
document_types: ["drivers_licence", "passport", "drivers_licence", "national_id"],
|
|
109
|
-
|
|
110
|
-
} as const
|
|
29
|
+
id: 1, type: "id_card" as const, order: 3, templateId: 1,
|
|
30
|
+
labels: { front: { en: "Upload front of ID card", fr: "Téléversez le recto de votre carte d'identité" }, back: { en: "Upload back of ID card", fr: "Téléversez le verso de votre carte d'identité" }, },
|
|
31
|
+
instructions: { front: { en: "Take a clear photo of the front of your ID.", fr: "Prenez une photo nette du recto de votre pièce." }, back: { en: "Take a clear photo of the back of your ID.", fr: "Prenez une photo nette du verso de votre pièce." }, },
|
|
32
|
+
ui: { front: { icon: "id-card-icon.png", themeColor: "#2DBD60", buttonText: { en: "Next", fr: "Suivant" } }, back: { icon: "id-card-icon.png", themeColor: "#2DBD60", buttonText: { en: "Next", fr: "Suivant" } }, },
|
|
33
|
+
config: { sides: ["front", "back"], allowed_formats: ["jpg", "png"], max_size_mb: 5, document_types: ["drivers_licence", "passport", "drivers_licence", "national_id"], } as const
|
|
111
34
|
},
|
|
112
35
|
{
|
|
113
|
-
id: 3,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
en: "Take selfies from different angles",
|
|
119
|
-
fr: "Prenez des selfies sous différents angles"
|
|
120
|
-
},
|
|
121
|
-
instructions: {
|
|
122
|
-
en: "Take selfies from front, left, and right angles.",
|
|
123
|
-
fr: "Prenez des selfies de face, profil gauche et profil droit."
|
|
124
|
-
},
|
|
125
|
-
ui: {
|
|
126
|
-
icon: "selfie-icon.png",
|
|
127
|
-
themeColor: "#2DBD60",
|
|
128
|
-
buttonText: { en: "Next", fr: "Suivant" }
|
|
129
|
-
},
|
|
130
|
-
config: {
|
|
131
|
-
liveness_check: true,
|
|
132
|
-
max_attempts: 3,
|
|
133
|
-
orientations: ["center", "left", "right"]
|
|
134
|
-
} as const
|
|
36
|
+
id: 3, type: "selfie" as const, order: 5, templateId: 1,
|
|
37
|
+
labels: { en: "Take selfies from different angles", fr: "Prenez des selfies sous différents angles" },
|
|
38
|
+
instructions: { en: "Take selfies from front, left, and right angles.", fr: "Prenez des selfies de face, profil gauche et profil droit." },
|
|
39
|
+
ui: { icon: "selfie-icon.png", themeColor: "#2DBD60", buttonText: { en: "Next", fr: "Suivant" } },
|
|
40
|
+
config: { liveness_check: true, max_attempts: 3, orientations: ["center", "left", "right"] } as const
|
|
135
41
|
},
|
|
136
42
|
]
|
|
137
43
|
};
|
|
138
44
|
|
|
139
|
-
|
|
140
45
|
export interface TemplateKYCExampleProps {
|
|
141
46
|
onComplete: (data: VerificationState) => void;
|
|
142
47
|
onCancel: () => void;
|
|
@@ -145,8 +50,8 @@ export interface TemplateKYCExampleProps {
|
|
|
145
50
|
API_KEY?: string;
|
|
146
51
|
templateId?: string;
|
|
147
52
|
template?: KYCTemplate;
|
|
148
|
-
env?: KycEnvironment;
|
|
149
|
-
serverEnv?: BackendEnvironment;
|
|
53
|
+
env?: KycEnvironment;
|
|
54
|
+
serverEnv?: BackendEnvironment;
|
|
150
55
|
existingSessionId?: string;
|
|
151
56
|
initialComponentIndex?: number;
|
|
152
57
|
initialCountryResume?: { code: string; documentType: string; region?: string };
|
|
@@ -155,28 +60,76 @@ export interface TemplateKYCExampleProps {
|
|
|
155
60
|
kyc_session_id?: string;
|
|
156
61
|
}
|
|
157
62
|
|
|
158
|
-
export function TemplateKYCExample({
|
|
159
|
-
onComplete,
|
|
160
|
-
onCancel,
|
|
161
|
-
onError,
|
|
162
|
-
language,
|
|
163
|
-
API_KEY,
|
|
164
|
-
templateId,
|
|
165
|
-
template,
|
|
166
|
-
env = 'PRODUCTION',
|
|
167
|
-
serverEnv,
|
|
168
|
-
existingSessionId,
|
|
169
|
-
initialComponentIndex,
|
|
170
|
-
initialCountryResume,
|
|
171
|
-
referenceId,
|
|
63
|
+
export function TemplateKYCExample({
|
|
64
|
+
onComplete,
|
|
65
|
+
onCancel,
|
|
66
|
+
onError,
|
|
67
|
+
language,
|
|
68
|
+
API_KEY,
|
|
69
|
+
templateId,
|
|
70
|
+
template,
|
|
71
|
+
env = 'PRODUCTION',
|
|
72
|
+
serverEnv,
|
|
73
|
+
existingSessionId,
|
|
74
|
+
initialComponentIndex,
|
|
75
|
+
initialCountryResume,
|
|
76
|
+
referenceId,
|
|
172
77
|
onSessionSave,
|
|
173
|
-
kyc_session_id
|
|
174
|
-
}: TemplateKYCExampleProps): JSX.Element {
|
|
78
|
+
kyc_session_id
|
|
79
|
+
}: TemplateKYCExampleProps): JSX.Element {
|
|
175
80
|
|
|
176
81
|
const handleComplete = (data: VerificationState) => {
|
|
177
|
-
console.log('KYC Template completed with data:', data);
|
|
178
|
-
|
|
179
|
-
|
|
82
|
+
console.log('KYC Template completed with raw data:', data);
|
|
83
|
+
|
|
84
|
+
let finalPayload: any = data; // Fallback to raw data if mapping fails
|
|
85
|
+
|
|
86
|
+
if ((data.status === 'success' || data.status === 'in_progress') && data.result) {
|
|
87
|
+
// 1. Extract the dynamic root key (e.g., 'national_id', 'passport')
|
|
88
|
+
const docKey = Object.keys(data.result)[0];
|
|
89
|
+
|
|
90
|
+
if (docKey && data.result[docKey]) {
|
|
91
|
+
const backendData = data.result[docKey].data;
|
|
92
|
+
const personalInfo = backendData?.user_data?.personal_information || {};
|
|
93
|
+
|
|
94
|
+
// Ensure we capture the session ID from whichever prop was provided
|
|
95
|
+
const activeSessionId = kyc_session_id || existingSessionId || "";
|
|
96
|
+
|
|
97
|
+
// 2. Strict mapping to your expected format
|
|
98
|
+
finalPayload = {
|
|
99
|
+
data: {
|
|
100
|
+
verification_id: backendData?.verification_id,
|
|
101
|
+
verification_status: backendData?.verification_status,
|
|
102
|
+
session_id: activeSessionId,
|
|
103
|
+
user_data: {
|
|
104
|
+
personal_information: {
|
|
105
|
+
first_name: personalInfo.first_name || undefined,
|
|
106
|
+
given_name: personalInfo.given_name || undefined,
|
|
107
|
+
last_name: personalInfo.last_name || undefined,
|
|
108
|
+
surname: personalInfo.last_name || undefined, // fallback
|
|
109
|
+
address: personalInfo.address || undefined,
|
|
110
|
+
city: personalInfo.city || undefined,
|
|
111
|
+
postal_code: personalInfo.postal_code || undefined,
|
|
112
|
+
expiry_date: personalInfo.expiry_date || undefined,
|
|
113
|
+
issue_date: personalInfo.issue_date || undefined,
|
|
114
|
+
date_of_birth: personalInfo.date_of_birth || undefined,
|
|
115
|
+
birth_date: personalInfo.date_of_birth || undefined, // fallback
|
|
116
|
+
country: personalInfo.country || undefined,
|
|
117
|
+
document_number: (personalInfo as any).document_number || undefined,
|
|
118
|
+
nationality: (personalInfo as any).nationality || undefined,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
sdkLogger.info("Wrapper formatted completion payload successfully", {
|
|
125
|
+
verification_id: finalPayload.data.verification_id
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
sdkLogger.warn("Wrapper received completion without a success result object", { status: data.status });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onComplete(finalPayload as any);
|
|
180
133
|
};
|
|
181
134
|
|
|
182
135
|
const handleCancel = () => {
|
|
@@ -189,8 +142,6 @@ export function TemplateKYCExample({
|
|
|
189
142
|
onError(error);
|
|
190
143
|
};
|
|
191
144
|
|
|
192
|
-
// Transform backend template to SDK format
|
|
193
|
-
// Determine which template to use: prefer templateId for dynamic loading, then template prop, then fallback to hardcoded
|
|
194
145
|
const templateToUse = templateId ? undefined : (template || advancedVerificationTemplate);
|
|
195
146
|
|
|
196
147
|
return (
|
|
@@ -202,7 +153,7 @@ export function TemplateKYCExample({
|
|
|
202
153
|
onComplete={handleComplete}
|
|
203
154
|
onError={handleError}
|
|
204
155
|
onCancel={handleCancel}
|
|
205
|
-
language={language}
|
|
156
|
+
language={language}
|
|
206
157
|
API_KEY={API_KEY}
|
|
207
158
|
env={env}
|
|
208
159
|
serverEnv={serverEnv}
|
|
@@ -212,10 +163,8 @@ export function TemplateKYCExample({
|
|
|
212
163
|
referenceId={referenceId}
|
|
213
164
|
onSessionSave={onSessionSave}
|
|
214
165
|
kyc_session_id={kyc_session_id}
|
|
215
|
-
|
|
216
166
|
/>
|
|
217
167
|
</View>
|
|
218
168
|
</SafeAreaView>
|
|
219
169
|
);
|
|
220
|
-
}
|
|
221
|
-
|
|
170
|
+
}
|