@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.
- package/build/package.json +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.js +40 -128
- package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +166 -129
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.js +62 -40
- 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/hooks/useTemplateKYCFlow.js +1 -1
- package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts +5 -0
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +82 -14
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +11 -0
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +104 -29
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/web/WebKYCEntry.d.ts.map +1 -1
- package/build/src/web/WebKYCEntry.js +34 -6
- package/build/src/web/WebKYCEntry.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/CountrySelectionTemplate.tsx +53 -133
- package/src/components/KYCElements/IDCardCapture.tsx +613 -556
- package/src/components/KYCElements/VerificationProgressTemplate.tsx +80 -71
- package/src/config/region_mapping.ts +2 -1
- package/src/hooks/useTemplateKYCFlow.tsx +14 -14
- package/src/modules/api/CardAuthentification.ts +147 -67
- package/src/modules/api/KYCService.ts +166 -43
- 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(
|
|
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,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
|
|
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: {
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
368
|
+
|
|
369
|
+
const jobId = startRes.data?.job_id;
|
|
370
|
+
if (!jobId) throw new Error("Backend did not return a job_id");
|
|
333
371
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
...
|
|
660
|
+
...data,
|
|
661
|
+
step: step,
|
|
662
|
+
templateId: templateId,
|
|
663
|
+
...(action === "location_permission" ? { permissionGranted: true } : {}),
|
|
592
664
|
},
|
|
593
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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(
|
package/src/web/WebKYCEntry.tsx
CHANGED
|
@@ -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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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;
|