@transfergratis/react-native-sdk 0.1.26 → 0.1.29

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 (39) hide show
  1. package/build/components/EnhancedCameraView.js +1 -1
  2. package/build/components/EnhancedCameraView.js.map +1 -1
  3. package/build/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
  4. package/build/components/KYCElements/CountrySelectionTemplate.js +13 -42
  5. package/build/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
  6. package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  7. package/build/components/KYCElements/IDCardCapture.js +76 -35
  8. package/build/components/KYCElements/IDCardCapture.js.map +1 -1
  9. package/build/components/KYCElements/SelfieCaptureTemplate.js +2 -2
  10. package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
  11. package/build/hooks/useTemplateKYCFlow.js +1 -1
  12. package/build/hooks/useTemplateKYCFlow.js.map +1 -1
  13. package/build/modules/api/CardAuthentification.d.ts +15 -7
  14. package/build/modules/api/CardAuthentification.d.ts.map +1 -1
  15. package/build/modules/api/CardAuthentification.js +215 -44
  16. package/build/modules/api/CardAuthentification.js.map +1 -1
  17. package/build/modules/api/KYCService.d.ts +2 -0
  18. package/build/modules/api/KYCService.d.ts.map +1 -1
  19. package/build/modules/api/KYCService.js +16 -19
  20. package/build/modules/api/KYCService.js.map +1 -1
  21. package/build/modules/camera/VisionCameraModule.js +2 -2
  22. package/build/modules/camera/VisionCameraModule.js.map +1 -1
  23. package/build/utils/cropByObb.d.ts +19 -1
  24. package/build/utils/cropByObb.d.ts.map +1 -1
  25. package/build/utils/cropByObb.js +49 -1
  26. package/build/utils/cropByObb.js.map +1 -1
  27. package/build/utils/pathToBase64.js +1 -1
  28. package/build/utils/pathToBase64.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/components/EnhancedCameraView.tsx +1 -1
  31. package/src/components/KYCElements/CountrySelectionTemplate.tsx +24 -52
  32. package/src/components/KYCElements/IDCardCapture.tsx +129 -90
  33. package/src/components/KYCElements/SelfieCaptureTemplate.tsx +2 -2
  34. package/src/hooks/useTemplateKYCFlow.tsx +1 -1
  35. package/src/modules/api/CardAuthentification.ts +290 -58
  36. package/src/modules/api/KYCService.ts +25 -33
  37. package/src/modules/camera/VisionCameraModule.ts +2 -2
  38. package/src/utils/cropByObb.ts +57 -1
  39. package/src/utils/pathToBase64.ts +1 -1
