@sanctum-key/react-native-sdk 1.0.20 → 1.0.22

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.
@@ -141,19 +141,24 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
141
141
  const getCurrentSideVerification = (currentSide: string, countryKey: string) => {
142
142
  const rawDocType = countrySelectionData?.documentType;
143
143
  if (!rawDocType || !countryKey) { return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' }; }
144
+
144
145
  const rawCountryName = ISO_TO_COUNTRY_NAME[countryData?.code || ''] || countryData?.code || countryKey;
145
146
  const baseMapping = (REGION_MAPPING as any).regionMapping || REGION_MAPPING;
147
+
146
148
  let countryMapping = baseMapping[rawCountryName];
147
149
  if (!countryMapping) {
148
150
  const foundKey = Object.keys(baseMapping).find(k => k.toLowerCase() === rawCountryName.toLowerCase() || k.toLowerCase() === countryKey.toLowerCase());
149
151
  if (foundKey) countryMapping = baseMapping[foundKey];
150
152
  }
151
153
  if (!countryMapping) { return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' }; }
154
+
152
155
  const regionMapping = countryMapping[rawDocType];
153
156
  if (!regionMapping) { return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' }; }
157
+
154
158
  const authMethod: string[] = [];
155
159
  const mrzTypes: string[] = [];
156
160
  const key = countrySelectionData.region?.trim()?.length > 0 ? countrySelectionData.region.trim() : 'root';
161
+
157
162
  if (regionMapping?.[key] && Array.isArray(regionMapping[key])) {
158
163
  regionMapping[key].forEach((item: any) => {
159
164
  if (item[currentSide]) {
@@ -163,25 +168,25 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
163
168
  });
164
169
  }
165
170
  return { authMethod: removeDuplicates(authMethod), mrzTypes: removeDuplicates(mrzTypes), regionMapping, key };
166
- }
171
+ };
167
172
 
168
- const getCorrespondingMrzType = (templatePath: string, mapping: any, selectedDocumentType: string = "root") => {
169
- if (!mapping || !mapping[selectedDocumentType]) return null;
170
- const fileName = templatePath.split("/").pop()?.replace(".jpg", "").replace(".png", "");
173
+ const getCorrespondingMrzType = (templatePath: string, mapping: any, regionKey: string = "root") => {
174
+ if (!mapping || !mapping[regionKey]) return null;
175
+ const fileName = templatePath.split("/").pop()?.replace(/\.(jpg|jpeg|png|py)$/i, "");
171
176
  if (!fileName) return null;
172
177
  const pyName = `${fileName}.py`;
173
- const found = mapping[selectedDocumentType].find((item: any) => item.py_file === pyName);
178
+ const found = mapping[regionKey].find((item: any) => item.py_file === pyName);
174
179
  return found?.mrz_type || null;
175
- }
180
+ };
176
181
 
177
- const getCorrespondingAuthMethod = (templatePath: string, mapping: any, selectedDocumentType: string = "root", side: 'front' | 'back') => {
178
- if (!mapping || !mapping[selectedDocumentType]) return null;
179
- const fileName = templatePath.split("/").pop()?.replace(".jpg", "").replace(".png", "");
182
+ const getCorrespondingAuthMethod = (templatePath: string, mapping: any, regionKey: string = "root", side: 'front' | 'back') => {
183
+ if (!mapping || !mapping[regionKey]) return null;
184
+ const fileName = templatePath.split("/").pop()?.replace(/\.(jpg|jpeg|png|py)$/i, "");
180
185
  if (!fileName) return null;
181
186
  const pyName = `${fileName}.py`;
182
- const found = mapping[selectedDocumentType].find((item: any) => item.py_file === pyName);
187
+ const found = mapping[regionKey].find((item: any) => item.py_file === pyName);
183
188
  return found?.[side] || null;
184
- }
189
+ };
185
190
 
186
191
  const autoCapture = async (capturePath: string, verified: ISilentCaptureResult & { region?: string }) => {
187
192
  if (isProcessingCapture) return;
@@ -214,7 +219,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
214
219
  ...newImages,
215
220
  country: verified.country,
216
221
  documentType: GovernmentDocumentTypeBackend[verified.documentType as keyof typeof GovernmentDocumentTypeBackend] || '',
217
- ...(verified.region ? { region: verified.region } : {})
222
+ ...(verified.region ? { region: verified.region } : {})
218
223
  });
219
224
  }
220
225
 
@@ -287,7 +292,10 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
287
292
 
288
293
  verificationRes = await frontVerification({
289
294
  path: result.path,
290
- regionMapping: { authMethod: matchedAuthMethod ? [matchedAuthMethod] : regionMappings.authMethod, mrzTypes: regionMappings.mrzTypes },
295
+ regionMapping: {
296
+ authMethod: matchedAuthMethod ? [matchedAuthMethod] : [],
297
+ mrzTypes: mrzType ? [mrzType] : []
298
+ },
291
299
  selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType?.type as keyof typeof GovernmentDocumentTypeShorted] || '',
292
300
  code: countryData?.code || '',
293
301
  currentSide,
@@ -296,16 +304,16 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
296
304
  issuingSubdivision: issuingSubdivision
297
305
  }, env);
