@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.
Files changed (106) hide show
  1. package/README.md +4 -4
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/AndroidManifest.xml +1 -1
  4. package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkModule.kt +6 -6
  5. package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkView.kt +2 -2
  6. package/build/package.json +5 -5
  7. package/build/src/App.d.ts +2 -2
  8. package/build/src/App.d.ts.map +1 -1
  9. package/build/src/App.js +2 -2
  10. package/build/src/App.js.map +1 -1
  11. package/build/src/{SanctumKeySdk.types.d.ts → TransfergratisSdk.types.d.ts} +3 -3
  12. package/build/src/TransfergratisSdk.types.d.ts.map +1 -0
  13. package/build/src/TransfergratisSdk.types.js +2 -0
  14. package/build/src/TransfergratisSdk.types.js.map +1 -0
  15. package/build/src/{SanctumKeySdkModule.d.ts → TransfergratisSdkModule.d.ts} +4 -4
  16. package/build/src/TransfergratisSdkModule.d.ts.map +1 -0
  17. package/build/src/{SanctumKeySdkModule.js → TransfergratisSdkModule.js} +2 -2
  18. package/build/src/TransfergratisSdkModule.js.map +1 -0
  19. package/build/src/{SanctumKeySdkModule.web.d.ts → TransfergratisSdkModule.web.d.ts} +4 -4
  20. package/build/src/TransfergratisSdkModule.web.d.ts.map +1 -0
  21. package/build/src/{SanctumKeySdkModule.web.js → TransfergratisSdkModule.web.js} +3 -3
  22. package/build/src/TransfergratisSdkModule.web.js.map +1 -0
  23. package/build/src/TransfergratisSdkView.d.ts +4 -0
  24. package/build/src/TransfergratisSdkView.d.ts.map +1 -0
  25. package/build/src/TransfergratisSdkView.js +7 -0
  26. package/build/src/TransfergratisSdkView.js.map +1 -0
  27. package/build/src/TransfergratisSdkView.web.d.ts +4 -0
  28. package/build/src/TransfergratisSdkView.web.d.ts.map +1 -0
  29. package/build/src/{SanctumKeySdkView.web.js → TransfergratisSdkView.web.js} +2 -2
  30. package/build/src/TransfergratisSdkView.web.js.map +1 -0
  31. package/build/src/api/axios.js +2 -2
  32. package/build/src/api/axios.js.map +1 -1
  33. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  34. package/build/src/components/EnhancedCameraView.js +12 -61
  35. package/build/src/components/EnhancedCameraView.js.map +1 -1
  36. package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
  37. package/build/src/components/KYCElements/CountrySelection.js +259 -63
  38. package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
  39. package/build/src/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -1
  40. package/build/src/components/KYCElements/EmailVerificationTemplate.js +11 -32
  41. package/build/src/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
  42. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  43. package/build/src/components/KYCElements/IDCardCapture.js +222 -68
  44. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  45. package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
  46. package/build/src/components/KYCElements/PhoneVerificationTemplate.js +9 -11
  47. package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
  48. package/build/src/components/NativeCameraView.js +1 -1
  49. package/build/src/components/NativeCameraView.js.map +1 -1
  50. package/build/src/config/KYCConfig.js +1 -1
  51. package/build/src/config/KYCConfig.js.map +1 -1
  52. package/build/src/config/allowedDomains.js +6 -6
  53. package/build/src/config/allowedDomains.js.map +1 -1
  54. package/build/src/config/region_mapping.json +727 -0
  55. package/build/src/index.d.ts +3 -3
  56. package/build/src/index.d.ts.map +1 -1
  57. package/build/src/index.js +3 -3
  58. package/build/src/index.js.map +1 -1
  59. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  60. package/build/src/modules/api/CardAuthentification.js +3 -7
  61. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  62. package/build/src/modules/api/KYCService.d.ts +1 -2
  63. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  64. package/build/src/modules/api/KYCService.js +103 -63
  65. package/build/src/modules/api/KYCService.js.map +1 -1
  66. package/build/src/modules/camera/NativeCameraModule.js +17 -17
  67. package/build/src/modules/camera/NativeCameraModule.js.map +1 -1
  68. package/expo-module.config.json +2 -2
  69. package/ios/TransfergratisSdk.podspec +2 -2
  70. package/ios/TransfergratisSdkModule.swift +12 -12
  71. package/package.json +5 -5
  72. package/src/App.tsx +2 -2
  73. package/src/{SanctumKeySdk.types.ts → TransfergratisSdk.types.ts} +2 -2
  74. package/src/{SanctumKeySdkModule.ts → TransfergratisSdkModule.ts} +3 -3
  75. package/src/{SanctumKeySdkModule.web.ts → TransfergratisSdkModule.web.ts} +3 -3
  76. package/src/TransfergratisSdkView.tsx +11 -0
  77. package/src/{SanctumKeySdkView.web.tsx → TransfergratisSdkView.web.tsx} +2 -2
  78. package/src/api/axios.ts +2 -2
  79. package/src/components/EnhancedCameraView.tsx +34 -99
  80. package/src/components/KYCElements/CountrySelection.tsx +300 -74
  81. package/src/components/KYCElements/EmailVerificationTemplate.tsx +10 -36
  82. package/src/components/KYCElements/IDCardCapture.tsx +310 -156
  83. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +9 -11
  84. package/src/components/NativeCameraView.tsx +1 -1
  85. package/src/config/KYCConfig.ts +1 -1
  86. package/src/config/allowedDomains.ts +6 -6
  87. package/src/i18n/README.md +1 -1
  88. package/src/index.ts +3 -3
  89. package/src/modules/api/CardAuthentification.ts +5 -8
  90. package/src/modules/api/KYCService.ts +167 -116
  91. package/src/modules/camera/NativeCameraModule.ts +17 -17
  92. package/build/src/SanctumKeySdk.types.d.ts.map +0 -1
  93. package/build/src/SanctumKeySdk.types.js +0 -2
  94. package/build/src/SanctumKeySdk.types.js.map +0 -1
  95. package/build/src/SanctumKeySdkModule.d.ts.map +0 -1
  96. package/build/src/SanctumKeySdkModule.js.map +0 -1
  97. package/build/src/SanctumKeySdkModule.web.d.ts.map +0 -1
  98. package/build/src/SanctumKeySdkModule.web.js.map +0 -1
  99. package/build/src/SanctumKeySdkView.d.ts +0 -4
  100. package/build/src/SanctumKeySdkView.d.ts.map +0 -1
  101. package/build/src/SanctumKeySdkView.js +0 -7
  102. package/build/src/SanctumKeySdkView.js.map +0 -1
  103. package/build/src/SanctumKeySdkView.web.d.ts +0 -4
  104. package/build/src/SanctumKeySdkView.web.d.ts.map +0 -1
  105. package/build/src/SanctumKeySdkView.web.js.map +0 -1
  106. 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