@@ -3,23 +3,48 @@ import { cropByObb, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from "../../uti
3
3
  import { GovernmentDocumentType, IBbox } from "../../types/KYC.types";
4
4
  import { KycEnvironment } from "../../types/env.types";
5
5
  import { logger } from "../../utils/logger";
6
+ import { countryData } from "../../config/countriesData";
6
7
 
7
- export async function frontVerification(result: { path?: string, regionMapping: { authMethod: string[], mrzTypes: string[] }, selectedDocumentType: string, code: string, currentSide: string, templatePath?: string, mrzType?: string }, env: KycEnvironment = 'PRODUCTION') {
8
+ // 1. Add this interface to tell TypeScript what the AI response actually looks like
9
+ interface CardObbData {
10
+ obb: number[][];
11
+ confidence: number;
12
+ card_in_frame?: boolean;
13
+ cropped_sides?: string[];
14
+ }
15
+
16
+ interface ApiVerificationResponse {
17
+ result?: boolean;
18
+ detail?: any[];
19
+ card_obb?: CardObbData[] | any;
20
+ [key: string]: any;
21
+ }
22
+
23
+ export async function frontVerification(
24
+ result: { path?: string, regionMapping: { authMethod: string[], mrzTypes: string[] }, selectedDocumentType: string, code: string, currentSide: string, templatePath?: string, mrzType?: string },
25
+ env: KycEnvironment = 'PRODUCTION'
26
+ ) {
8
27
  try {
9
28
  console.log("Front verification START", JSON.stringify({ result }, null, 2));
10
29
  logger.log("Front verification", JSON.stringify({ result }, null, 2));
11
30
 
12
- // SANDBOX mode: skip AI verification and return mock response
31
+ // SANDBOX mode
13
32
  if (env === 'SANDBOX') {
14
33
  console.log("SANDBOX mode: Skipping AI verification for front document");
15
34
  logger.log("SANDBOX mode: Returning mock front verification response");
16
- const mockBbox: IBbox = { minX: 50, minY: 50, width: 200, height: 200 };
35
+ const mockBbox: IBbox = { minX: 400, minY: 800, width: 2200, height: 1400 };
36
+
17
37
  const mockResponse = {
18
38
  result: true,
19
39
  detail: [{ confidence: 0.95 }],
20
- card_obb: { x: 50, y: 50, width: 200, height: 200 },
40
+ card_obb: [{
41
+ obb: [[400,800], [2600,800], [2600,2200], [400,2200]],
42
+ confidence: 0.95,
43
+ card_in_frame: true,
44
+ cropped_sides: []
45
+ }],
21
46
  bbox: mockBbox,
22
- ...(result.regionMapping.authMethod.includes('MRZ') ? {
47
+ ...(result.regionMapping?.authMethod?.includes('MRZ') ? {
23
48
  mrz: {
24
49
  success: true,
25
50
  parsed_data: {
@@ -34,30 +59,48 @@ export async function frontVerification(result: { path?: string, regionMapping:
34
59
  }
35
60
 
36
61
  const token = await authentification();
37
- const detected = await kycService.detectFaceOnId(result?.path || '', token, result?.selectedDocumentType || '', env)
62
+
63
+ // Cast the response so TypeScript knows about card_obb
64
+ const detected = await kycService.detectFaceOnId(result?.path || '', token, result?.selectedDocumentType || '', env) as ApiVerificationResponse;
38
65
 
39
66
  if (!detected.result) {
40
67
  throw new Error('Aucun visage détecté sur la carte. Veuillez reprendre.');
41
68
  }
42
69
 
43
- const obbConfidence = getObbConfidence((detected as any).card_obb);
70
+ const cardData = detected.card_obb && Array.isArray(detected.card_obb) && detected.card_obb.length > 0
71
+ ? detected.card_obb[0]
72
+ : null;
73
+
74
+ // --- STRICT FRAMING CHECK ---
75
+ if (cardData && typeof cardData.card_in_frame !== 'undefined') {
76
+ const isCardInFrame = cardData.card_in_frame === true;
77
+ const hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
78
+
79
+ // If it is NOT in the frame, OR if any side is cropped, block progression
80
+ if (!isCardInFrame || hasCroppedSides) {
81
+ logger.log(`Framing failed. Cropped sides: ${hasCroppedSides ? cardData.cropped_sides?.join(', ') : 'none'}`);
82
+ throw new Error('CARD_NOT_FULLY_IN_FRAME');
83
+ }
84
+ }
85
+
86
+ // Check Confidence Threshold
87
+ const obbConfidence = getObbConfidence(detected.card_obb);
44
88
  if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
45
89
  throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
46
90
  }
47
91
 
48
- // Optional: crop image using card_obb for better MRZ/barcode extraction (only when confidence >= threshold)
92
+ // Optional: crop image using card_obb for better MRZ/barcode extraction
49
93
  let croppedBase64: string | undefined;
50
94
  let bbox: IBbox | undefined;
51
95
  let mrz: any | undefined;
52
96
  try {
53
- const crop = await cropByObb(result?.path || '', (detected as any).card_obb);
97
+ const crop = await cropByObb(result?.path || '', detected.card_obb);
54
98
  croppedBase64 = crop.base64;
55
99
  bbox = crop.bbox;
56
100
  } catch { }
57
101
 
58
- if (result.regionMapping.authMethod.length > 0 && result.regionMapping.authMethod.includes('MRZ')) {
59
-
60
-
102
+ // MRZ Extraction
103
+ if (result.regionMapping?.authMethod?.length > 0 && result.regionMapping.authMethod.includes('MRZ')) {
61
104
  mrz = await kycService.extractMrzText(
62
105
  {
63
106
  fileUri: result.path || '',
@@ -68,8 +111,8 @@ export async function frontVerification(result: { path?: string, regionMapping:
68
111
  template_path: result?.templatePath || '',
69
112
  mrz_type: result?.mrzType || ''
70
113
  },
71
- env)
72
-
114
+ env
115
+ );
73
116
  }
74
117
 
75
118
  return { ...detected, croppedBase64, bbox, ...(mrz ? { mrz } : {}) };
@@ -79,18 +122,147 @@ export async function frontVerification(result: { path?: string, regionMapping:
79
122
  }
80
123
  }
81
124
 
82
- export async function backVerification(result: { path?: string, regionMapping: { authMethod: string[], mrzTypes: string[] }, selectedDocumentType: string, code: string, currentSide: string, templatePath?: string, mrzType?: string }, env: KycEnvironment = 'PRODUCTION') {
125
+ // export async function frontVerification(
126
+ // result: { path?: string, regionMapping: { authMethod: string[], mrzTypes: string[] }, selectedDocumentType: string, code: string, currentSide: string, templatePath?: string, mrzType?: string },
127
+ // env: KycEnvironment = 'PRODUCTION'
128
+ // ) {
129
+ // try {
130
+ // console.log("Front verification START", JSON.stringify({ result }, null, 2));
131
+ // logger.log("Front verification", JSON.stringify({ result }, null, 2));
132
+
133
+ // // SANDBOX mode
134
+ // if (env === 'SANDBOX') {
135
+ // console.log("SANDBOX mode: Skipping AI verification for front document");
136
+ // logger.log("SANDBOX mode: Returning mock front verification response");
137
+ // const mockBbox: IBbox = { minX: 400, minY: 800, width: 2200, height: 1400 };
138
+
139
+ // // ==========================================
140
+ // // 🧪 TEST OVERRIDE (SANDBOX) 🧪
141
+ // // Immediately throw the error to test the UI
142
+ // // ==========================================
143
+ // throw new Error('CARD_NOT_FULLY_IN_FRAME');
144
+
145
+ // const mockResponse = {
146
+ // result: true,
147
+ // detail: [{ confidence: 0.95 }],
148
+ // card_obb: [{
149
+ // obb: [[400,800], [2600,800], [2600,2200], [400,2200]],
150
+ // confidence: 0.95,
151
+ // card_in_frame: false, // Hardcoded to false
152
+ // cropped_sides: ['left', 'top'] // Hardcoded cropped sides
153
+ // }],
154
+ // bbox: mockBbox,
155
+ // ...(result.regionMapping?.authMethod?.includes('MRZ') ? {
156
+ // mrz: {
157
+ // success: true,
158
+ // parsed_data: {
159
+ // status: 'success',
160
+ // document_type: result.selectedDocumentType,
161
+ // mrz_type: result.mrzType || 'TD1'
162
+ // }
163
+ // }
164
+ // } : {})
165
+ // };
166
+ // return mockResponse;
167
+ // }
168
+
169
+ // const token = await authentification();
170
+
171
+ // // Cast the response so TypeScript knows about card_obb
172
+ // const detected = await kycService.detectFaceOnId(result?.path || '', token, result?.selectedDocumentType || '', env) as ApiVerificationResponse;
173
+
174
+ // // ==========================================
175
+ // // 🧪 TEST OVERRIDE (PRODUCTION) 🧪
176
+ // // Hijack the real API response and force it to look cropped
177
+ // // ==========================================
178
+ // if (detected.card_obb && Array.isArray(detected.card_obb) && detected.card_obb.length > 0) {
179
+ // detected.card_obb[0].card_in_frame = false;
180
+ // detected.card_obb[0].cropped_sides = ['left', 'top'];
181
+ // console.warn("⚠️ FORCING CROPPED ERROR FOR UI TESTING ⚠️");
182
+ // }
183
+ // // ==========================================
184
+
185
+ // if (!detected.result) {
186
+ // throw new Error('Aucun visage détecté sur la carte. Veuillez reprendre.');
187
+ // }
188
+
189
+ // const cardData = detected.card_obb && Array.isArray(detected.card_obb) && detected.card_obb.length > 0
190
+ // ? detected.card_obb[0]
191
+ // : null;
192
+
193
+ // // --- STRICT FRAMING CHECK ---
194
+ // if (cardData && typeof cardData.card_in_frame !== 'undefined') {
195
+ // const isCardInFrame = cardData.card_in_frame === true;
196
+ // const hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
197
+
198
+ // // If it is NOT in the frame, OR if any side is cropped, block progression
199
+ // if (!isCardInFrame || hasCroppedSides) {
200
+ // logger.log(`Framing failed. Cropped sides: ${hasCroppedSides ? cardData.cropped_sides?.join(', ') : 'none'}`);
201
+ // throw new Error('CARD_NOT_FULLY_IN_FRAME');
202
+ // }
203
+ // }
204
+
205
+ // // Check Confidence Threshold
206
+ // const obbConfidence = getObbConfidence(detected.card_obb);
207
+ // if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
208
+ // throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
209
+ // }
210
+
211
+ // // Optional: crop image using card_obb for better MRZ/barcode extraction
212
+ // let croppedBase64: string | undefined;
213
+ // let bbox: IBbox | undefined;
214
+ // let mrz: any | undefined;
215
+ // try {
216
+ // const crop = await cropByObb(result?.path || '', detected.card_obb);
217
+ // croppedBase64 = crop.base64;
218
+ // bbox = crop.bbox;
219
+ // } catch { }
220
+
221
+ // // MRZ Extraction
222
+ // if (result.regionMapping?.authMethod?.length > 0 && result.regionMapping.authMethod.includes('MRZ')) {
223
+ // mrz = await kycService.extractMrzText(
224
+ // {
225
+ // fileUri: result.path || '',
226
+ // docType: result?.selectedDocumentType || '',
227
+ // docRegion: result?.code || "",
228
+ // postfix: result?.currentSide,
229
+ // token: token,
230
+ // template_path: result?.templatePath || '',
231
+ // mrz_type: result?.mrzType || ''
232
+ // },
233
+ // env
234
+ // );
235
+ // }
236
+
237
+ // return { ...detected, croppedBase64, bbox, ...(mrz ? { mrz } : {}) };
238
+ // } catch (e: any) {
239
+ // logger.error('Error front verification:', JSON.stringify(errorMessage(e), null, 2));
240
+ // throw new Error(e?.message || 'Erreur de détection du visage');
241
+ // }
242
+ // }
243
+
244
+
245
+ export async function backVerification(
246
+ result: { path?: string, regionMapping: { authMethod: string[], mrzTypes: string[] }, selectedDocumentType: string, code: string, currentSide: string, templatePath?: string, mrzType?: string },
247
+ env: KycEnvironment = 'PRODUCTION'
248
+ ) {
83
249
  try {
84
250
  if (!result.path) throw new Error('No path provided');
85
251
  logger.log("result.regionMapping", result.regionMapping, result.currentSide, result.code);
86
252
 
87
- // SANDBOX mode: skip AI verification and return mock response
253
+
88
254
  if (env === 'SANDBOX') {
89
255
  console.log("SANDBOX mode: Skipping AI verification for back document");
90
- logger.log("SANDBOX mode: Returning mock back verification response");
91
- const mockBbox: IBbox = { minX: 50, minY: 50, width: 200, height: 200 };
92
256
 
93
- if (result.regionMapping.authMethod.includes('MRZ')) {
257
+ const mockBbox: IBbox = { minX: 400, minY: 800, width: 2200, height: 1400 };
258
+ const mockCardObb = [{
259
+ obb: [[400,800], [2600,800], [2600,2200], [400,2200]],
260
+ confidence: 0.95,
261
+ card_in_frame: true,
262
+ cropped_sides: []
263
+ }];
264
+
265
+ if (result.regionMapping?.authMethod?.includes('MRZ')) {
94
266
  return {
95
267
  success: true,
96
268
  parsed_data: {
@@ -99,13 +271,13 @@ export async function backVerification(result: { path?: string, regionMapping: {
99
271
  mrz_type: result.mrzType || 'TD1'
100
272
  },
101
273
  bbox: mockBbox,
102
- card_obb: { x: 50, y: 50, width: 200, height: 200 }
274
+ card_obb: mockCardObb
103
275
  };
104
- } else if (result.regionMapping.authMethod.includes('2D_barcode')) {
276
+ } else if (result.regionMapping?.authMethod?.includes('2D_barcode')) {
105
277
  return {
106
278
  barcode_data: 'SANDBOX_MOCK_BARCODE',
107
279
  bbox: mockBbox,
108
- card_obb: { x: 50, y: 50, width: 200, height: 200 }
280
+ card_obb: mockCardObb
109
281
  };
110
282
  }
111
283
  return { bbox: mockBbox };
@@ -113,12 +285,57 @@ export async function backVerification(result: { path?: string, regionMapping: {
113
285
 
114
286
  const token = await authentification();
115
287
 
288
+
289
+ logger.log("1. Checking template and framing for back document...");
290
+
291
+ const templateResponse = await kycService.checkTemplateType({
292
+ fileUri: result.path,
293
+ docType: result.selectedDocumentType as any,
294
+ docRegion: result.code,
295
+ postfix: 'back',
296
+ token: token
297
+ }, env) as ApiVerificationResponse;
298
+
299
+ // STRICT FRAMING CHECK
300
+ const cardData = templateResponse?.card_obb && Array.isArray(templateResponse.card_obb) && templateResponse.card_obb.length > 0
301
+ ? templateResponse.card_obb[0]
302
+ : null;
116
303
 
304
+ if (cardData && typeof cardData.card_in_frame !== 'undefined') {
305
+ const isCardInFrame = cardData.card_in_frame === true;
306
+ const hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
117
307
 
118
- // Fonction helper pour essayer MRZ puis barcode en fallback
308
+ if (!isCardInFrame || hasCroppedSides) {
309
+ logger.log(`Back Framing failed. Cropped sides: ${hasCroppedSides ? cardData.cropped_sides?.join(', ') : 'none'}`);
310
+ throw new Error('CARD_NOT_FULLY_IN_FRAME');
311
+ }
312
+ }
313
+
314
+ const obbConf = getObbConfidence(templateResponse?.card_obb);
315
+ if (obbConf !== null && obbConf < OBB_CONFIDENCE_THRESHOLD) {
316
+ throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
317
+ }
318
+
319
+
320
+ const activeTemplatePath = templateResponse.template_path || result.templatePath || '';
321
+
322
+ if (activeTemplatePath) {
323
+ const expectedCountryName = countryData[result.code]?.name_en || '';
324
+
325
+ const hasCodeMatch = activeTemplatePath.includes(`_${result.code}_`);
326
+ const hasNameMatch = expectedCountryName && activeTemplatePath.toLowerCase().includes(expectedCountryName.toLowerCase());
327
+
328
+ if (!hasCodeMatch && !hasNameMatch) {
329
+ logger.log(`Template mismatch! Expected country: ${result.code} (${expectedCountryName}), Detected: ${activeTemplatePath}`);
330
+ throw new Error(`Le document ne correspond pas au pays sélectionné (${result.code}).`);
331
+ }
332
+ }
333
+
334
+ logger.log("Framing and Country Template verified successfully. Proceeding to Data Extraction.");
335
+
336
+
119
337
  const tryMrzWithBarcodeFallback = async () => {
120
338
  try {
121
-
122
339
  logger.log("Tentative d'extraction MRZ");
123
340
  const mrz = await kycService.extractMrzText({
124
341
  fileUri: result.path!,
@@ -126,31 +343,25 @@ export async function backVerification(result: { path?: string, regionMapping: {
126
343
  docRegion: result?.code || '',
127
344
  postfix: 'back',
128
345
  token: token,
129
- template_path: result?.templatePath || '',
346
+ template_path: activeTemplatePath, // Use the verified template
130
347
  mrz_type: result?.mrzType || ''
131
348
  }, env);
132
- const mrzObbConf = getObbConfidence((mrz as any).card_obb);
133
- if (mrzObbConf !== null && mrzObbConf < OBB_CONFIDENCE_THRESHOLD) {
134
- throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
135
- }
349
+
136
350
  let bbox: IBbox | undefined;
137
351
  let croppedBase64: string | undefined;
138
352
 
139
353
  try {
140
- const crop = await cropByObb(result?.path || '', (mrz as any).card_obb);
354
+ // We use the OBB from our template check to ensure clean cropping
355
+ const crop = await cropByObb(result?.path || '', templateResponse.card_obb);
141
356
  bbox = crop.bbox;
142
357
  croppedBase64 = crop.base64;
143
-
144
358
  } catch { }
145
359
  return { ...mrz, bbox, croppedBase64 }
360
+
146
361
  } catch (mrzError: any) {
147
362
  logger.log("MRZ échoué, tentative d'extraction barcode");
148
363
  try {
149
364
  const barcode = await kycService.extractBarcode({ fileUri: result.path!, token: token }, env);
150
- const barcodeObbConf = getObbConfidence((barcode as any).card_obb);
151
- if (barcodeObbConf !== null && barcodeObbConf < OBB_CONFIDENCE_THRESHOLD) {
152
- throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
153
- }
154
365
  return barcode;
155
366
  } catch (barcodeError: any) {
156
367
  throw new Error(`MRZ et barcode ont échoué. MRZ: ${mrzError?.message}, Barcode: ${barcodeError?.message}`);
@@ -158,32 +369,25 @@ export async function backVerification(result: { path?: string, regionMapping: {
158
369
  }
159
370
  };
160
371
 
161
-
162
-
163
- if (result.regionMapping.authMethod.length > 2 && (!result?.mrzType || result?.mrzType.length === 0)) {
372
+ if (result.regionMapping?.authMethod?.length > 2 && (!result?.mrzType || result?.mrzType.length === 0)) {
164
373
  return await tryMrzWithBarcodeFallback();
165
374
  }
166
375
 
167
- if (result.regionMapping.authMethod.length > 0 && result.regionMapping.authMethod.includes('MRZ') && result?.mrzType && result?.mrzType.length > 0) {
376
+ if (result.regionMapping?.authMethod?.length > 0 && result.regionMapping.authMethod.includes('MRZ') && result?.mrzType && result?.mrzType.length > 0) {
168
377
  try {
169
-
170
- let mrz: any | undefined;
171
- mrz = await kycService.extractMrzText({
378
+ const mrz = await kycService.extractMrzText({
172
379
  fileUri: result.path!,
173
380
  docType: result?.selectedDocumentType || '',
174
381
  docRegion: result?.code || '',
175
382
  postfix: 'back',
176
383
  token: token,
177
- template_path: result?.templatePath || '',
384
+ template_path: activeTemplatePath,
178
385
  mrz_type: result?.mrzType || ''
179
386
  }, env);
180
- const mrzObbConf = getObbConfidence((mrz as any).card_obb);
181
- if (mrzObbConf !== null && mrzObbConf < OBB_CONFIDENCE_THRESHOLD) {
182
- throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
183
- }
387
+
184
388
  let bbox: IBbox | undefined;
185
389
  try {
186
- const crop = await cropByObb(result?.path || '', (mrz as any).card_obb);
390
+ const crop = await cropByObb(result?.path || '', templateResponse.card_obb);
187
391
  bbox = crop.bbox;
188
392
  } catch { }
189
393
  return { ...mrz, bbox };
@@ -192,17 +396,14 @@ export async function backVerification(result: { path?: string, regionMapping: {
192
396
  }
193
397
  }
194
398
 
195
- if (result.regionMapping.authMethod.length > 0 && result.regionMapping.authMethod.includes('2D_barcode')) {
399
+ if (result.regionMapping?.authMethod?.length > 0 && result.regionMapping.authMethod.includes('2D_barcode')) {
196
400
  try {
197
401
  logger.log("Tentative d'extraction barcode");
198
402
  const barcode = await kycService.extractBarcode({ fileUri: result.path!, token: token }, env);
199
- const barcodeObbConf = getObbConfidence((barcode as any).card_obb);
200
- if (barcodeObbConf !== null && barcodeObbConf < OBB_CONFIDENCE_THRESHOLD) {
201
- throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
202
- }
403
+
203
404
  let bbox: IBbox | undefined;
204
405
  try {
205
- const crop = await cropByObb(result?.path || '', (barcode as any).card_obb);
406
+ const crop = await cropByObb(result?.path || '', templateResponse.card_obb);
206
407
  bbox = crop.bbox;
207
408
  } catch { }
208
409
  return { ...barcode, bbox };
@@ -212,10 +413,12 @@ export async function backVerification(result: { path?: string, regionMapping: {
212
413
  }
213
414
  return null;
214
415
  } catch (e: any) {
416
+ if (e?.message === 'CARD_NOT_FULLY_IN_FRAME') throw e; // Bubble the strict framing error specifically
215
417
  throw new Error(e?.message || 'Erreur de détection du MRZ ou barcode');
216
418
  }
217
419
  }
218
420
 
421
+
219
422
  /**
220
423
  * Check template type
221
424
  * @param result
@@ -223,23 +426,52 @@ export async function backVerification(result: { path?: string, regionMapping: {
223
426
  */
224
427
  export async function checkTemplateType(result: { path?: string, docType: string, docRegion: string, postfix: string }, env: KycEnvironment = 'PRODUCTION') {
225
428
  try {
226
- // SANDBOX mode: skip AI verification and return mock response
227
429
  if (env === 'SANDBOX') {
228
- console.log("SANDBOX mode: Skipping AI template type check");
229
- logger.log("SANDBOX mode: Returning mock template type response");
230
430
  return {
231
431
  template_path: `templates/${result.docType}_${result.docRegion}_${result.postfix}.jpg`,
232
- card_obb: { x: 50, y: 50, width: 200, height: 200 }
432
+ card_obb: [{
433
+ obb: [[400,800], [2600,800], [2600,2200], [400,2200]],
434
+ confidence: 0.95,
435
+ card_in_frame: true,
436
+ cropped_sides: []
437
+ }]
233
438
  };
234
439
  }
235
440
 
236
441
  const token = await authentification();
237
442
  const templateType = await kycService.checkTemplateType({ fileUri: result.path || '', docType: result?.docType as GovernmentDocumentType, docRegion: result?.docRegion || "", postfix: result?.postfix, token: token }, env);
238
443
 
444
+ const cardData = templateType.card_obb && Array.isArray(templateType.card_obb) && templateType.card_obb.length > 0
445
+ ? templateType.card_obb[0]
446
+ : null;
447
+
448
+ if (cardData && typeof cardData.card_in_frame !== 'undefined') {
449
+ const isCardInFrame = cardData.card_in_frame === true;
450
+ const hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
451
+
452
+ if (!isCardInFrame || hasCroppedSides) {
453
+ logger.log(`Template Framing failed. Cropped sides: ${hasCroppedSides ? cardData.cropped_sides?.join(', ') : 'none'}`);
454
+ throw new Error('CARD_NOT_FULLY_IN_FRAME');
455
+ }
456
+ }
457
+
458
+
459
+ const LPIPS_THRESHOLD = 0.75;
460
+
461
+ if (templateType.lpips_score !== undefined && templateType.lpips_score > LPIPS_THRESHOLD) {
462
+ logger.log(`🛑 Country Mismatch! LPIPS Score too high: ${templateType.lpips_score}`);
463
+ throw new Error(`Le document présenté ne correspond pas au pays sélectionné (${result.docRegion}).`);
464
+ }
465
+
239
466
  logger.log("templateType result", JSON.stringify(truncateFields(templateType), null, 2));
240
467
  return templateType;
241
468
  } catch (e: any) {
242
469
  logger.error('Error checking template type:', JSON.stringify(errorMessage(e), null, 2));
470
+
471
+ if (e?.message === 'CARD_NOT_FULLY_IN_FRAME' || e?.message?.includes('ne correspond pas')) {
472
+ throw e;
473
+ }
474
+
243
475
  throw new Error(e?.message || 'Erreur de vérification du template');
244
476
  }
245
477
  }