@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.
@@ -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; // Break out of the function so it DOES NOT RETRY
44
+ return;
46
45
  }
47
46
 
48
- // Check standard nested payload format
49
- const responseData = result?.data;
50
- const verificationResult = responseData?.[state.session.session_id]?.data;
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
- // Vérifier le statut de vérification
58
- if (verificationResult.verification_status === null || verificationResult.verification_status === "processing" || verificationResult.verification_status === "pending") {
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
- // Traiter le résultat final (success, failed, etc.)
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: result
85
+ result: {
86
+ ...result,
87
+ referenceId: verificationResult.verification_id || verificationResult.session_id
88
+ } as any
85
89
  });
86
- } else if (verificationResult.verification_status === "failed" || verificationResult.verification_status === "rejected") {
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
- // 🚨 STRICT FRONT VS BACK TEMPLATE VALIDATION 🚨
298
+ // 🚨 FRONT VS BACK TEMPLATE VALIDATION 🚨
299
299
  if (result.frontTemplatePath && activeTemplatePath) {
300
- const frontPy = result.frontTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
301
- const backPy = activeTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
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
- // If the API succeeds but explicitly returns false, stop immediately.
317
- if (!matchValidation || matchValidation.is_match === false) {
318
- logger.log(`Front/Back Mismatch: ${matchValidation?.status_message}`);
319
- throw new Error(`Le verso ne correspond pas au recto fourni. Vous avez scanné un document différent.`);
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
- path?: string,
407
- docType: string,
408
- docRegion: string,
409
- postfix: string,
410
- issuingSubdivision?: string
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(params: { fileUri: string; docType: string; docRegion: string; token: string; postfix: string}, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
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
- const { fileUri, docType, docRegion, token, postfix } = params;
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
- const url = `${this.mrzServiceURL}/get_template_version/?doc_type=${encodeURIComponent(docTypeShorted)}&doc_region=${encodeURIComponent(docRegion)}&postfix=${postfix}`;
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 checkingg template type:', JSON.stringify(e));
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: {