@sanctum-key/react-native-sdk 1.0.19 → 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.
Files changed (34) hide show
  1. package/build/package.json +1 -1
  2. package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
  3. package/build/src/components/KYCElements/CountrySelectionTemplate.js +40 -128
  4. package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
  5. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  6. package/build/src/components/KYCElements/IDCardCapture.js +166 -129
  7. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  8. package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
  9. package/build/src/components/KYCElements/VerificationProgressTemplate.js +62 -40
  10. package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
  11. package/build/src/config/region_mapping.d.ts.map +1 -1
  12. package/build/src/config/region_mapping.js.map +1 -1
  13. package/build/src/hooks/useTemplateKYCFlow.js +1 -1
  14. package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
  15. package/build/src/modules/api/CardAuthentification.d.ts +5 -0
  16. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  17. package/build/src/modules/api/CardAuthentification.js +82 -14
  18. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  19. package/build/src/modules/api/KYCService.d.ts +11 -0
  20. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  21. package/build/src/modules/api/KYCService.js +104 -29
  22. package/build/src/modules/api/KYCService.js.map +1 -1
  23. package/build/src/web/WebKYCEntry.d.ts.map +1 -1
  24. package/build/src/web/WebKYCEntry.js +34 -6
  25. package/build/src/web/WebKYCEntry.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/components/KYCElements/CountrySelectionTemplate.tsx +53 -133
  28. package/src/components/KYCElements/IDCardCapture.tsx +613 -556
  29. package/src/components/KYCElements/VerificationProgressTemplate.tsx +80 -71
  30. package/src/config/region_mapping.ts +2 -1
  31. package/src/hooks/useTemplateKYCFlow.tsx +14 -14
  32. package/src/modules/api/CardAuthentification.ts +147 -67
  33. package/src/modules/api/KYCService.ts +166 -43
  34. package/src/web/WebKYCEntry.tsx +42 -7
@@ -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,35 +330,80 @@ 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
- async extractDocumentInformation(params: { fileUri: string; docType: string; docRegion: string; token: string; postfix?: string }): Promise<any> {
320
- const { fileUri, docType, docRegion, token, postfix = 'front' } = params;
321
- const formData = new FormData();
338
+ async extractDocumentInformation(params: {
339
+ fileUri: string;
340
+ docType: string;
341
+ docRegion: string;
342
+ templatePath: string; // 🚨 Required by API
343
+ issuingSubdivision?: string; // 🚨 Required for non-passports
344
+ token: string;
345
+ postfix?: string
346
+ }): Promise<any> {
347
+ const { fileUri, docType, docRegion, templatePath, issuingSubdivision, token, postfix = 'front' } = params;
322
348
 
323
- // ✅ FIX: Dynamic filename
349
+ // 1. Prepare Form Data
350
+ const formData = new FormData();
351
+ // Prefixing with 'front' or 'back' as strictly required by the docs
324
352
  await appendFileToFormData(formData, 'file', fileUri, `id_card_${postfix}.jpg`, 'image/jpeg');
325
353
 
326
- const url = `${this.textExtractionServiceURL}/extract_doc_information/?doc_type=${encodeURIComponent(docType)}&doc_region=${encodeURIComponent(docRegion)}`;
327
- const attempt = async () => {
328
- const res = await axios.post(url, formData, {
329
- headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}`, },
354
+ // 2. Build the URL with ALL required parameters
355
+ let queryParams = `doc_type=${encodeURIComponent(docType)}&doc_region=${encodeURIComponent(docRegion)}&template_path=${encodeURIComponent(templatePath)}`;
356
+ if (issuingSubdivision) {
357
+ queryParams += `&issuing_subdivision=${encodeURIComponent(issuingSubdivision)}`;
358
+ }
359
+
360
+ const url = `${this.textExtractionServiceURL}/extract_doc_information/?${queryParams}`;
361
+
362
+ try {
363
+ // 3. Start the Job
364
+ const startRes = await axios.post(url, formData, {
365
+ headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}` },
330
366
  timeout: 60000,
331
367
  });
332
- logger.log('extractDocumentInformation res', JSON.stringify(truncateFields(res)));
368
+
369
+ const jobId = startRes.data?.job_id;
370
+ if (!jobId) throw new Error("Backend did not return a job_id");
333
371
 
334
- if (res.data?.detail) throw new Error(res.data.detail);
335
- return res.data;
336
- };
337
- try {
338
- return await attempt();
339
- } catch (e) {
340
- logger.error('Error extracting document information:', JSON.stringify(truncateFields(e)));
341
- throw e;
372
+ logger.log(`Extraction job started: ${jobId}. Polling for results...`);
373
+
374
+ // 4. Poll the Status Endpoint
375
+ const statusUrl = `${this.textExtractionServiceURL}/extract_doc_information/status/${jobId}`;
376
+
377
+ // Poll every 2 seconds, up to 15 times (30 seconds max)
378
+ const maxRetries = 15;
379
+ for (let i = 0; i < maxRetries; i++) {
380
+ // Wait 2 seconds before checking
381
+ await new Promise(resolve => setTimeout(resolve, 2000));
382
+
383
+ const statusRes = await axios.get(statusUrl, {
384
+ headers: { 'Authorization': `Bearer ${token}` }
385
+ });
386
+
387
+ const statusData = statusRes.data;
388
+
389
+ if (statusData.status === 'completed') {
390
+ logger.log('Extraction completed successfully', JSON.stringify(truncateFields(statusData.result)));
391
+ return statusData.result; // 👈 Returns the actual extracted data
392
+ }
393
+
394
+ if (statusData.status === 'failed') {
395
+ throw new Error(statusData.error || "Document extraction failed during processing.");
396
+ }
397
+
398
+ // If 'processing', loop continues...
399
+ }
400
+
401
+ throw new Error("Document extraction timed out waiting for results.");
402
+
403
+ } catch (e: any) {
404
+ const errorMsg = e.response?.data?.detail || e.message;
405
+ logger.error('Error extracting document information:', errorMsg);
406
+ throw new Error(errorMsg);
342
407
  }
