@transfergratis/react-native-sdk 0.1.9 → 0.1.11
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/README.md +433 -94
- package/build/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/components/EnhancedCameraView.js +8 -1
- package/build/components/EnhancedCameraView.js.map +1 -1
- package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/components/KYCElements/IDCardCapture.js +115 -25
- package/build/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/components/KYCElements/ReviewSubmitTemplate.js +1 -1
- package/build/components/KYCElements/ReviewSubmitTemplate.js.map +1 -1
- package/build/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
- package/build/components/KYCElements/SelfieCaptureTemplate.js +1 -0
- package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
- package/build/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
- package/build/components/KYCElements/VerificationProgressTemplate.js +10 -9
- package/build/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
- package/build/components/OverLay/IdCard.js +3 -3
- package/build/components/OverLay/IdCard.js.map +1 -1
- package/build/components/OverLay/StepOverlay.d.ts.map +1 -1
- package/build/components/OverLay/StepOverlay.js +3 -1
- package/build/components/OverLay/StepOverlay.js.map +1 -1
- package/build/components/OverLay/type.d.ts +5 -0
- package/build/components/OverLay/type.d.ts.map +1 -1
- package/build/components/OverLay/type.js.map +1 -1
- package/build/components/TemplateKYCExample.js +2 -2
- package/build/components/TemplateKYCExample.js.map +1 -1
- package/build/config/countriesData.d.ts.map +1 -1
- package/build/config/countriesData.js +1 -0
- package/build/config/countriesData.js.map +1 -1
- package/build/config/region_mapping.d.ts.map +1 -1
- package/build/config/region_mapping.js +98 -49
- package/build/config/region_mapping.js.map +1 -1
- package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
- package/build/hooks/useTemplateKYCFlow.js +7 -6
- package/build/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/i18n/en/index.d.ts +6 -0
- package/build/i18n/en/index.d.ts.map +1 -1
- package/build/i18n/en/index.js +7 -1
- package/build/i18n/en/index.js.map +1 -1
- package/build/i18n/fr/index.d.ts +6 -0
- package/build/i18n/fr/index.d.ts.map +1 -1
- package/build/i18n/fr/index.js +7 -1
- package/build/i18n/fr/index.js.map +1 -1
- package/build/modules/api/CardAuthentification.d.ts +23 -2
- package/build/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/modules/api/CardAuthentification.js +53 -15
- package/build/modules/api/CardAuthentification.js.map +1 -1
- package/build/modules/api/KYCService.d.ts +9 -0
- package/build/modules/api/KYCService.d.ts.map +1 -1
- package/build/modules/api/KYCService.js +52 -29
- package/build/modules/api/KYCService.js.map +1 -1
- package/build/types/KYC.types.d.ts +3 -0
- package/build/types/KYC.types.d.ts.map +1 -1
- package/build/types/KYC.types.js.map +1 -1
- package/build/utils/logger.d.ts +12 -0
- package/build/utils/logger.d.ts.map +1 -0
- package/build/utils/logger.js +45 -0
- package/build/utils/logger.js.map +1 -0
- package/package.json +1 -1
- package/src/components/EnhancedCameraView.tsx +8 -2
- package/src/components/KYCElements/IDCardCapture.tsx +124 -29
- package/src/components/KYCElements/ReviewSubmitTemplate.tsx +1 -1
- package/src/components/KYCElements/SelfieCaptureTemplate.tsx +1 -0
- package/src/components/KYCElements/VerificationProgressTemplate.tsx +10 -9
- package/src/components/OverLay/IdCard.tsx +3 -3
- package/src/components/OverLay/StepOverlay.tsx +3 -1
- package/src/components/OverLay/type.ts +15 -9
- package/src/components/TemplateKYCExample.tsx +2 -2
- package/src/config/countriesData.ts +1 -0
- package/src/config/region_mapping.json +735 -0
- package/src/config/region_mapping.ts +98 -49
- package/src/hooks/useTemplateKYCFlow.tsx +7 -6
- package/src/i18n/en/index.ts +7 -1
- package/src/i18n/fr/index.ts +9 -2
- package/src/modules/api/CardAuthentification.ts +61 -18
- package/src/modules/api/KYCService.ts +57 -32
- package/src/types/KYC.types.ts +3 -0
- package/src/utils/logger.ts +48 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { View, Text, TouchableOpacity, StyleSheet, Image, Alert, ScrollView, Dimensions } from 'react-native';
|
|
3
3
|
import { EnhancedCameraView } from '../EnhancedCameraView';
|
|
4
|
-
import { TemplateComponent, IDCardConfig, LocalizedText, GovernmentDocumentType, ISilentCaptureResult } from '../../types/KYC.types';
|
|
4
|
+
import { TemplateComponent, IDCardConfig, LocalizedText, GovernmentDocumentType, ISilentCaptureResult, IBbox } from '../../types/KYC.types';
|
|
5
5
|
import IdCardOverlay from '../OverLay/IdCard';
|
|
6
6
|
import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
|
|
7
7
|
import { useI18n } from '../../hooks/useI18n';
|
|
8
8
|
import { Button } from '../ui/Button';
|
|
9
9
|
import { removeDuplicates } from '../../utils/remove-duplicate';
|
|
10
|
-
import { backVerification, frontVerification } from '../../modules/api/CardAuthentification';
|
|
10
|
+
import { backVerification, checkTemplateType, frontVerification } from '../../modules/api/CardAuthentification';
|
|
11
11
|
import { getDocumentTypeInfo } from '../../utils/get-document-type-info';
|
|
12
12
|
import pathToBase64 from '../../utils/pathToBase64';
|
|
13
13
|
import { truncateFields } from '../../modules/api/KYCService';
|
|
14
|
-
import { cropImageWithBBox } from '../../utils/cropByObb';
|
|
14
|
+
import { cropByObb, cropImageWithBBox } from '../../utils/cropByObb';
|
|
15
|
+
import { logger } from '../../utils/logger';
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
interface IIDCardPayload {
|
|
@@ -36,11 +37,13 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
36
37
|
error,
|
|
37
38
|
language = 'en',
|
|
38
39
|
}) => {
|
|
39
|
-
const { t } = useI18n();
|
|
40
|
+
const { t, locale } = useI18n();
|
|
40
41
|
const [showCamera, setShowCamera] = useState(false);
|
|
41
42
|
const [capturedImages, setCapturedImages] = useState<Record<string, IIDCardPayload>>(value || {});
|
|
42
43
|
const [cropImageUri, setCropImageUri] = useState<string>('');
|
|
43
44
|
const [currentSide, setCurrentSide] = useState<string>('front');
|
|
45
|
+
// Stocker les bbox par côté pour pouvoir restaurer les images croppées
|
|
46
|
+
const [bboxBySide, setBboxBySide] = useState<Record<string, IBbox>>({});
|
|
44
47
|
const [selectedDocumentType, setSelectedDocumentType] = useState<{
|
|
45
48
|
type: GovernmentDocumentType;
|
|
46
49
|
region: string;
|
|
@@ -60,12 +63,12 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
60
63
|
const { actions, state, } = useTemplateKYCFlowContext();
|
|
61
64
|
const config = component.config as IDCardConfig;
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
|
|
64
67
|
|
|
65
68
|
const getLocalizedText = (text: LocalizedText | Record<string, LocalizedText>): string => {
|
|
66
69
|
// console.log("text", text, JSON.stringify(component, null, 2));
|
|
67
|
-
if (text && typeof text[currentSide] === 'object' && text[currentSide][
|
|
68
|
-
return text[currentSide][
|
|
70
|
+
if (text && typeof text[currentSide] === 'object' && text[currentSide][locale]) {
|
|
71
|
+
return text[currentSide][locale] || '';
|
|
69
72
|
}
|
|
70
73
|
return "";
|
|
71
74
|
};
|
|
@@ -92,13 +95,13 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
92
95
|
});
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
|
-
|
|
98
|
+
logger.log("newconfig", truncateFields(newconfig));
|
|
96
99
|
return newconfig;
|
|
97
100
|
}, []);
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
useEffect(() => {
|
|
101
|
-
|
|
104
|
+
logger.log("cropImageUri", JSON.stringify(truncateFields({ box: silentCaptureResult }), null, 2));
|
|
102
105
|
if (capturedImages[currentSide]?.dir && silentCaptureResult?.bbox) {
|
|
103
106
|
cropImageWithBBox(capturedImages[currentSide].dir, silentCaptureResult.bbox)?.then((uri) => {
|
|
104
107
|
setCropImageUri(uri);
|
|
@@ -117,7 +120,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
117
120
|
maxRetakes: 3,
|
|
118
121
|
overlay: {
|
|
119
122
|
showGuide: true,
|
|
120
|
-
guideText: selectedDocumentType ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getLocalizedText(component.instructions as Record<string, LocalizedText>),
|
|
123
|
+
guideText: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr) : getLocalizedText(component.instructions as Record<string, LocalizedText>),
|
|
121
124
|
bbox: selectedDocumentType && config.bbox_configs[selectedDocumentType.type] ? config.bbox_configs[selectedDocumentType.type] : {
|
|
122
125
|
xMin: 20,
|
|
123
126
|
yMin: 140,
|
|
@@ -158,6 +161,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
158
161
|
const getCurrentSideVerification = (currentSide: string) => {
|
|
159
162
|
const regionMapping = countryData?.regionMapping[selectedDocumentType?.type as GovernmentDocumentType];
|
|
160
163
|
const authMethod: string[] = [];
|
|
164
|
+
const mrzTypes: string[] = [];
|
|
161
165
|
|
|
162
166
|
const key = selectedDocumentType?.region?.trim()?.length > 0 ? selectedDocumentType?.region?.trim() : 'root';
|
|
163
167
|
// console.log("regionMapping", JSON.stringify(regionMapping, null, 2), selectedDocumentType?.region);
|
|
@@ -167,44 +171,99 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
167
171
|
regionMapping[key].forEach((item: any) => {
|
|
168
172
|
if (item[currentSide as keyof typeof item]) {
|
|
169
173
|
authMethod.push(item[currentSide as keyof typeof item]);
|
|
174
|
+
if (item?.mrz_type) {
|
|
175
|
+
mrzTypes.push(item?.mrz_type);
|
|
176
|
+
}
|
|
170
177
|
}
|
|
171
178
|
});
|
|
172
179
|
}
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
logger.log("regionMapping", JSON.stringify(truncateFields({ regionMapping, selectedDocumentType: selectedDocumentType?.region, key }), null, 2));
|
|
175
182
|
|
|
176
|
-
return removeDuplicates(authMethod);
|
|
183
|
+
return { authMethod: removeDuplicates(authMethod), mrzTypes: removeDuplicates(mrzTypes), regionMapping: regionMapping, key: key };
|
|
177
184
|
}
|
|
178
185
|
|
|
186
|
+
const getCorrespondingMrzType = (templatePath: string, mapping: any, selectedDocumentType: string = "root") => {
|
|
187
|
+
if (!mapping[selectedDocumentType]) return null;
|
|
188
|
+
|
|
189
|
+
// Extraire le nom du fichier depuis le template_path
|
|
190
|
+
const fileName = templatePath.split("/").pop()?.replace(".jpg", "").replace(".png", "");
|
|
191
|
+
|
|
192
|
+
if (!fileName) return null;
|
|
193
|
+
|
|
194
|
+
// Normaliser en .py
|
|
195
|
+
const pyName = `${fileName}.py`; // ex: cameroon_id_back_3_1.py
|
|
179
196
|
|
|
180
|
-
|
|
197
|
+
// Chercher dans la liste
|
|
198
|
+
const found = mapping[selectedDocumentType].find((item: any) => item.py_file === pyName);
|
|
199
|
+
|
|
200
|
+
return found?.mrz_type || null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const handleSilentCapture = async (result: { success: boolean; path?: string; error?: string }) => {
|
|
181
204
|
if (silentCaptureResult.isAnalyzing) {
|
|
182
205
|
return;
|
|
183
206
|
}
|
|
184
207
|
if (result.success && result.path) {
|
|
185
208
|
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
|
|
209
|
+
let templatePath = silentCaptureResult.templatePath || '';
|
|
210
|
+
let templateBbox: IBbox | undefined;
|
|
211
|
+
|
|
212
|
+
const regionMappings = getCurrentSideVerification(currentSide)
|
|
213
|
+
// logger.log("regionMappings", JSON.stringify(truncateFields({regionMappings, templatePath}), null, 2));
|
|
214
|
+
if (regionMappings.authMethod.includes('MRZ') && templatePath.length === 0) {
|
|
215
|
+
try {
|
|
216
|
+
const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type as GovernmentDocumentType, docRegion: countryData?.code || "", postfix: currentSide });
|
|
217
|
+
|
|
218
|
+
if (templateType.template_path) {
|
|
219
|
+
templatePath = templateType.template_path;
|
|
220
|
+
logger.log("templatePath", templatePath);
|
|
221
|
+
setSilentCaptureResult((prev) => ({ ...prev, templatePath: templatePath }));
|
|
222
|
+
}
|
|
223
|
+
if (templateType.card_obb) {
|
|
224
|
+
let bbox: IBbox | undefined;
|
|
225
|
+
try {
|
|
226
|
+
const crop = await cropByObb(result?.path || '', (templateType as any).card_obb);
|
|
227
|
+
bbox = crop.bbox;
|
|
228
|
+
templateBbox = bbox;
|
|
229
|
+
} catch { }
|
|
230
|
+
}
|
|
231
|
+
} catch (e: any) {
|
|
232
|
+
logger.log("error checking template type", truncateFields(e));
|
|
233
|
+
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: e?.message || 'Erreur de vérification du template' }));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
186
236
|
if (currentSide === 'front') {
|
|
237
|
+
|
|
187
238
|
frontVerification(
|
|
188
239
|
{
|
|
189
240
|
path: result.path,
|
|
190
|
-
regionMapping:
|
|
241
|
+
regionMapping: regionMappings,
|
|
191
242
|
selectedDocumentType: selectedDocumentType?.type || '',
|
|
192
243
|
code: countryData?.code || '',
|
|
193
244
|
currentSide: currentSide,
|
|
194
|
-
|
|
245
|
+
templatePath: templatePath,
|
|
246
|
+
mrzType: getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '',
|
|
195
247
|
}).then((mrz) => {
|
|
196
|
-
|
|
248
|
+
logger.log("front verification result", truncateFields(mrz));
|
|
249
|
+
const bbox = (mrz as any)?.bbox || templateBbox;
|
|
197
250
|
setSilentCaptureResult((prev) => ({
|
|
198
251
|
...prev, path: result.path,
|
|
199
|
-
bbox:
|
|
252
|
+
bbox: bbox, success: true,
|
|
200
253
|
mrz: JSON.stringify(mrz), isAnalyzing: false,
|
|
201
254
|
country: countryData?.code,
|
|
202
|
-
documentType: selectedDocumentType?.type
|
|
255
|
+
documentType: selectedDocumentType?.type,
|
|
256
|
+
templatePath: "",
|
|
203
257
|
}),
|
|
204
258
|
);
|
|
259
|
+
// Stocker le bbox pour ce côté
|
|
260
|
+
if (bbox && typeof bbox === 'object') {
|
|
261
|
+
setBboxBySide((prev) => ({ ...prev, [currentSide]: bbox }));
|
|
262
|
+
}
|
|
263
|
+
|
|
205
264
|
}).catch((e: any) => {
|
|
206
|
-
|
|
207
|
-
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
|
|
265
|
+
logger.log("error front verification", truncateFields(e));
|
|
266
|
+
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
|
|
208
267
|
// Alert.alert('Erreur', e?.message || 'Erreur de détection du MRZ');
|
|
209
268
|
})
|
|
210
269
|
} else {
|
|
@@ -214,18 +273,25 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
214
273
|
selectedDocumentType: selectedDocumentType?.type || '',
|
|
215
274
|
code: countryData?.code || '',
|
|
216
275
|
currentSide: currentSide,
|
|
217
|
-
|
|
276
|
+
templatePath: templatePath,
|
|
277
|
+
mrzType: getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '',
|
|
218
278
|
}).then((mrz) => {
|
|
219
|
-
|
|
279
|
+
logger.log("back verification result", truncateFields(mrz));
|
|
280
|
+
const bbox = (mrz as any)?.bbox || templateBbox;
|
|
220
281
|
setSilentCaptureResult((prev) => ({
|
|
221
|
-
...prev, path: result.path, bbox:
|
|
282
|
+
...prev, path: result.path, bbox: bbox,
|
|
222
283
|
success: true, mrz: JSON.stringify(mrz), isAnalyzing: false,
|
|
223
284
|
country: countryData?.code,
|
|
224
|
-
documentType: selectedDocumentType?.type
|
|
285
|
+
documentType: selectedDocumentType?.type,
|
|
286
|
+
templatePath: "",
|
|
225
287
|
}));
|
|
288
|
+
// Stocker le bbox pour ce côté
|
|
289
|
+
if (bbox && typeof bbox === 'object') {
|
|
290
|
+
setBboxBySide((prev) => ({ ...prev, [currentSide]: bbox }));
|
|
291
|
+
}
|
|
226
292
|
}).catch((e: any) => {
|
|
227
|
-
|
|
228
|
-
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
|
|
293
|
+
logger.log("error back verification", truncateFields(e));
|
|
294
|
+
setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
|
|
229
295
|
// Alert.alert('Erreur', e?.message || 'Erreur de détection du MRZ');
|
|
230
296
|
})
|
|
231
297
|
}
|
|
@@ -344,7 +410,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
344
410
|
}
|
|
345
411
|
|
|
346
412
|
if (showDocumentSelection.regionSelection) {
|
|
347
|
-
|
|
413
|
+
logger.log("showDocumentSelection", JSON.stringify(truncateFields(showDocumentSelection), null, 2));
|
|
348
414
|
setShowDocumentSelection({
|
|
349
415
|
documentSelection: false,
|
|
350
416
|
regionSelection: false
|
|
@@ -420,7 +486,26 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
420
486
|
documentSelection: false,
|
|
421
487
|
regionSelection: false
|
|
422
488
|
});
|
|
423
|
-
|
|
489
|
+
// Si une image front existe, on la restaure pour l'afficher
|
|
490
|
+
if (capturedImages['front']?.dir) {
|
|
491
|
+
// Restaurer l'état de capture du front pour afficher l'image
|
|
492
|
+
const frontImage = capturedImages['front'];
|
|
493
|
+
const frontBbox = bboxBySide['front'];
|
|
494
|
+
setSilentCaptureResult((prev) => ({
|
|
495
|
+
path: frontImage.dir,
|
|
496
|
+
success: true,
|
|
497
|
+
isAnalyzing: false,
|
|
498
|
+
error: '',
|
|
499
|
+
mrz: frontImage.mrz || '',
|
|
500
|
+
country: silentCaptureResult.country,
|
|
501
|
+
documentType: silentCaptureResult.documentType,
|
|
502
|
+
bbox: frontBbox // Restaurer le bbox pour recalculer le cropImageUri
|
|
503
|
+
}));
|
|
504
|
+
// Le useEffect va automatiquement recalculer le cropImageUri si bbox existe
|
|
505
|
+
} else {
|
|
506
|
+
// Pas d'image front, on réinitialise
|
|
507
|
+
setSilentCaptureResult((prev) => ({ path: '', success: false, isAnalyzing: false, error: '' }));
|
|
508
|
+
}
|
|
424
509
|
} else {
|
|
425
510
|
setShowDocumentSelection({
|
|
426
511
|
documentSelection: true,
|
|
@@ -429,9 +514,10 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
429
514
|
actions.showCustomStepper(true);
|
|
430
515
|
}
|
|
431
516
|
},
|
|
432
|
-
selectedDocumentType: getDocumentTypeInfo(selectedDocumentType?.type || "identity_card").name.en || '',
|
|
517
|
+
selectedDocumentType: locale === 'en' ? getDocumentTypeInfo(selectedDocumentType?.type || "identity_card").name.en : getDocumentTypeInfo(selectedDocumentType?.type || "identity_card").name.fr || '',
|
|
433
518
|
step: state.currentComponentIndex + 1,
|
|
434
519
|
totalSteps: state.template.components.length,
|
|
520
|
+
side: currentSide,
|
|
435
521
|
}}
|
|
436
522
|
/>
|
|
437
523
|
}
|
|
@@ -482,6 +568,15 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
|
|
|
482
568
|
}}
|
|
483
569
|
/>
|
|
484
570
|
) : null}
|
|
571
|
+
{silentCaptureResult.path ? (<Image
|
|
572
|
+
source={{ uri: silentCaptureResult.path }}
|
|
573
|
+
style={{
|
|
574
|
+
width: '100%',
|
|
575
|
+
height: 200,
|
|
576
|
+
borderRadius: 12,
|
|
577
|
+
resizeMode: 'cover',
|
|
578
|
+
}}
|
|
579
|
+
/>) : null}
|
|
485
580
|
|
|
486
581
|
</View>
|
|
487
582
|
{/* retake button */}
|
|
@@ -89,7 +89,7 @@ export const ReviewSubmitTemplate: React.FC<ReviewSubmitTemplateProps> = () => {
|
|
|
89
89
|
const colorStyle = o === 'center' ? styles.pink : o === 'right' ? styles.blue : styles.purple;
|
|
90
90
|
return (
|
|
91
91
|
<View key={o} style={[styles.tile, colorStyle, has ? styles.active : null]}>
|
|
92
|
-
<Text style={styles.tileLabel}>{
|
|
92
|
+
<Text style={styles.tileLabel}>{t(`kyc.reviewSubmit.selfieSide.${o}`)}</Text>
|
|
93
93
|
{has ? <Text style={styles.check}>✓</Text> : null}
|
|
94
94
|
</View>
|
|
95
95
|
);
|
|
@@ -253,6 +253,7 @@ export const SelfieCaptureTemplate: React.FC<SelfieCaptureTemplateProps> = ({
|
|
|
253
253
|
selectedDocumentType: "Selfie",
|
|
254
254
|
step: state.currentComponentIndex + 1,
|
|
255
255
|
totalSteps: state.template.components.length,
|
|
256
|
+
side: currentOrientation,
|
|
256
257
|
}} />}
|
|
257
258
|
/>
|
|
258
259
|
</View>
|
|
@@ -4,6 +4,7 @@ import { TemplateComponent } from '../../types/KYC.types';
|
|
|
4
4
|
import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
|
|
5
5
|
import { useI18n } from '../../hooks/useI18n';
|
|
6
6
|
import kycService from '../../modules/api/KYCService';
|
|
7
|
+
import { logger } from '../../utils/logger';
|
|
7
8
|
|
|
8
9
|
interface VerificationProgressTemplateProps {
|
|
9
10
|
component: TemplateComponent;
|
|
@@ -28,12 +29,12 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
28
29
|
// Fonction pour récupérer le résultat de vérification avec retry
|
|
29
30
|
const getVerificationResult = async (currentRetryCount = 0, maxRetries = 40) => {
|
|
30
31
|
try {
|
|
31
|
-
|
|
32
|
+
logger.log(`Starting verification check (attempt ${currentRetryCount + 1}/${maxRetries + 1})`);
|
|
32
33
|
|
|
33
34
|
const result = await kycService.getVerificationResult(state.session.session_id);
|
|
34
35
|
const verificationResult = result[state.session.session_id].data;
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
logger.log('getVerificationResult result', JSON.stringify(result, null, 2));
|
|
37
38
|
|
|
38
39
|
// Vérifier le statut de vérification
|
|
39
40
|
if (verificationResult.verification_status === null || verificationResult.verification_status === "processing" || verificationResult.verification_status === "pending") {
|
|
@@ -42,7 +43,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
42
43
|
setRetryCount(nextRetryCount);
|
|
43
44
|
setIsRetrying(true);
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
logger.log(`Verification still processing, retrying in 10s... (${nextRetryCount}/${maxRetries})`);
|
|
46
47
|
|
|
47
48
|
// Nettoyer le timeout précédent s'il existe
|
|
48
49
|
if (timeoutId) {
|
|
@@ -57,7 +58,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
57
58
|
setTimeoutId(newTimeoutId);
|
|
58
59
|
return; // Sortir de la fonction pour éviter de traiter le résultat
|
|
59
60
|
} else {
|
|
60
|
-
|
|
61
|
+
logger.error('Max retries reached, verification still processing');
|
|
61
62
|
setIsRetrying(false);
|
|
62
63
|
return;
|
|
63
64
|
}
|
|
@@ -86,7 +87,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
86
87
|
setIsRetrying(false);
|
|
87
88
|
|
|
88
89
|
} catch (error) {
|
|
89
|
-
|
|
90
|
+
logger.error('getVerificationResult error', error);
|
|
90
91
|
|
|
91
92
|
// En cas d'erreur, on peut aussi retry
|
|
92
93
|
if (currentRetryCount < maxRetries) {
|
|
@@ -94,7 +95,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
94
95
|
setRetryCount(nextRetryCount);
|
|
95
96
|
setIsRetrying(true);
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
logger.log(`Error occurred, retrying in 10s... (${nextRetryCount}/${maxRetries})`);
|
|
98
99
|
|
|
99
100
|
// Nettoyer le timeout précédent s'il existe
|
|
100
101
|
if (timeoutId) {
|
|
@@ -108,7 +109,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
108
109
|
setTimeoutId(newTimeoutId);
|
|
109
110
|
} else {
|
|
110
111
|
setIsRetrying(false);
|
|
111
|
-
|
|
112
|
+
logger.error('Max retries reached after errors');
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
};
|
|
@@ -155,7 +156,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
155
156
|
))}
|
|
156
157
|
</View>
|
|
157
158
|
<TouchableOpacity style={[styles.button, styles.primary]} onPress={() => actions.goToComponent(state.template.components.find(c => c.type !== 'review_submit' && c.type !== 'verification_progress')?.id || 0)}>
|
|
158
|
-
<Text style={styles.buttonText}>{t('
|
|
159
|
+
<Text style={styles.buttonText}>{t('kyc.verificationProgress.status.retry')}</Text>
|
|
159
160
|
</TouchableOpacity>
|
|
160
161
|
<TouchableOpacity style={[styles.button, styles.secondary]}>
|
|
161
162
|
<Text style={styles.secondaryText}>{t('common.info')}</Text>
|
|
@@ -178,7 +179,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
178
179
|
<Text style={styles.title}>{t('kyc.verificationProgress.title')}</Text>
|
|
179
180
|
<Text style={styles.subtitle}>
|
|
180
181
|
{isRetrying
|
|
181
|
-
? `${t('kyc.verificationProgress.retrying'
|
|
182
|
+
? `${t('kyc.verificationProgress.retrying', { current: retryCount, max: 40 })}`
|
|
182
183
|
: t('kyc.verificationProgress.estimatedTime')
|
|
183
184
|
}
|
|
184
185
|
</Text>
|
|
@@ -141,14 +141,14 @@ const IdCardOverlay = ({ xMin: xMinProps, yMin: yMinProps, xMax: xMaxProps, yMax
|
|
|
141
141
|
|
|
142
142
|
<View style={styles.bottomIntruction}>
|
|
143
143
|
<Text style={styles.bottomTitle}>
|
|
144
|
-
{language === "en" ?
|
|
144
|
+
{language === "en" ? `Scan the ${stepperProps?.side === 'front' ? 'front' : 'back'} side of your ${stepperProps?.selectedDocumentType}.`:`Scanner de la face ${stepperProps?.side === 'front' ? 'avant' : 'arrière'} de votre ${stepperProps?.selectedDocumentType}.`}
|
|
145
145
|
|
|
146
146
|
</Text>
|
|
147
147
|
<Text style={styles.bottomSubtitle}>
|
|
148
148
|
{
|
|
149
149
|
language === "en"
|
|
150
|
-
? "
|
|
151
|
-
: "
|
|
150
|
+
? "Hold your ID card steady and make sure all corners are visible. Do not move your phone while the image is being captured."
|
|
151
|
+
: "Maintenez votre carte d'identité stable et assurez-vous que tous les coins sont visibles. Ne déplacez pas votre téléphone pendant la capture."
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
</Text>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StyleSheet, View, TouchableOpacity, Text } from "react-native";
|
|
2
|
+
import useI18n from "../../hooks/useI18n";
|
|
2
3
|
|
|
3
4
|
interface StepOverlayProps {
|
|
4
5
|
back: () => void;
|
|
@@ -7,6 +8,7 @@ interface StepOverlayProps {
|
|
|
7
8
|
totalSteps: number;
|
|
8
9
|
}
|
|
9
10
|
const StepOverlay = ({ back, selectedDocumentType, step, totalSteps }: StepOverlayProps) => {
|
|
11
|
+
const { t } = useI18n();
|
|
10
12
|
return (
|
|
11
13
|
<View style={styles.stepperContainer}>
|
|
12
14
|
<TouchableOpacity onPress={back} style={styles.back}>
|
|
@@ -16,7 +18,7 @@ const StepOverlay = ({ back, selectedDocumentType, step, totalSteps }: StepOverl
|
|
|
16
18
|
<Text style={styles.stepperItemText}>{selectedDocumentType}</Text>
|
|
17
19
|
</View>
|
|
18
20
|
<View style={styles.stepperDetails}>
|
|
19
|
-
<Text style={styles.stepperDetailsText}>
|
|
21
|
+
<Text style={styles.stepperDetailsText}>{t('kyc.step', { current: step, total: totalSteps })}</Text>
|
|
20
22
|
</View>
|
|
21
23
|
</View>
|
|
22
24
|
)
|
|
@@ -15,7 +15,7 @@ export interface IdCardOverlayProps {
|
|
|
15
15
|
orientation?: 'center' | 'left' | 'right';
|
|
16
16
|
isAnalyzing?: boolean;
|
|
17
17
|
isSuccess?: boolean;
|
|
18
|
-
language:string
|
|
18
|
+
language: string
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
|
|
@@ -24,6 +24,7 @@ interface StepperProps {
|
|
|
24
24
|
selectedDocumentType: string;
|
|
25
25
|
step: number;
|
|
26
26
|
totalSteps: number;
|
|
27
|
+
side: string;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export interface ExtractMrzTextResponse {
|
|
@@ -65,13 +66,13 @@ export interface EnhancedCameraViewProps {
|
|
|
65
66
|
showSwitchCamera?: boolean;
|
|
66
67
|
overlayComponent?: React.ReactNode;
|
|
67
68
|
bbox?: {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
xMin: number;
|
|
70
|
+
yMin: number;
|
|
71
|
+
xMax: number;
|
|
72
|
+
yMax: number;
|
|
73
|
+
borderColor?: string;
|
|
74
|
+
borderWidth?: number;
|
|
75
|
+
cornerRadius?: number;
|
|
75
76
|
};
|
|
76
77
|
canFlip?: boolean;
|
|
77
78
|
// Video recording props
|
|
@@ -80,4 +81,9 @@ export interface EnhancedCameraViewProps {
|
|
|
80
81
|
onVideoRecordingStart?: () => void;
|
|
81
82
|
onVideoRecordingStop?: (result: { success: boolean; path?: string; error?: string }) => void;
|
|
82
83
|
videoDuration?: number;
|
|
83
|
-
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface CheckTemplateTypeResponse {
|
|
87
|
+
template_path: string;
|
|
88
|
+
lpips_score: number;
|
|
89
|
+
}
|
|
@@ -56,8 +56,8 @@ const advancedVerificationTemplate: KYCTemplate = {
|
|
|
56
56
|
buttonText: { en: "Confirm", fr: "Confirmer" }
|
|
57
57
|
},
|
|
58
58
|
config: {
|
|
59
|
-
allowed_countries: ["
|
|
60
|
-
default_country: "
|
|
59
|
+
allowed_countries: ["CM", "NG", "CI", "KE","GH"],
|
|
60
|
+
default_country: "CM",
|
|
61
61
|
required: true
|
|
62
62
|
} as const
|
|
63
63
|
},
|
|
@@ -81,4 +81,5 @@ export const countryData: Record<string, Country> = {
|
|
|
81
81
|
IS: { name: 'Islande', name_en: "Iceland", flag: '🇮🇸' },
|
|
82
82
|
IE: { name: 'Irlande', name_en: "Ireland", flag: '🇮🇪' },
|
|
83
83
|
NZ: { name: 'Nouvelle-Zélande', name_en: "New Zealand", flag: '🇳🇿' },
|
|
84
|
+
GH: { name: 'Ghana', name_en: "Ghana", flag: '🇬🇭' },
|
|
84
85
|
};
|