@sanctum-key/react-native-sdk 1.0.51 → 1.0.52
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/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/xml_file_paths.xml.flat +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +2 -2
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +1 -1
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +1 -1
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +1 -1
- package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +1 -1
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +41 -41
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +52 -52
- package/build/package.json +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +73 -66
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/TemplateKYCExample.d.ts +2 -1
- package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
- package/build/src/components/TemplateKYCExample.js +2 -2
- package/build/src/components/TemplateKYCExample.js.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.d.ts +1 -0
- package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.js +20 -14
- package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
- package/build/src/config/KYCConfig.d.ts.map +1 -1
- package/build/src/config/KYCConfig.js +4 -2
- package/build/src/config/KYCConfig.js.map +1 -1
- package/build/src/hooks/useTemplateKYCFlow.d.ts +1 -0
- package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
- package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts +0 -23
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +46 -83
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +96 -38
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/modules/api/SelfieVerification.d.ts.map +1 -1
- package/build/src/modules/api/SelfieVerification.js +12 -19
- package/build/src/modules/api/SelfieVerification.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/IDCardCapture.tsx +192 -191
- package/src/components/TemplateKYCExample.tsx +3 -0
- package/src/components/TemplateKYCFlowRefactored.tsx +31 -25
- package/src/config/KYCConfig.ts +7 -5
- package/src/hooks/useTemplateKYCFlow.tsx +1 -0
- package/src/modules/api/CardAuthentification.ts +409 -445
- package/src/modules/api/KYCService.ts +599 -449
- package/src/modules/api/SelfieVerification.ts +22 -29
|
@@ -4,479 +4,443 @@ import { GovernmentDocumentType, IBbox } from "../../types/KYC.types";
|
|
|
4
4
|
import { KycEnvironment } from "../../types/env.types";
|
|
5
5
|
import { logger } from "../../utils/logger";
|
|
6
6
|
import { countryData } from "../../config/countriesData";
|
|
7
|
+
import { Platform } from "react-native";
|
|
7
8
|
|
|
8
9
|
interface CardObbData {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
obb: number[][];
|
|
11
|
+
confidence: number;
|
|
12
|
+
card_in_frame?: boolean;
|
|
13
|
+
cropped_sides?: string[];
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
interface ApiVerificationResponse {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
result?: boolean;
|
|
18
|
+
success?: boolean;
|
|
19
|
+
detail?: any[];
|
|
20
|
+
card_obb?: CardObbData[] | any;
|
|
21
|
+
template_path?: string;
|
|
22
|
+
Error?: any;
|
|
23
|
+
[key: string]: any;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export async function frontVerification(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
27
|
+
result: {
|
|
28
|
+
path?: string,
|
|
29
|
+
regionMapping: { authMethod: string[], mrzTypes: string[] },
|
|
30
|
+
selectedDocumentType: string,
|
|
31
|
+
code: string,
|
|
32
|
+
currentSide: string,
|
|
33
|
+
templatePath?: string,
|
|
34
|
+
mrzType?: string,
|
|
35
|
+
issuingSubdivision?: string,
|
|
36
|
+
clientSideBarcodeData?: string
|
|
37
|
+
},
|
|
38
|
+
env: KycEnvironment = 'PRODUCTION'
|
|
39
|
+
) {
|
|
40
|
+
try {
|
|
41
|
+
console.log("Front verification START", JSON.stringify({ result }, null, 2));
|
|
42
|
+
logger.log("Front verification", JSON.stringify({ result }, null, 2));
|
|
43
|
+
|
|
44
|
+
const authMethods = Array.isArray(result.regionMapping?.authMethod) ? result.regionMapping.authMethod.map(m => String(m).toUpperCase()) : [];
|
|
45
|
+
const hasMrz = authMethods.some(m => m.includes('MRZ'));
|
|
46
|
+
const hasBarcode = authMethods.some(m => m.includes('BARCODE') || m.includes('2D'));
|
|
47
|
+
|
|
48
|
+
const token = await authentification();
|
|
49
|
+
const detected = await kycService.detectFaceOnId(
|
|
50
|
+
result?.path || '',
|
|
51
|
+
token,
|
|
52
|
+
result?.selectedDocumentType || '',
|
|
53
|
+
env
|
|
54
|
+
) as ApiVerificationResponse;
|
|
55
|
+
|
|
56
|
+
if (!detected.result) {
|
|
57
|
+
throw new Error('Aucun visage détecté sur la carte. Veuillez reprendre.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cardData = detected.card_obb && Array.isArray(detected.card_obb) && detected.card_obb.length > 0 ? detected.card_obb[0] : null;
|
|
61
|
+
let points: number[][] | null = null;
|
|
62
|
+
console.log(points)
|
|
63
|
+
let isCardInFrame = true;
|
|
64
|
+
let hasCroppedSides = false;
|
|
65
|
+
|
|
66
|
+
if (cardData) {
|
|
67
|
+
points = cardData.obb;
|
|
68
|
+
if (typeof cardData.card_in_frame !== 'undefined') {
|
|
69
|
+
isCardInFrame = cardData.card_in_frame === true;
|
|
70
|
+
hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
|
|
48
71
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!isCardInFrame || hasCroppedSides) {
|
|
75
|
+
logger.log(`Front Framing failed. Cropped sides detected by backend.`);
|
|
76
|
+
throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const obbConfidence = getObbConfidence(detected.card_obb);
|
|
80
|
+
if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
|
|
81
|
+
throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let croppedBase64: string | undefined;
|
|
85
|
+
let bbox: IBbox | undefined;
|
|
86
|
+
let mrz: any | undefined;
|
|
87
|
+
let barcode: any | undefined;
|
|
88
|
+
let documentData: any | undefined;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const crop = await cropByObb(result?.path || '', detected.card_obb);
|
|
92
|
+
croppedBase64 = crop.base64;
|
|
93
|
+
bbox = crop.bbox;
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (result.code === 'CA') {
|
|
98
|
+
try {
|
|
99
|
+
logger.log("Tentative d'extraction OCR (Front) - Canada Only");
|
|
100
|
+
documentData = await kycService.extractDocumentInformation({
|
|
101
|
+
fileUri: result.path || '',
|
|
102
|
+
docType: result.selectedDocumentType,
|
|
103
|
+
docRegion: result.code,
|
|
104
|
+
templatePath: result.templatePath || '',
|
|
105
|
+
issuingSubdivision: result.issuingSubdivision,
|
|
106
|
+
token: token,
|
|
107
|
+
postfix: result.currentSide || 'front'
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (documentData?.no_data || (documentData && documentData.data && Object.keys(documentData.data).length === 0)) {
|
|
111
|
+
logger.log("Front OCR Extraction Failed: Backend returned empty data dict.");
|
|
112
|
+
if (!hasBarcode && !hasMrz) {
|
|
113
|
+
throw new Error("Impossible de lire les informations du document. L'image est peut-être floue ou mal éclairée.");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (err: any) {
|
|
117
|
+
if (err?.response?.status === 401 || err?.response?.status === 403 || err?.status === 401) throw err;
|
|
118
|
+
const errorMsg = typeof err === 'object' ? JSON.stringify(err) : err;
|
|
119
|
+
logger.log("Front OCR Extraction Error:", errorMsg);
|
|
120
|
+
throw new Error(err?.message || "Impossible d'extraire les données. Veuillez reprendre la photo.");
|
|
57
121
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (hasMrz) {
|
|
125
|
+
try {
|
|
126
|
+
logger.log("Tentative d'extraction MRZ (Front)");
|
|
127
|
+
const mrzResponse = await kycService.extractMrzText(
|
|
128
|
+
{
|
|
129
|
+
fileUri: result.path || '',
|
|
130
|
+
docType: result?.selectedDocumentType || '',
|
|
131
|
+
docRegion: result?.code || "",
|
|
132
|
+
postfix: result?.currentSide || 'front',
|
|
133
|
+
token: token,
|
|
134
|
+
template_path: result?.templatePath || '',
|
|
135
|
+
mrz_type: result?.mrzType || 'TD1'
|
|
136
|
+
},
|
|
137
|
+
env
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
mrz = typeof mrzResponse === 'string' ? JSON.parse(mrzResponse) : mrzResponse;
|
|
141
|
+
|
|
142
|
+
if (mrz?.parsed_data?.status === 'FAILURE' || mrz?.success === false) {
|
|
143
|
+
logger.log("Front MRZ Extraction Failed:", mrz?.parsed_data?.status_message);
|
|
144
|
+
throw new Error(`MRZ illisible: ${mrz?.parsed_data?.status_message || 'Veuillez nettoyer l\'objectif et réessayer'}`);
|
|
70
145
|
}
|
|
146
|
+
} catch (err: any) {
|
|
147
|
+
if (err?.response?.status === 401 || err?.response?.status === 403 || err?.status === 401) throw err;
|
|
148
|
+
logger.log("MRZ Extraction Error:", err);
|
|
149
|
+
throw new Error(err?.message || "Erreur lors de l'extraction MRZ. Veuillez reprendre la photo.");
|
|
71
150
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
...detected,
|
|
155
|
+
croppedBase64,
|
|
156
|
+
bbox,
|
|
157
|
+
...(mrz ? { mrz } : {}),
|
|
158
|
+
...(barcode ? { barcode } : {}),
|
|
159
|
+
...(documentData ? { documentData } : {})
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
} catch (e: any) {
|
|
163
|
+
logger.log('Error front verification:', e?.message);
|
|
164
|
+
if (e?.response?.status || e?.status || e?.isAxiosError) {
|
|
165
|
+
throw e;
|
|
166
|
+
}
|
|
167
|
+
throw new Error(e?.message || 'Erreur de détection du visage');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function backVerification(
|
|
172
|
+
result: {
|
|
173
|
+
path?: string,
|
|
174
|
+
regionMapping: { authMethod: string[], mrzTypes: string[] },
|
|
175
|
+
selectedDocumentType: string,
|
|
176
|
+
code: string,
|
|
177
|
+
currentSide: string,
|
|
178
|
+
templatePath?: string,
|
|
179
|
+
mrzType?: string,
|
|
180
|
+
templateResponse?: ApiVerificationResponse,
|
|
181
|
+
issuingSubdivision?: string,
|
|
182
|
+
frontTemplatePath?: string,
|
|
183
|
+
clientSideBarcodeData?: string
|
|
184
|
+
},
|
|
185
|
+
env: KycEnvironment = 'PRODUCTION'
|
|
186
|
+
) {
|
|
187
|
+
try {
|
|
188
|
+
if (!result.path) throw new Error('No path provided');
|
|
189
|
+
logger.log("result.regionMapping", result.regionMapping, result.currentSide, result.code);
|
|
190
|
+
|
|
191
|
+
const authMethods = Array.isArray(result.regionMapping?.authMethod) ? result.regionMapping.authMethod.map(m => String(m).toUpperCase()) : [];
|
|
192
|
+
const hasMrz = authMethods.some(m => m.includes('MRZ'));
|
|
193
|
+
const hasBarcode = authMethods.some(m => m.includes('BARCODE') || m.includes('2D'));
|
|
194
|
+
|
|
195
|
+
const token = await authentification();
|
|
196
|
+
logger.log("1. Checking template and framing for back document...");
|
|
197
|
+
|
|
198
|
+
const templateResponse: ApiVerificationResponse = result.templateResponse || await kycService.checkTemplateType({
|
|
199
|
+
fileUri: result.path,
|
|
200
|
+
docType: result.selectedDocumentType as any,
|
|
201
|
+
docRegion: result.code,
|
|
202
|
+
postfix: 'back',
|
|
203
|
+
token: token,
|
|
204
|
+
}, env);
|
|
205
|
+
|
|
206
|
+
if (templateResponse.success === false || templateResponse.Error) {
|
|
207
|
+
throw new Error('Impossible de lire le document. Veuillez vous rapprocher et stabiliser la caméra.');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const cardData = templateResponse?.card_obb && Array.isArray(templateResponse.card_obb) && templateResponse.card_obb.length > 0 ? templateResponse.card_obb[0] : null;
|
|
211
|
+
if (!cardData) throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
212
|
+
|
|
213
|
+
let points: number[][] | null = null;
|
|
214
|
+
console.log(points);
|
|
215
|
+
let isCardInFrame = false;
|
|
216
|
+
let hasCroppedSides = true;
|
|
217
|
+
|
|
218
|
+
if (cardData.obb) {
|
|
219
|
+
points = cardData.obb;
|
|
220
|
+
if (typeof cardData.card_in_frame !== 'undefined') {
|
|
221
|
+
isCardInFrame = cardData.card_in_frame === true;
|
|
222
|
+
hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
|
|
76
223
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
224
|
+
} else if (Array.isArray(cardData) && Array.isArray(cardData[0])) {
|
|
225
|
+
points = cardData[0];
|
|
226
|
+
isCardInFrame = true;
|
|
227
|
+
hasCroppedSides = false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!isCardInFrame || hasCroppedSides) {
|
|
231
|
+
logger.log(`Back Framing failed. Backend reported document out of bounds.`);
|
|
232
|
+
throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const obbConf = getObbConfidence(templateResponse?.card_obb);
|
|
236
|
+
if (obbConf !== null && obbConf < OBB_CONFIDENCE_THRESHOLD) {
|
|
237
|
+
throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const activeTemplatePath = templateResponse.template_path || result.templatePath || '';
|
|
241
|
+
|
|
242
|
+
if (activeTemplatePath) {
|
|
243
|
+
const countryInfo = countryData[result.code];
|
|
244
|
+
const countryName = countryInfo?.name_en || countryInfo?.name || '';
|
|
245
|
+
|
|
246
|
+
const hasCodeMatch = activeTemplatePath.toUpperCase().includes(`_${result.code.toUpperCase()}_`) || activeTemplatePath.toUpperCase().includes(`${result.code.toUpperCase()}/`);
|
|
247
|
+
const hasNameMatch = countryName && activeTemplatePath.toLowerCase().includes(countryName.toLowerCase());
|
|
248
|
+
|
|
249
|
+
if (!hasCodeMatch && !hasNameMatch) {
|
|
250
|
+
logger.log(`Template mismatch! Expected region: ${result.code} (${countryName}), Detected: ${activeTemplatePath}`);
|
|
251
|
+
throw new Error(`Le document ne correspond pas au pays sélectionné (${result.code}).`);
|
|
81
252
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let mrz: any | undefined;
|
|
86
|
-
let barcode: any | undefined;
|
|
87
|
-
let documentData: any | undefined;
|
|
88
|
-
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (result.frontTemplatePath && activeTemplatePath) {
|
|
89
256
|
try {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
257
|
+
const frontPy = result.frontTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
|
|
258
|
+
const backPy = activeTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
|
|
259
|
+
|
|
260
|
+
if (frontPy && backPy) {
|
|
261
|
+
logger.log(`Validating Front (${frontPy}) vs Back (${backPy}) for region ${result.code}`);
|
|
262
|
+
const isCanadianSubRegionDoc = result.code === 'CA' && ['pr'].includes(result.selectedDocumentType);
|
|
263
|
+
const safeSubRegion = isCanadianSubRegionDoc ? (result.issuingSubdivision || "") : "";
|
|
264
|
+
|
|
265
|
+
const matchValidation = await kycService.validateBackTemplate({
|
|
266
|
+
region: result.code,
|
|
267
|
+
docType: result.selectedDocumentType,
|
|
268
|
+
frontTemplateName: frontPy,
|
|
269
|
+
backTemplateName: backPy,
|
|
270
|
+
subRegion: safeSubRegion,
|
|
271
|
+
token: token
|
|
272
|
+
}, env);
|
|
273
|
+
|
|
274
|
+
if (!matchValidation || matchValidation.is_match === false) {
|
|
275
|
+
logger.log(`Front/Back Mismatch: ${matchValidation?.status_message}`);
|
|
276
|
+
throw new Error(`Le verso ne correspond pas au recto fourni. Vous avez scanné un document différent.`);
|
|
107
277
|
}
|
|
108
|
-
} catch (err: any) {
|
|
109
|
-
// 🚨 FIX: Allow 401/403 errors to bubble up directly!
|
|
110
|
-
if (err?.response?.status === 401 || err?.response?.status === 403 || err?.status === 401) throw err;
|
|
111
|
-
|
|
112
|
-
const errorMsg = typeof err === 'object' ? JSON.stringify(err) : err;
|
|
113
|
-
logger.log("Front OCR Extraction Error:", errorMsg);
|
|
114
|
-
throw new Error(err?.message || "Impossible d'extraire les données. Veuillez reprendre la photo.");
|
|
115
278
|
}
|
|
279
|
+
} catch (err: any) {
|
|
280
|
+
if (err?.message?.includes('ne correspond pas')) throw err;
|
|
281
|
+
logger.log("Template matching validation skipped/failed internally:", err?.message);
|
|
116
282
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
logger.log("Framing, Country, and Match verified successfully. Proceeding to Data Extraction.");
|
|
286
|
+
let extractionResult: any = {};
|
|
287
|
+
|
|
288
|
+
if (result.code === 'CA') {
|
|
289
|
+
try {
|
|
290
|
+
logger.log("Tentative d'extraction OCR (Back) - Canada Only");
|
|
291
|
+
const documentData = await kycService.extractDocumentInformation({
|
|
292
|
+
fileUri: result.path!,
|
|
293
|
+
docType: result.selectedDocumentType,
|
|
294
|
+
docRegion: result.code,
|
|
295
|
+
templatePath: activeTemplatePath,
|
|
296
|
+
issuingSubdivision: result.issuingSubdivision,
|
|
297
|
+
token: token,
|
|
298
|
+
postfix: result.currentSide || 'back'
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
if (documentData?.no_data || (documentData?.data && Object.keys(documentData.data).length === 0)) {
|
|
302
|
+
logger.log("Back OCR Extraction: No text data found (Normal for barcode-only sides).");
|
|
303
|
+
if (!hasBarcode && !hasMrz) {
|
|
304
|
+
throw new Error("Impossible de lire les informations du document. L'image est peut-être floue ou mal éclairée.");
|
|
129
305
|
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (err?.response?.status === 401 || err?.response?.status === 403 || err?.status === 401) throw err;
|
|
133
|
-
|
|
134
|
-
logger.log("MRZ Extraction Error:", err);
|
|
135
|
-
throw new Error(err?.message || "Erreur lors de l'extraction MRZ. Veuillez reprendre la photo.");
|
|
306
|
+
} else {
|
|
307
|
+
extractionResult.documentData = documentData;
|
|
136
308
|
}
|
|
309
|
+
} catch (err: any) {
|
|
310
|
+
const errorMsg = typeof err === 'object' ? JSON.stringify(err) : err;
|
|
311
|
+
logger.log("Back OCR Extraction Error:", errorMsg);
|
|
312
|
+
throw new Error(err?.message || "Impossible d'extraire les données. Veuillez reprendre la photo.");
|
|
137
313
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (hasMrz) {
|
|
317
|
+
try {
|
|
318
|
+
logger.log("Tentative d'extraction MRZ (Back)");
|
|
319
|
+
const mrzResponse = await kycService.extractMrzText({
|
|
320
|
+
fileUri: result.path!,
|
|
321
|
+
docType: result?.selectedDocumentType || '',
|
|
322
|
+
docRegion: result?.code || '',
|
|
323
|
+
postfix: result?.currentSide || 'back',
|
|
324
|
+
token: token,
|
|
325
|
+
template_path: activeTemplatePath,
|
|
326
|
+
mrz_type: result?.mrzType || 'TD1'
|
|
327
|
+
}, env);
|
|
328
|
+
|
|
329
|
+
const mrz = typeof mrzResponse === 'string' ? JSON.parse(mrzResponse) : mrzResponse;
|
|
330
|
+
if (mrz?.parsed_data?.status === 'FAILURE' || mrz?.success === false) {
|
|
331
|
+
throw new Error(mrz?.parsed_data?.status_message || 'Lecture MRZ échouée');
|
|
332
|
+
}
|
|
333
|
+
extractionResult.mrz = mrz;
|
|
334
|
+
} catch (mrzError: any) {
|
|
335
|
+
logger.log(`MRZ échoué: ${mrzError?.message}`);
|
|
336
|
+
throw new Error(`Lecture MRZ invalide: ${mrzError?.message || 'Veuillez nettoyer l\'objectif et réessayer'}`);
|
|
148
337
|
}
|
|
149
|
-
|
|
150
|
-
throw new Error(e?.message || 'Erreur de détection du visage');
|
|
151
338
|
}
|
|
152
|
-
}
|
|
153
339
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
regionMapping: { authMethod: string[], mrzTypes: string[] },
|
|
158
|
-
selectedDocumentType: string,
|
|
159
|
-
code: string,
|
|
160
|
-
currentSide: string,
|
|
161
|
-
templatePath?: string,
|
|
162
|
-
mrzType?: string,
|
|
163
|
-
templateResponse?: ApiVerificationResponse,
|
|
164
|
-
issuingSubdivision?: string,
|
|
165
|
-
frontTemplatePath?: string,
|
|
166
|
-
clientSideBarcodeData?: string // 🚨 ADDED: Accept the SDK barcode data
|
|
167
|
-
},
|
|
168
|
-
env: KycEnvironment = 'PRODUCTION'
|
|
169
|
-
) {
|
|
340
|
+
let bbox: IBbox | undefined;
|
|
341
|
+
let croppedBase64: string | undefined;
|
|
342
|
+
|
|
170
343
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
? result.regionMapping.authMethod.map(m => String(m).toUpperCase())
|
|
176
|
-
: [];
|
|
177
|
-
|
|
178
|
-
const hasMrz = authMethods.some(m => m.includes('MRZ'));
|
|
179
|
-
const hasBarcode = authMethods.some(m => m.includes('BARCODE') || m.includes('2D'));
|
|
180
|
-
|
|
181
|
-
if (env === 'SANDBOX') {
|
|
182
|
-
console.log("SANDBOX mode: Skipping AI verification for back document");
|
|
183
|
-
const mockBbox: IBbox = { minX: 400, minY: 800, width: 2200, height: 1400 };
|
|
184
|
-
const mockCardObb = [{
|
|
185
|
-
obb: [[400, 800], [2600, 800], [2600, 2200], [400, 2200]],
|
|
186
|
-
confidence: 0.95,
|
|
187
|
-
card_in_frame: true,
|
|
188
|
-
cropped_sides: []
|
|
189
|
-
}];
|
|
190
|
-
|
|
191
|
-
if (hasMrz) {
|
|
192
|
-
return {
|
|
193
|
-
success: true,
|
|
194
|
-
mrz: {
|
|
195
|
-
success: true,
|
|
196
|
-
parsed_data: { status: 'SUCCESS', document_type: result.selectedDocumentType, mrz_type: result.mrzType || 'TD1' }
|
|
197
|
-
},
|
|
198
|
-
bbox: mockBbox,
|
|
199
|
-
card_obb: mockCardObb
|
|
200
|
-
};
|
|
201
|
-
} else if (hasBarcode) {
|
|
202
|
-
return {
|
|
203
|
-
success: true,
|
|
204
|
-
barcode: { barcode_data: 'SANDBOX_MOCK_BARCODE' },
|
|
205
|
-
bbox: mockBbox,
|
|
206
|
-
card_obb: mockCardObb
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
return { success: true, bbox: mockBbox, card_obb: mockCardObb };
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const token = await authentification();
|
|
213
|
-
logger.log("1. Checking template and framing for back document...");
|
|
214
|
-
|
|
215
|
-
const templateResponse: ApiVerificationResponse = result.templateResponse ||
|
|
216
|
-
await kycService.checkTemplateType({
|
|
217
|
-
fileUri: result.path,
|
|
218
|
-
docType: result.selectedDocumentType as any,
|
|
219
|
-
docRegion: result.code,
|
|
220
|
-
postfix: 'back',
|
|
221
|
-
token: token,
|
|
222
|
-
}, env);
|
|
223
|
-
|
|
224
|
-
if (templateResponse.success === false || templateResponse.Error) {
|
|
225
|
-
throw new Error('Impossible de lire le document. Veuillez vous rapprocher et stabiliser la caméra.');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const cardData = templateResponse?.card_obb && Array.isArray(templateResponse.card_obb) && templateResponse.card_obb.length > 0
|
|
229
|
-
? templateResponse.card_obb[0]
|
|
230
|
-
: null;
|
|
231
|
-
|
|
232
|
-
if (!cardData) throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
233
|
-
|
|
234
|
-
let points: number[][] | null = null;
|
|
235
|
-
console.log(points);
|
|
236
|
-
let isCardInFrame = false;
|
|
237
|
-
let hasCroppedSides = true;
|
|
238
|
-
|
|
239
|
-
if (cardData.obb) {
|
|
240
|
-
points = cardData.obb;
|
|
241
|
-
if (typeof cardData.card_in_frame !== 'undefined') {
|
|
242
|
-
isCardInFrame = cardData.card_in_frame === true;
|
|
243
|
-
hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
|
|
244
|
-
}
|
|
245
|
-
} else if (Array.isArray(cardData) && Array.isArray(cardData[0])) {
|
|
246
|
-
points = cardData[0];
|
|
247
|
-
isCardInFrame = true;
|
|
248
|
-
hasCroppedSides = false;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (!isCardInFrame || hasCroppedSides) {
|
|
252
|
-
logger.log(`Back Framing failed. Backend reported document out of bounds.`);
|
|
253
|
-
throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const obbConf = getObbConfidence(templateResponse?.card_obb);
|
|
257
|
-
if (obbConf !== null && obbConf < OBB_CONFIDENCE_THRESHOLD) {
|
|
258
|
-
throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const activeTemplatePath = templateResponse.template_path || result.templatePath || '';
|
|
262
|
-
|
|
263
|
-
if (activeTemplatePath) {
|
|
264
|
-
const countryInfo = countryData[result.code];
|
|
265
|
-
|
|
266
|
-
const countryName = countryInfo?.name_en || countryInfo?.name || '';
|
|
267
|
-
|
|
268
|
-
const hasCodeMatch = activeTemplatePath.toUpperCase().includes(`_${result.code.toUpperCase()}_`) ||
|
|
269
|
-
activeTemplatePath.toUpperCase().includes(`${result.code.toUpperCase()}/`);
|
|
270
|
-
|
|
271
|
-
const hasNameMatch = countryName && activeTemplatePath.toLowerCase().includes(countryName.toLowerCase());
|
|
272
|
-
|
|
273
|
-
// 5. If NEITHER match, throw the error
|
|
274
|
-
if (!hasCodeMatch && !hasNameMatch) {
|
|
275
|
-
logger.log(`Template mismatch! Expected region: ${result.code} (${countryName}), Detected: ${activeTemplatePath}`);
|
|
276
|
-
throw new Error(`Le document ne correspond pas au pays sélectionné (${result.code}).`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (result.frontTemplatePath && activeTemplatePath) {
|
|
281
|
-
try {
|
|
282
|
-
const frontPy = result.frontTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
|
|
283
|
-
const backPy = activeTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
|
|
284
|
-
|
|
285
|
-
if (frontPy && backPy) {
|
|
286
|
-
logger.log(`Validating Front (${frontPy}) vs Back (${backPy}) for region ${result.code}`);
|
|
287
|
-
|
|
288
|
-
const isCanadianSubRegionDoc = result.code === 'CA' && ['pr'].includes(result.selectedDocumentType);
|
|
289
|
-
const safeSubRegion = isCanadianSubRegionDoc ? (result.issuingSubdivision || "") : "";
|
|
290
|
-
|
|
291
|
-
const matchValidation = await kycService.validateBackTemplate({
|
|
292
|
-
region: result.code,
|
|
293
|
-
docType: result.selectedDocumentType,
|
|
294
|
-
frontTemplateName: frontPy,
|
|
295
|
-
backTemplateName: backPy,
|
|
296
|
-
subRegion: safeSubRegion,
|
|
297
|
-
token: token
|
|
298
|
-
}, env);
|
|
299
|
-
|
|
300
|
-
if (!matchValidation || matchValidation.is_match === false) {
|
|
301
|
-
logger.log(`Front/Back Mismatch: ${matchValidation?.status_message}`);
|
|
302
|
-
throw new Error(`Le verso ne correspond pas au recto fourni. Vous avez scanné un document différent.`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
} catch (err: any) {
|
|
306
|
-
if (err?.message?.includes('ne correspond pas')) throw err;
|
|
307
|
-
logger.log("Template matching validation skipped/failed internally:", err?.message);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
logger.log("Framing, Country, and Match verified successfully. Proceeding to Data Extraction.");
|
|
312
|
-
let extractionResult: any = {};
|
|
313
|
-
|
|
314
|
-
if (result.code === 'CA') {
|
|
315
|
-
try {
|
|
316
|
-
logger.log("Tentative d'extraction OCR (Back) - Canada Only");
|
|
317
|
-
const documentData = await kycService.extractDocumentInformation({
|
|
318
|
-
fileUri: result.path!,
|
|
319
|
-
docType: result.selectedDocumentType,
|
|
320
|
-
docRegion: result.code,
|
|
321
|
-
templatePath: activeTemplatePath,
|
|
322
|
-
issuingSubdivision: result.issuingSubdivision,
|
|
323
|
-
token: token,
|
|
324
|
-
postfix: result.currentSide || 'back'
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
if (documentData?.no_data || (documentData?.data && Object.keys(documentData.data).length === 0)) {
|
|
328
|
-
logger.log("Back OCR Extraction: No text data found (Normal for barcode-only sides).");
|
|
329
|
-
if (!hasBarcode && !hasMrz) {
|
|
330
|
-
throw new Error("Impossible de lire les informations du document. L'image est peut-être floue ou mal éclairée.");
|
|
331
|
-
}
|
|
332
|
-
} else {
|
|
333
|
-
extractionResult.documentData = documentData;
|
|
334
|
-
}
|
|
335
|
-
} catch (err: any) {
|
|
336
|
-
const errorMsg = typeof err === 'object' ? JSON.stringify(err) : err;
|
|
337
|
-
logger.log("Back OCR Extraction Error:", errorMsg);
|
|
338
|
-
throw new Error(err?.message || "Impossible d'extraire les données. Veuillez reprendre la photo.");
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (hasMrz) {
|
|
343
|
-
try {
|
|
344
|
-
logger.log("Tentative d'extraction MRZ (Back)");
|
|
345
|
-
const mrzResponse = await kycService.extractMrzText({
|
|
346
|
-
fileUri: result.path!,
|
|
347
|
-
docType: result?.selectedDocumentType || '',
|
|
348
|
-
docRegion: result?.code || '',
|
|
349
|
-
postfix: result?.currentSide || 'back',
|
|
350
|
-
token: token,
|
|
351
|
-
template_path: activeTemplatePath,
|
|
352
|
-
mrz_type: result?.mrzType || 'TD1'
|
|
353
|
-
}, env);
|
|
354
|
-
|
|
355
|
-
const mrz = typeof mrzResponse === 'string' ? JSON.parse(mrzResponse) : mrzResponse;
|
|
356
|
-
|
|
357
|
-
if (mrz?.parsed_data?.status === 'FAILURE' || mrz?.success === false) {
|
|
358
|
-
throw new Error(mrz?.parsed_data?.status_message || 'Lecture MRZ échouée');
|
|
359
|
-
}
|
|
360
|
-
extractionResult.mrz = mrz;
|
|
361
|
-
} catch (mrzError: any) {
|
|
362
|
-
logger.log(`MRZ échoué: ${mrzError?.message}`);
|
|
363
|
-
throw new Error(`Lecture MRZ invalide: ${mrzError?.message || 'Veuillez nettoyer l\'objectif et réessayer'}`);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
let bbox: IBbox | undefined;
|
|
369
|
-
let croppedBase64: string | undefined;
|
|
370
|
-
try {
|
|
371
|
-
const crop = await cropByObb(result?.path || '', templateResponse.card_obb);
|
|
372
|
-
bbox = crop.bbox;
|
|
373
|
-
croppedBase64 = crop.base64;
|
|
374
|
-
} catch { }
|
|
375
|
-
|
|
376
|
-
return {
|
|
377
|
-
success: true,
|
|
378
|
-
...extractionResult,
|
|
379
|
-
bbox,
|
|
380
|
-
croppedBase64,
|
|
381
|
-
card_obb: templateResponse.card_obb
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
} catch (e: any) {
|
|
385
|
-
if (e?.message === 'CARD_NOT_FULLY_IN_FRAME') throw e;
|
|
386
|
-
throw new Error(e?.message || 'Erreur de détection des données');
|
|
344
|
+
const crop = await cropByObb(result?.path || '', templateResponse.card_obb);
|
|
345
|
+
bbox = crop.bbox;
|
|
346
|
+
croppedBase64 = crop.base64;
|
|
347
|
+
} catch {
|
|
387
348
|
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
success: true,
|
|
352
|
+
...extractionResult,
|
|
353
|
+
bbox,
|
|
354
|
+
croppedBase64,
|
|
355
|
+
card_obb: templateResponse.card_obb
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
} catch (e: any) {
|
|
359
|
+
if (e?.message === 'CARD_NOT_FULLY_IN_FRAME') throw e;
|
|
360
|
+
throw new Error(e?.message || 'Erreur de détection des données');
|
|
361
|
+
}
|
|
388
362
|
}
|
|
389
363
|
|
|
390
364
|
export async function checkTemplateType(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (typeof cardData.card_in_frame !== 'undefined') {
|
|
431
|
-
isCardInFrame = cardData.card_in_frame === true;
|
|
432
|
-
hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
|
|
433
|
-
}
|
|
434
|
-
} else if (Array.isArray(cardData) && Array.isArray(cardData[0])) {
|
|
435
|
-
points = cardData[0];
|
|
436
|
-
isCardInFrame = true;
|
|
437
|
-
hasCroppedSides = false;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (!isCardInFrame || hasCroppedSides) {
|
|
441
|
-
logger.log(`Template Framing failed by backend check.`);
|
|
442
|
-
throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const LPIPS_THRESHOLD = 0.75;
|
|
446
|
-
if (templateType.lpips_score !== undefined && templateType.lpips_score > LPIPS_THRESHOLD) {
|
|
447
|
-
logger.log(`Country Mismatch! LPIPS: ${templateType.lpips_score}`);
|
|
448
|
-
throw new Error(`Le document présenté ne correspond pas au pays sélectionné (${result.docRegion}).`);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
logger.log("templateType result", JSON.stringify(truncateFields(templateType), null, 2));
|
|
452
|
-
|
|
453
|
-
return templateType;
|
|
454
|
-
} catch (e: any) {
|
|
455
|
-
logger.log('Error checking template type:', JSON.stringify(errorMessage(e), null, 2));
|
|
456
|
-
|
|
457
|
-
// Safely extract the raw message to check for string-based errors like "Unauthorized"
|
|
458
|
-
const rawMessage = (typeof e === 'string' ? e : (e?.message || '')).toLowerCase();
|
|
459
|
-
|
|
460
|
-
// 🚨 CRITICAL FIX: Ensure 401/403 and string-based "Unauthorized" errors bubble up exactly as they are!
|
|
461
|
-
if (
|
|
462
|
-
e?.response?.status === 401 ||
|
|
463
|
-
e?.response?.status === 403 ||
|
|
464
|
-
e?.status === 401 ||
|
|
465
|
-
rawMessage.includes('unauthorized') ||
|
|
466
|
-
rawMessage.includes('401')
|
|
467
|
-
) {
|
|
468
|
-
throw e;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (e?.isAxiosError) {
|
|
472
|
-
throw e;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (e?.message === 'CARD_NOT_FULLY_IN_FRAME' || e?.message?.includes('ne correspond pas')) {
|
|
476
|
-
throw e;
|
|
365
|
+
result: { path?: string, docType: string, docRegion: string, postfix: string, issuingSubdivision?: string },
|
|
366
|
+
env: KycEnvironment = 'PRODUCTION'
|
|
367
|
+
) {
|
|
368
|
+
try {
|
|
369
|
+
const token = await authentification();
|
|
370
|
+
const isCanada = result.docRegion?.toUpperCase() === 'CA';
|
|
371
|
+
|
|
372
|
+
let safeCapturePath = result.path || '';
|
|
373
|
+
if (Platform.OS === 'ios' && safeCapturePath && !safeCapturePath.startsWith('file://')) {
|
|
374
|
+
safeCapturePath = `file://${safeCapturePath}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const templateType = await kycService.checkTemplateType({
|
|
378
|
+
fileUri: safeCapturePath || '',
|
|
379
|
+
docType: result?.docType as GovernmentDocumentType,
|
|
380
|
+
docRegion: result?.docRegion || "",
|
|
381
|
+
postfix: result?.postfix,
|
|
382
|
+
token: token,
|
|
383
|
+
issuingSubdivision: isCanada ? result.issuingSubdivision : undefined
|
|
384
|
+
}, env);
|
|
385
|
+
|
|
386
|
+
if (templateType.success === false || templateType.Error) {
|
|
387
|
+
logger.log("Backend returned an error:", templateType.Error);
|
|
388
|
+
throw new Error('Impossible de lire le document. Veuillez vous rapprocher et stabiliser la caméra.');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const cardData = templateType.card_obb && Array.isArray(templateType.card_obb) && templateType.card_obb.length > 0 ? templateType.card_obb[0] : null;
|
|
392
|
+
if (!cardData) throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
393
|
+
|
|
394
|
+
let points: number[][] | null = null;
|
|
395
|
+
console.log(points);
|
|
396
|
+
let isCardInFrame = false;
|
|
397
|
+
let hasCroppedSides = true;
|
|
398
|
+
|
|
399
|
+
if (cardData.obb) {
|
|
400
|
+
points = cardData.obb;
|
|
401
|
+
if (typeof cardData.card_in_frame !== 'undefined') {
|
|
402
|
+
isCardInFrame = cardData.card_in_frame === true;
|
|
403
|
+
hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
|
|
477
404
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
405
|
+
} else if (Array.isArray(cardData) && Array.isArray(cardData[0])) {
|
|
406
|
+
points = cardData[0];
|
|
407
|
+
isCardInFrame = true;
|
|
408
|
+
hasCroppedSides = false;
|
|
481
409
|
}
|
|
482
|
-
|
|
410
|
+
|
|
411
|
+
if (!isCardInFrame || hasCroppedSides) {
|
|
412
|
+
logger.log(`Template Framing failed by backend check.`);
|
|
413
|
+
throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const LPIPS_THRESHOLD = 0.75;
|
|
417
|
+
if (templateType.lpips_score !== undefined && templateType.lpips_score > LPIPS_THRESHOLD) {
|
|
418
|
+
logger.log(`Country Mismatch! LPIPS: ${templateType.lpips_score}`);
|
|
419
|
+
throw new Error(`Le document présenté ne correspond pas au pays sélectionné (${result.docRegion}).`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
logger.log("templateType result", JSON.stringify(truncateFields(templateType), null, 2));
|
|
423
|
+
return templateType;
|
|
424
|
+
|
|
425
|
+
} catch (e: any) {
|
|
426
|
+
logger.log('Error checking template type:', JSON.stringify(errorMessage(e), null, 2));
|
|
427
|
+
const rawMessage = (typeof e === 'string' ? e : (e?.message || '')).toLowerCase();
|
|
428
|
+
|
|
429
|
+
if (
|
|
430
|
+
e?.response?.status === 401 || e?.response?.status === 403 ||
|
|
431
|
+
e?.status === 401 || rawMessage.includes('unauthorized') || rawMessage.includes('401')
|
|
432
|
+
) {
|
|
433
|
+
throw e;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (e?.isAxiosError) {
|
|
437
|
+
throw e;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (e?.message === 'CARD_NOT_FULLY_IN_FRAME' || e?.message?.includes('ne correspond pas')) {
|
|
441
|
+
throw e;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
throw new Error(typeof e === 'string' ? e : (e?.message || 'Erreur de vérification du template'));
|
|
445
|
+
}
|
|
446
|
+
}
|