@sanctum-key/react-native-sdk 1.0.8 → 1.0.10
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/README.md +4 -4
- package/android/build.gradle +2 -2
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkModule.kt +6 -6
- package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkView.kt +2 -2
- package/build/package.json +5 -5
- package/build/src/App.d.ts +2 -2
- package/build/src/App.d.ts.map +1 -1
- package/build/src/App.js +2 -2
- package/build/src/App.js.map +1 -1
- package/build/src/{SanctumKeySdk.types.d.ts → TransfergratisSdk.types.d.ts} +3 -3
- package/build/src/TransfergratisSdk.types.d.ts.map +1 -0
- package/build/src/TransfergratisSdk.types.js +2 -0
- package/build/src/TransfergratisSdk.types.js.map +1 -0
- package/build/src/{SanctumKeySdkModule.d.ts → TransfergratisSdkModule.d.ts} +4 -4
- package/build/src/TransfergratisSdkModule.d.ts.map +1 -0
- package/build/src/{SanctumKeySdkModule.js → TransfergratisSdkModule.js} +2 -2
- package/build/src/TransfergratisSdkModule.js.map +1 -0
- package/build/src/{SanctumKeySdkModule.web.d.ts → TransfergratisSdkModule.web.d.ts} +4 -4
- package/build/src/TransfergratisSdkModule.web.d.ts.map +1 -0
- package/build/src/{SanctumKeySdkModule.web.js → TransfergratisSdkModule.web.js} +3 -3
- package/build/src/TransfergratisSdkModule.web.js.map +1 -0
- package/build/src/TransfergratisSdkView.d.ts +4 -0
- package/build/src/TransfergratisSdkView.d.ts.map +1 -0
- package/build/src/TransfergratisSdkView.js +7 -0
- package/build/src/TransfergratisSdkView.js.map +1 -0
- package/build/src/TransfergratisSdkView.web.d.ts +4 -0
- package/build/src/TransfergratisSdkView.web.d.ts.map +1 -0
- package/build/src/{SanctumKeySdkView.web.js → TransfergratisSdkView.web.js} +2 -2
- package/build/src/TransfergratisSdkView.web.js.map +1 -0
- package/build/src/api/axios.js +2 -2
- package/build/src/api/axios.js.map +1 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +12 -61
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.js +259 -63
- package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.js +11 -32
- package/build/src/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +222 -68
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js +9 -11
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
- package/build/src/components/NativeCameraView.js +1 -1
- package/build/src/components/NativeCameraView.js.map +1 -1
- package/build/src/config/KYCConfig.js +1 -1
- package/build/src/config/KYCConfig.js.map +1 -1
- package/build/src/config/allowedDomains.js +6 -6
- package/build/src/config/allowedDomains.js.map +1 -1
- package/build/src/config/region_mapping.json +727 -0
- package/build/src/index.d.ts +3 -3
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +3 -3
- package/build/src/index.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +3 -7
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +1 -2
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +103 -63
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/modules/camera/NativeCameraModule.js +17 -17
- package/build/src/modules/camera/NativeCameraModule.js.map +1 -1
- package/expo-module.config.json +2 -2
- package/ios/TransfergratisSdk.podspec +2 -2
- package/ios/TransfergratisSdkModule.swift +12 -12
- package/package.json +5 -5
- package/src/App.tsx +2 -2
- package/src/{SanctumKeySdk.types.ts → TransfergratisSdk.types.ts} +2 -2
- package/src/{SanctumKeySdkModule.ts → TransfergratisSdkModule.ts} +3 -3
- package/src/{SanctumKeySdkModule.web.ts → TransfergratisSdkModule.web.ts} +3 -3
- package/src/TransfergratisSdkView.tsx +11 -0
- package/src/{SanctumKeySdkView.web.tsx → TransfergratisSdkView.web.tsx} +2 -2
- package/src/api/axios.ts +2 -2
- package/src/components/EnhancedCameraView.tsx +34 -99
- package/src/components/KYCElements/CountrySelection.tsx +300 -74
- package/src/components/KYCElements/EmailVerificationTemplate.tsx +10 -36
- package/src/components/KYCElements/IDCardCapture.tsx +310 -156
- package/src/components/KYCElements/PhoneVerificationTemplate.tsx +9 -11
- package/src/components/NativeCameraView.tsx +1 -1
- package/src/config/KYCConfig.ts +1 -1
- package/src/config/allowedDomains.ts +6 -6
- package/src/i18n/README.md +1 -1
- package/src/index.ts +3 -3
- package/src/modules/api/CardAuthentification.ts +5 -8
- package/src/modules/api/KYCService.ts +167 -116
- package/src/modules/camera/NativeCameraModule.ts +17 -17
- package/build/src/SanctumKeySdk.types.d.ts.map +0 -1
- package/build/src/SanctumKeySdk.types.js +0 -2
- package/build/src/SanctumKeySdk.types.js.map +0 -1
- package/build/src/SanctumKeySdkModule.d.ts.map +0 -1
- package/build/src/SanctumKeySdkModule.js.map +0 -1
- package/build/src/SanctumKeySdkModule.web.d.ts.map +0 -1
- package/build/src/SanctumKeySdkModule.web.js.map +0 -1
- package/build/src/SanctumKeySdkView.d.ts +0 -4
- package/build/src/SanctumKeySdkView.d.ts.map +0 -1
- package/build/src/SanctumKeySdkView.js +0 -7
- package/build/src/SanctumKeySdkView.js.map +0 -1
- package/build/src/SanctumKeySdkView.web.d.ts +0 -4
- package/build/src/SanctumKeySdkView.web.d.ts.map +0 -1
- package/build/src/SanctumKeySdkView.web.js.map +0 -1
- package/src/SanctumKeySdkView.tsx +0 -11
|
@@ -17,7 +17,7 @@ interface PhoneVerificationTemplateProps {
|
|
|
17
17
|
type VerificationStep = 'phone' | 'otp';
|
|
18
18
|
const CODE_LENGTH = 6;
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// 🌍 The Country Codes Array
|
|
21
21
|
const COUNTRY_CODES = [
|
|
22
22
|
{ code: '+254', label: '🇰🇪 Kenya (+254)' },
|
|
23
23
|
{ code: '+255', label: '🇹🇿 Tanzania (+255)' },
|
|
@@ -52,7 +52,7 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
52
52
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
53
53
|
const [isSimulating, setIsSimulating] = useState(false);
|
|
54
54
|
|
|
55
|
-
//
|
|
55
|
+
// Track actual focus state for visual feedback
|
|
56
56
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
57
57
|
|
|
58
58
|
const inputRef = useRef<TextInput>(null);
|
|
@@ -71,20 +71,21 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
71
71
|
}
|
|
72
72
|
}, [otp]);
|
|
73
73
|
|
|
74
|
-
// --- AUTO FOCUS LOGIC ---
|
|
74
|
+
// --- SAFE AUTO FOCUS LOGIC ---
|
|
75
75
|
useEffect(() => {
|
|
76
76
|
let focusTimer: ReturnType<typeof setTimeout>;
|
|
77
77
|
|
|
78
|
+
// Only attempt focus when we are on the OTP step AND the loading state is completely finished
|
|
78
79
|
if (step === 'otp' && !isSimulating) {
|
|
79
80
|
focusTimer = setTimeout(() => {
|
|
80
81
|
inputRef.current?.focus();
|
|
81
|
-
},
|
|
82
|
+
}, 300); // 300ms guarantees view is painted before requesting keyboard
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
return () => {
|
|
85
86
|
if (focusTimer) clearTimeout(focusTimer);
|
|
86
87
|
};
|
|
87
|
-
}, [step, isSimulating]);
|
|
88
|
+
}, [step, isSimulating]);
|
|
88
89
|
|
|
89
90
|
const handleSendCode = async () => {
|
|
90
91
|
const trimmedPhone = phone.trim();
|
|
@@ -130,7 +131,6 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
130
131
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.wrongCode') || 'Invalid verification code');
|
|
131
132
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
132
133
|
setOtp('');
|
|
133
|
-
// Refocus so they can type immediately after an error
|
|
134
134
|
inputRef.current?.focus();
|
|
135
135
|
} finally {
|
|
136
136
|
setIsSimulating(false);
|
|
@@ -163,8 +163,7 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
163
163
|
const digit = otp[index] || '';
|
|
164
164
|
const isFilled = index < otp.length;
|
|
165
165
|
|
|
166
|
-
//
|
|
167
|
-
// Highlights the next empty box, or the last box if full.
|
|
166
|
+
// Only highlight if the input is ACTUALLY focused.
|
|
168
167
|
const isActiveIndex = index === otp.length || (index === CODE_LENGTH - 1 && otp.length === CODE_LENGTH);
|
|
169
168
|
const isCurrent = isInputFocused && isActiveIndex;
|
|
170
169
|
|
|
@@ -223,7 +222,8 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
223
222
|
<Text style={styles.label}>{t('common.verificationCode') || 'Verification Code'}</Text>
|
|
224
223
|
|
|
225
224
|
<View style={styles.otpWrapper}>
|
|
226
|
-
{renderOtpBoxes()}
|
|
225
|
+
{renderOtpBoxes()}
|
|
226
|
+
|
|
227
227
|
<TextInput
|
|
228
228
|
ref={inputRef}
|
|
229
229
|
style={styles.hiddenInput}
|
|
@@ -272,7 +272,6 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
272
272
|
t('common.codeResent') || 'Code Resent',
|
|
273
273
|
t('common.codeResentMessage', { email: fullPhoneNumber }) || 'Code resent to ' + fullPhoneNumber
|
|
274
274
|
);
|
|
275
|
-
// Refocus after resending
|
|
276
275
|
inputRef.current?.focus();
|
|
277
276
|
} catch (err: any) {
|
|
278
277
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send code');
|
|
@@ -332,7 +331,6 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
332
331
|
};
|
|
333
332
|
|
|
334
333
|
const styles = StyleSheet.create({
|
|
335
|
-
// ... Keeping all previous styles identical for safety ...
|
|
336
334
|
container: {
|
|
337
335
|
padding: 24,
|
|
338
336
|
backgroundColor: 'white',
|
|
@@ -10,7 +10,7 @@ export interface NativeCameraViewProps {
|
|
|
10
10
|
onError?: (event: any) => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const NativeCameraViewComponent = requireNativeViewManager('
|
|
13
|
+
const NativeCameraViewComponent = requireNativeViewManager('TransfergratisSdk_TransfergratisSdkView');
|
|
14
14
|
|
|
15
15
|
export const NativeCameraView: React.FC<NativeCameraViewProps> = (props) => {
|
|
16
16
|
|
package/src/config/KYCConfig.ts
CHANGED
|
@@ -6,7 +6,7 @@ class KYCConfig {
|
|
|
6
6
|
|
|
7
7
|
private backendUrls: Record<BackendEnvironment, string> = {
|
|
8
8
|
PRODUCTION: 'https://service.sanctumkey.com/api/v1',
|
|
9
|
-
TEST: 'https://kyc-backend.
|
|
9
|
+
TEST: 'https://kyc-backend.transfergratis.net/api/v1',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
private constructor() { }
|
|
@@ -12,11 +12,11 @@ export interface AllowedDomainConfig {
|
|
|
12
12
|
// Default configuration - can be overridden via environment variables
|
|
13
13
|
const DEFAULT_CONFIG: AllowedDomainConfig = {
|
|
14
14
|
domains: [
|
|
15
|
-
'
|
|
16
|
-
'www.
|
|
17
|
-
'admin.
|
|
18
|
-
'dashboard.
|
|
19
|
-
'preweb.
|
|
15
|
+
'transfergratis.com',
|
|
16
|
+
'www.transfergratis.com',
|
|
17
|
+
'admin.transfergratis.com',
|
|
18
|
+
'dashboard.transfergratis.com',
|
|
19
|
+
'preweb.transfergratis.net',
|
|
20
20
|
// Add other trusted domains here
|
|
21
21
|
],
|
|
22
22
|
enforceHttps: true,
|
|
@@ -59,7 +59,7 @@ export const isDomainAllowed = (domain: string): boolean => {
|
|
|
59
59
|
return true;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// Subdomain match (e.g., app.
|
|
62
|
+
// Subdomain match (e.g., app.transfergratis.com matches transfergratis.com)
|
|
63
63
|
if (domain.endsWith('.' + allowedDomain)) {
|
|
64
64
|
return true;
|
|
65
65
|
}
|
package/src/i18n/README.md
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './TransfergratisSdk.types';
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
// Export KYC types
|
|
@@ -8,9 +8,9 @@ export * from './types/KYC.types';
|
|
|
8
8
|
export * from './types/env.types';
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
export { TemplateKYCExample as
|
|
11
|
+
export { TemplateKYCExample as LaunchTransferGratisKYC } from './components/TemplateKYCExample';
|
|
12
12
|
// Backward compatibility for existing integrations using the typo.
|
|
13
|
-
export { TemplateKYCExample as
|
|
13
|
+
export { TemplateKYCExample as LauchTransferGratisKYC } from './components/TemplateKYCExample';
|
|
14
14
|
|
|
15
15
|
// Export Template Flow Components
|
|
16
16
|
export { TemplateKYCFlow } from './components/TemplateKYCFlowRefactored';
|
|
@@ -82,7 +82,6 @@ export async function frontVerification(
|
|
|
82
82
|
console.log("Front verification START", JSON.stringify({ result }, null, 2));
|
|
83
83
|
logger.log("Front verification", JSON.stringify({ result }, null, 2));
|
|
84
84
|
|
|
85
|
-
// 🚨 FIX 1: Bulletproof Case-Insensitive Check
|
|
86
85
|
const authMethods = Array.isArray(result.regionMapping?.authMethod)
|
|
87
86
|
? result.regionMapping.authMethod.map(m => String(m).toUpperCase())
|
|
88
87
|
: [];
|
|
@@ -164,7 +163,6 @@ export async function frontVerification(
|
|
|
164
163
|
bbox = crop.bbox;
|
|
165
164
|
} catch { }
|
|
166
165
|
|
|
167
|
-
// 🚨 FIX 2: Execute MRZ if required (Removed restrictive guards)
|
|
168
166
|
if (hasMrz) {
|
|
169
167
|
try {
|
|
170
168
|
logger.log("Tentative d'extraction MRZ (Front)");
|
|
@@ -176,7 +174,7 @@ export async function frontVerification(
|
|
|
176
174
|
postfix: result?.currentSide || 'front',
|
|
177
175
|
token: token,
|
|
178
176
|
template_path: result?.templatePath || '',
|
|
179
|
-
mrz_type: result?.mrzType || 'TD1'
|
|
177
|
+
mrz_type: result?.mrzType || 'TD1'
|
|
180
178
|
},
|
|
181
179
|
env
|
|
182
180
|
);
|
|
@@ -201,7 +199,6 @@ export async function frontVerification(
|
|
|
201
199
|
}
|
|
202
200
|
}
|
|
203
201
|
|
|
204
|
-
// ─── backVerification ────────────────────────────────────────────────────────
|
|
205
202
|
|
|
206
203
|
export async function backVerification(
|
|
207
204
|
result: {
|
|
@@ -219,12 +216,13 @@ export async function backVerification(
|
|
|
219
216
|
try {
|
|
220
217
|
if (!result.path) throw new Error('No path provided');
|
|
221
218
|
logger.log("result.regionMapping", result.regionMapping, result.currentSide, result.code);
|
|
219
|
+
logger.log('result object', result)
|
|
222
220
|
|
|
223
|
-
// 🚨 FIX 3: Robust Auth Method Resolution for the Back Side
|
|
224
221
|
const authMethods = Array.isArray(result.regionMapping?.authMethod)
|
|
225
222
|
? result.regionMapping.authMethod.map(m => String(m).toUpperCase())
|
|
226
223
|
: [];
|
|
227
|
-
|
|
224
|
+
|
|
225
|
+
const hasMrz = authMethods.some(m => m.includes('MRZ'))
|
|
228
226
|
const hasBarcode = authMethods.some(m => m.includes('BARCODE') || m.includes('2D'));
|
|
229
227
|
|
|
230
228
|
if (env === 'SANDBOX') {
|
|
@@ -338,7 +336,6 @@ export async function backVerification(
|
|
|
338
336
|
|
|
339
337
|
let extractionResult: any = {};
|
|
340
338
|
|
|
341
|
-
// 🚨 FIX 4: Guaranteed MRZ Extraction Attachment
|
|
342
339
|
if (hasMrz) {
|
|
343
340
|
try {
|
|
344
341
|
logger.log("Tentative d'extraction MRZ (Back)");
|
|
@@ -481,7 +478,7 @@ export async function checkTemplateType(
|
|
|
481
478
|
logger.log("templateType result", JSON.stringify(truncateFields(templateType), null, 2));
|
|
482
479
|
return templateType;
|
|
483
480
|
} catch (e: any) {
|
|
484
|
-
logger.error('
|
|
481
|
+
logger.error('Errorrr checking template type:', JSON.stringify(errorMessage(e), null, 2));
|
|
485
482
|
if (e?.message === 'CARD_NOT_FULLY_IN_FRAME' || e?.message === 'TOO_FAR_AWAY' || e?.message?.includes('ne correspond pas')) throw e;
|
|
486
483
|
throw new Error(e?.message || 'Erreur de vérification du template');
|
|
487
484
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { GovernmentDocumentType, GovernmentDocumentTypeShorted, OrientationVideoResponse } from '../../types/KYC.types';
|
|
3
|
-
import { CheckTemplateTypeResponse
|
|
3
|
+
import { CheckTemplateTypeResponse } from '../../components/OverLay/type';
|
|
4
4
|
import { SessionResponse, VerificationResult, VerificationSessionRequest } from './types';
|
|
5
5
|
import { KycEnvironment } from '../../types/env.types';
|
|
6
6
|
import { logger } from '../../utils/logger';
|
|
@@ -60,10 +60,10 @@ export class KYCService {
|
|
|
60
60
|
private baseURL: string;
|
|
61
61
|
private apiKey: string;
|
|
62
62
|
// Additional service base URLs (fixed as per current infra)
|
|
63
|
-
private faceServiceURL = 'https://
|
|
64
|
-
private textExtractionServiceURL = 'https://
|
|
65
|
-
private mrzServiceURL = 'https://
|
|
66
|
-
private barcodeServiceURL = 'https://kyc-engine.
|
|
63
|
+
private faceServiceURL = 'https://face-infera.sanctumkey.com:8000';
|
|
64
|
+
private textExtractionServiceURL = 'https://text-infera.sanctumkey.com:8006';
|
|
65
|
+
private mrzServiceURL = 'https://mrz-infera.sanctumkey.com:8002';
|
|
66
|
+
private barcodeServiceURL = 'https://kyc-engine.transfergratis.net:8000';
|
|
67
67
|
private orientationServiceURL = 'http://18.188.180.154:8080';
|
|
68
68
|
|
|
69
69
|
constructor(baseURL: string, apiKey: string) {
|
|
@@ -239,8 +239,8 @@ export class KYCService {
|
|
|
239
239
|
const formData = new FormData();
|
|
240
240
|
await appendFileToFormData(formData, 'file', idCardImageUri, 'id_card_photo.jpg', 'image/jpeg');
|
|
241
241
|
|
|
242
|
-
const docTypeShorted = GovernmentDocumentTypeShorted[docType as GovernmentDocumentType];
|
|
243
|
-
|
|
242
|
+
const docTypeShorted = GovernmentDocumentTypeShorted[docType as GovernmentDocumentType] || docType;
|
|
243
|
+
|
|
244
244
|
// Log metadata, NOT the FormData object
|
|
245
245
|
logger.log('detectFaceOnId Request:', { docTypeShorted, imageUri: idCardImageUri });
|
|
246
246
|
|
|
@@ -305,7 +305,7 @@ export class KYCService {
|
|
|
305
305
|
logger.log('checkTemplateType res', JSON.stringify(res.data, null, 2));
|
|
306
306
|
return res.data;
|
|
307
307
|
} catch (e: any) {
|
|
308
|
-
logger.error('Error
|
|
308
|
+
logger.error('Error checkingg template type:', JSON.stringify(e));
|
|
309
309
|
throw e;
|
|
310
310
|
}
|
|
311
311
|
|
|
@@ -379,78 +379,104 @@ export class KYCService {
|
|
|
379
379
|
|
|
380
380
|
// STEP 3 - MRZ TEXT EXTRACTION
|
|
381
381
|
async extractMrzText(
|
|
382
|
-
params: {
|
|
383
|
-
fileUri: string;
|
|
384
|
-
docType: string;
|
|
385
|
-
docRegion: string;
|
|
386
|
-
postfix?: string;
|
|
387
|
-
token: string;
|
|
388
|
-
template_path: string;
|
|
389
|
-
mrz_type?: string;
|
|
390
|
-
},
|
|
382
|
+
params: {
|
|
383
|
+
fileUri: string;
|
|
384
|
+
docType: string;
|
|
385
|
+
docRegion: string;
|
|
386
|
+
postfix?: string;
|
|
387
|
+
token: string;
|
|
388
|
+
template_path: string;
|
|
389
|
+
mrz_type?: string;
|
|
390
|
+
},
|
|
391
391
|
env: KycEnvironment = 'PRODUCTION'
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
392
|
+
): Promise<any> {
|
|
393
|
+
// SANDBOX mode
|
|
394
|
+
if (env === 'SANDBOX') {
|
|
395
|
+
console.log("SANDBOX mode: Skipping AI MRZ extraction");
|
|
396
|
+
const { docType, docRegion, postfix = 'back', mrz_type } = params;
|
|
397
|
+
return {
|
|
398
|
+
success: true,
|
|
399
|
+
parsed_data: {
|
|
400
|
+
status: 'success',
|
|
401
|
+
document_type: docType,
|
|
402
|
+
mrz_type: mrz_type || 'TD1',
|
|
403
|
+
doc_region: docRegion,
|
|
404
|
+
postfix: postfix
|
|
405
|
+
},
|
|
406
|
+
card_obb: { x: 50, y: 50, width: 200, height: 200 }
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const { fileUri, docType, docRegion, postfix = 'back', token, template_path, mrz_type } = params;
|
|
411
|
+
|
|
412
|
+
// 1. Build the FormData ONLY for the file
|
|
413
|
+
const formData = new FormData();
|
|
414
|
+
const filePayload = {
|
|
415
|
+
uri: fileUri,
|
|
416
|
+
type: 'image/jpeg',
|
|
417
|
+
name: `id_card_${postfix}.jpg`,
|
|
418
|
+
};
|
|
419
|
+
formData.append('file', filePayload as any);
|
|
420
|
+
|
|
421
|
+
const docTypeShorted = GovernmentDocumentTypeShorted[docType as GovernmentDocumentType] || docType;
|
|
422
|
+
const safeMrzType = mrz_type && mrz_type.trim() !== '' ? mrz_type : 'TD1';
|
|
423
|
+
|
|
424
|
+
logger.log("docTypeShorted", docTypeShorted, docRegion, postfix);
|
|
425
|
+
|
|
426
|
+
// 🚨 THE FIX: Pass all required text parameters in the URL query string for FastAPI
|
|
427
|
+
const queryParams = new URLSearchParams({
|
|
428
|
+
doc_type: docTypeShorted,
|
|
429
|
+
doc_region: docRegion,
|
|
430
|
+
postfix: postfix,
|
|
431
|
+
template_path: template_path,
|
|
432
|
+
mrz_type: safeMrzType,
|
|
433
|
+
reset_cache: 'true'
|
|
434
|
+
}).toString();
|
|
435
|
+
|
|
436
|
+
const url = `${this.mrzServiceURL}/extract_mrz_text/?${queryParams}`;
|
|
437
|
+
logger.log("url", url);
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
const response = await fetch(url, {
|
|
441
|
+
method: 'POST',
|
|
442
|
+
headers: {
|
|
443
|
+
'Authorization': `Bearer ${token}`,
|
|
444
|
+
'Accept': 'application/json'
|
|
406
445
|
},
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
446
|
+
body: formData, // Only the file goes in the body
|
|
447
|
+
});
|
|
410
448
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
449
|
+
if (!response.ok) {
|
|
450
|
+
const errorText = await response.text();
|
|
451
|
+
logger.error('Backend MRZ Error Details:', errorText);
|
|
452
|
+
throw new Error(`Erreur serveur: ${response.status}`);
|
|
453
|
+
}
|
|
416
454
|
|
|
417
|
-
const
|
|
418
|
-
logger.log(
|
|
455
|
+
// const data = await response.json();
|
|
456
|
+
// logger.log('extractMrzText res', JSON.stringify(data, null, 2));
|
|
419
457
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (mrz_type && mrz_type.trim() !== '') {
|
|
423
|
-
url += `&mrz_type=${encodeURIComponent(mrz_type)}`;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
logger.log("url", url);
|
|
458
|
+
// if (Object.keys(data).length === 0) throw new Error('No data found');
|
|
459
|
+
// if (data?.success === false) throw new Error(data.parsed_data?.status || 'Échec de l\'extraction MRZ');
|
|
427
460
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${token}` },
|
|
432
|
-
// Note: Reduced timeout to 10000ms (10s) based on our earlier network fix to prevent infinite hanging!
|
|
433
|
-
timeout: 10000,
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
logger.log('extractMrzText res', JSON.stringify(res.data, null, 2));
|
|
437
|
-
|
|
438
|
-
if (Object.keys(res.data).length === 0) throw new Error('No data found');
|
|
439
|
-
if (res.data?.success === false) throw new Error(res.data.parsed_data?.status || 'Échec de l\'extraction MRZ');
|
|
440
|
-
|
|
441
|
-
return res.data;
|
|
442
|
-
} catch (e: any) {
|
|
443
|
-
throw new Error(e?.message || 'Erreur de détection du MRZ');
|
|
444
|
-
}
|
|
445
|
-
};
|
|
461
|
+
// return data;
|
|
462
|
+
const data = await response.json();
|
|
463
|
+
logger.log('extractMrzText res', JSON.stringify(data, null, 2));
|
|
446
464
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
465
|
+
if (Object.keys(data).length === 0) throw new Error('No data found');
|
|
466
|
+
|
|
467
|
+
// 🚨 UPDATE THIS LINE to grab the actual status_message:
|
|
468
|
+
if (data?.success === false) {
|
|
469
|
+
const serverMessage = data.parsed_data?.status_message || data.parsed_data?.status || 'Échec de l\'extraction MRZ';
|
|
470
|
+
throw new Error(`Lecture MRZ refusée: ${serverMessage}`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return data;
|
|
474
|
+
|
|
475
|
+
} catch (error: any) {
|
|
476
|
+
logger.error("MRZ Fetch Error: ", error?.message || error);
|
|
477
|
+
throw new Error(error?.message || 'Erreur de connexion lors de l\'extraction MRZ');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
454
480
|
|
|
455
481
|
// STEP 2 - SELFIE VALIDATION
|
|
456
482
|
async recognizeFace(params: { idPhotoUri: string; selfiePhotoUri: string }, env: KycEnvironment = 'PRODUCTION'): Promise<{ is_match: boolean; similarity: number; id_bbox?: number[]; selfie_bbox?: number[] }> {
|
|
@@ -620,36 +646,51 @@ export class KYCService {
|
|
|
620
646
|
}
|
|
621
647
|
}
|
|
622
648
|
|
|
623
|
-
/** Send WhatsApp verification code. */
|
|
649
|
+
/** Send WhatsApp verification code. POST /api/v1/accounts/send-whatsapp-verification/ */
|
|
624
650
|
async sendWhatsAppVerificationCode(
|
|
625
651
|
sessionId: string,
|
|
626
652
|
phoneNumber: string,
|
|
627
653
|
auth?: { apiKey?: string; token?: string }
|
|
628
654
|
): Promise<unknown> {
|
|
629
655
|
const url = `${KYCConfig.getBackendUrl()}/accounts/send-whatsapp-verification/`;
|
|
630
|
-
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
631
656
|
|
|
632
|
-
//
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
{
|
|
657
|
+
// 1. Formatting (Ensure consistency with verification step)
|
|
658
|
+
const formattedPhoneNumber = phoneNumber.replace(/^\+/, '');
|
|
659
|
+
|
|
660
|
+
// 2. Prepare Payload
|
|
661
|
+
const payload = {
|
|
638
662
|
session_id: sessionId,
|
|
639
|
-
phone_number:
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
663
|
+
phone_number: formattedPhoneNumber
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
669
|
+
const headers = {
|
|
670
|
+
'Content-Type': 'application/json',
|
|
671
|
+
...(auth?.apiKey ? { 'Authorization': `ApiKey ${auth.apiKey}` } : { 'Authorization': `Bearer ${token}` }),
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
try {
|
|
677
|
+
const res = await axios.post(url, payload, { headers });
|
|
678
|
+
|
|
679
|
+
logger.log("✅ WhatsApp Send Success:", res.data);
|
|
680
|
+
return res.data;
|
|
681
|
+
|
|
682
|
+
} catch (error: any) {
|
|
683
|
+
if (error.response) {
|
|
684
|
+
logger.error("🛑 WhatsApp Send Failed (Server Response):", {
|
|
685
|
+
status: error.response.status,
|
|
686
|
+
data: error.response.data,
|
|
687
|
+
headers: error.response.headers
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
throw error;
|
|
691
|
+
}
|
|
650
692
|
}
|
|
651
693
|
|
|
652
|
-
/** Verify WhatsApp code with OTP. */
|
|
653
694
|
async verifyWhatsAppCode(
|
|
654
695
|
sessionId: string,
|
|
655
696
|
otp: string,
|
|
@@ -657,32 +698,40 @@ export class KYCService {
|
|
|
657
698
|
auth?: { apiKey?: string; token?: string }
|
|
658
699
|
): Promise<unknown> {
|
|
659
700
|
const url = `${KYCConfig.getBackendUrl()}/accounts/verify-whatsapp-code/`;
|
|
660
|
-
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
661
701
|
|
|
662
|
-
const
|
|
663
|
-
|
|
702
|
+
const formattedPhoneNumber = phoneNumber.replace(/^\+/, '');
|
|
703
|
+
|
|
704
|
+
const payload = {
|
|
705
|
+
session_id: sessionId,
|
|
706
|
+
verification_code: otp,
|
|
707
|
+
phone_number: formattedPhoneNumber
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
713
|
+
const headers = {
|
|
714
|
+
'Content-Type': 'application/json',
|
|
715
|
+
...(auth?.apiKey ? { 'Authorization': `ApiKey ${auth.apiKey}` } : { 'Authorization': `Bearer ${token}` }),
|
|
716
|
+
};
|
|
717
|
+
|
|
664
718
|
|
|
665
719
|
try {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
phone_number: formattedPhone,
|
|
671
|
-
verification_code: cleanOtp
|
|
672
|
-
},
|
|
673
|
-
{
|
|
674
|
-
headers: {
|
|
675
|
-
'Content-Type': 'application/json',
|
|
676
|
-
...(auth?.apiKey ? { 'Authorization': `ApiKey ${auth.apiKey}` } : { 'Authorization': `Bearer ${token}` }),
|
|
677
|
-
},
|
|
678
|
-
}
|
|
679
|
-
);
|
|
680
|
-
return res.data;
|
|
720
|
+
const res = await axios.post(url, payload, { headers });
|
|
721
|
+
|
|
722
|
+
return res.data;
|
|
723
|
+
|
|
681
724
|
} catch (error: any) {
|
|
682
|
-
|
|
683
|
-
|
|
725
|
+
if (error.response) {
|
|
726
|
+
logger.error("🛑 WhatsApp Verification Failed (Server Response):", {
|
|
727
|
+
status: error.response.status,
|
|
728
|
+
data: error.response.data,
|
|
729
|
+
headers: error.response.headers
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
throw error;
|
|
684
733
|
}
|
|
685
|
-
|
|
734
|
+
}
|
|
686
735
|
}
|
|
687
736
|
|
|
688
737
|
const kycService = new KYCService("", "");
|
|
@@ -727,11 +776,12 @@ export const authentification = async (): Promise<string> => {
|
|
|
727
776
|
activeAuthRequest = (async () => {
|
|
728
777
|
logger.log('Authenticating: Fetching new Keycloak token...');
|
|
729
778
|
|
|
730
|
-
|
|
779
|
+
// 🚨 FIX: Do NOT use URLSearchParams in React Native with Axios.
|
|
780
|
+
// Use a raw URL-encoded string to guarantee Keycloak understands the payload.
|
|
731
781
|
const params = 'client_id=kyc_frontend&client_secret=QhgAmvKgmwODzsEp98dnA4PeUEMMaFHd&grant_type=client_credentials';
|
|
732
782
|
|
|
733
783
|
const res = await axios.post(
|
|
734
|
-
`https://
|
|
784
|
+
`https://idms.sanctumkey.com/realms/kyc/protocol/openid-connect/token`,
|
|
735
785
|
params,
|
|
736
786
|
{
|
|
737
787
|
headers: {
|
|
@@ -759,6 +809,7 @@ export const authentification = async (): Promise<string> => {
|
|
|
759
809
|
} catch (error: any) {
|
|
760
810
|
cachedToken = null; // Clear bad cache
|
|
761
811
|
|
|
812
|
+
// 🚨 FIX: Extract Keycloak's exact error message so it doesn't log as {}
|
|
762
813
|
const safeErrorMessage =
|
|
763
814
|
error?.response?.data?.error_description ||
|
|
764
815
|
error?.response?.data?.error ||
|