343
408
  }
344
409
 
@@ -412,7 +477,7 @@ export class KYCService {
412
477
  };
413
478
  }
414
479
  const { fileUri, docType, docRegion, postfix = 'back', token, template_path, mrz_type } = params;
415
-
480
+
416
481
  // ... top of extractMrzText (params extraction, etc) ...
417
482
  const docTypeShorted = GovernmentDocumentTypeShorted[docType as GovernmentDocumentType] || docType;
418
483
  const safeMrzType = mrz_type && mrz_type.trim() !== '' ? mrz_type : 'TD1';
@@ -578,41 +643,53 @@ export class KYCService {
578
643
  }
579
644
 
580
645
  async verificationSession(payload: VerificationSessionRequest): Promise<any> {
581
- console.log('apiKey in verificationSession', payload.apiKey);
582
646
  try {
583
647
  const { session_id, step, data, templateId, action, apiKey } = payload;
648
+
649
+ // 1. Safe Auth Resolution
584
650
  const token = apiKey ? undefined : await authentification();
651
+
652
+ if (!apiKey && !token) {
653
+ throw new Error("Authentication failed: No API key or Token available.");
654
+ }
655
+
656
+
585
657
  const payloadData = {
586
658
  action: action,
587
659
  data: {
588
- step,
589
- templateId,
590
- ...(action === "location_permission" ? { permissionGranted: true, } : {}),
591
- ...data,
660
+ ...data,
661
+ step: step,
662
+ templateId: templateId,
663
+ ...(action === "location_permission" ? { permissionGranted: true } : {}),
592
664
  },
593
- ...({ session_id: session_id }),
594
- timestamp: new Date().toISOString()
595
- }
665
+ session_id: session_id,
666
+ timestamp: new Date().toISOString(),
667
+ };
668
+
596
669
  const url = `${KYCConfig.getBackendUrl()}/verification/api/kyc/sessions/${session_id}/steps/${step}/`;
597
670
 
671
+ // 3. Clean Logging
598
672
  const logPayload = truncateFields({ payloadData, session_id, step });
599
- logger.log('verificationSession payload',
600
- JSON.stringify(truncateFields({ logPayload, token: token ?? "-", path: url, apiKey }),
601
- null, 2))
602
-
603
- const res = await axios.post<SessionResponse>(url,
604
- payloadData,
605
- {
673
+ logger.log('verificationSession payload', JSON.stringify({
674
+ url,
675
+ apiKey: apiKey ? '***HIDDEN***' : 'None', // Don't log the raw API key!
676
+ token: token ? '***HIDDEN***' : 'None',
677
+ payload: logPayload
678
+ }, null, 2));
679
+
680
+ // 4. API Execution
681
+ const res = await axios.post<SessionResponse>(url, payloadData, {
606
682
  headers: {
607
683
  'Content-Type': 'application/json',
608
684
  ...(apiKey ? { 'Authorization': `ApiKey ${apiKey}` } : { 'Authorization': `Bearer ${token}` })
609
685
  }
610
- });
686
+ });
687
+
611
688
  logger.log('verificationSession res', JSON.stringify(truncateFields(res.data), null, 2));
612
689
  return res.data;
613
690
 
614
691
  } catch (error) {
615
- logger.error('Error validating component:', JSON.stringify(error, null, 2));
692
+ logger.error('Error validating component:', JSON.stringify(errorMessage(error), null, 2));
616
693
  throw new Error(errorMessage(error));
617
694
  }
