@sanctum-key/react-native-sdk 1.0.20 → 1.0.21
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/package.json +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.js +30 -13
- package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
- package/build/src/config/region_mapping.d.ts.map +1 -1
- package/build/src/config/region_mapping.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +32 -18
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +1 -0
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +10 -4
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/VerificationProgressTemplate.tsx +35 -14
- package/src/config/region_mapping.ts +2 -1
- package/src/modules/api/CardAuthentification.ts +40 -27
- package/src/modules/api/KYCService.ts +25 -6
|
@@ -25,7 +25,6 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
25
25
|
const isSuccess = verification.status === 'success';
|
|
26
26
|
const isFailed = verification.status === 'failed';
|
|
27
27
|
|
|
28
|
-
// Fonction pour récupérer le résultat de vérification avec retry
|
|
29
28
|
const getVerificationResult = async (currentRetryCount = 0, maxRetries = 40) => {
|
|
30
29
|
try {
|
|
31
30
|
logger.log(`Starting verification check (attempt ${currentRetryCount + 1}/${maxRetries + 1})`);
|
|
@@ -42,20 +41,23 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
42
41
|
} as any
|
|
43
42
|
});
|
|
44
43
|
setIsRetrying(false);
|
|
45
|
-
return;
|
|
44
|
+
return;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
let verificationResult = result?.data;
|
|
48
|
+
|
|
49
|
+
if (verificationResult && !verificationResult.verification_status && verificationResult[state.session.session_id]) {
|
|
50
|
+
const nestedData = verificationResult[state.session.session_id]?.data;
|
|
51
|
+
verificationResult = typeof nestedData === 'string' ? JSON.parse(nestedData) : nestedData;
|
|
52
|
+
}
|
|
51
53
|
|
|
52
54
|
if (!verificationResult) {
|
|
53
|
-
// If we still didn't get data and the status wasn't failed, throw to trigger a retry
|
|
54
55
|
throw new Error('Verification payload missing for current session');
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
const currentStatus = verificationResult.verification_status;
|
|
59
|
+
|
|
60
|
+
if (currentStatus === null || currentStatus === "processing" || currentStatus === "pending") {
|
|
59
61
|
if (currentRetryCount < maxRetries) {
|
|
60
62
|
const nextRetryCount = currentRetryCount + 1;
|
|
61
63
|
setRetryCount(nextRetryCount);
|
|
@@ -69,7 +71,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
69
71
|
}, 10000);
|
|
70
72
|
|
|
71
73
|
setTimeoutId(newTimeoutId as any);
|
|
72
|
-
return;
|
|
74
|
+
return;
|
|
73
75
|
} else {
|
|
74
76
|
logger.error('Max retries reached, verification still processing');
|
|
75
77
|
setIsRetrying(false);
|
|
@@ -77,13 +79,15 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
if (verificationResult.verification_status === "completed" || verificationResult.verification_status === "approved") {
|
|
82
|
+
if (currentStatus === "completed" || currentStatus === "approved") {
|
|
82
83
|
actions.setVerificationState({
|
|
83
84
|
status: 'success',
|
|
84
|
-
result:
|
|
85
|
+
result: {
|
|
86
|
+
...result,
|
|
87
|
+
referenceId: verificationResult.verification_id || verificationResult.session_id
|
|
88
|
+
} as any
|
|
85
89
|
});
|
|
86
|
-
} else if (
|
|
90
|
+
} else if (currentStatus === "failed" || currentStatus === "rejected") {
|
|
87
91
|
actions.setVerificationState({
|
|
88
92
|
status: 'failed',
|
|
89
93
|
result: {
|
|
@@ -130,6 +134,24 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
130
134
|
};
|
|
131
135
|
}, [timeoutId]);
|
|
132
136
|
|
|
137
|
+
// 🚨 THE FIX: Automatically trigger the webhook/completion flow on success
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
let autoSubmitTimer: ReturnType<typeof setTimeout>;
|
|
140
|
+
|
|
141
|
+
if (isSuccess) {
|
|
142
|
+
logger.log("Verification successful. Initiating auto-submit countdown...");
|
|
143
|
+
// Wait 3 seconds so the user can see the "Complete" screen, then automatically push the data
|
|
144
|
+
autoSubmitTimer = setTimeout(() => {
|
|
145
|
+
actions.submitVerification();
|
|
146
|
+
}, 3000);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Cleanup the timer if the user clicks the button manually before 3 seconds are up
|
|
150
|
+
return () => {
|
|
151
|
+
if (autoSubmitTimer) clearTimeout(autoSubmitTimer);
|
|
152
|
+
};
|
|
153
|
+
}, [isSuccess, actions]);
|
|
154
|
+
|
|
133
155
|
if (isSuccess) {
|
|
134
156
|
return (
|
|
135
157
|
<View style={styles.container}>
|
|
@@ -157,7 +179,6 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
|
|
|
157
179
|
))}
|
|
158
180
|
</View>
|
|
159
181
|
|
|
160
|
-
{/* 🚨 FIX 2: Give the user actionable options to End or Restart */}
|
|
161
182
|
<TouchableOpacity style={[styles.button, styles.primary]} onPress={() => actions.submitVerification()}>
|
|
162
183
|
<Text style={styles.buttonText}>
|
|
163
184
|
{state.currentLanguage === 'en' ? 'OK / Finish' : 'Terminer'}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ICountryMapping } from "../types/KYC.types";
|
|
2
2
|
|
|
3
|
-
export const countryMapping: Record<string, Record<string, Record<string, ICountryMapping[]>>> =
|
|
3
|
+
export const countryMapping: Record<string, Record<string, Record<string, ICountryMapping[]>>> =
|
|
4
|
+
{
|
|
4
5
|
"BE": {
|
|
5
6
|
"passport": {
|
|
6
7
|
"root": [
|
|
@@ -295,29 +295,38 @@ export async function backVerification(
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
// 🚨
|
|
298
|
+
// 🚨 FRONT VS BACK TEMPLATE VALIDATION 🚨
|
|
299
299
|
if (result.frontTemplatePath && activeTemplatePath) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (frontPy && backPy) {
|
|
304
|
-
logger.log(`Validating Front (${frontPy}) vs Back (${backPy}) for region ${result.code}`);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const matchValidation = await kycService.validateBackTemplate({
|
|
308
|
-
region: result.code,
|
|
309
|
-
docType: result.selectedDocumentType,
|
|
310
|
-
frontTemplateName: frontPy,
|
|
311
|
-
backTemplateName: backPy,
|
|
312
|
-
subRegion: result.issuingSubdivision || "",
|
|
313
|
-
token: token
|
|
314
|
-
}, env);
|
|
300
|
+
try {
|
|
301
|
+
const frontPy = result.frontTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
|
|
302
|
+
const backPy = activeTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
|
|
315
303
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
304
|
+
if (frontPy && backPy) {
|
|
305
|
+
logger.log(`Validating Front (${frontPy}) vs Back (${backPy}) for region ${result.code}`);
|
|
306
|
+
|
|
307
|
+
// 🚨 THE FIX: Only populate subRegion for Canadian 'hic' documents. Empty for all others.
|
|
308
|
+
const isCanadianHic = result.code === 'CA' && result.selectedDocumentType === 'hic';
|
|
309
|
+
const safeSubRegion = isCanadianHic ? (result.issuingSubdivision || "") : "";
|
|
310
|
+
|
|
311
|
+
const matchValidation = await kycService.validateBackTemplate({
|
|
312
|
+
region: result.code,
|
|
313
|
+
docType: result.selectedDocumentType,
|
|
314
|
+
frontTemplateName: frontPy,
|
|
315
|
+
backTemplateName: backPy,
|
|
316
|
+
subRegion: safeSubRegion,
|
|
317
|
+
token: token
|
|
318
|
+
}, env);
|
|
319
|
+
|
|
320
|
+
// If the API succeeds but explicitly returns false, stop immediately.
|
|
321
|
+
if (!matchValidation || matchValidation.is_match === false) {
|
|
322
|
+
logger.log(`Front/Back Mismatch: ${matchValidation?.status_message}`);
|
|
323
|
+
throw new Error(`Le verso ne correspond pas au recto fourni. Vous avez scanné un document différent.`);
|
|
324
|
+
}
|
|
320
325
|
}
|
|
326
|
+
} catch (err: any) {
|
|
327
|
+
// Throw hard errors (like the mismatch). Ignore harmless 400s if a template mapping doesn't exist yet
|
|
328
|
+
if (err?.message?.includes('ne correspond pas')) throw err;
|
|
329
|
+
logger.log("Template matching validation skipped/failed internally:", err?.message);
|
|
321
330
|
}
|
|
322
331
|
}
|
|
323
332
|
|
|
@@ -403,11 +412,11 @@ export async function backVerification(
|
|
|
403
412
|
|
|
404
413
|
export async function checkTemplateType(
|
|
405
414
|
result: {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
415
|
+
path?: string,
|
|
416
|
+
docType: string,
|
|
417
|
+
docRegion: string,
|
|
418
|
+
postfix: string,
|
|
419
|
+
issuingSubdivision?: string
|
|
411
420
|
},
|
|
412
421
|
env: KycEnvironment = 'PRODUCTION'
|
|
413
422
|
) {
|
|
@@ -425,12 +434,16 @@ export async function checkTemplateType(
|
|
|
425
434
|
}
|
|
426
435
|
|
|
427
436
|
const token = await authentification();
|
|
437
|
+
const isCanada = result.docRegion?.toUpperCase() === 'CA';
|
|
438
|
+
|
|
428
439
|
const templateType = await kycService.checkTemplateType({
|
|
429
440
|
fileUri: result.path || '',
|
|
430
441
|
docType: result?.docType as GovernmentDocumentType,
|
|
431
442
|
docRegion: result?.docRegion || "",
|
|
432
443
|
postfix: result?.postfix,
|
|
433
|
-
token: token
|
|
444
|
+
token: token,
|
|
445
|
+
// 🚨 Pass the subdivision strictly if the region is Canada
|
|
446
|
+
issuingSubdivision: isCanada ? result.issuingSubdivision : undefined
|
|
434
447
|
}, env);
|
|
435
448
|
|
|
436
449
|
if (templateType.success === false || templateType.Error) {
|
|
@@ -445,7 +458,7 @@ export async function checkTemplateType(
|
|
|
445
458
|
if (!cardData) throw new Error('CARD_NOT_FULLY_IN_FRAME');
|
|
446
459
|
|
|
447
460
|
let points: number[][] | null = null;
|
|
448
|
-
console.log(points)
|
|
461
|
+
console.log(points);
|
|
449
462
|
let isCardInFrame = false;
|
|
450
463
|
let hasCroppedSides = true;
|
|
451
464
|
|
|
@@ -281,7 +281,18 @@ export class KYCService {
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
//check templatetemplate_type
|
|
284
|
-
async checkTemplateType(
|
|
284
|
+
async checkTemplateType(
|
|
285
|
+
params: {
|
|
286
|
+
fileUri: string;
|
|
287
|
+
docType: string;
|
|
288
|
+
docRegion: string;
|
|
289
|
+
token: string;
|
|
290
|
+
postfix: string;
|
|
291
|
+
issuingSubdivision?: string; // 🚨 Added parameter
|
|
292
|
+
},
|
|
293
|
+
env: KycEnvironment = 'PRODUCTION'
|
|
294
|
+
): Promise<any> {
|
|
295
|
+
|
|
285
296
|
// SANDBOX mode
|
|
286
297
|
if (env === 'SANDBOX') {
|
|
287
298
|
console.log("SANDBOX mode: Skipping AI template type check");
|
|
@@ -293,15 +304,24 @@ export class KYCService {
|
|
|
293
304
|
};
|
|
294
305
|
}
|
|
295
306
|
|
|
296
|
-
|
|
307
|
+
// 🚨 Destructure issuingSubdivision
|
|
308
|
+
const { fileUri, docType, docRegion, token, postfix, issuingSubdivision } = params;
|
|
297
309
|
const formData = new FormData();
|
|
298
310
|
|
|
299
311
|
await appendFileToFormData(formData, 'file', fileUri, `id_card_${postfix}.jpg`, 'image/jpeg');
|
|
300
312
|
|
|
301
313
|
const docTypeShorted = GovernmentDocumentTypeShorted[docType as GovernmentDocumentType];
|
|
302
|
-
|
|
314
|
+
|
|
315
|
+
// Build the base URL
|
|
316
|
+
let url = `${this.mrzServiceURL}/get_template_version/?doc_type=${encodeURIComponent(docTypeShorted)}&doc_region=${encodeURIComponent(docRegion)}&postfix=${postfix}`;
|
|
317
|
+
|
|
318
|
+
// 🚨 Append the subdivision strictly for Canada/regional docs
|
|
319
|
+
if (issuingSubdivision) {
|
|
320
|
+
url += `&issuing_subdivision=${encodeURIComponent(issuingSubdivision)}`;
|
|
321
|
+
}
|
|
303
322
|
|
|
304
|
-
logger.log('checkTemplateType params', this.mrzServiceURL, JSON.stringify({ fileUri, docTypeShorted, docRegion, token, postfix, url }, null, 2));
|
|
323
|
+
logger.log('checkTemplateType params', this.mrzServiceURL, JSON.stringify({ fileUri, docTypeShorted, docRegion, issuingSubdivision, token, postfix, url }, null, 2));
|
|
324
|
+
|
|
305
325
|
try {
|
|
306
326
|
const res = await axios.post<CheckTemplateTypeResponse>(url, formData, {
|
|
307
327
|
headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}`, },
|
|
@@ -310,10 +330,9 @@ export class KYCService {
|
|
|
310
330
|
logger.log('checkTemplateType res', JSON.stringify(res.data, null, 2));
|
|
311
331
|
return res.data;
|
|
312
332
|
} catch (e: any) {
|
|
313
|
-
logger.error('Error
|
|
333
|
+
logger.error('Error checking template type:', JSON.stringify(e));
|
|
314
334
|
throw e;
|
|
315
335
|
}
|
|
316
|
-
|
|
317
336
|
}
|
|
318
337
|
|
|
319
338
|
async extractDocumentInformation(params: {
|