@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.
- 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 +64 -31
- 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 -50
- 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 +8 -0
- package/build/utils/cropByObb.d.ts.map +1 -1
- package/build/utils/cropByObb.js +20 -3
- 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 +115 -86
- package/src/components/KYCElements/SelfieCaptureTemplate.tsx +2 -2
- package/src/hooks/useTemplateKYCFlow.tsx +1 -1
- package/src/modules/api/CardAuthentification.ts +289 -64
- package/src/modules/api/KYCService.ts +25 -33
- package/src/modules/camera/VisionCameraModule.ts +2 -2
- package/src/utils/cropByObb.ts +22 -3
- package/src/utils/pathToBase64.ts +1 -1
|
@@ -1,25 +1,50 @@
|
|
|
1
1
|
import kycService, { authentification, errorMessage, truncateFields } from "./KYCService";
|
|
2
|
-
import { 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
|
-
|
|
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,37 +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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
|
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 || '',
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
274
|
+
card_obb: mockCardObb
|
|
110
275
|
};
|
|
111
|
-
} else if (result.regionMapping
|
|
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:
|
|
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
|
-
|
|
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:
|
|
346
|
+
template_path: activeTemplatePath, // Use the verified template
|
|
137
347
|
mrz_type: result?.mrzType || ''
|
|
138
348
|
}, env);
|
|
139
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
384
|
+
template_path: activeTemplatePath,
|
|
185
385
|
mrz_type: result?.mrzType || ''
|
|
186
386
|
}, env);
|
|
187
|
-
|
|
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 || '',
|
|
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
|
|
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
|
-
|
|
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 || '',
|
|
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: {
|
|
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
|
}
|