@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.
- package/build/components/EnhancedCameraView.js +1 -1
- package/build/components/EnhancedCameraView.js.map +1 -1
- package/build/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
- package/build/components/KYCElements/CountrySelectionTemplate.js +13 -42
- package/build/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
- package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/components/KYCElements/IDCardCapture.js +76 -35
- package/build/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/components/KYCElements/SelfieCaptureTemplate.js +2 -2
- package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
- package/build/hooks/useTemplateKYCFlow.js +1 -1
- package/build/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/modules/api/CardAuthentification.d.ts +15 -7
- package/build/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/modules/api/CardAuthentification.js +215 -44
- package/build/modules/api/CardAuthentification.js.map +1 -1
- package/build/modules/api/KYCService.d.ts +2 -0
- package/build/modules/api/KYCService.d.ts.map +1 -1
- package/build/modules/api/KYCService.js +16 -19
- package/build/modules/api/KYCService.js.map +1 -1
- package/build/modules/camera/VisionCameraModule.js +2 -2
- package/build/modules/camera/VisionCameraModule.js.map +1 -1
- package/build/utils/cropByObb.d.ts +19 -1
- package/build/utils/cropByObb.d.ts.map +1 -1
- package/build/utils/cropByObb.js +49 -1
- package/build/utils/cropByObb.js.map +1 -1
- package/build/utils/pathToBase64.js +1 -1
- package/build/utils/pathToBase64.js.map +1 -1
- package/package.json +1 -1
- package/src/components/EnhancedCameraView.tsx +1 -1
- package/src/components/KYCElements/CountrySelectionTemplate.tsx +24 -52
- package/src/components/KYCElements/IDCardCapture.tsx +129 -90
- package/src/components/KYCElements/SelfieCaptureTemplate.tsx +2 -2
- package/src/hooks/useTemplateKYCFlow.tsx +1 -1
- package/src/modules/api/CardAuthentification.ts +290 -58
- package/src/modules/api/KYCService.ts +25 -33
- package/src/modules/camera/VisionCameraModule.ts +2 -2
- package/src/utils/cropByObb.ts +57 -1
- 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
|
-
|
|
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
|
|
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:
|
|
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: {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 || '',
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
274
|
+
card_obb: mockCardObb
|
|
103
275
|
};
|
|
104
|
-
} else if (result.regionMapping
|
|
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:
|
|
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
|
-
|
|
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:
|
|
346
|
+
template_path: activeTemplatePath, // Use the verified template
|
|
130
347
|
mrz_type: result?.mrzType || ''
|
|
131
348
|
}, env);
|
|
132
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
384
|
+
template_path: activeTemplatePath,
|
|
178
385
|
mrz_type: result?.mrzType || ''
|
|
179
386
|
}, env);
|
|
180
|
-
|
|
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 || '',
|
|
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
|
|
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
|
-
|
|
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 || '',
|
|
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: {
|
|
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
|
}
|