298
306
  } else {
299
- let matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
300
- if (!matchedBackAuthMethod && currentSide === 'back') { matchedBackAuthMethod = 'MRZ'; }
307
+ const matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
301
308
  const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
302
-
303
- // 🚨 EXTRACT FRONT TEMPLATE TO VALIDATE AGAINST BACK TEMPLATE
304
309
  const frontTemplatePath = capturedImages['front']?.templatePath || '';
305
310
 
306
311
  verificationRes = await backVerification({
307
312
  path: result.path,
308
- regionMapping: { authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : regionMappings.authMethod, mrzTypes: regionMappings.mrzTypes },
313
+ regionMapping: {
314
+ authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : [],
315
+ mrzTypes: backMrzType ? [backMrzType] : []
316
+ },
309
317
  selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type as keyof typeof GovernmentDocumentTypeShorted] || '',
310
318
  code: countryData?.code || '',
311
319
  currentSide,
@@ -313,7 +321,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
313
321
  mrzType: backMrzType,
314
322
  templateResponse,
315
323
  issuingSubdivision: issuingSubdivision,
316
- frontTemplatePath: frontTemplatePath // 🚨 PASSED DOWN TO TRIGGER VALIDATION
324
+ frontTemplatePath: frontTemplatePath
317
325
  }, env);
318
326
  }
319
327
 
@@ -348,7 +356,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
348
356
  userFriendlyMessage = 'The document does not match your selected country.';
349
357
  } else if (rawMessage.includes('card_not_fully_in_frame')) {
350
358
  userFriendlyMessage = 'Move closer to the document and ensure all corners are visible.';
351
- } else if (rawMessage.includes('ne correspond pas au recto')) { // 🚨 New Security Error Handler
359
+ } else if (rawMessage.includes('ne correspond pas au recto')) {
352
360
  userFriendlyMessage = 'The back of the document does not match the front. You must scan the same ID.';
353
361
  }
354
362
 
@@ -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": [
@@ -31,7 +31,7 @@ export async function frontVerification(
31
31
  currentSide: string,
32
32
  templatePath?: string,
33
33
  mrzType?: string,
34
- issuingSubdivision?: string
34
+ issuingSubdivision?: string
35
35
  },
36
36
  env: KycEnvironment = 'PRODUCTION'