- // Easily expand this list with any countries your app supports
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
- // 🚨 NEW: Track actual focus state for visual feedback
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
- }, 100);
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]); // 🚨 Must watch both states
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
- // 🚨 NEW: Only highlight if the input is ACTUALLY focused.
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('SanctumKeySdk_SanctumKeySdkView');
13
+ const NativeCameraViewComponent = requireNativeViewManager('TransfergratisSdk_TransfergratisSdkView');
14
14
 
15
15
  export const NativeCameraView: React.FC<NativeCameraViewProps> = (props) => {
16
16
 
@@ -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.SanctumKey.net/api/v1',
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
- 'SanctumKey.com',
16
- 'www.SanctumKey.com',
17
- 'admin.SanctumKey.com',
18
- 'dashboard.SanctumKey.com',
19
- 'preweb.SanctumKey.net',
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.SanctumKey.com matches SanctumKey.com)
62
+ // Subdomain match (e.g., app.transfergratis.com matches transfergratis.com)
63
63
  if (domain.endsWith('.' + allowedDomain)) {
64
64
  return true;
65
65
  }
@@ -1,4 +1,4 @@
1
- # 🌍 Système d'Internationalisation (i18n) - SanctumKey SDK
1
+ # 🌍 Système d'Internationalisation (i18n) - Transfergratis SDK
2
2
 
3
3
  Ce système d'internationalisation permet de traduire facilement votre application KYC en plusieurs langues.
