@transfergratis/react-native-sdk 0.1.28 → 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 +64 -31
  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 -50
  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 +8 -0
  24. package/build/utils/cropByObb.d.ts.map +1 -1
  25. package/build/utils/cropByObb.js +20 -3
  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 +115 -86
  33. package/src/components/KYCElements/SelfieCaptureTemplate.tsx +2 -2
  34. package/src/hooks/useTemplateKYCFlow.tsx +1 -1
  35. package/src/modules/api/CardAuthentification.ts +289 -64
  36. package/src/modules/api/KYCService.ts +25 -33
  37. package/src/modules/camera/VisionCameraModule.ts +2 -2
  38. package/src/utils/cropByObb.ts +22 -3
  39. package/src/utils/pathToBase64.ts +1 -1
@@ -1,25 +1,50 @@
1
1
  import kycService, { authentification, errorMessage, truncateFields } from "./KYCService";
2
- import { cropByObb, getCardInFrame, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from "../../utils/cropByObb";
2
+ import { cropByObb, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from "../../utils/cropByObb";
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,37 +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
- // If the backend indicates that the card is not fully in frame, stop early
44
- const cardInFrame = getCardInFrame((detected as any).card_obb);
45
- if (cardInFrame === false) {
46
- // Use a stable error code; the UI maps this to a localized i18n message (cardNotFullyInFrame)
47
- throw new Error('CARD_NOT_FULLY_IN_FRAME');
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
+ }
48
84
  }
49
85
 
50
- const obbConfidence = getObbConfidence((detected as any).card_obb);
86
+ // Check Confidence Threshold
87
+ const obbConfidence = getObbConfidence(detected.card_obb);
51
88
  if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
52
89
  throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
53
90
  }
54
91
 
55
- // 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
56
93
  let croppedBase64: string | undefined;
57
94
  let bbox: IBbox | undefined;
58
95
  let mrz: any | undefined;
59
96
  try {
60
- const crop = await cropByObb(result?.path || '', (detected as any).card_obb);
97
+ const crop = await cropByObb(result?.path || '', detected.card_obb);
61
98
  croppedBase64 = crop.base64;
62
99
  bbox = crop.bbox;
63
100
  } catch { }
64
101
 