37
37
  ) {
@@ -42,7 +42,9 @@ export async function frontVerification(
42
42
  const authMethods = Array.isArray(result.regionMapping?.authMethod)
43
43
  ? result.regionMapping.authMethod.map(m => String(m).toUpperCase())
44
44
  : [];
45
+
45
46
  const hasMrz = authMethods.some(m => m.includes('MRZ'));
47
+ const hasBarcode = authMethods.some(m => m.includes('BARCODE') || m.includes('2D'));
46
48
 
47
49
  if (env === 'SANDBOX') {
48
50
  console.log("SANDBOX mode: Skipping AI verification for front document");
@@ -60,12 +62,11 @@ export async function frontVerification(
60
62
  ...(hasMrz ? {
61
63
  mrz: {
62
64
  success: true,
63
- parsed_data: {
64
- status: 'SUCCESS',
65
- document_type: result.selectedDocumentType,
66
- mrz_type: result.mrzType || 'TD1'
67
- }
65
+ parsed_data: { status: 'SUCCESS', document_type: result.selectedDocumentType, mrz_type: result.mrzType || 'TD1' }
68
66
  }
67
+ } : {}),
68
+ ...(hasBarcode ? {
69
+ barcode: { barcode_data: 'SANDBOX_MOCK_BARCODE' }
69
70
  } : {})
70
71
  };
71
72
  return mockResponse;
@@ -110,6 +111,7 @@ export async function frontVerification(
110
111
  let croppedBase64: string | undefined;
111
112
  let bbox: IBbox | undefined;
112
113
  let mrz: any | undefined;
114
+ let barcode: any | undefined;
113
115
  let documentData: any | undefined;
114
116
 
115
117
  try {
@@ -126,7 +128,7 @@ export async function frontVerification(
126
128
  docType: result.selectedDocumentType,
127
129
  docRegion: result.code,
128
130
  templatePath: result.templatePath || '',
129
- issuingSubdivision: result.issuingSubdivision,
131
+ issuingSubdivision: result.issuingSubdivision,
130
132
  token: token,
131
133
  postfix: result.currentSide || 'front'
132
134
  });
@@ -162,9 +164,28 @@ export async function frontVerification(
162
164
  throw new Error(err?.message || "Erreur lors de l'extraction MRZ. Veuillez reprendre la photo.");
163
165
  }
164
166
  }
167
+
168
+ if (hasBarcode) {
169
+ try {
170
+ logger.log("Tentative d'extraction barcode (Front)");
171
+ barcode = await kycService.extractBarcode({ fileUri: result.path || '', token: token }, env);
172
+ } catch (e: any) {
173
+ logger.log("Front Barcode Extraction Error:", e?.message);
174
+ // If it exclusively has a barcode (no MRZ) and barcode fails, throw.
175
+ if (!mrz) {
176
+ throw new Error(`Lecture Barcode échouée: ${e?.message || 'Veuillez réessayer'}`);
177
+ }
178
+ }
179
+ }
165
180
 
166
- // Return detected frame, crop, MRZ, AND the new extracted OCR documentData
167
- return { ...detected, croppedBase64, bbox, ...(mrz ? { mrz } : {}), ...(documentData ? { documentData } : {}) };
181
+ return {
182
+ ...detected,
183
+ croppedBase64,
184
+ bbox,
185
+ ...(mrz ? { mrz } : {}),
186
+ ...(barcode ? { barcode } : {}),
187
+ ...(documentData ? { documentData } : {})
188
+ };
168
189
 
169
190
  } catch (e: any) {
170
191
  logger.log('Error front verification:', e?.message);
@@ -182,8 +203,8 @@ export async function backVerification(
182
203
  templatePath?: string,
183
204
  mrzType?: string,
184
205
  templateResponse?: ApiVerificationResponse,
185
- issuingSubdivision?: string, // Capture province/state
186
- frontTemplatePath?: string // Capture the front template to compare against
206
+ issuingSubdivision?: string,
207
+ frontTemplatePath?: string
187
208
  },
188
209
  env: KycEnvironment = 'PRODUCTION'
189
210
  ) {
@@ -213,11 +234,7 @@ export async function backVerification(
213
234
  success: true,
214
235
  mrz: {
215
236
  success: true,
216
- parsed_data: {
217
- status: 'SUCCESS',
218
- document_type: result.selectedDocumentType,
219
- mrz_type: result.mrzType || 'TD1'
220
- }
237
+ parsed_data: { status: 'SUCCESS', document_type: result.selectedDocumentType, mrz_type: result.mrzType || 'TD1' }
221
238
  },
222
239
  bbox: mockBbox,
223
240
  card_obb: mockCardObb
@@ -295,29 +312,34 @@ export async function backVerification(
295
312
  }
296
313
  }
297
314
 
298
- // 🚨 STRICT FRONT VS BACK TEMPLATE VALIDATION 🚨
299
315
  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);
316
+ try {
317
+ const frontPy = result.frontTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
318
+ const backPy = activeTemplatePath.split("/").pop()?.replace(/\.(jpg|png)$/i, ".py");
315
319
 
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.`);
320
+ if (frontPy && backPy) {
321
+ logger.log(`Validating Front (${frontPy}) vs Back (${backPy}) for region ${result.code}`);
322
+
323
+ const isCanadianSubRegionDoc = result.code === 'CA' && ['hic'].includes(result.selectedDocumentType);
324
+ const safeSubRegion = isCanadianSubRegionDoc ? (result.issuingSubdivision || "") : "";
325
+
326
+ const matchValidation = await kycService.validateBackTemplate({
327
+ region: result.code,
328
+ docType: result.selectedDocumentType,
329
+ frontTemplateName: frontPy,
330
+ backTemplateName: backPy,
331
+ subRegion: safeSubRegion,
332
+ token: token
333
+ }, env);
334
+
335
+ if (!matchValidation || matchValidation.is_match === false) {
336
+ logger.log(`Front/Back Mismatch: ${matchValidation?.status_message}`);
337
+ throw new Error(`Le verso ne correspond pas au recto fourni. Vous avez scanné un document différent.`);
338
+ }
320
339
  }
340
+ } catch (err: any) {
341
+ if (err?.message?.includes('ne correspond pas')) throw err;
342
+ logger.log("Template matching validation skipped/failed internally:", err?.message);
321
343
  }
322
344
  }
323
345
 
@@ -369,10 +391,11 @@ export async function backVerification(
369
391
 
370
392
  if (hasBarcode) {
371
393
  try {
372
- logger.log("Tentative d'extraction barcode");
394
+ logger.log("Tentative d'extraction barcode (Back)");
373
395
  const barcode = await kycService.extractBarcode({ fileUri: result.path!, token: token }, env);
374
396
  extractionResult.barcode = barcode;
375
397
  } catch (e: any) {
398
+ // If it explicitly expects a barcode (and not an MRZ) and fails, throw to user
376
399
  if (!extractionResult.mrz) {
377
400
  throw new Error(`Lecture Barcode échouée: ${e?.message}`);
378
401
  }
@@ -403,11 +426,11 @@ export async function backVerification(
403
426
 
404
427
  export async function checkTemplateType(
405
428
  result: {
406
- path?: string,
407
- docType: string,
408
- docRegion: string,
409
- postfix: string,
410
- issuingSubdivision?: string
429
+ path?: string,
430
+ docType: string,
431
+ docRegion: string,
432
+ postfix: string,
433
+ issuingSubdivision?: string
411
434
  },
412
435
  env: KycEnvironment = 'PRODUCTION'
413
436
  ) {
@@ -425,12 +448,15 @@ export async function checkTemplateType(
425
448
  }
426
449
 
427
450
  const token = await authentification();
451
+ const isCanada = result.docRegion?.toUpperCase() === 'CA';
452
+
428
453
  const templateType = await kycService.checkTemplateType({
429
454
  fileUri: result.path || '',
430
455
  docType: result?.docType as GovernmentDocumentType,
431
456
  docRegion: result?.docRegion || "",
432
457
  postfix: result?.postfix,
433
- token: token
458
+ token: token,
459
+ issuingSubdivision: isCanada ? result.issuingSubdivision : undefined
434
460
  }, env);
435
461
 
436
462
  if (templateType.success === false || templateType.Error) {
@@ -468,7 +494,7 @@ export async function checkTemplateType(
468
494
 
469
495
  const LPIPS_THRESHOLD = 0.75;
470
496
  if (templateType.lpips_score !== undefined && templateType.lpips_score > LPIPS_THRESHOLD) {
471
- logger.log(`🛑 Country Mismatch! LPIPS: ${templateType.lpips_score}`);
497
+ logger.log(`Country Mismatch! LPIPS: ${templateType.lpips_score}`);
472
498
  throw new Error(`Le document présenté ne correspond pas au pays sélectionné (${result.docRegion}).`);
473
499
  }
474
500
 
@@ -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: {