@transfergratis/react-native-sdk 0.1.24 → 0.1.26

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.
Files changed (182) hide show
  1. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +12 -5
  2. package/android/build/intermediates/aar_main_jar/debug/syncDebugLibJars/classes.jar +0 -0
  3. package/android/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt +0 -0
  4. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  5. package/android/build/intermediates/incremental/debug-mergeJavaRes/merge-state +0 -0
  6. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +61 -59
  7. package/android/build/intermediates/merged_java_res/debug/mergeDebugJavaResource/feature-transfergratis-react-native-sdk.jar +0 -0
  8. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +12 -5
  9. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  10. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  11. package/android/build/outputs/aar/transfergratis-react-native-sdk-debug.aar +0 -0
  12. package/android/build/outputs/logs/manifest-merger-debug-report.txt +26 -34
  13. package/android/src/main/AndroidManifest.xml +22 -7
  14. package/build/components/EnhancedCameraView.web.d.ts.map +1 -1
  15. package/build/components/EnhancedCameraView.web.js +76 -21
  16. package/build/components/EnhancedCameraView.web.js.map +1 -1
  17. package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts +12 -0
  18. package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts.map +1 -0
  19. package/build/components/KYCElements/AdditionalDocumentsTemplate.js +283 -0
  20. package/build/components/KYCElements/AdditionalDocumentsTemplate.js.map +1 -0
  21. package/build/components/KYCElements/EmailVerificationTemplate.d.ts +12 -0
  22. package/build/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -0
  23. package/build/components/KYCElements/EmailVerificationTemplate.js +212 -0
  24. package/build/components/KYCElements/EmailVerificationTemplate.js.map +1 -0
  25. package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  26. package/build/components/KYCElements/IDCardCapture.js +216 -14
  27. package/build/components/KYCElements/IDCardCapture.js.map +1 -1
  28. package/build/components/KYCElements/OrientationVideoCapture.d.ts +2 -0
  29. package/build/components/KYCElements/OrientationVideoCapture.d.ts.map +1 -1
  30. package/build/components/KYCElements/OrientationVideoCapture.js +2 -2
  31. package/build/components/KYCElements/OrientationVideoCapture.js.map +1 -1
  32. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts +2 -0
  33. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts.map +1 -1
  34. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js +2 -2
  35. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js.map +1 -1
  36. package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts +2 -0
  37. package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts.map +1 -1
  38. package/build/components/KYCElements/OrientationVideoCaptureFinal.js +2 -2
  39. package/build/components/KYCElements/OrientationVideoCaptureFinal.js.map +1 -1
  40. package/build/components/KYCElements/PersonalInformationTemplate.d.ts +12 -0
  41. package/build/components/KYCElements/PersonalInformationTemplate.d.ts.map +1 -0
  42. package/build/components/KYCElements/PersonalInformationTemplate.js +120 -0
  43. package/build/components/KYCElements/PersonalInformationTemplate.js.map +1 -0
  44. package/build/components/KYCElements/PhoneVerificationTemplate.d.ts +12 -0
  45. package/build/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -0
  46. package/build/components/KYCElements/PhoneVerificationTemplate.js +185 -0
  47. package/build/components/KYCElements/PhoneVerificationTemplate.js.map +1 -0
  48. package/build/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
  49. package/build/components/KYCElements/SelfieCaptureTemplate.js +7 -3
  50. package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
  51. package/build/components/KYCElements/WelcomeTemplate.js +2 -1
  52. package/build/components/KYCElements/WelcomeTemplate.js.map +1 -1
  53. package/build/components/OverLay/type.d.ts +2 -0
  54. package/build/components/OverLay/type.d.ts.map +1 -1
  55. package/build/components/OverLay/type.js.map +1 -1
  56. package/build/components/TemplateKYCExample.d.ts +10 -0
  57. package/build/components/TemplateKYCExample.d.ts.map +1 -1
  58. package/build/components/TemplateKYCExample.js +7 -30
  59. package/build/components/TemplateKYCExample.js.map +1 -1
  60. package/build/components/TemplateKYCFlowRefactored.d.ts +12 -0
  61. package/build/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  62. package/build/components/TemplateKYCFlowRefactored.js +25 -3
  63. package/build/components/TemplateKYCFlowRefactored.js.map +1 -1
  64. package/build/config/KYCConfig.d.ts +14 -0
  65. package/build/config/KYCConfig.d.ts.map +1 -0
  66. package/build/config/KYCConfig.js +26 -0
  67. package/build/config/KYCConfig.js.map +1 -0
  68. package/build/config/allowedDomains.d.ts.map +1 -1
  69. package/build/config/allowedDomains.js +4 -19
  70. package/build/config/allowedDomains.js.map +1 -1
  71. package/build/hooks/useOrientationVideo.d.ts +2 -1
  72. package/build/hooks/useOrientationVideo.d.ts.map +1 -1
  73. package/build/hooks/useOrientationVideo.js +3 -3
  74. package/build/hooks/useOrientationVideo.js.map +1 -1
  75. package/build/hooks/useTemplateKYCFlow.d.ts +18 -1
  76. package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  77. package/build/hooks/useTemplateKYCFlow.js +410 -56
  78. package/build/hooks/useTemplateKYCFlow.js.map +1 -1
  79. package/build/i18n/en/index.d.ts +42 -0
  80. package/build/i18n/en/index.d.ts.map +1 -1
  81. package/build/i18n/en/index.js +44 -2
  82. package/build/i18n/en/index.js.map +1 -1
  83. package/build/i18n/fr/index.d.ts +28 -0
  84. package/build/i18n/fr/index.d.ts.map +1 -1
  85. package/build/i18n/fr/index.js +30 -2
  86. package/build/i18n/fr/index.js.map +1 -1
  87. package/build/i18n/types.d.ts +2 -0
  88. package/build/i18n/types.d.ts.map +1 -1
  89. package/build/i18n/types.js.map +1 -1
  90. package/build/index.d.ts +1 -0
  91. package/build/index.d.ts.map +1 -1
  92. package/build/index.js +2 -0
  93. package/build/index.js.map +1 -1
  94. package/build/modules/api/CardAuthentification.d.ts +24 -3
  95. package/build/modules/api/CardAuthentification.d.ts.map +1 -1
  96. package/build/modules/api/CardAuthentification.js +90 -12
  97. package/build/modules/api/CardAuthentification.js.map +1 -1
  98. package/build/modules/api/KYCService.d.ts +17 -7
  99. package/build/modules/api/KYCService.d.ts.map +1 -1
  100. package/build/modules/api/KYCService.js +125 -37
  101. package/build/modules/api/KYCService.js.map +1 -1
  102. package/build/modules/api/SelfieVerification.d.ts +3 -1
  103. package/build/modules/api/SelfieVerification.d.ts.map +1 -1
  104. package/build/modules/api/SelfieVerification.js +17 -1
  105. package/build/modules/api/SelfieVerification.js.map +1 -1
  106. package/build/modules/api/TemplateService.d.ts +0 -1
  107. package/build/modules/api/TemplateService.d.ts.map +1 -1
  108. package/build/modules/api/TemplateService.js +3 -3
  109. package/build/modules/api/TemplateService.js.map +1 -1
  110. package/build/modules/camera/VisionCameraModule.web.d.ts.map +1 -1
  111. package/build/modules/camera/VisionCameraModule.web.js +27 -8
  112. package/build/modules/camera/VisionCameraModule.web.js.map +1 -1
  113. package/build/types/KYC.types.d.ts +130 -5
  114. package/build/types/KYC.types.d.ts.map +1 -1
  115. package/build/types/KYC.types.js.map +1 -1
  116. package/build/types/env.types.d.ts +13 -0
  117. package/build/types/env.types.d.ts.map +1 -0
  118. package/build/types/env.types.js +2 -0
  119. package/build/types/env.types.js.map +1 -0
  120. package/build/utils/cropByObb.d.ts +7 -0
  121. package/build/utils/cropByObb.d.ts.map +1 -1
  122. package/build/utils/cropByObb.js +20 -1
  123. package/build/utils/cropByObb.js.map +1 -1
  124. package/build/utils/deviceDetection.d.ts +6 -0
  125. package/build/utils/deviceDetection.d.ts.map +1 -0
  126. package/build/utils/deviceDetection.js +12 -0
  127. package/build/utils/deviceDetection.js.map +1 -0
  128. package/build/utils/platformAlert.d.ts.map +1 -1
  129. package/build/utils/platformAlert.js.map +1 -1
  130. package/build/utils/template-transformer.d.ts.map +1 -1
  131. package/build/utils/template-transformer.js +12 -0
  132. package/build/utils/template-transformer.js.map +1 -1
  133. package/build/web/WebKYCEntry.d.ts.map +1 -1
  134. package/build/web/WebKYCEntry.js +88 -38
  135. package/build/web/WebKYCEntry.js.map +1 -1
  136. package/package.json +1 -1
  137. package/plugin/build/index.d.ts +1 -0
  138. package/plugin/build/index.js +3 -1
  139. package/plugin/build/withRemovePermissions.d.ts +3 -0
  140. package/plugin/build/withRemovePermissions.js +67 -0
  141. package/plugin/build/withVisionCamera.js +3 -4
  142. package/plugin/src/index.ts +2 -1
  143. package/plugin/src/withRemovePermissions.js +85 -0
  144. package/plugin/src/withRemovePermissions.ts +83 -0
  145. package/plugin/src/withVisionCamera.js +3 -4
  146. package/plugin/src/withVisionCamera.ts +3 -4
  147. package/plugin/tsconfig.tsbuildinfo +1 -1
  148. package/plugin.js +6 -1
  149. package/src/components/EnhancedCameraView.web.tsx +76 -21
  150. package/src/components/KYCElements/AdditionalDocumentsTemplate.tsx +346 -0
  151. package/src/components/KYCElements/EmailVerificationTemplate.tsx +278 -0
  152. package/src/components/KYCElements/IDCardCapture.tsx +253 -21
  153. package/src/components/KYCElements/OrientationVideoCapture.tsx +4 -1
  154. package/src/components/KYCElements/OrientationVideoCaptureEnhanced.tsx +4 -1
  155. package/src/components/KYCElements/OrientationVideoCaptureFinal.tsx +4 -1
  156. package/src/components/KYCElements/PersonalInformationTemplate.tsx +158 -0
  157. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +253 -0
  158. package/src/components/KYCElements/SelfieCaptureTemplate.tsx +6 -3
  159. package/src/components/KYCElements/WelcomeTemplate.tsx +2 -1
  160. package/src/components/OverLay/type.ts +2 -0
  161. package/src/components/TemplateKYCExample.tsx +35 -46
  162. package/src/components/TemplateKYCFlowRefactored.tsx +46 -2
  163. package/src/config/KYCConfig.ts +34 -0
  164. package/src/config/allowedDomains.ts +7 -26
  165. package/src/hooks/useOrientationVideo.ts +5 -4
  166. package/src/hooks/useTemplateKYCFlow.tsx +443 -56
  167. package/src/i18n/en/index.ts +46 -3
  168. package/src/i18n/fr/index.ts +31 -2
  169. package/src/i18n/types.ts +2 -0
  170. package/src/index.ts +3 -0
  171. package/src/modules/api/CardAuthentification.ts +98 -12
  172. package/src/modules/api/KYCService.ts +158 -37
  173. package/src/modules/api/SelfieVerification.ts +25 -3
  174. package/src/modules/api/TemplateService.ts +4 -4
  175. package/src/modules/camera/VisionCameraModule.web.ts +30 -12
  176. package/src/types/KYC.types.ts +153 -6
  177. package/src/types/env.types.ts +13 -0
  178. package/src/utils/cropByObb.ts +20 -1
  179. package/src/utils/deviceDetection.ts +11 -0
  180. package/src/utils/platformAlert.ts +1 -0
  181. package/src/utils/template-transformer.ts +20 -8
  182. package/src/web/WebKYCEntry.tsx +123 -61
