@transfergratis/react-native-sdk 0.1.25 → 0.1.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/android/src/main/AndroidManifest.xml +12 -0
- package/build/components/EnhancedCameraView.web.d.ts.map +1 -1
- package/build/components/EnhancedCameraView.web.js +76 -21
- package/build/components/EnhancedCameraView.web.js.map +1 -1
- package/build/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -1
- package/build/components/KYCElements/EmailVerificationTemplate.js +48 -29
- package/build/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
- package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/components/KYCElements/IDCardCapture.js +48 -11
- package/build/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/components/KYCElements/WelcomeTemplate.js +2 -1
- package/build/components/KYCElements/WelcomeTemplate.js.map +1 -1
- package/build/components/OverLay/type.d.ts +2 -0
- package/build/components/OverLay/type.d.ts.map +1 -1
- package/build/components/OverLay/type.js.map +1 -1
- package/build/components/TemplateKYCExample.d.ts +8 -2
- package/build/components/TemplateKYCExample.d.ts.map +1 -1
- package/build/components/TemplateKYCExample.js +2 -2
- package/build/components/TemplateKYCExample.js.map +1 -1
- package/build/components/TemplateKYCFlowRefactored.d.ts +10 -2
- package/build/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
- package/build/components/TemplateKYCFlowRefactored.js +13 -3
- package/build/components/TemplateKYCFlowRefactored.js.map +1 -1
- package/build/config/KYCConfig.js +1 -1
- package/build/config/KYCConfig.js.map +1 -1
- package/build/hooks/useTemplateKYCFlow.d.ts +14 -2
- package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
- package/build/hooks/useTemplateKYCFlow.js +175 -84
- package/build/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/i18n/en/index.d.ts +2 -0
- package/build/i18n/en/index.d.ts.map +1 -1
- package/build/i18n/en/index.js +3 -1
- package/build/i18n/en/index.js.map +1 -1
- package/build/i18n/fr/index.d.ts +2 -0
- package/build/i18n/fr/index.d.ts.map +1 -1
- package/build/i18n/fr/index.js +3 -1
- package/build/i18n/fr/index.js.map +1 -1
- package/build/i18n/types.d.ts +2 -0
- package/build/i18n/types.d.ts.map +1 -1
- package/build/i18n/types.js.map +1 -1
- package/build/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/modules/api/CardAuthentification.js +28 -2
- package/build/modules/api/CardAuthentification.js.map +1 -1
- package/build/modules/api/KYCService.d.ts +10 -0
- package/build/modules/api/KYCService.d.ts.map +1 -1
- package/build/modules/api/KYCService.js +24 -0
- package/build/modules/api/KYCService.js.map +1 -1
- package/build/modules/camera/VisionCameraModule.web.d.ts.map +1 -1
- package/build/modules/camera/VisionCameraModule.web.js +27 -8
- package/build/modules/camera/VisionCameraModule.web.js.map +1 -1
- package/build/types/KYC.types.d.ts +6 -2
- package/build/types/KYC.types.d.ts.map +1 -1
- package/build/types/KYC.types.js.map +1 -1
- package/build/utils/cropByObb.d.ts +17 -0
- package/build/utils/cropByObb.d.ts.map +1 -1
- package/build/utils/cropByObb.js +51 -1
- package/build/utils/cropByObb.js.map +1 -1
- package/build/web/WebKYCEntry.d.ts.map +1 -1
- package/build/web/WebKYCEntry.js +11 -5
- package/build/web/WebKYCEntry.js.map +1 -1
- package/package.json +1 -1
- package/plugin/build/index.d.ts +1 -0
- package/plugin/build/index.js +3 -1
- package/plugin/build/withRemovePermissions.d.ts +3 -0
- package/plugin/build/withRemovePermissions.js +67 -0
- package/plugin/src/index.ts +2 -1
- package/plugin/src/withRemovePermissions.js +85 -0
- package/plugin/src/withRemovePermissions.ts +83 -0
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/plugin.js +6 -1
- package/src/components/EnhancedCameraView.web.tsx +76 -21
- package/src/components/KYCElements/EmailVerificationTemplate.tsx +47 -33
- package/src/components/KYCElements/IDCardCapture.tsx +51 -10
- package/src/components/KYCElements/WelcomeTemplate.tsx +2 -1
- package/src/components/OverLay/type.ts +2 -0
- package/src/components/TemplateKYCExample.tsx +9 -5
- package/src/components/TemplateKYCFlowRefactored.tsx +24 -6
- package/src/config/KYCConfig.ts +1 -1
- package/src/hooks/useTemplateKYCFlow.tsx +189 -95
- package/src/i18n/en/index.ts +3 -1
- package/src/i18n/fr/index.ts +3 -1
- package/src/i18n/types.ts +2 -0
- package/src/modules/api/CardAuthentification.ts +30 -2
- package/src/modules/api/KYCService.ts +41 -0
- package/src/modules/camera/VisionCameraModule.web.ts +30 -12
- package/src/types/KYC.types.ts +7 -3
- package/src/utils/cropByObb.ts +57 -1
- package/src/web/WebKYCEntry.tsx +17 -6
|
@@ -2,9 +2,11 @@ import React, { useState, useCallback, useMemo, createContext, useContext, useEf
|
|
|
2
2
|
import kycService, { authentification, truncateFields } from '../modules/api/KYCService';
|
|
3
3
|
import useI18n from './useI18n';
|
|
4
4
|
import { logger } from '../utils/logger';
|
|
5
|
+
import { countryMapping } from '../config/region_mapping';
|
|
6
|
+
import { countryData } from '../config/countriesData';
|
|
5
7
|
const TemplateKYCFlowContext = createContext(undefined);
|
|
6
|
-
export const TemplateKYCFlowProvider = ({ children, template, onComplete, onError, onCancel, initialLanguage = 'en', apiKey, env = 'PRODUCTION', existingSessionId, }) => {
|
|
7
|
-
const hookResult = useTemplateKYCFlow(template, onComplete, onError, onCancel, initialLanguage, apiKey, env, existingSessionId);
|
|
8
|
+
export const TemplateKYCFlowProvider = ({ children, template, onComplete, onError, onCancel, initialLanguage = 'en', apiKey, env = 'PRODUCTION', existingSessionId, initialComponentIndex, initialCountryResume, }) => {
|
|
9
|
+
const hookResult = useTemplateKYCFlow(template, onComplete, onError, onCancel, initialLanguage, apiKey, env, existingSessionId, initialComponentIndex, initialCountryResume);
|
|
8
10
|
return (<TemplateKYCFlowContext.Provider value={hookResult}>
|
|
9
11
|
{children}
|
|
10
12
|
</TemplateKYCFlowContext.Provider>);
|
|
@@ -17,7 +19,7 @@ export const useTemplateKYCFlowContext = () => {
|
|
|
17
19
|
}
|
|
18
20
|
return context;
|
|
19
21
|
};
|
|
20
|
-
export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, initialLanguage = 'en', apiKey, env = 'PRODUCTION', existingSessionId,
|
|
22
|
+
export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, initialLanguage = 'en', apiKey, env = 'PRODUCTION', existingSessionId, initialComponentIndex, initialCountryResume) => {
|
|
21
23
|
const { setLocale } = useI18n();
|
|
22
24
|
useEffect(() => {
|
|
23
25
|
setLocale(initialLanguage);
|
|
@@ -70,44 +72,39 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
70
72
|
}, [apiKey]);
|
|
71
73
|
const templateWithReview = useMemo(() => ensureReviewSubmitStep(template), [template, ensureReviewSubmitStep, apiKey]);
|
|
72
74
|
const templateWithReviewAndVerification = useMemo(() => ensureVerificationProgressStep(templateWithReview), [templateWithReview, ensureVerificationProgressStep, apiKey]);
|
|
73
|
-
// État initial du flux
|
|
75
|
+
// État initial du flux (initialComponentIndex = index dans template.components pour reprendre au bon composant)
|
|
74
76
|
const buildInitialState = () => {
|
|
75
|
-
|
|
76
|
-
let validInitialStep = 0;
|
|
77
|
+
let resumeAtIndex = 0;
|
|
77
78
|
let completedComponents = [];
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
let initialComponentData = {};
|
|
80
|
+
logger.log('buildInitialState called', { initialComponentIndex, existingSessionId, initialCountryResume });
|
|
81
|
+
if (initialComponentIndex !== undefined && initialComponentIndex >= 0) {
|
|
80
82
|
const maxIndex = templateWithReviewAndVerification.components.length - 1;
|
|
81
|
-
const
|
|
82
|
-
const requestedComponent = templateWithReviewAndVerification.components[
|
|
83
|
-
logger.log('Processing
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
const requestedIndex = Math.min(initialComponentIndex, maxIndex);
|
|
84
|
+
const requestedComponent = templateWithReviewAndVerification.components[requestedIndex];
|
|
85
|
+
logger.log('Processing initialComponentIndex (component in template)', {
|
|
86
|
+
initialComponentIndex,
|
|
87
|
+
requestedIndex,
|
|
86
88
|
maxIndex,
|
|
87
89
|
componentType: requestedComponent?.type,
|
|
88
|
-
componentId: requestedComponent?.id
|
|
90
|
+
componentId: requestedComponent?.id,
|
|
89
91
|
});
|
|
90
|
-
//
|
|
91
|
-
// car les données de country_selection peuvent être chargées depuis la session
|
|
92
|
+
// Reprendre au composant en cours (id_card, selfie, etc.)
|
|
92
93
|
if (requestedComponent?.type === 'id_card') {
|
|
93
|
-
// Si on a une session existante, on peut rester à id_card et charger les données
|
|
94
94
|
if (existingSessionId) {
|
|
95
95
|
logger.log('id_card with existing session - staying at id_card');
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (validInitialStep > 0) {
|
|
96
|
+
resumeAtIndex = requestedIndex;
|
|
97
|
+
if (resumeAtIndex > 0) {
|
|
99
98
|
completedComponents = templateWithReviewAndVerification.components
|
|
100
|
-
.slice(0,
|
|
99
|
+
.slice(0, resumeAtIndex)
|
|
101
100
|
.map(component => component.id);
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
103
|
else {
|
|
105
|
-
// Si pas de session, revenir à country_selection pour refaire le choix
|
|
106
104
|
const countrySelectionIndex = templateWithReviewAndVerification.components.findIndex(c => c.type === 'country_selection');
|
|
107
105
|
logger.log('id_card without session - going back to country_selection', { countrySelectionIndex });
|
|
108
106
|
if (countrySelectionIndex >= 0) {
|
|
109
|
-
|
|
110
|
-
// Marquer les composants avant country_selection comme complétés
|
|
107
|
+
resumeAtIndex = countrySelectionIndex;
|
|
111
108
|
if (countrySelectionIndex > 0) {
|
|
112
109
|
completedComponents = templateWithReviewAndVerification.components
|
|
113
110
|
.slice(0, countrySelectionIndex)
|
|
@@ -115,40 +112,45 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
115
112
|
}
|
|
116
113
|
}
|
|
117
114
|
else {
|
|
118
|
-
|
|
119
|
-
validInitialStep = 0;
|
|
115
|
+
resumeAtIndex = 0;
|
|
120
116
|
}
|
|
121
117
|
}
|
|
122
118
|
}
|
|
123
119
|
else if (requestedComponent?.type === 'review_submit') {
|
|
124
|
-
|
|
125
|
-
// pour permettre à l'utilisateur de revenir en arrière et vérifier/modifier les données
|
|
126
|
-
validInitialStep = requestedStep;
|
|
127
|
-
// Ne pas marquer les composants précédents comme complétés
|
|
120
|
+
resumeAtIndex = requestedIndex;
|
|
128
121
|
completedComponents = [];
|
|
129
122
|
}
|
|
130
123
|
else {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// Marquer tous les composants précédents comme complétés
|
|
134
|
-
// Cela permet à l'utilisateur de continuer sans refaire les étapes précédentes
|
|
135
|
-
if (validInitialStep > 0) {
|
|
124
|
+
resumeAtIndex = requestedIndex;
|
|
125
|
+
if (resumeAtIndex > 0) {
|
|
136
126
|
completedComponents = templateWithReviewAndVerification.components
|
|
137
|
-
.slice(0,
|
|
127
|
+
.slice(0, resumeAtIndex)
|
|
138
128
|
.map(component => component.id);
|
|
139
129
|
}
|
|
140
130
|
}
|
|
141
|
-
logger.log('Final initial state', {
|
|
142
|
-
|
|
131
|
+
logger.log('Final initial state (resume at component index)', {
|
|
132
|
+
resumeAtIndex,
|
|
143
133
|
completedComponentsCount: completedComponents.length,
|
|
144
|
-
|
|
134
|
+
componentAtResume: templateWithReviewAndVerification.components[resumeAtIndex]?.type,
|
|
145
135
|
});
|
|
146
136
|
}
|
|
137
|
+
if (initialCountryResume?.code && initialCountryResume?.documentType) {
|
|
138
|
+
const countrySel = templateWithReviewAndVerification.components.find(c => c.type === 'country_selection');
|
|
139
|
+
if (countrySel && countryData[initialCountryResume.code]) {
|
|
140
|
+
initialComponentData[countrySel.id] = {
|
|
141
|
+
code: initialCountryResume.code,
|
|
142
|
+
documentType: initialCountryResume.documentType,
|
|
143
|
+
region: initialCountryResume.region || 'root',
|
|
144
|
+
...countryData[initialCountryResume.code],
|
|
145
|
+
};
|
|
146
|
+
logger.log('Prefilled country_selection from URL', { code: initialCountryResume.code, documentType: initialCountryResume.documentType });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
147
149
|
return {
|
|
148
150
|
template: templateWithReviewAndVerification,
|
|
149
|
-
currentComponentIndex:
|
|
151
|
+
currentComponentIndex: resumeAtIndex,
|
|
150
152
|
completedComponents: completedComponents,
|
|
151
|
-
componentData:
|
|
153
|
+
componentData: initialComponentData,
|
|
152
154
|
errors: {},
|
|
153
155
|
isProcessing: false,
|
|
154
156
|
currentLanguage: initialLanguage,
|
|
@@ -159,6 +161,7 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
159
161
|
isInitialized: false,
|
|
160
162
|
isProcessing: false,
|
|
161
163
|
error: null,
|
|
164
|
+
sessionDataRestored: !existingSessionId || Boolean(initialCountryResume?.code && initialCountryResume?.documentType),
|
|
162
165
|
},
|
|
163
166
|
verification: {
|
|
164
167
|
status: 'idle',
|
|
@@ -184,9 +187,9 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
184
187
|
logger.log('No existingSessionId, skipping data load');
|
|
185
188
|
return;
|
|
186
189
|
}
|
|
187
|
-
// Si
|
|
188
|
-
if (
|
|
189
|
-
logger.log('
|
|
190
|
+
// Si initialComponentIndex n'est pas défini ou est 0, on ne charge pas (début de session)
|
|
191
|
+
if (initialComponentIndex === undefined || initialComponentIndex === 0) {
|
|
192
|
+
logger.log('initialComponentIndex is 0 or undefined, skipping data load');
|
|
190
193
|
return;
|
|
191
194
|
}
|
|
192
195
|
// Attendre que la session soit initialisée
|
|
@@ -198,7 +201,7 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
198
201
|
return;
|
|
199
202
|
}
|
|
200
203
|
try {
|
|
201
|
-
logger.log('Loading session data for resume:', { sessionId: existingSessionId,
|
|
204
|
+
logger.log('Loading session data for resume:', { sessionId: existingSessionId, componentIndex: initialComponentIndex });
|
|
202
205
|
const result = await kycService.getVerificationResult(state.session.session_id);
|
|
203
206
|
const sessionData = result[state.session.session_id]?.data;
|
|
204
207
|
if (sessionData) {
|
|
@@ -206,10 +209,9 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
206
209
|
// Utiliser 'as any' car VerificationResult peut avoir des propriétés dynamiques
|
|
207
210
|
const data = sessionData;
|
|
208
211
|
const restoredComponentData = {};
|
|
209
|
-
// Parcourir les composants jusqu'
|
|
210
|
-
// Utiliser initialStep + 1 pour inclure le composant à l'étape initialStep
|
|
212
|
+
// Parcourir les composants jusqu'au composant de reprise (inclu) pour restaurer leurs données
|
|
211
213
|
templateWithReviewAndVerification.components
|
|
212
|
-
.slice(0,
|
|
214
|
+
.slice(0, initialComponentIndex + 1)
|
|
213
215
|
.forEach((component) => {
|
|
214
216
|
// Essayer de restaurer les données selon le type de composant
|
|
215
217
|
if (component.type === 'id_card' || component.type === 'file_upload') {
|
|
@@ -292,8 +294,29 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
292
294
|
}
|
|
293
295
|
}
|
|
294
296
|
else if (component.type === 'country_selection') {
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
+
// Reconstruire country_selection au format attendu par IDCardCapture / CountrySelectionTemplate (code, documentType, region, regionMapping)
|
|
298
|
+
const meta = data.metadata || data.user_data || data;
|
|
299
|
+
const code = meta.country || meta.country_code || meta.code;
|
|
300
|
+
const documentType = meta.document_type || meta.documentType;
|
|
301
|
+
const region = meta.region;
|
|
302
|
+
if (code && documentType != null) {
|
|
303
|
+
const country = countryData[code];
|
|
304
|
+
const mapping = countryMapping[code];
|
|
305
|
+
if (country) {
|
|
306
|
+
restoredComponentData[component.id] = {
|
|
307
|
+
code,
|
|
308
|
+
...country,
|
|
309
|
+
documentType,
|
|
310
|
+
region: region || undefined,
|
|
311
|
+
regionMapping: mapping || undefined,
|
|
312
|
+
};
|
|
313
|
+
logger.log('Restored country_selection for resume', { code, documentType, region });
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
restoredComponentData[component.id] = { code, documentType, region: region || undefined, regionMapping: mapping || undefined };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (data.metadata || data.user_data) {
|
|
297
320
|
restoredComponentData[component.id] = {
|
|
298
321
|
...(data.metadata || {}),
|
|
299
322
|
...(data.user_data || {}),
|
|
@@ -310,6 +333,29 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
310
333
|
}
|
|
311
334
|
}
|
|
312
335
|
});
|
|
336
|
+
// Fallback: si pas de country_selection restauré mais on a des documents avec templatePath, déduire code/documentType (ex. "templates/national_id_CM_front.jpg")
|
|
337
|
+
const countrySelectionComponent = templateWithReviewAndVerification.components.find(c => c.type === 'country_selection');
|
|
338
|
+
if (countrySelectionComponent && !restoredComponentData[countrySelectionComponent.id] && data.documents) {
|
|
339
|
+
const docs = data.documents;
|
|
340
|
+
const firstDoc = Object.values(docs).find(d => d?.templatePath);
|
|
341
|
+
const path = firstDoc?.templatePath || '';
|
|
342
|
+
const match = path.match(/([a-z_]+)_([A-Z]{2})(?:_|$)/i) || path.match(/([A-Z]{2})/);
|
|
343
|
+
const code = match ? (match[2] || match[1]).toUpperCase().slice(0, 2) : null;
|
|
344
|
+
const docTypeFromPath = path.match(/(national_id|identity_card|passport|passport_card)/i)?.[1]?.toLowerCase().replace('identity_card', 'national_id') || null;
|
|
345
|
+
if (code && countryData[code]) {
|
|
346
|
+
const documentType = docTypeFromPath || 'national_id';
|
|
347
|
+
const mapping = countryMapping[code];
|
|
348
|
+
const country = countryData[code];
|
|
349
|
+
restoredComponentData[countrySelectionComponent.id] = {
|
|
350
|
+
code,
|
|
351
|
+
...country,
|
|
352
|
+
documentType,
|
|
353
|
+
region: undefined,
|
|
354
|
+
regionMapping: mapping || undefined,
|
|
355
|
+
};
|
|
356
|
+
logger.log('Restored country_selection from document templatePath', { code, documentType, path });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
313
359
|
// Mettre à jour l'état avec les données restaurées
|
|
314
360
|
if (Object.keys(restoredComponentData).length > 0) {
|
|
315
361
|
logger.log('Session data restored - components:', Object.keys(restoredComponentData));
|
|
@@ -320,21 +366,42 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
320
366
|
...prev.componentData,
|
|
321
367
|
...restoredComponentData,
|
|
322
368
|
},
|
|
369
|
+
session: { ...prev.session, sessionDataRestored: true },
|
|
323
370
|
}));
|
|
324
371
|
logger.log('Component data updated in state');
|
|
325
372
|
}
|
|
326
373
|
else {
|
|
327
374
|
logger.log('No component data to restore from session');
|
|
375
|
+
setState(prev => ({ ...prev, session: { ...prev.session, sessionDataRestored: true } }));
|
|
328
376
|
}
|
|
329
377
|
}
|
|
378
|
+
else {
|
|
379
|
+
setState(prev => ({ ...prev, session: { ...prev.session, sessionDataRestored: true } }));
|
|
380
|
+
}
|
|
330
381
|
}
|
|
331
382
|
catch (error) {
|
|
332
383
|
logger.error('Error loading session data:', truncateFields(error));
|
|
333
|
-
|
|
384
|
+
setState(prev => ({ ...prev, session: { ...prev.session, sessionDataRestored: true } }));
|
|
334
385
|
}
|
|
335
386
|
};
|
|
336
387
|
loadSessionData();
|
|
337
|
-
}, [existingSessionId,
|
|
388
|
+
}, [existingSessionId, initialComponentIndex, state.session.isInitialized, state.session.session_id, templateWithReviewAndVerification.components, base64ToDataUri]);
|
|
389
|
+
// Si l'index pointe vers Review alors que des étapes ne sont pas complétées, ramener à la première étape incomplète
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
const comp = state.template.components[state.currentComponentIndex];
|
|
392
|
+
if (!comp || comp.type !== 'review_submit')
|
|
393
|
+
return;
|
|
394
|
+
const nonReview = state.template.components.filter(c => c.type !== 'review_submit' && c.type !== 'verification_progress');
|
|
395
|
+
if (nonReview.every(c => state.completedComponents.includes(c.id)))
|
|
396
|
+
return;
|
|
397
|
+
const firstIncomplete = nonReview.find(c => !state.completedComponents.includes(c.id));
|
|
398
|
+
if (!firstIncomplete)
|
|
399
|
+
return;
|
|
400
|
+
const targetIndex = state.template.components.findIndex(c => c.id === firstIncomplete.id);
|
|
401
|
+
if (targetIndex >= 0 && targetIndex !== state.currentComponentIndex) {
|
|
402
|
+
setState(prev => ({ ...prev, currentComponentIndex: targetIndex }));
|
|
403
|
+
}
|
|
404
|
+
}, [state.currentComponentIndex, state.completedComponents, state.template.components]);
|
|
338
405
|
const mapComponentTypeToAction = useCallback((type) => {
|
|
339
406
|
switch (type) {
|
|
340
407
|
case 'id_card':
|
|
@@ -386,7 +453,7 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
386
453
|
if (!action) {
|
|
387
454
|
return base;
|
|
388
455
|
}
|
|
389
|
-
// Document upload expects
|
|
456
|
+
// Document upload expects documents; include country_selection in metadata so resume can restore it
|
|
390
457
|
if (action === 'document_upload') {
|
|
391
458
|
const documents = {};
|
|
392
459
|
if (rawData && typeof rawData === 'object') {
|
|
@@ -394,9 +461,19 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
394
461
|
documents[key] = rawData[key];
|
|
395
462
|
});
|
|
396
463
|
}
|
|
464
|
+
const countryComp = state.template.components.find(c => c.type === 'country_selection');
|
|
465
|
+
const countryDataForPayload = countryComp ? state.componentData[countryComp.id] : null;
|
|
466
|
+
const metadata = {};
|
|
467
|
+
if (countryDataForPayload?.code) {
|
|
468
|
+
metadata.country = countryDataForPayload.code;
|
|
469
|
+
metadata.document_type = countryDataForPayload.documentType;
|
|
470
|
+
if (countryDataForPayload.region != null)
|
|
471
|
+
metadata.region = countryDataForPayload.region;
|
|
472
|
+
}
|
|
397
473
|
return {
|
|
398
474
|
...base,
|
|
399
475
|
documents,
|
|
476
|
+
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
|
|
400
477
|
};
|
|
401
478
|
}
|
|
402
479
|
if (action === 'selfie_capture') {
|
|
@@ -424,16 +501,28 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
424
501
|
}, [state.componentData, apiKey]);
|
|
425
502
|
// Ensure the template contains a final review step
|
|
426
503
|
// console.log('apiKey in useTemplateKYCFlow', apiKey);
|
|
427
|
-
// Composant actuel
|
|
504
|
+
// Composant actuel (peut être redirigé si on pointe vers Review alors que le flux n'est pas terminé)
|
|
428
505
|
const currentComponent = useMemo(() => {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
506
|
+
const comp = state.template.components[state.currentComponentIndex] || null;
|
|
507
|
+
if (!comp || comp.type !== 'review_submit')
|
|
508
|
+
return comp;
|
|
509
|
+
const nonReview = state.template.components.filter(c => c.type !== 'review_submit' && c.type !== 'verification_progress');
|
|
510
|
+
const allDone = nonReview.every(c => state.completedComponents.includes(c.id));
|
|
511
|
+
if (allDone)
|
|
512
|
+
return comp;
|
|
513
|
+
const firstIncomplete = nonReview.find(c => !state.completedComponents.includes(c.id));
|
|
514
|
+
return firstIncomplete || comp;
|
|
515
|
+
}, [state.template.components, state.currentComponentIndex, state.completedComponents, apiKey]);
|
|
516
|
+
// Progression du flux (basée sur le composant effectivement affiché)
|
|
432
517
|
const progress = useMemo(() => {
|
|
518
|
+
const idx = currentComponent
|
|
519
|
+
? state.template.components.findIndex(c => c.id === currentComponent.id)
|
|
520
|
+
: state.currentComponentIndex;
|
|
521
|
+
const i = idx >= 0 ? idx : state.currentComponentIndex;
|
|
433
522
|
return state.template.components.length > 0
|
|
434
|
-
? ((
|
|
523
|
+
? ((i + 1) / state.template.components.length) * 100
|
|
435
524
|
: 0;
|
|
436
|
-
}, [
|
|
525
|
+
}, [currentComponent, state.template.components, state.currentComponentIndex, apiKey]);
|
|
437
526
|
// Vérifications de navigation
|
|
438
527
|
const canGoNext = useMemo(() => {
|
|
439
528
|
return state.currentComponentIndex < state.template.components.length - 1;
|
|
@@ -518,12 +607,21 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
518
607
|
}));
|
|
519
608
|
}
|
|
520
609
|
}, [apiKey]);
|
|
521
|
-
//
|
|
522
|
-
|
|
610
|
+
// When user switches device: resume with existingSessionId + initialStep — ensure session is initialized so loadSessionData runs
|
|
611
|
+
useEffect(() => {
|
|
612
|
+
if (!existingSessionId || state.session.isInitialized)
|
|
613
|
+
return;
|
|
614
|
+
if (state.session.session_id !== existingSessionId)
|
|
615
|
+
return;
|
|
616
|
+
logger.log('Resuming on new device: initializing session so data can load', { existingSessionId, initialComponentIndex });
|
|
617
|
+
initializeSession();
|
|
618
|
+
}, [existingSessionId, state.session.session_id, state.session.isInitialized, initializeSession]);
|
|
619
|
+
// Validation d'un composant (dataOverride permet de valider sans attendre la mise à jour du state)
|
|
620
|
+
const validateComponent = useCallback((componentId, dataOverride) => {
|
|
523
621
|
const component = state.template.components.find(c => c.id === componentId);
|
|
524
622
|
if (!component)
|
|
525
623
|
return false;
|
|
526
|
-
const componentData = state.componentData[componentId];
|
|
624
|
+
const componentData = dataOverride !== undefined ? dataOverride : state.componentData[componentId];
|
|
527
625
|
switch (component.type) {
|
|
528
626
|
case 'id_card':
|
|
529
627
|
// Vérifier si au moins un côté a été capturé
|
|
@@ -579,8 +677,8 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
579
677
|
verification: { status: 'idle', result: undefined },
|
|
580
678
|
}));
|
|
581
679
|
}, [ensureReviewSubmitStep, ensureVerificationProgressStep, apiKey]),
|
|
582
|
-
// Passer au composant suivant
|
|
583
|
-
nextComponent: useCallback(async () => {
|
|
680
|
+
// Passer au composant suivant (overrideData = données du step courant si déjà connues, évite un double clic)
|
|
681
|
+
nextComponent: useCallback(async (overrideData) => {
|
|
584
682
|
if (!canGoNext)
|
|
585
683
|
return;
|
|
586
684
|
const currentComp = state.template.components[state.currentComponentIndex];
|
|
@@ -596,8 +694,8 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
596
694
|
isProcessing: true,
|
|
597
695
|
};
|
|
598
696
|
});
|
|
599
|
-
// Valider
|
|
600
|
-
if (!validateComponent(currentComp.id)) {
|
|
697
|
+
// Valider avec override ou state
|
|
698
|
+
if (!validateComponent(currentComp.id, overrideData)) {
|
|
601
699
|
setState(prev => ({
|
|
602
700
|
...prev,
|
|
603
701
|
isProcessing: false,
|
|
@@ -618,17 +716,14 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
618
716
|
return;
|
|
619
717
|
}
|
|
620
718
|
if (component.type === 'review_submit') {
|
|
621
|
-
// Move to verification screen and mark verification in progress
|
|
622
719
|
setState(prev => ({
|
|
623
720
|
...prev,
|
|
624
721
|
currentComponentIndex: prev.currentComponentIndex + 1,
|
|
625
722
|
completedComponents: [...prev.completedComponents, currentComp.id],
|
|
723
|
+
componentData: overrideData !== undefined ? { ...prev.componentData, [currentComp.id]: overrideData } : prev.componentData,
|
|
626
724
|
isProcessing: false,
|
|
627
725
|
verification: { status: 'in_progress' },
|
|
628
|
-
errors: {
|
|
629
|
-
...prev.errors,
|
|
630
|
-
[currentComp.id]: ''
|
|
631
|
-
}
|
|
726
|
+
errors: { ...prev.errors, [currentComp.id]: '' }
|
|
632
727
|
}));
|
|
633
728
|
return;
|
|
634
729
|
}
|
|
@@ -658,17 +753,15 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
658
753
|
...prev,
|
|
659
754
|
currentComponentIndex: prev.currentComponentIndex + 1,
|
|
660
755
|
completedComponents: [...prev.completedComponents, currentComp.id],
|
|
756
|
+
componentData: overrideData !== undefined ? { ...prev.componentData, [currentComp.id]: overrideData } : prev.componentData,
|
|
661
757
|
isProcessing: false,
|
|
662
|
-
errors: {
|
|
663
|
-
...prev.errors,
|
|
664
|
-
[currentComp.id]: ''
|
|
665
|
-
}
|
|
758
|
+
errors: { ...prev.errors, [currentComp.id]: '' }
|
|
666
759
|
}));
|
|
667
760
|
return;
|
|
668
761
|
}
|
|
669
762
|
const step = serverStep === 0 && action !== 'initialize_session' ? 1 : serverStep;
|
|
670
|
-
|
|
671
|
-
const payloadData = buildPayloadForComponent(action, component,
|
|
763
|
+
const currentStepData = overrideData !== undefined ? overrideData : state.componentData[currentComp.id];
|
|
764
|
+
const payloadData = buildPayloadForComponent(action, component, currentStepData, templateId, step);
|
|
672
765
|
console.log('payloadData', action, apiKey);
|
|
673
766
|
await kycService.verificationSession({
|
|
674
767
|
session_id: state.session.session_id,
|
|
@@ -680,17 +773,14 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
680
773
|
apiKey: apiKey ?? "-",
|
|
681
774
|
});
|
|
682
775
|
logger.log("currentComp state", truncateFields(state));
|
|
683
|
-
// Marquer comme complété et passer au suivant
|
|
684
776
|
setState(prev => ({
|
|
685
777
|
...prev,
|
|
686
778
|
currentComponentIndex: prev.currentComponentIndex + 1,
|
|
687
779
|
completedComponents: [...prev.completedComponents, currentComp.id],
|
|
780
|
+
componentData: overrideData !== undefined ? { ...prev.componentData, [currentComp.id]: overrideData } : prev.componentData,
|
|
688
781
|
isProcessing: false,
|
|
689
782
|
...(action === "location_permission" ? { permissionGranted: true } : {}),
|
|
690
|
-
errors: {
|
|
691
|
-
...prev.errors,
|
|
692
|
-
[currentComp.id]: ''
|
|
693
|
-
}
|
|
783
|
+
errors: { ...prev.errors, [currentComp.id]: '' }
|
|
694
784
|
}));
|
|
695
785
|
}
|
|
696
786
|
catch (error) {
|
|
@@ -740,8 +830,8 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
740
830
|
}));
|
|
741
831
|
}, [apiKey]),
|
|
742
832
|
// Valider un composant
|
|
743
|
-
validateComponent: useCallback((componentId) => {
|
|
744
|
-
return validateComponent(componentId);
|
|
833
|
+
validateComponent: useCallback((componentId, dataOverride) => {
|
|
834
|
+
return validateComponent(componentId, dataOverride);
|
|
745
835
|
}, [validateComponent, apiKey]),
|
|
746
836
|
// complet verification
|
|
747
837
|
submitVerification: useCallback(async () => {
|
|
@@ -814,6 +904,7 @@ export const useTemplateKYCFlow = (template, onComplete, onError, onCancel, init
|
|
|
814
904
|
getLocalizedText,
|
|
815
905
|
initializeSession,
|
|
816
906
|
env,
|
|
907
|
+
apiKey,
|
|
817
908
|
};
|
|
818
909
|
};
|
|
819
910
|
//# sourceMappingURL=useTemplateKYCFlow.js.map
|