4
4
 
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from './SanctumKeySdk.types';
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 LaunchSanctumKeyKYC } from './components/TemplateKYCExample';
11
+ export { TemplateKYCExample as LaunchTransferGratisKYC } from './components/TemplateKYCExample';
12
12
  // Backward compatibility for existing integrations using the typo.
13
- export { TemplateKYCExample as LauchSanctumKeyKYC } from './components/TemplateKYCExample';
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' // Fallback to ensure it always runs
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
- const hasMrz = authMethods.some(m => m.includes('MRZ'));
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('Error checking template type:', JSON.stringify(errorMessage(e), null, 2));
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, ExtractMrzTextResponse } from '../../components/OverLay/type';
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://kyc-engine.SanctumKey.net:8000';
64
- private textExtractionServiceURL = 'https://kyc-engine.SanctumKey.net:8006';
65
- private mrzServiceURL = 'https://kyc-engine.SanctumKey.net:8002';
66
- private barcodeServiceURL = 'https://kyc-engine.SanctumKey.net:8000';
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 checking template type:', JSON.stringify(e));
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
- ): Promise<any> {
393
- // SANDBOX mode
394
- if (env === 'SANDBOX') {
395
- console.log("SANDBOX mode: Skipping AI MRZ extraction");
396
- logger.log("SANDBOX mode: Returning mock MRZ response");
397
- const { docType, docRegion, postfix = 'back', mrz_type } = params;
398
- return {
399
- success: true,
400
- parsed_data: {
401
- status: 'success',
402
- document_type: docType,
403
- mrz_type: mrz_type || 'TD1',
404
- doc_region: docRegion,
405
- postfix: postfix
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
- card_obb: { x: 50, y: 50, width: 200, height: 200 }
408
- };
409
- }
446
+ body: formData, // Only the file goes in the body
447
+ });
410
448
 
411
- const { fileUri, docType, docRegion, postfix = 'back', token, template_path, mrz_type } = params;
412
- const formData = new FormData();
413
-
414
- // Dynamic filename to prevent passports from crashing (since their MRZ is on the front)
415
- await appendFileToFormData(formData, 'file', fileUri, `id_card_${postfix}.jpg`, 'image/jpeg');
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 docTypeShorted = GovernmentDocumentTypeShorted[docType as GovernmentDocumentType] || docType;
418
- logger.log("docTypeShorted", docTypeShorted, docRegion, postfix);
455
+ // const data = await response.json();
456
+ // logger.log('extractMrzText res', JSON.stringify(data, null, 2));
419
457
 
420
- let url = `${this.mrzServiceURL}/extract_mrz_text/?doc_type=${encodeURIComponent(docTypeShorted)}&doc_region=${encodeURIComponent(docRegion)}&postfix=${encodeURIComponent(postfix)}&template_path=${encodeURIComponent(template_path)}`;
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
- const attempt = async () => {
429
- try {
430
- const res = await axios.post<ExtractMrzTextResponse>(url, formData, {
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
- try {
448
- return await attempt();
449
- } catch (e) {
450
- logger.error('Error extracting MRZ text:', JSON.stringify(errorMessage(e), null, 2));
451
- throw e;
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
- // 🚨 FORMATTING FIX: Strips the "+" sign (e.g., "+254712345" becomes "254712345")
633
- const formattedPhone = phoneNumber.replace(/[^0-9]/g, '');
634
-
635
- const res = await axios.post(
636
- url,
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: formattedPhone
640
- },
641
- {
642
- headers: {
643
- 'Content-Type': 'application/json',
644
- 'Referer': KYCConfig.getBackendUrl(), // CSRF Protection
645
- ...(auth?.apiKey ? { 'Authorization': `ApiKey ${auth.apiKey}` } : { 'Authorization': `Bearer ${token}` }),
646
- },
647
- }
648
- );
649
- return res.data;
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 formattedPhone = phoneNumber.replace(/[^0-9]/g, '');
663
- const cleanOtp = otp.replace(/[^0-9]/g, '');
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
- const res = await axios.post(
667
- url,
668
- {
669
- session_id: sessionId,
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
- console.error("WhatsApp Verify API Error: ", error.response?.data || error.message);
683
- throw error;
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://keycloak.SanctumKey.net/realms/kyc/protocol/openid-connect/token`,
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 ||