@@ -1,8 +1,11 @@
1
1
  import React, { useState, useCallback, useMemo, createContext, useContext, ReactNode, useEffect } from 'react';
2
2
  import { KYCTemplate, TemplateState, TemplateActions, UseTemplateReturn, TemplateComponent, GovernmentDocumentType, VerificationState } from '../types/KYC.types';
3
+ import { KycEnvironment } from '../types/env.types';
3
4
  import kycService, { authentification, truncateFields } from '../modules/api/KYCService';
4
5
  import useI18n from './useI18n';
5
6
  import { logger } from '../utils/logger';
7
+ import { countryMapping } from '../config/region_mapping';
8
+ import { countryData } from '../config/countriesData';
6
9
 
7
10
  // Context pour le provider
8
11
  interface TemplateKYCFlowContextType {
@@ -15,6 +18,8 @@ interface TemplateKYCFlowContextType {
15
18
  isComplete: boolean;
16
19
  getLocalizedText: (text: { en: string; fr: string;[key: string]: string }) => string;
17
20
  initializeSession: () => Promise<void>;
21
+ env: KycEnvironment;
22
+ apiKey?: string;
18
23
  }
19
24
 
20
25
  const TemplateKYCFlowContext = createContext<TemplateKYCFlowContextType | undefined>(undefined);
@@ -28,6 +33,12 @@ interface TemplateKYCFlowProviderProps {
28
33
  onCancel?: () => void;
29
34
  initialLanguage?: string;
30
35
  apiKey?: string;
36
+ env?: KycEnvironment;
37
+ existingSessionId?: string;
38
+ /** Index in template.components where to resume (0-based). Simple to store in template table. */
39
+ initialComponentIndex?: number;
40
+ /** Pays / type de document depuis l'URL de reprise — évite de dépendre du backend pour afficher les pays. */
41
+ initialCountryResume?: { code: string; documentType: string; region?: string };
31
42
  }
32
43
 
33
44
  export const TemplateKYCFlowProvider: React.FC<TemplateKYCFlowProviderProps> = ({
@@ -38,8 +49,12 @@ export const TemplateKYCFlowProvider: React.FC<TemplateKYCFlowProviderProps> = (
38
49
  onCancel,
39
50
  initialLanguage = 'en',
40
51
  apiKey,
52
+ env = 'PRODUCTION',
53
+ existingSessionId,
54
+ initialComponentIndex,
55
+ initialCountryResume,
41
56
  }) => {
42
- const hookResult = useTemplateKYCFlow(template, onComplete, onError, onCancel, initialLanguage, apiKey);
57
+ const hookResult = useTemplateKYCFlow(template, onComplete, onError, onCancel, initialLanguage, apiKey, env, existingSessionId, initialComponentIndex, initialCountryResume);
43
58
 
44
59
  return (
45
60
  <TemplateKYCFlowContext.Provider value={hookResult}>
@@ -64,6 +79,10 @@ export const useTemplateKYCFlow = (
64
79
  onCancel?: () => void,
65
80
  initialLanguage: string = 'en',
66
81
  apiKey?: string,
82
+ env: KycEnvironment = 'PRODUCTION',
83
+ existingSessionId?: string,
84
+ initialComponentIndex?: number,
85
+ initialCountryResume?: { code: string; documentType: string; region?: string },
67
86
  ): UseTemplateReturn => {
68
87
 
69
88
  const { setLocale } = useI18n();
@@ -130,30 +149,348 @@ export const useTemplateKYCFlow = (
130
149
  const templateWithReview = useMemo(() => ensureReviewSubmitStep(template), [template, ensureReviewSubmitStep, apiKey]);
131
150
  const templateWithReviewAndVerification = useMemo(() => ensureVerificationProgressStep(templateWithReview), [templateWithReview, ensureVerificationProgressStep, apiKey]);
132
151
 
133
- // État initial du flux
134
- const buildInitialState = (): TemplateState => ({
135
- template: templateWithReviewAndVerification,
136
- currentComponentIndex: 0,
137
- completedComponents: [],
138
- componentData: {},
139
- errors: {},
140
- isProcessing: false,
141
- currentLanguage: initialLanguage,
142
- showCustomStepper: true,
143
- session: {
144
- session_id: '',
145
- token: '',
146
- isInitialized: false,
152
+ // État initial du flux (initialComponentIndex = index dans template.components pour reprendre au bon composant)
153
+ const buildInitialState = (): TemplateState => {
154
+ let resumeAtIndex = 0;
155
+ let completedComponents: number[] = [];
156
+ let initialComponentData: Record<number, unknown> = {};
157
+
158
+ logger.log('buildInitialState called', { initialComponentIndex, existingSessionId, initialCountryResume });
159
+
160
+ if (initialComponentIndex !== undefined && initialComponentIndex >= 0) {
161
+ const maxIndex = templateWithReviewAndVerification.components.length - 1;
162
+ const requestedIndex = Math.min(initialComponentIndex, maxIndex);
163
+ const requestedComponent = templateWithReviewAndVerification.components[requestedIndex];
164
+
165
+ logger.log('Processing initialComponentIndex (component in template)', {
166
+ initialComponentIndex,
167
+ requestedIndex,
168
+ maxIndex,
169
+ componentType: requestedComponent?.type,
170
+ componentId: requestedComponent?.id,
171
+ });
172
+
173
+ // Reprendre au composant en cours (id_card, selfie, etc.)
174
+ if (requestedComponent?.type === 'id_card') {
175
+ if (existingSessionId) {
176
+ logger.log('id_card with existing session - staying at id_card');
177
+ resumeAtIndex = requestedIndex;
178
+ if (resumeAtIndex > 0) {
179
+ completedComponents = templateWithReviewAndVerification.components
180
+ .slice(0, resumeAtIndex)
181
+ .map(component => component.id);
182
+ }
183
+ } else {
184
+ const countrySelectionIndex = templateWithReviewAndVerification.components.findIndex(
185
+ c => c.type === 'country_selection'
186
+ );
187
+ logger.log('id_card without session - going back to country_selection', { countrySelectionIndex });
188
+ if (countrySelectionIndex >= 0) {
189
+ resumeAtIndex = countrySelectionIndex;
190
+ if (countrySelectionIndex > 0) {
191
+ completedComponents = templateWithReviewAndVerification.components
192
+ .slice(0, countrySelectionIndex)
193
+ .map(component => component.id);
194
+ }
195
+ } else {
196
+ resumeAtIndex = 0;
197
+ }
198
+ }
199
+ } else if (requestedComponent?.type === 'review_submit') {
200
+ resumeAtIndex = requestedIndex;
201
+ completedComponents = [];
202
+ } else {
203
+ resumeAtIndex = requestedIndex;
204
+ if (resumeAtIndex > 0) {
205
+ completedComponents = templateWithReviewAndVerification.components
206
+ .slice(0, resumeAtIndex)
207
+ .map(component => component.id);
208
+ }
209
+ }
210
+
211
+ logger.log('Final initial state (resume at component index)', {
212
+ resumeAtIndex,
213
+ completedComponentsCount: completedComponents.length,
214
+ componentAtResume: templateWithReviewAndVerification.components[resumeAtIndex]?.type,
215
+ });
216
+ }
217
+
218
+ if (initialCountryResume?.code && initialCountryResume?.documentType) {
219
+ const countrySel = templateWithReviewAndVerification.components.find(c => c.type === 'country_selection');
220
+ if (countrySel && countryData[initialCountryResume.code]) {
221
+ initialComponentData[countrySel.id] = {
222
+ code: initialCountryResume.code,
223
+ documentType: initialCountryResume.documentType,
224
+ region: initialCountryResume.region || 'root',
225
+ ...countryData[initialCountryResume.code],
226
+ };
227
+ logger.log('Prefilled country_selection from URL', { code: initialCountryResume.code, documentType: initialCountryResume.documentType });
228
+ }
229
+ }
230
+
231
+ return {
232
+ template: templateWithReviewAndVerification,
233
+ currentComponentIndex: resumeAtIndex,
234
+ completedComponents: completedComponents,
235
+ componentData: initialComponentData,
236
+ errors: {},
147
237
  isProcessing: false,
148
- error: null,
149
- },
150
- verification: {
151
- status: 'idle',
152
- },
153
- });
238
+ currentLanguage: initialLanguage,
239
+ showCustomStepper: true,
240
+ session: {
241
+ session_id: existingSessionId || '',
242
+ token: '',
243
+ isInitialized: false,
244
+ isProcessing: false,
245
+ error: null,
246
+ sessionDataRestored: !existingSessionId || Boolean(initialCountryResume?.code && initialCountryResume?.documentType),
247
+ },
248
+ verification: {
249
+ status: 'idle',
250
+ },
251
+ };
252
+ };
154
253
 
155
254
  // État du flux
156
255
  const [state, setState] = useState<TemplateState>(() => buildInitialState());
256
+
257
+ // Fonction utilitaire pour convertir base64 en data URI pour l'affichage
258
+ const base64ToDataUri = useCallback((base64: string, mimeType: string = 'image/jpeg'): string => {
259
+ // Si c'est déjà une data URI, retourner tel quel
260
+ if (base64.startsWith('data:')) {
261
+ return base64;
262
+ }
263
+ // Sinon, créer une data URI
264
+ return `data:${mimeType};base64,${base64}`;
265
+ }, []);
266
+
267
+ // Charger les données de session si on reprend une session existante
268
+ useEffect(() => {
269
+ const loadSessionData = async () => {
270
+ // Ne charger que si on a une session existante
271
+ if (!existingSessionId) {
272
+ logger.log('No existingSessionId, skipping data load');
273
+ return;
274
+ }
275
+
276
+ // Si initialComponentIndex n'est pas défini ou est 0, on ne charge pas (début de session)
277
+ if (initialComponentIndex === undefined || initialComponentIndex === 0) {
278
+ logger.log('initialComponentIndex is 0 or undefined, skipping data load');
279
+ return;
280
+ }
281
+
282
+ // Attendre que la session soit initialisée
283
+ if (!state.session.isInitialized || !state.session.session_id) {
284
+ logger.log('Session not initialized yet, waiting...', {
285
+ isInitialized: state.session.isInitialized,
286
+ sessionId: state.session.session_id
287
+ });
288
+ return;
289
+ }
290
+
291
+ try {
292
+ logger.log('Loading session data for resume:', { sessionId: existingSessionId, componentIndex: initialComponentIndex });
293
+ const result = await kycService.getVerificationResult(state.session.session_id);
294
+ const sessionData = result[state.session.session_id]?.data;
295
+
296
+ if (sessionData) {
297
+ // Restaurer les données des composants depuis la session
298
+ // Utiliser 'as any' car VerificationResult peut avoir des propriétés dynamiques
299
+ const data: any = sessionData;
300
+ const restoredComponentData: Record<number, any> = {};
301
+
302
+ // Parcourir les composants jusqu'au composant de reprise (inclu) pour restaurer leurs données
303
+ templateWithReviewAndVerification.components
304
+ .slice(0, initialComponentIndex + 1)
305
+ .forEach((component) => {
306
+ // Essayer de restaurer les données selon le type de composant
307
+ if (component.type === 'id_card' || component.type === 'file_upload') {
308
+ // Les documents peuvent être dans différentes structures
309
+ let documents: any = null;
310
+
311
+ // Chercher dans différentes structures possibles
312
+ if (data.documents) {
313
+ documents = data.documents;
314
+ } else if (data.user_data?.documents) {
315
+ documents = data.user_data.documents;
316
+ } else if (data.document_images) {
317
+ documents = data.document_images;
318
+ }
319
+
320
+ if (documents) {
321
+ // Convertir les images base64 en format utilisable
322
+ const restoredDocuments: Record<string, any> = {};
323
+
324
+ Object.keys(documents).forEach((key) => {
325
+ const doc = documents[key];
326
+ if (typeof doc === 'object' && doc !== null) {
327
+ // Si on a un fichier base64, créer une structure avec dir et file
328
+ if (doc.file || doc.base64) {
329
+ const base64Data = doc.file || doc.base64;
330
+ restoredDocuments[key] = {
331
+ dir: base64ToDataUri(base64Data), // Utiliser data URI pour l'affichage
332
+ file: base64Data, // Garder le base64 pour l'envoi
333
+ mrz: doc.mrz || '',
334
+ templatePath: doc.templatePath || '',
335
+ };
336
+ } else {
337
+ // Sinon, garder la structure originale
338
+ restoredDocuments[key] = doc;
339
+ }
340
+ } else if (typeof doc === 'string') {
341
+ // Si c'est directement une string base64
342
+ restoredDocuments[key] = {
343
+ dir: base64ToDataUri(doc),
344
+ file: doc,
345
+ mrz: '',
346
+ templatePath: '',
347
+ };
348
+ }
349
+ });
350
+
351
+ if (Object.keys(restoredDocuments).length > 0) {
352
+ restoredComponentData[component.id] = restoredDocuments;
353
+ }
354
+ }
355
+ } else if (component.type === 'selfie') {
356
+ // Les selfies peuvent être dans sessionData.selfie_info
357
+ if (data.selfie_info) {
358
+ const selfieData: Record<string, any> = {};
359
+ const selfieInfo: any = data.selfie_info;
360
+
361
+ // Si selfie_info contient une image
362
+ if (selfieInfo.image) {
363
+ const base64Image = selfieInfo.image;
364
+ selfieData['front'] = {
365
+ dir: base64ToDataUri(base64Image),
366
+ file: base64Image,
367
+ };
368
+ }
369
+
370
+ // Si on a plusieurs orientations
371
+ if (selfieInfo.orientations) {
372
+ Object.keys(selfieInfo.orientations).forEach((orientation) => {
373
+ const img = selfieInfo.orientations[orientation];
374
+ if (img) {
375
+ selfieData[orientation] = {
376
+ dir: base64ToDataUri(img),
377
+ file: img,
378
+ };
379
+ }
380
+ });
381
+ }
382
+
383
+ if (Object.keys(selfieData).length > 0) {
384
+ restoredComponentData[component.id] = selfieData;
385
+ }
386
+ }
387
+ } else if (component.type === 'country_selection') {
388
+ // Reconstruire country_selection au format attendu par IDCardCapture / CountrySelectionTemplate (code, documentType, region, regionMapping)
389
+ const meta = data.metadata || data.user_data || data;
390
+ const code = meta.country || meta.country_code || meta.code;
391
+ const documentType = meta.document_type || meta.documentType;
392
+ const region = meta.region;
393
+ if (code && documentType != null) {
394
+ const country = countryData[code];
395
+ const mapping = countryMapping[code as keyof typeof countryMapping];
396
+ if (country) {
397
+ restoredComponentData[component.id] = {
398
+ code,
399
+ ...country,
400
+ documentType,
401
+ region: region || undefined,
402
+ regionMapping: mapping || undefined,
403
+ };
404
+ logger.log('Restored country_selection for resume', { code, documentType, region });
405
+ } else {
406
+ restoredComponentData[component.id] = { code, documentType, region: region || undefined, regionMapping: mapping || undefined };
407
+ }
408
+ } else if (data.metadata || data.user_data) {
409
+ restoredComponentData[component.id] = {
410
+ ...(data.metadata || {}),
411
+ ...(data.user_data || {}),
412
+ };
413
+ }
414
+ } else if (component.type === 'location') {
415
+ // Les données de localisation peuvent être dans metadata
416
+ if (data.metadata?.location || data.user_data?.location) {
417
+ restoredComponentData[component.id] = {
418
+ ...(data.metadata?.location || {}),
419
+ ...(data.user_data?.location || {}),
420
+ };
421
+ }
422
+ }
423
+ });
424
+
425
+ // 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")
426
+ const countrySelectionComponent = templateWithReviewAndVerification.components.find(c => c.type === 'country_selection');
427
+ if (countrySelectionComponent && !restoredComponentData[countrySelectionComponent.id] && data.documents) {
428
+ const docs = data.documents as Record<string, { templatePath?: string }>;
429
+ const firstDoc = Object.values(docs).find(d => d?.templatePath);
430
+ const path = firstDoc?.templatePath || '';
431
+ const match = path.match(/([a-z_]+)_([A-Z]{2})(?:_|$)/i) || path.match(/([A-Z]{2})/);
432
+ const code = match ? (match[2] || match[1]).toUpperCase().slice(0, 2) : null;
433
+ const docTypeFromPath = path.match(/(national_id|identity_card|passport|passport_card)/i)?.[1]?.toLowerCase().replace('identity_card', 'national_id') || null;
434
+ if (code && countryData[code]) {
435
+ const documentType = docTypeFromPath || 'national_id';
436
+ const mapping = countryMapping[code as keyof typeof countryMapping];
437
+ const country = countryData[code];
438
+ restoredComponentData[countrySelectionComponent.id] = {
439
+ code,
440
+ ...country,
441
+ documentType,
442
+ region: undefined,
443
+ regionMapping: mapping || undefined,
444
+ };
445
+ logger.log('Restored country_selection from document templatePath', { code, documentType, path });
446
+ }
447
+ }
448
+
449
+ // Mettre à jour l'état avec les données restaurées
450
+ if (Object.keys(restoredComponentData).length > 0) {
451
+ logger.log('Session data restored - components:', Object.keys(restoredComponentData));
452
+ logger.log('Session data restored - sample data:', truncateFields(restoredComponentData));
453
+ setState(prev => ({
454
+ ...prev,
455
+ componentData: {
456
+ ...prev.componentData,
457
+ ...restoredComponentData,
458
+ },
459
+ session: { ...prev.session, sessionDataRestored: true },
460
+ }));
461
+ logger.log('Component data updated in state');
462
+ } else {
463
+ logger.log('No component data to restore from session');
464
+ setState(prev => ({ ...prev, session: { ...prev.session, sessionDataRestored: true } }));
465
+ }
466
+ } else {
467
+ setState(prev => ({ ...prev, session: { ...prev.session, sessionDataRestored: true } }));
468
+ }
469
+ } catch (error) {
470
+ logger.error('Error loading session data:', truncateFields(error));
471
+ setState(prev => ({ ...prev, session: { ...prev.session, sessionDataRestored: true } }));
472
+ }
473
+ };
474
+
475
+ loadSessionData();
476
+ }, [existingSessionId, initialComponentIndex, state.session.isInitialized, state.session.session_id, templateWithReviewAndVerification.components, base64ToDataUri]);
477
+
478
+ // Si l'index pointe vers Review alors que des étapes ne sont pas complétées, ramener à la première étape incomplète
479
+ useEffect(() => {
480
+ const comp = state.template.components[state.currentComponentIndex];
481
+ if (!comp || comp.type !== 'review_submit') return;
482
+ const nonReview = state.template.components.filter(
483
+ c => c.type !== 'review_submit' && c.type !== 'verification_progress'
484
+ );
485
+ if (nonReview.every(c => state.completedComponents.includes(c.id))) return;
486
+ const firstIncomplete = nonReview.find(c => !state.completedComponents.includes(c.id));
487
+ if (!firstIncomplete) return;
488
+ const targetIndex = state.template.components.findIndex(c => c.id === firstIncomplete.id);
489
+ if (targetIndex >= 0 && targetIndex !== state.currentComponentIndex) {
490
+ setState(prev => ({ ...prev, currentComponentIndex: targetIndex }));
491
+ }
492
+ }, [state.currentComponentIndex, state.completedComponents, state.template.components]);
493
+
157
494
  const mapComponentTypeToAction = useCallback((type: TemplateComponent['type']): string | null => {
158
495
  switch (type) {
159
496
  case 'id_card':
@@ -208,7 +545,7 @@ export const useTemplateKYCFlow = (
208
545
  if (!action) {
209
546
  return base;
210
547
  }
211
- // Document upload expects an array of documents with base64 and metadata
548
+ // Document upload expects documents; include country_selection in metadata so resume can restore it
212
549
  if (action === 'document_upload') {
213
550
  const documents: Record<string, any> = {};
214
551
  if (rawData && typeof rawData === 'object') {
@@ -216,9 +553,18 @@ export const useTemplateKYCFlow = (
216
553
  documents[key] = rawData[key];
217
554
  });
218
555
  }
556
+ const countryComp = state.template.components.find(c => c.type === 'country_selection');
557
+ const countryDataForPayload = countryComp ? state.componentData[countryComp.id] : null;
558
+ const metadata: Record<string, unknown> = {};
559
+ if (countryDataForPayload?.code) {
560
+ metadata.country = countryDataForPayload.code;
561
+ metadata.document_type = countryDataForPayload.documentType;
562
+ if (countryDataForPayload.region != null) metadata.region = countryDataForPayload.region;
563
+ }
219
564
  return {
220
565
  ...base,
221
566
  documents,
567
+ ...(Object.keys(metadata).length > 0 ? { metadata } : {}),
222
568
  };
223
569
  }
224
570
 
@@ -253,17 +599,29 @@ export const useTemplateKYCFlow = (
253
599
  // console.log('apiKey in useTemplateKYCFlow', apiKey);
254
600
 
255
601
 
256
- // Composant actuel
602
+ // Composant actuel (peut être redirigé si on pointe vers Review alors que le flux n'est pas terminé)
257
603
  const currentComponent = useMemo(() => {
258
- return state.template.components[state.currentComponentIndex] || null;
259
- }, [state.template.components, state.currentComponentIndex, apiKey]);
260
-
261
- // Progression du flux
604
+ const comp = state.template.components[state.currentComponentIndex] || null;
605
+ if (!comp || comp.type !== 'review_submit') return comp;
606
+ const nonReview = state.template.components.filter(
607
+ c => c.type !== 'review_submit' && c.type !== 'verification_progress'
608
+ );
609
+ const allDone = nonReview.every(c => state.completedComponents.includes(c.id));
610
+ if (allDone) return comp;
611
+ const firstIncomplete = nonReview.find(c => !state.completedComponents.includes(c.id));
612
+ return firstIncomplete || comp;
613
+ }, [state.template.components, state.currentComponentIndex, state.completedComponents, apiKey]);
614
+
615
+ // Progression du flux (basée sur le composant effectivement affiché)
262
616
  const progress = useMemo(() => {
617
+ const idx = currentComponent
618
+ ? state.template.components.findIndex(c => c.id === currentComponent.id)
619
+ : state.currentComponentIndex;
620
+ const i = idx >= 0 ? idx : state.currentComponentIndex;
263
621
  return state.template.components.length > 0
264
- ? ((state.currentComponentIndex + 1) / state.template.components.length) * 100
622
+ ? ((i + 1) / state.template.components.length) * 100
265
623
  : 0;
266
- }, [state.currentComponentIndex, state.template.components.length, apiKey]);
624
+ }, [currentComponent, state.template.components, state.currentComponentIndex, apiKey]);
267
625
 
268
626
  // Vérifications de navigation
269
627
  const canGoNext = useMemo(() => {
@@ -301,7 +659,18 @@ export const useTemplateKYCFlow = (
301
659
 
302
660
  const token = apiKey ? undefined : await authentification();
303
661
  console.log('token in initializeSession', { token, apiKey },);
304
- const session = await kycService.newSession({token,apiKey});
662
+
663
+ // Check if we already have a session ID from URL params (passed via state or prop)
664
+ let session;
665
+ const existingSessionId = state.session.session_id; // This might be set from initial props if we add logic for it
666
+
667
+ if (existingSessionId && existingSessionId.length > 0) {
668
+ logger.log('Resuming existing session:', existingSessionId);
669
+ // Verify existence/validity if needed, for now trust the ID and just fetch/use it
670
+ session = { session_id: existingSessionId };
671
+ } else {
672
+ session = await kycService.newSession({ token, apiKey });
673
+ }
305
674
 
306
675
  // Align backend flow from step 0 with initialize_session
307
676
  try {
@@ -345,12 +714,21 @@ export const useTemplateKYCFlow = (
345
714
  }));
346
715
  }
347
716
  }, [apiKey]);
348
- // Validation d'un composant
349
- const validateComponent = useCallback((componentId: number): boolean => {
717
+
718
+ // When user switches device: resume with existingSessionId + initialStep — ensure session is initialized so loadSessionData runs
719
+ useEffect(() => {
720
+ if (!existingSessionId || state.session.isInitialized) return;
721
+ if (state.session.session_id !== existingSessionId) return;
722
+ logger.log('Resuming on new device: initializing session so data can load', { existingSessionId, initialComponentIndex });
723
+ initializeSession();
724
+ }, [existingSessionId, state.session.session_id, state.session.isInitialized, initializeSession]);
725
+
726
+ // Validation d'un composant (dataOverride permet de valider sans attendre la mise à jour du state)
727
+ const validateComponent = useCallback((componentId: number, dataOverride?: any): boolean => {
350
728
  const component = state.template.components.find(c => c.id === componentId);
351
729
  if (!component) return false;
352
730
 
353
- const componentData = state.componentData[componentId];
731
+ const componentData = dataOverride !== undefined ? dataOverride : state.componentData[componentId];
354
732
 
355
733
  switch (component.type) {
356
734
  case 'id_card':
@@ -381,6 +759,20 @@ export const useTemplateKYCFlow = (
381
759
  // Welcome is valid once user has given consent (componentData is set when they click Get Started)
382
760
  return componentData && componentData.consentGiven !== false;
383
761
 
762
+ case 'email_verification':
763
+ return componentData && componentData.verified === true;
764
+
765
+ case 'phone_verification':
766
+ return componentData && componentData.verified === true;
767
+
768
+ case 'personal_information':
769
+ return componentData && Object.keys(componentData).length > 0;
770
+
771
+ case 'additional_documents':
772
+ // Optional by default in template config, but if required we should check based on config
773
+ // For now, return true or check length if present
774
+ return true;
775
+
384
776
  case 'review_submit':
385
777
  return true;
386
778
  default:
@@ -406,8 +798,8 @@ export const useTemplateKYCFlow = (
406
798
  }));
407
799
  }, [ensureReviewSubmitStep, ensureVerificationProgressStep, apiKey]),
408
800
 
409
- // Passer au composant suivant
410
- nextComponent: useCallback(async () => {
801
+ // Passer au composant suivant (overrideData = données du step courant si déjà connues, évite un double clic)
802
+ nextComponent: useCallback(async (overrideData?: any) => {
411
803
  if (!canGoNext) return;
412
804
 
413
805
  const currentComp = state.template.components[state.currentComponentIndex];
@@ -423,8 +815,8 @@ export const useTemplateKYCFlow = (
423
815
  isProcessing: true,
424
816
  };
425
817
  });
426
- // Valider le composant actuel
427
- if (!validateComponent(currentComp.id)) {
818
+ // Valider avec override ou state
819
+ if (!validateComponent(currentComp.id, overrideData)) {
428
820
  setState(prev => ({
429
821
  ...prev,
430
822
  isProcessing: false,
@@ -446,18 +838,16 @@ export const useTemplateKYCFlow = (
446
838
  }));
447
839
  return;
448
840
  }
841
+
449
842
  if (component.type === 'review_submit') {
450
- // Move to verification screen and mark verification in progress
451
843
  setState(prev => ({
452
844
  ...prev,
453
845
  currentComponentIndex: prev.currentComponentIndex + 1,
454
846
  completedComponents: [...prev.completedComponents, currentComp.id],
847
+ componentData: overrideData !== undefined ? { ...prev.componentData, [currentComp.id]: overrideData } : prev.componentData,
455
848
  isProcessing: false,
456
849
  verification: { status: 'in_progress' },
457
- errors: {
458
- ...prev.errors,
459
- [currentComp.id]: ''
460
- }
850
+ errors: { ...prev.errors, [currentComp.id]: '' }
461
851
  }));
462
852
  return;
463
853
  }
@@ -488,18 +878,16 @@ export const useTemplateKYCFlow = (
488
878
  ...prev,
489
879
  currentComponentIndex: prev.currentComponentIndex + 1,
490
880
  completedComponents: [...prev.completedComponents, currentComp.id],
881
+ componentData: overrideData !== undefined ? { ...prev.componentData, [currentComp.id]: overrideData } : prev.componentData,
491
882
  isProcessing: false,
492
- errors: {
493
- ...prev.errors,
494
- [currentComp.id]: ''
495
- }
883
+ errors: { ...prev.errors, [currentComp.id]: '' }
496
884
  }));
497
885
  return;
498
886
  }
499
887
 
500
888
  const step = serverStep === 0 && action !== 'initialize_session' ? 1 : serverStep;
501
- // Build payload data per action
502
- const payloadData = buildPayloadForComponent(action, component, state.componentData[currentComp.id], templateId, step);
889
+ const currentStepData = overrideData !== undefined ? overrideData : state.componentData[currentComp.id];
890
+ const payloadData = buildPayloadForComponent(action, component, currentStepData, templateId, step);
503
891
  console.log('payloadData', action, apiKey);
504
892
 
505
893
  await kycService.verificationSession({
@@ -512,17 +900,14 @@ export const useTemplateKYCFlow = (
512
900
  apiKey: apiKey ?? "-",
513
901
  });
514
902
  logger.log("currentComp state", truncateFields(state));
515
- // Marquer comme complété et passer au suivant
516
903
  setState(prev => ({
517
904
  ...prev,
518
905
  currentComponentIndex: prev.currentComponentIndex + 1,
519
906
  completedComponents: [...prev.completedComponents, currentComp.id],
907
+ componentData: overrideData !== undefined ? { ...prev.componentData, [currentComp.id]: overrideData } : prev.componentData,
520
908
  isProcessing: false,
521
909
  ...(action === "location_permission" ? { permissionGranted: true } : {}),
522
- errors: {
523
- ...prev.errors,
524
- [currentComp.id]: ''
525
- }
910
+ errors: { ...prev.errors, [currentComp.id]: '' }
526
911
  }));
527
912
 
528
913
  } catch (error) {
@@ -579,8 +964,8 @@ export const useTemplateKYCFlow = (
579
964
  }, [apiKey]),
580
965
 
581
966
  // Valider un composant
582
- validateComponent: useCallback((componentId: number) => {
583
- return validateComponent(componentId);
967
+ validateComponent: useCallback((componentId: number, dataOverride?: any) => {
968
+ return validateComponent(componentId, dataOverride);
584
969
  }, [validateComponent, apiKey]),
585
970
  // complet verification
586
971
  submitVerification: useCallback(async () => {
@@ -660,5 +1045,7 @@ export const useTemplateKYCFlow = (
660
1045
  isComplete,
661
1046
  getLocalizedText,
662
1047
  initializeSession,
663
- };
1048
+ env,
1049
+ apiKey,
1050
+ } as UseTemplateReturn;
664
1051
  };