618
695
  }
@@ -757,6 +834,54 @@ export class KYCService {
757
834
  throw error;
758
835
  }
759
836
  }
837
+
838
+ // Inside KYCService.ts
839
+
840
+ async validateBackTemplate(params: {
841
+ region: string;
842
+ docType: string;
843
+ frontTemplateName: string;
844
+ backTemplateName: string;
845
+ subRegion: string;
846
+ token: string;
847
+ }, env: KycEnvironment = 'PRODUCTION'): Promise<any> {
848
+ if (env === 'SANDBOX') {
849
+ logger.log("SANDBOX mode: Skipping back template validation");
850
+ return { is_match: true, status_message: "SANDBOX: Valid Match" };
851
+ }
852
+
853
+ const { region, docType, frontTemplateName, backTemplateName, subRegion, token } = params;
854
+
855
+ // 🚨 THE FIX: Format the payload as URL Query Parameters
856
+ const queryParams = new URLSearchParams({
857
+ region: region,
858
+ doc_type: docType,
859
+ front_template_name: frontTemplateName,
860
+ back_template_name: backTemplateName,
861
+ sub_region: subRegion || ""
862
+ }).toString();
863
+
864
+ const url = `${this.mrzServiceURL}/validate_back_template/?${queryParams}`;
865
+
866
+ logger.log('validateBackTemplate URL', url);
867
+
868
+ try {
869
+ // Pass `null` for the body since the data is now in the URL
870
+ const res = await axios.post(url, null, {
871
+ headers: {
872
+ 'Accept': 'application/json',
873
+ 'Authorization': `Bearer ${token}`
874
+ },
875
+ timeout: 20000,
876
+ });
877
+ logger.log('validateBackTemplate res', JSON.stringify(res.data, null, 2));
878
+ return res.data;
879
+ } catch (e: any) {
880
+ const errorMsg = e.response?.data?.detail || e.response?.data?.Error || e.message;
881
+ logger.error('Error validating back template:', JSON.stringify(errorMsg));
882
+ throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Template validation failed');
883
+ }
884
+ }
760
885
  }
761
886
 
762
887
  const kycService = new KYCService("", "");