65
- if (result.regionMapping.authMethod.length > 0 && result.regionMapping.authMethod.includes('MRZ')) {
66
-
67
-
102
+ // MRZ Extraction
103
+ if (result.regionMapping?.authMethod?.length > 0 && result.regionMapping.authMethod.includes('MRZ')) {
68
104
  mrz = await kycService.extractMrzText(
69
105
  {
70
106
  fileUri: result.path || '',
@@ -75,8 +111,8 @@ export async function frontVerification(result: { path?: string, regionMapping:
75
111
  template_path: result?.templatePath || '',
76
112
  mrz_type: result?.mrzType || ''
77
113
  },
78
- env)
79
-
114
+ env
115
+ );
80
116
  }
81
117
 
82
118
  return { ...detected, croppedBase64, bbox, ...(mrz ? { mrz } : {}) };
@@ -86,18 +122,147 @@ export async function frontVerification(result: { path?: string, regionMapping:
86
122
  }
87
123
  }
88
124
 
89
- 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
+ ) {
90
249
  try {
91
250
  if (!result.path) throw new Error('No path provided');
92
251
  logger.log("result.regionMapping", result.regionMapping, result.currentSide, result.code);
93
252
 
94
- // SANDBOX mode: skip AI verification and return mock response
253
+
95
254
  if (env === 'SANDBOX') {
96
255
  console.log("SANDBOX mode: Skipping AI verification for back document");
97
- logger.log("SANDBOX mode: Returning mock back verification response");
98
- const mockBbox: IBbox = { minX: 50, minY: 50, width: 200, height: 200 };
99
256
 
100
- 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')) {
101
266
  return {
102
267
  success: true,
103
268
  parsed_data: {
@@ -106,13 +271,13 @@ export async function backVerification(result: { path?: string, regionMapping: {
106
271
  mrz_type: result.mrzType || 'TD1'
107
272
  },
108
273
  bbox: mockBbox,
109
- card_obb: { x: 50, y: 50, width: 200, height: 200 }
274
+ card_obb: mockCardObb
110
275
  };
111
- } else if (result.regionMapping.authMethod.includes('2D_barcode')) {
276
+ } else if (result.regionMapping?.authMethod?.includes('2D_barcode')) {
112
277
  return {
113
278
  barcode_data: 'SANDBOX_MOCK_BARCODE',
114
279
  bbox: mockBbox,
115
- card_obb: { x: 50, y: 50, width: 200, height: 200 }
280
+ card_obb: mockCardObb
116
281
  };
117
282
  }
118
283
  return { bbox: mockBbox };
@@ -120,12 +285,57 @@ export async function backVerification(result: { path?: string, regionMapping: {
120
285
 
121
286
  const token = await authentification();
122
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;
123
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;
124
307
 
125
- // 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
+
126
337
  const tryMrzWithBarcodeFallback = async () => {
127
338
  try {
128
-
129
339
  logger.log("Tentative d'extraction MRZ");
130
340
  const mrz = await kycService.extractMrzText({
131
341
  fileUri: result.path!,
@@ -133,31 +343,25 @@ export async function backVerification(result: { path?: string, regionMapping: {
133
343
  docRegion: result?.code || '',
134
344
  postfix: 'back',
135
345
  token: token,
136
- template_path: result?.templatePath || '',
346
+ template_path: activeTemplatePath, // Use the verified template
137
347
  mrz_type: result?.mrzType || ''
138
348
  }, env);
139
- const mrzObbConf = getObbConfidence((mrz as any).card_obb);
140
- if (mrzObbConf !== null && mrzObbConf < OBB_CONFIDENCE_THRESHOLD) {
141
- throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
142
- }
349
+
143
350
  let bbox: IBbox | undefined;
144
351
  let croppedBase64: string | undefined;
145
352
 
146
353
  try {
147
- 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);
148
356
  bbox = crop.bbox;
149
357
  croppedBase64 = crop.base64;
150
-
151
358
  } catch { }
152
359
  return { ...mrz, bbox, croppedBase64 }
360
+
153
361
  } catch (mrzError: any) {
154
362
  logger.log("MRZ échoué, tentative d'extraction barcode");
155
363
  try {
156
364
  const barcode = await kycService.extractBarcode({ fileUri: result.path!, token: token }, env);
157
- const barcodeObbConf = getObbConfidence((barcode as any).card_obb);
158
- if (barcodeObbConf !== null && barcodeObbConf < OBB_CONFIDENCE_THRESHOLD) {
159
- throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
160
- }
161
365
  return barcode;
162
366
  } catch (barcodeError: any) {
163
367
  throw new Error(`MRZ et barcode ont échoué. MRZ: ${mrzError?.message}, Barcode: ${barcodeError?.message}`);
@@ -165,32 +369,25 @@ export async function backVerification(result: { path?: string, regionMapping: {
165
369
  }
166
370
  };
167
371
 
168
-
169
-
170
- 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)) {
171
373
  return await tryMrzWithBarcodeFallback();
172
374
  }
173
375
 
174
- 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) {
175
377
  try {
176
-
177
- let mrz: any | undefined;
178
- mrz = await kycService.extractMrzText({
378
+ const mrz = await kycService.extractMrzText({
179
379
  fileUri: result.path!,
180
380
  docType: result?.selectedDocumentType || '',
181
381
  docRegion: result?.code || '',
182
382
  postfix: 'back',
183
383
  token: token,
184
- template_path: result?.templatePath || '',
384
+ template_path: activeTemplatePath,
185
385
  mrz_type: result?.mrzType || ''
186
386
  }, env);
187
- const mrzObbConf = getObbConfidence((mrz as any).card_obb);
188
- if (mrzObbConf !== null && mrzObbConf < OBB_CONFIDENCE_THRESHOLD) {
189
- throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
190
- }
387
+
191
388
  let bbox: IBbox | undefined;
192
389
  try {
193
- const crop = await cropByObb(result?.path || '', (mrz as any).card_obb);
390
+ const crop = await cropByObb(result?.path || '', templateResponse.card_obb);
194
391
  bbox = crop.bbox;
195
392
  } catch { }
196
393
  return { ...mrz, bbox };
@@ -199,17 +396,14 @@ export async function backVerification(result: { path?: string, regionMapping: {
199
396
  }
200
397
  }
201
398
 
202
- 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')) {
203
400
  try {
204
401
  logger.log("Tentative d'extraction barcode");
205
402
  const barcode = await kycService.extractBarcode({ fileUri: result.path!, token: token }, env);
206
- const barcodeObbConf = getObbConfidence((barcode as any).card_obb);
207
- if (barcodeObbConf !== null && barcodeObbConf < OBB_CONFIDENCE_THRESHOLD) {
208
- throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
209
- }
403
+
210
404
  let bbox: IBbox | undefined;
211
405
  try {
212
- const crop = await cropByObb(result?.path || '', (barcode as any).card_obb);
406
+ const crop = await cropByObb(result?.path || '', templateResponse.card_obb);
213
407
  bbox = crop.bbox;
214
408
  } catch { }
215
409
  return { ...barcode, bbox };
@@ -219,10 +413,12 @@ export async function backVerification(result: { path?: string, regionMapping: {
219
413
  }
220
414
  return null;
221
415
  } catch (e: any) {
416
+ if (e?.message === 'CARD_NOT_FULLY_IN_FRAME') throw e; // Bubble the strict framing error specifically
222
417
  throw new Error(e?.message || 'Erreur de détection du MRZ ou barcode');
223
418
  }
224
419
  }
225
420
 
421
+
226
422
  /**
227
423
  * Check template type
228
424
  * @param result
@@ -230,23 +426,52 @@ export async function backVerification(result: { path?: string, regionMapping: {
230
426
  */
231
427
  export async function checkTemplateType(result: { path?: string, docType: string, docRegion: string, postfix: string }, env: KycEnvironment = 'PRODUCTION') {
232
428
  try {
233
- // SANDBOX mode: skip AI verification and return mock response
234
429
  if (env === 'SANDBOX') {
235
- console.log("SANDBOX mode: Skipping AI template type check");
236
- logger.log("SANDBOX mode: Returning mock template type response");
237
430
  return {
238
431
  template_path: `templates/${result.docType}_${result.docRegion}_${result.postfix}.jpg`,
239
- 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
+ }]
240
438
  };
241
439
  }
242
440
 
243
441
  const token = await authentification();
244
442
  const templateType = await kycService.checkTemplateType({ fileUri: result.path || '', docType: result?.docType as GovernmentDocumentType, docRegion: result?.docRegion || "", postfix: result?.postfix, token: token }, env);
245
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
+
246
466
  logger.log("templateType result", JSON.stringify(truncateFields(templateType), null, 2));
247
467
  return templateType;
248
468
  } catch (e: any) {
249
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
+
250
475
  throw new Error(e?.message || 'Erreur de vérification du template');
251
476
  }
252
477
  }