@@ -801,8 +926,6 @@ export const authentification = async (): Promise<string> => {
801
926
  activeAuthRequest = (async () => {
802
927
  logger.log('Authenticating: Fetching new Keycloak token...');
803
928
 
804
- // 🚨 FIX: Do NOT use URLSearchParams in React Native with Axios.
805
- // Use a raw URL-encoded string to guarantee Keycloak understands the payload.
806
929
  const params = 'client_id=kyc_frontend&client_secret=QhgAmvKgmwODzsEp98dnA4PeUEMMaFHd&grant_type=client_credentials';
807
930
 
808
931
  const res = await axios.post(
@@ -111,6 +111,7 @@ export function WebKYCEntry({
111
111
  onCancel,
112
112
  onSessionSave,
113
113
  }: WebKYCEntryProps): JSX.Element {
114
+ console.log('first')
114
115
  const { setLocale } = useI18n();
115
116
  const [urlParams, setUrlParams] = useState<URLParams>({});
116
117
  const [isLoading, setIsLoading] = useState(true);
@@ -204,7 +205,6 @@ export function WebKYCEntry({
204
205
  }
205
206
  }, [urlParams]);
206
207
 
207
- // Handle KYC completion
208
208
  const handleComplete = useCallback(async (data: any) => {
209
209
  console.log('KYC completed:', data);
210
210
 
@@ -214,15 +214,48 @@ export function WebKYCEntry({
214
214
  // Handle Push URL webhook if provided
215
215
  if (urlParams.push_url) {
216
216
  try {
217
+ const innerResult = data?.result?.result || data?.result || data;
218
+ const nestedDataMap = innerResult?.data;
219
+
220
+ let kycSessionId = data?.session_id || urlParams.kyc_id || '';
221
+ let parsedKycData: any = null;
222
+
223
+ if (nestedDataMap) {
224
+ const mapKeys = Object.keys(nestedDataMap);
225
+ if (mapKeys.length > 0) {
226
+ const detectedSessionId = mapKeys.find(key => key.startsWith('kyc-')) || mapKeys[0];
227
+ if (detectedSessionId) {
228
+ kycSessionId = detectedSessionId;
229
+ }
230
+
231
+ const rawData = nestedDataMap[kycSessionId]?.data;
232
+
233
+ if (typeof rawData === 'string') {
234
+ try {
235
+ parsedKycData = JSON.parse(rawData);
236
+ } catch (parseErr) {
237
+ console.warn('Failed parsing inner stringified JSON string:', parseErr);
238
+ parsedKycData = rawData;
239
+ }
240
+ } else {
241
+ parsedKycData = rawData;
242
+ }
243
+ }
244
+ }
245
+
246
+ const finalSessionId = kycSessionId || parsedKycData?.session_id || urlParams.kyc_id || 'unknown';
247
+
248
+ // 3. Build your exact un-nested payload signature
217
249
  const verificationState = {
218
- status: 'success', // verification completed successfully
219
- result: data, // pass complete data object as result
220
- reference_id: urlParams.reference_id,
250
+ session_id: finalSessionId,
251
+ status: 'success',
252
+ message: innerResult?.message || 'Verification data retrieved successfully.',
253
+ data: parsedKycData || {}
221
254
  };
255
+
222
256
  await postWebhook(urlParams.push_url, verificationState, 5000);
223
- console.log('Successfully pushed KYC data to webhook');
257
+ console.log('Successfully pushed beautifully clean KYC data payload to webhook');
224
258
  } catch (err) {
225
- // Fail open - redirect anyway even if webhook fails
226
259
  console.warn('Failed to push data to webhook URL:', err);
227
260
  }
228
261
  }
@@ -238,6 +271,7 @@ export function WebKYCEntry({
238
271
  liveness_checked: data.livenessCheckComplete || false,
239
272
  },
240
273
  });
274
+
241
275
  onComplete?.(data);
242
276
  }, [redirectToReturnUrl, urlParams, onComplete]);
243
277
 
@@ -311,6 +345,7 @@ export function WebKYCEntry({
311
345
  useEffect(() => {
312
346
  try {
313
347
  const params = parseUrlParams();
348
+
314
349
  setUrlParams(params);
315
350
 
316
351
  // Set language if provided
@@ -447,4 +482,4 @@ const styles = StyleSheet.create({
447
482
  },
448
483
  });
449
484
 
450
- export default WebKYCEntry;
485
+ export default WebKYCEntry;