@sanctum-key/react-native-sdk 1.0.32 → 1.0.34
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 +2 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +35 -19
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +91 -39
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +0 -7
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +48 -119
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/package.json +2 -1
- package/src/components/EnhancedCameraView.tsx +41 -23
- package/src/components/KYCElements/IDCardCapture.tsx +121 -58
- package/src/modules/api/KYCService.ts +357 -534
|
@@ -105,18 +105,15 @@ export declare class KYCService {
|
|
|
105
105
|
apiKey?: string;
|
|
106
106
|
}): Promise<SessionResponse>;
|
|
107
107
|
verificationSession(payload: VerificationSessionRequest): Promise<any>;
|
|
108
|
-
/** Send email verification code. POST /api/v1/accounts/email/send/ */
|
|
109
108
|
sendEmailVerificationCode(email: string, auth?: {
|
|
110
109
|
apiKey?: string;
|
|
111
110
|
token?: string;
|
|
112
111
|
}): Promise<unknown>;
|
|
113
|
-
/** Verify email with OTP. POST /api/v1/accounts/email/verify/ */
|
|
114
112
|
verifyEmailCode(otp: string, auth?: {
|
|
115
113
|
apiKey?: string;
|
|
116
114
|
token?: string;
|
|
117
115
|
}): Promise<unknown>;
|
|
118
116
|
getVerificationResult(session_id: string, apiKey?: string): Promise<any>;
|
|
119
|
-
/** Send WhatsApp verification code. POST /api/v1/accounts/send-whatsapp-verification/ */
|
|
120
117
|
sendWhatsAppVerificationCode(sessionId: string, phoneNumber: string, auth?: {
|
|
121
118
|
apiKey?: string;
|
|
122
119
|
token?: string;
|
|
@@ -133,10 +130,6 @@ export declare class KYCService {
|
|
|
133
130
|
subRegion: string;
|
|
134
131
|
token: string;
|
|
135
132
|
}, env?: KycEnvironment): Promise<any>;
|
|
136
|
-
/**
|
|
137
|
-
* Generates a new referral code for a specific session.
|
|
138
|
-
* Endpoint: POST /api/v1/core/referral-code/generate/
|
|
139
|
-
*/
|
|
140
133
|
generateReferralCode(payload: {
|
|
141
134
|
session_id: string;
|
|
142
135
|
[key: string]: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KYCService.d.ts","sourceRoot":"","sources":["../../../../src/modules/api/KYCService.ts"],"names":[],"mappings":"AACA,OAAO,EAAyD,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAExH,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"KYCService.d.ts","sourceRoot":"","sources":["../../../../src/modules/api/KYCService.ts"],"names":[],"mappings":"AACA,OAAO,EAAyD,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAExH,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAiCvD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,UAAU,GAAG,SAAS,GAAG,iBAAiB,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,qBAAqB,EAAE,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,cAAc,CAAwC;IAC9D,OAAO,CAAC,wBAAwB,CAAwC;IACxE,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,iBAAiB,CAAwC;IACjE,OAAO,CAAC,qBAAqB,CAAgC;gBAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAK3C,OAAO,CAAC,UAAU;IAOZ,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAcjD,UAAU,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IActE,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAc3E,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAa1D,+BAA+B,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAuBjG,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,GAAE,cAA6B,GAAG,OAAO,CAAC,wBAAwB,CAAC;IA2DjH,cAAc,CAClB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,cAA6B,GACjC,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,GAAG,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IA2CxD,iBAAiB,CACrB,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B,EACD,GAAG,GAAE,cAA6B,GACjC,OAAO,CAAC,GAAG,CAAC;IA0CT,0BAA0B,CAAC,MAAM,EAAE;QACvC,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,GAAG,OAAO,CAAC,GAAG,CAAC;IAuDV,cAAc,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,GAAE,cAA6B,GAAG,OAAO,CAAC,GAAG,CAAC;IAuC/I,iBAAiB,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,GAAE,cAA6B,GAAG,OAAO,CAAC,GAAG,CAAC;IAuCjH,cAAc,CAClB,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,EACD,GAAG,GAAE,cAA6B,GACjC,OAAO,CAAC,GAAG,CAAC;IAgGT,aAAa,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,GAAE,cAA6B,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAsBzM,mBAAmB,IAAI,OAAO,CAAC,GAAG,CAAC;IAKnC,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC;IAKpC,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC;IAKhC,wBAAwB,IAAI,OAAO,CAAC,GAAG,CAAC;IAKxC,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC;IAwB5F,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,GAAG,CAAC;IAgGtE,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBtG,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB1F,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAkCxE,4BAA4B,CAChC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACzC,OAAO,CAAC,OAAO,CAAC;IA0Bb,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACzC,OAAO,CAAC,OAAO,CAAC;IAyBb,oBAAoB,CAAC,MAAM,EAAE;QACjC,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,GAAG,GAAE,cAA6B,GAAG,OAAO,CAAC,GAAG,CAAC;IAiC9C,oBAAoB,CACxB,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,EACnD,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,GAAG,CAAC;CAsBhB;AAED,QAAA,MAAM,UAAU,YAAyB,CAAC;AAC1C,eAAe,UAAU,CAAC;AAE1B,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,SAAM,GAAG,GAAG,CAgB7D;AAED,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,MAAM,CAwCvD,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,OAAO,GAAG,QAEtC,CAAA"}
|
|
@@ -19,12 +19,12 @@ const documentTypeMap = {
|
|
|
19
19
|
healthInsuranceCard: "health_insurance_card",
|
|
20
20
|
residencePermit: "permanent_residence_card",
|
|
21
21
|
identityCard: "identity_card",
|
|
22
|
-
nationalId: "identity_card",
|
|
22
|
+
nationalId: "identity_card",
|
|
23
23
|
national_id: "identity_card",
|
|
24
|
-
drivers_licence: "driving_licence",
|
|
25
|
-
drivers_license: "driving_licence",
|
|
26
|
-
driving_license: "driving_licence",
|
|
27
|
-
permanent_residence: "permanent_residence_card",
|
|
24
|
+
drivers_licence: "driving_licence",
|
|
25
|
+
drivers_license: "driving_licence",
|
|
26
|
+
driving_license: "driving_licence",
|
|
27
|
+
permanent_residence: "permanent_residence_card",
|
|
28
28
|
};
|
|
29
29
|
let cachedToken = null;
|
|
30
30
|
let tokenExpiryTime = null;
|
|
@@ -32,7 +32,6 @@ let activeAuthRequest = null;
|
|
|
32
32
|
export class KYCService {
|
|
33
33
|
baseURL;
|
|
34
34
|
apiKey;
|
|
35
|
-
// Additional service base URLs (fixed as per current infra)
|
|
36
35
|
faceServiceURL = 'https://face-infera.sanctumkey.com';
|
|
37
36
|
textExtractionServiceURL = 'https://text-infera.sanctumkey.com';
|
|
38
37
|
mrzServiceURL = 'https://mrz-infera.sanctumkey.com';
|
|
@@ -94,7 +93,10 @@ export class KYCService {
|
|
|
94
93
|
await appendFileToFormData(formData, 'file', videoFile, 'selfie_picture.jpg', 'image/jpeg');
|
|
95
94
|
const response = await axios.post(`${this.faceServiceURL}/detect_face_orientation/`, formData, {
|
|
96
95
|
timeout: 20000,
|
|
97
|
-
headers: {
|
|
96
|
+
headers: {
|
|
97
|
+
// 🚨 Removed explicit Content-Type to allow boundary injection
|
|
98
|
+
'Authorization': `Bearer ${token}`,
|
|
99
|
+
},
|
|
98
100
|
});
|
|
99
101
|
return response.data;
|
|
100
102
|
}
|
|
@@ -105,7 +107,6 @@ export class KYCService {
|
|
|
105
107
|
}
|
|
106
108
|
async processOrientationVideo(videoFile, env = 'PRODUCTION') {
|
|
107
109
|
try {
|
|
108
|
-
// SANDBOX mode: skip AI verification and return mock response
|
|
109
110
|
if (env === 'SANDBOX') {
|
|
110
111
|
console.log("SANDBOX mode: Skipping AI orientation video processing");
|
|
111
112
|
logger.log("SANDBOX mode: Returning mock orientation video response");
|
|
@@ -119,7 +120,6 @@ export class KYCService {
|
|
|
119
120
|
message: 'SANDBOX: Orientation video processed successfully (mocked)'
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
|
-
// Create FormData for multipart/form-data request
|
|
123
123
|
const formData = new FormData();
|
|
124
124
|
await appendFileToFormData(formData, 'file', videoFile, 'orientation_video.mp4', 'video/mp4');
|
|
125
125
|
const response = await axios.post(`${this.orientationServiceURL}/process_orientation_video_stream/`, formData, {
|
|
@@ -134,7 +134,6 @@ export class KYCService {
|
|
|
134
134
|
}
|
|
135
135
|
catch (error) {
|
|
136
136
|
logger.error('Error processing orientation video:', JSON.stringify(error, null, 2));
|
|
137
|
-
// Handle specific error cases
|
|
138
137
|
if (error.response?.status === 503) {
|
|
139
138
|
return {
|
|
140
139
|
success: false,
|
|
@@ -159,7 +158,6 @@ export class KYCService {
|
|
|
159
158
|
}
|
|
160
159
|
// STEP 1 - ID CARD VALIDATION
|
|
161
160
|
async detectFaceOnId(idCardImageUri, token, docType, env = 'PRODUCTION') {
|
|
162
|
-
// SANDBOX mode
|
|
163
161
|
if (env === 'SANDBOX') {
|
|
164
162
|
logger.log("SANDBOX mode: Returning mock face detection response");
|
|
165
163
|
return {
|
|
@@ -171,17 +169,15 @@ export class KYCService {
|
|
|
171
169
|
const formData = new FormData();
|
|
172
170
|
await appendFileToFormData(formData, 'file', idCardImageUri, 'id_card_photo.jpg', 'image/jpeg');
|
|
173
171
|
const docTypeShorted = GovernmentDocumentTypeShorted[docType] || docType;
|
|
174
|
-
// Log metadata, NOT the FormData object
|
|
175
172
|
logger.log('detectFaceOnId Request:', { docTypeShorted, imageUri: idCardImageUri });
|
|
176
173
|
try {
|
|
177
174
|
const res = await axios.post(`${this.faceServiceURL}/detect_face/?doc_type=${docTypeShorted}`, formData, {
|
|
178
175
|
headers: {
|
|
179
|
-
|
|
176
|
+
// 🚨 Removed explicit Content-Type to allow boundary injection
|
|
180
177
|
'Authorization': `Bearer ${token}`
|
|
181
178
|
},
|
|
182
|
-
timeout: 20000
|
|
179
|
+
timeout: 20000
|
|
183
180
|
});
|
|
184
|
-
// It's safe to stringify res.data if the backend returns a clean JSON object
|
|
185
181
|
logger.log('detectFaceOnId res', JSON.stringify(res.data, null, 2));
|
|
186
182
|
if (res.data?.result) {
|
|
187
183
|
return res.data;
|
|
@@ -189,7 +185,6 @@ export class KYCService {
|
|
|
189
185
|
throw new Error(res.data?.detail || 'detect_face failed');
|
|
190
186
|
}
|
|
191
187
|
catch (error) {
|
|
192
|
-
// Use your safe error extractor, avoid JSON.stringify on raw Axios errors
|
|
193
188
|
const safeError = errorMessage(error) || error.message;
|
|
194
189
|
logger.error('Error detecting face on id:', safeError);
|
|
195
190
|
throw error;
|
|
@@ -197,7 +192,6 @@ export class KYCService {
|
|
|
197
192
|
}
|
|
198
193
|
//check templatetemplate_type
|
|
199
194
|
async checkTemplateType(params, env = 'PRODUCTION') {
|
|
200
|
-
// SANDBOX mode
|
|
201
195
|
if (env === 'SANDBOX') {
|
|
202
196
|
console.log("SANDBOX mode: Skipping AI template type check");
|
|
203
197
|
logger.log("SANDBOX mode: Returning mock template type response");
|
|
@@ -211,7 +205,6 @@ export class KYCService {
|
|
|
211
205
|
const formData = new FormData();
|
|
212
206
|
await appendFileToFormData(formData, 'file', fileUri, `id_card_${postfix}.jpg`, 'image/jpeg');
|
|
213
207
|
const docTypeShorted = GovernmentDocumentTypeShorted[docType];
|
|
214
|
-
// Build the base URL
|
|
215
208
|
let url = `${this.mrzServiceURL}/get_template_version/?doc_type=${encodeURIComponent(docTypeShorted)}&doc_region=${encodeURIComponent(docRegion)}&postfix=${postfix}`;
|
|
216
209
|
if (issuingSubdivision) {
|
|
217
210
|
url += `&issuing_subdivision=${encodeURIComponent(issuingSubdivision)}`;
|
|
@@ -219,7 +212,10 @@ export class KYCService {
|
|
|
219
212
|
logger.log('checkTemplateType params', this.mrzServiceURL, JSON.stringify({ fileUri, docTypeShorted, docRegion, issuingSubdivision, token, postfix, url }, null, 2));
|
|
220
213
|
try {
|
|
221
214
|
const res = await axios.post(url, formData, {
|
|
222
|
-
headers: {
|
|
215
|
+
headers: {
|
|
216
|
+
// 🚨 Removed explicit Content-Type to allow boundary injection
|
|
217
|
+
'Authorization': `Bearer ${token}`,
|
|
218
|
+
},
|
|
223
219
|
timeout: 60000,
|
|
224
220
|
});
|
|
225
221
|
logger.log('checkTemplateType res', JSON.stringify(res.data, null, 2));
|
|
@@ -232,7 +228,6 @@ export class KYCService {
|
|
|
232
228
|
}
|
|
233
229
|
async extractDocumentInformation(params) {
|
|
234
230
|
const { fileUri, docType, docRegion, templatePath, issuingSubdivision, token, postfix = 'front' } = params;
|
|
235
|
-
// 1. Prepare Form Data
|
|
236
231
|
const formData = new FormData();
|
|
237
232
|
await appendFileToFormData(formData, 'file', fileUri, `id_card_${postfix}.jpg`, 'image/jpeg');
|
|
238
233
|
let queryParams = `doc_type=${encodeURIComponent(docType)}&doc_region=${encodeURIComponent(docRegion)}&template_path=${encodeURIComponent(templatePath)}&postfix=${postfix}&cleaned=true`;
|
|
@@ -243,22 +238,24 @@ export class KYCService {
|
|
|
243
238
|
const url = `${this.textExtractionServiceURL}/extract_doc_information/?${queryParams}`;
|
|
244
239
|
try {
|
|
245
240
|
const startRes = await axios.post(url, formData, {
|
|
246
|
-
headers: {
|
|
241
|
+
headers: {
|
|
242
|
+
// 🚨 Removed explicit Content-Type to allow boundary injection
|
|
243
|
+
'Authorization': `Bearer ${token}`
|
|
244
|
+
},
|
|
247
245
|
timeout: 60000,
|
|
248
246
|
});
|
|
249
247
|
const jobId = startRes.data?.job_id;
|
|
250
248
|
if (!jobId)
|
|
251
249
|
throw new Error("Backend did not return a job_id");
|
|
252
250
|
logger.log(`Extraction job started: ${jobId}. Polling for results...`);
|
|
253
|
-
// 4. Poll the Status Endpoint
|
|
254
251
|
const statusUrl = `${this.textExtractionServiceURL}/extract_doc_information/status/${jobId}`;
|
|
255
|
-
// Poll every 2 seconds, up to 15 times (30 seconds max)
|
|
256
252
|
const maxRetries = 15;
|
|
257
253
|
for (let i = 0; i < maxRetries; i++) {
|
|
258
|
-
// Wait 2 seconds before checking
|
|
259
254
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
260
255
|
const statusRes = await axios.get(statusUrl, {
|
|
261
|
-
headers: {
|
|
256
|
+
headers: {
|
|
257
|
+
'Authorization': `Bearer ${token}`
|
|
258
|
+
}
|
|
262
259
|
});
|
|
263
260
|
const statusData = statusRes.data;
|
|
264
261
|
if (statusData.status === 'completed') {
|
|
@@ -278,14 +275,10 @@ export class KYCService {
|
|
|
278
275
|
}
|
|
279
276
|
}
|
|
280
277
|
async extractBarcode(params, env = 'PRODUCTION') {
|
|
281
|
-
// SANDBOX mode
|
|
282
278
|
if (env === 'SANDBOX') {
|
|
283
279
|
console.log("SANDBOX mode: Skipping AI barcode extraction");
|
|
284
280
|
logger.log("SANDBOX mode: Returning mock barcode response");
|
|
285
|
-
return {
|
|
286
|
-
barcode_data: 'SANDBOX_MOCK_BARCODE_DATA',
|
|
287
|
-
card_obb: { x: 50, y: 50, width: 200, height: 200 }
|
|
288
|
-
};
|
|
281
|
+
return { barcode_data: 'SANDBOX_MOCK_BARCODE_DATA', card_obb: { x: 50, y: 50, width: 200, height: 200 } };
|
|
289
282
|
}
|
|
290
283
|
const { fileUri, token, docType, postfix = 'back' } = params;
|
|
291
284
|
logger.log(docType);
|
|
@@ -295,7 +288,10 @@ export class KYCService {
|
|
|
295
288
|
const attempt = async () => {
|
|
296
289
|
try {
|
|
297
290
|
const res = await axios.post(url, formData, {
|
|
298
|
-
headers: {
|
|
291
|
+
headers: {
|
|
292
|
+
// 🚨 Removed explicit Content-Type to allow boundary injection
|
|
293
|
+
'Authorization': `Bearer ${token}`
|
|
294
|
+
},
|
|
299
295
|
timeout: 60000,
|
|
300
296
|
});
|
|
301
297
|
logger.log('📦 Raw extractBarcode response:', JSON.stringify(res.data, null, 2));
|
|
@@ -314,18 +310,13 @@ export class KYCService {
|
|
|
314
310
|
}
|
|
315
311
|
}
|
|
316
312
|
async formatBarcodeData(params, env = 'PRODUCTION') {
|
|
317
|
-
// SANDBOX mode
|
|
318
313
|
if (env === 'SANDBOX') {
|
|
319
314
|
console.log("SANDBOX mode: Skipping AI barcode formatting");
|
|
320
315
|
logger.log("SANDBOX mode: Returning mock formatted barcode response");
|
|
321
|
-
return {
|
|
322
|
-
barcode_type: "SANDBOX_MOCK",
|
|
323
|
-
first_name: "JOHN",
|
|
324
|
-
last_name: "DOE",
|
|
325
|
-
card_number: "000000000000"
|
|
326
|
-
};
|
|
316
|
+
return { barcode_type: "SANDBOX_MOCK", first_name: "JOHN", last_name: "DOE", card_number: "000000000000" };
|
|
327
317
|
}
|
|
328
318
|
const { textInput, token } = params;
|
|
319
|
+
// URLSearchParams requires urlencoded, this remains untouched
|
|
329
320
|
const formData = new URLSearchParams();
|
|
330
321
|
formData.append('text_input', textInput);
|
|
331
322
|
const url = `${this.barcodeServiceURL}/format_barcode_data/`;
|
|
@@ -354,7 +345,6 @@ export class KYCService {
|
|
|
354
345
|
}
|
|
355
346
|
}
|
|
356
347
|
async extractMrzText(params, env = 'PRODUCTION') {
|
|
357
|
-
// SANDBOX mode
|
|
358
348
|
if (env === 'SANDBOX') {
|
|
359
349
|
console.log("SANDBOX mode: Skipping AI MRZ extraction");
|
|
360
350
|
const { docType, docRegion, postfix = 'back', mrz_type } = params;
|
|
@@ -371,7 +361,6 @@ export class KYCService {
|
|
|
371
361
|
};
|
|
372
362
|
}
|
|
373
363
|
const { fileUri, docType, docRegion, postfix = 'back', token, template_path, mrz_type } = params;
|
|
374
|
-
// ... top of extractMrzText (params extraction, etc) ...
|
|
375
364
|
const docTypeShorted = GovernmentDocumentTypeShorted[docType] || docType;
|
|
376
365
|
const safeMrzType = mrz_type && mrz_type.trim() !== '' ? mrz_type : 'TD1';
|
|
377
366
|
logger.log("docTypeShorted", docTypeShorted, docRegion, postfix);
|
|
@@ -391,12 +380,9 @@ export class KYCService {
|
|
|
391
380
|
const nativeFetch = window.fetch;
|
|
392
381
|
const NativeFormData = window.FormData;
|
|
393
382
|
const formData = new NativeFormData();
|
|
394
|
-
// Fetch the Blob natively
|
|
395
383
|
const fileReq = await nativeFetch(fileUri);
|
|
396
384
|
const blob = await fileReq.blob();
|
|
397
|
-
// Append the Blob. The native browser will perfectly handle the multipart boundary.
|
|
398
385
|
formData.append('file', blob, `id_card_${postfix}.jpg`);
|
|
399
|
-
// Execute using Native Fetch!
|
|
400
386
|
response = await nativeFetch(url, {
|
|
401
387
|
method: 'POST',
|
|
402
388
|
headers: {
|
|
@@ -407,7 +393,7 @@ export class KYCService {
|
|
|
407
393
|
});
|
|
408
394
|
}
|
|
409
395
|
else {
|
|
410
|
-
// 📱
|
|
396
|
+
// 📱 NATIVE FETCH APPROACH (Untouched, robust as is)
|
|
411
397
|
const formData = new FormData();
|
|
412
398
|
const filePayload = {
|
|
413
399
|
uri: fileUri,
|
|
@@ -446,22 +432,19 @@ export class KYCService {
|
|
|
446
432
|
}
|
|
447
433
|
// STEP 2 - SELFIE VALIDATION
|
|
448
434
|
async recognizeFace(params, env = 'PRODUCTION') {
|
|
449
|
-
// SANDBOX mode
|
|
450
435
|
if (env === 'SANDBOX') {
|
|
451
436
|
console.log("SANDBOX mode: Skipping AI face recognition");
|
|
452
437
|
logger.log("SANDBOX mode: Returning mock face recognition response");
|
|
453
|
-
return {
|
|
454
|
-
is_match: true,
|
|
455
|
-
similarity: 0.95,
|
|
456
|
-
id_bbox: [50, 50, 200, 200],
|
|
457
|
-
selfie_bbox: [50, 50, 200, 200]
|
|
458
|
-
};
|
|
438
|
+
return { is_match: true, similarity: 0.95, id_bbox: [50, 50, 200, 200], selfie_bbox: [50, 50, 200, 200] };
|
|
459
439
|
}
|
|
460
440
|
const { idPhotoUri, selfiePhotoUri } = params;
|
|
461
441
|
const formData = new FormData();
|
|
462
442
|
await appendFileToFormData(formData, 'id_photo', idPhotoUri, 'id_card_photo.jpg', 'image/jpeg');
|
|
463
443
|
await appendFileToFormData(formData, 'selfie_photo', selfiePhotoUri, 'selfie_final.jpg', 'image/jpeg');
|
|
464
|
-
const res = await axios.post(`${this.faceServiceURL}/recognize_face/`, formData, {
|
|
444
|
+
const res = await axios.post(`${this.faceServiceURL}/recognize_face/`, formData, {
|
|
445
|
+
// Let Axios determine Content-Type boundary natively
|
|
446
|
+
timeout: 45000
|
|
447
|
+
});
|
|
465
448
|
if (res.data?.detail)
|
|
466
449
|
throw new Error(res.data.detail);
|
|
467
450
|
return res.data;
|
|
@@ -499,7 +482,6 @@ export class KYCService {
|
|
|
499
482
|
}
|
|
500
483
|
catch (error) {
|
|
501
484
|
logger.error('Error creating session:', JSON.stringify(errorMessage(error), null, 2));
|
|
502
|
-
// Extract backend error message if available
|
|
503
485
|
const backendMessage = errorMessage(error);
|
|
504
486
|
if (backendMessage) {
|
|
505
487
|
logger.error('Backend error message:', JSON.stringify(truncateFields(backendMessage), null, 2));
|
|
@@ -515,14 +497,10 @@ export class KYCService {
|
|
|
515
497
|
if (!apiKey && !token) {
|
|
516
498
|
throw new Error("Authentication failed: No API key or Token available.");
|
|
517
499
|
}
|
|
518
|
-
// 1. Original Shallow Clone (Preserves file paths perfectly)
|
|
519
500
|
const normalizedData = { ...data };
|
|
520
501
|
logger.log("🔍 [DocType Check] Raw incoming data.metadata.document_type:", normalizedData?.metadata?.document_type);
|
|
521
502
|
logger.log("🔍 [DocType Check] Raw incoming data.documentType:", normalizedData?.documentType);
|
|
522
|
-
|
|
523
|
-
const rawDocType = normalizedData?.metadata?.document_type ||
|
|
524
|
-
normalizedData?.documentType ||
|
|
525
|
-
normalizedData?.documents?.documentType;
|
|
503
|
+
const rawDocType = normalizedData?.metadata?.document_type || normalizedData?.documentType || normalizedData?.documents?.documentType;
|
|
526
504
|
if (rawDocType) {
|
|
527
505
|
const originalDocType = rawDocType;
|
|
528
506
|
const mappedDocType = documentTypeMap[originalDocType] ?? originalDocType;
|
|
@@ -532,20 +510,14 @@ export class KYCService {
|
|
|
532
510
|
};
|
|
533
511
|
normalizedData.documentType = mappedDocType;
|
|
534
512
|
if (normalizedData.documents) {
|
|
535
|
-
normalizedData.documents = {
|
|
536
|
-
...normalizedData.documents,
|
|
537
|
-
documentType: mappedDocType
|
|
538
|
-
};
|
|
513
|
+
normalizedData.documents = { ...normalizedData.documents, documentType: mappedDocType };
|
|
539
514
|
const isSingleSided = ['passport', 'visa'].includes(mappedDocType.toLowerCase());
|
|
540
515
|
if (isSingleSided && !normalizedData.documents.back) {
|
|
541
516
|
normalizedData.documents.back = {};
|
|
542
517
|
}
|
|
543
518
|
if (normalizedData.documents.front?.mrz && typeof normalizedData.documents.front.mrz === 'string') {
|
|
544
519
|
try {
|
|
545
|
-
normalizedData.documents.front = {
|
|
546
|
-
...normalizedData.documents.front,
|
|
547
|
-
mrz: JSON.parse(normalizedData.documents.front.mrz)
|
|
548
|
-
};
|
|
520
|
+
normalizedData.documents.front = { ...normalizedData.documents.front, mrz: JSON.parse(normalizedData.documents.front.mrz) };
|
|
549
521
|
}
|
|
550
522
|
catch (e) {
|
|
551
523
|
logger.log("Warning: Could not parse front MRZ JSON");
|
|
@@ -553,10 +525,7 @@ export class KYCService {
|
|
|
553
525
|
}
|
|
554
526
|
if (normalizedData.documents.back?.mrz && typeof normalizedData.documents.back.mrz === 'string') {
|
|
555
527
|
try {
|
|
556
|
-
normalizedData.documents.back = {
|
|
557
|
-
...normalizedData.documents.back,
|
|
558
|
-
mrz: JSON.parse(normalizedData.documents.back.mrz)
|
|
559
|
-
};
|
|
528
|
+
normalizedData.documents.back = { ...normalizedData.documents.back, mrz: JSON.parse(normalizedData.documents.back.mrz) };
|
|
560
529
|
}
|
|
561
530
|
catch (e) {
|
|
562
531
|
logger.log("Warning: Could not parse back MRZ JSON");
|
|
@@ -598,7 +567,6 @@ export class KYCService {
|
|
|
598
567
|
throw new Error(typeof finalErrorMessage === 'string' ? finalErrorMessage : JSON.stringify(finalErrorMessage));
|
|
599
568
|
}
|
|
600
569
|
}
|
|
601
|
-
/** Send email verification code. POST /api/v1/accounts/email/send/ */
|
|
602
570
|
async sendEmailVerificationCode(email, auth) {
|
|
603
571
|
const url = `${KYCConfig.getBackendUrl()}/accounts/email/send/`;
|
|
604
572
|
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
@@ -610,7 +578,6 @@ export class KYCService {
|
|
|
610
578
|
});
|
|
611
579
|
return res.data;
|
|
612
580
|
}
|
|
613
|
-
/** Verify email with OTP. POST /api/v1/accounts/email/verify/ */
|
|
614
581
|
async verifyEmailCode(otp, auth) {
|
|
615
582
|
const url = `${KYCConfig.getBackendUrl()}/accounts/email/verify/`;
|
|
616
583
|
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
@@ -641,16 +608,11 @@ export class KYCService {
|
|
|
641
608
|
headers["Authorization"] = `Bearer ${token}`;
|
|
642
609
|
}
|
|
643
610
|
logger.log("3. Firing Axios GET to:", url);
|
|
644
|
-
const res = await axios.get(url, {
|
|
645
|
-
headers,
|
|
646
|
-
signal: controller.signal
|
|
647
|
-
});
|
|
648
|
-
// Clear the timeout if the request succeeds!
|
|
611
|
+
const res = await axios.get(url, { headers, signal: controller.signal });
|
|
649
612
|
clearTimeout(timeoutId);
|
|
650
613
|
return res.data;
|
|
651
614
|
}
|
|
652
615
|
catch (error) {
|
|
653
|
-
// Catch the AbortController kill signal and format it as a timeout
|
|
654
616
|
if (axios.isCancel(error) || error.name === 'AbortError' || error.name === 'CanceledError') {
|
|
655
617
|
logger.error('Axios request was forcefully aborted due to timeout');
|
|
656
618
|
throw new Error('Timeout exceeded');
|
|
@@ -659,16 +621,10 @@ export class KYCService {
|
|
|
659
621
|
throw error;
|
|
660
622
|
}
|
|
661
623
|
}
|
|
662
|
-
/** Send WhatsApp verification code. POST /api/v1/accounts/send-whatsapp-verification/ */
|
|
663
624
|
async sendWhatsAppVerificationCode(sessionId, phoneNumber, auth) {
|
|
664
625
|
const url = `${KYCConfig.getBackendUrl()}/accounts/send-whatsapp-verification/`;
|
|
665
|
-
// 1. Formatting (Ensure consistency with verification step)
|
|
666
626
|
const formattedPhoneNumber = phoneNumber.replace(/^\+/, '');
|
|
667
|
-
|
|
668
|
-
const payload = {
|
|
669
|
-
session_id: sessionId,
|
|
670
|
-
phone_number: formattedPhoneNumber
|
|
671
|
-
};
|
|
627
|
+
const payload = { session_id: sessionId, phone_number: formattedPhoneNumber };
|
|
672
628
|
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
673
629
|
const headers = {
|
|
674
630
|
'Content-Type': 'application/json',
|
|
@@ -693,11 +649,7 @@ export class KYCService {
|
|
|
693
649
|
async verifyWhatsAppCode(sessionId, otp, phoneNumber, auth) {
|
|
694
650
|
const url = `${KYCConfig.getBackendUrl()}/accounts/verify-whatsapp-code/`;
|
|
695
651
|
const formattedPhoneNumber = phoneNumber.replace(/^\+/, '');
|
|
696
|
-
const payload = {
|
|
697
|
-
session_id: sessionId,
|
|
698
|
-
verification_code: otp,
|
|
699
|
-
phone_number: formattedPhoneNumber
|
|
700
|
-
};
|
|
652
|
+
const payload = { session_id: sessionId, verification_code: otp, phone_number: formattedPhoneNumber };
|
|
701
653
|
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
702
654
|
const headers = {
|
|
703
655
|
'Content-Type': 'application/json',
|
|
@@ -718,7 +670,6 @@ export class KYCService {
|
|
|
718
670
|
throw error;
|
|
719
671
|
}
|
|
720
672
|
}
|
|
721
|
-
// Inside KYCService.ts
|
|
722
673
|
async validateBackTemplate(params, env = 'PRODUCTION') {
|
|
723
674
|
if (env === 'SANDBOX') {
|
|
724
675
|
logger.log("SANDBOX mode: Skipping back template validation");
|
|
@@ -735,7 +686,6 @@ export class KYCService {
|
|
|
735
686
|
const url = `${this.mrzServiceURL}/validate_back_template/?${queryParams}`;
|
|
736
687
|
logger.log('validateBackTemplate URL', url);
|
|
737
688
|
try {
|
|
738
|
-
// Pass `null` for the body since the data is now in the URL
|
|
739
689
|
const res = await axios.post(url, null, {
|
|
740
690
|
headers: {
|
|
741
691
|
'Accept': 'application/json',
|
|
@@ -752,12 +702,7 @@ export class KYCService {
|
|
|
752
702
|
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Template validation failed');
|
|
753
703
|
}
|
|
754
704
|
}
|
|
755
|
-
|
|
756
|
-
* Generates a new referral code for a specific session.
|
|
757
|
-
* Endpoint: POST /api/v1/core/referral-code/generate/
|
|
758
|
-
*/
|
|
759
|
-
async generateReferralCode(payload, apiKey // 🚨 Now strictly required
|
|
760
|
-
) {
|
|
705
|
+
async generateReferralCode(payload, apiKey) {
|
|
761
706
|
try {
|
|
762
707
|
logger.log(`Generating referral code for session: ${payload.session_id}`);
|
|
763
708
|
if (!apiKey) {
|
|
@@ -766,12 +711,9 @@ export class KYCService {
|
|
|
766
711
|
const url = `${KYCConfig.getBackendUrl()}/core/referral-code/generate/`;
|
|
767
712
|
const headers = {
|
|
768
713
|
"Content-Type": "application/json",
|
|
769
|
-
"Authorization": `ApiKey ${apiKey}`
|
|
714
|
+
"Authorization": `ApiKey ${apiKey}`
|
|
770
715
|
};
|
|
771
|
-
const res = await axios.post(url, payload, {
|
|
772
|
-
headers,
|
|
773
|
-
timeout: 8000
|
|
774
|
-
});
|
|
716
|
+
const res = await axios.post(url, payload, { headers, timeout: 8000 });
|
|
775
717
|
logger.log("generateReferralCode SUCCESS:", JSON.stringify(res.data, null, 2));
|
|
776
718
|
return res.data;
|
|
777
719
|
}
|
|
@@ -785,7 +727,6 @@ export class KYCService {
|
|
|
785
727
|
}
|
|
786
728
|
const kycService = new KYCService("", "");
|
|
787
729
|
export default kycService;
|
|
788
|
-
// Pour éviter d'afficher des champs trop longs, on tronque les valeurs longues dans le log
|
|
789
730
|
export function truncateFields(obj, maxLength = 420) {
|
|
790
731
|
if (typeof obj !== 'object' || obj === null)
|
|
791
732
|
return obj;
|
|
@@ -808,18 +749,14 @@ export function truncateFields(obj, maxLength = 420) {
|
|
|
808
749
|
}
|
|
809
750
|
return truncated;
|
|
810
751
|
}
|
|
811
|
-
// Strictly define the return type as Promise<string>
|
|
812
752
|
export const authentification = async () => {
|
|
813
753
|
try {
|
|
814
|
-
// 1. If we have a valid token (with 10 seconds of breathing room), return it immediately
|
|
815
754
|
if (cachedToken && tokenExpiryTime && Date.now() < tokenExpiryTime - 10000) {
|
|
816
755
|
return cachedToken;
|
|
817
756
|
}
|
|
818
|
-
// 2. RACE CONDITION PREVENTION
|
|
819
757
|
if (activeAuthRequest) {
|
|
820
758
|
return activeAuthRequest;
|
|
821
759
|
}
|
|
822
|
-
// 3. Create the request and lock it
|
|
823
760
|
activeAuthRequest = (async () => {
|
|
824
761
|
logger.log('Authenticating: Fetching new Keycloak token...');
|
|
825
762
|
const params = 'client_id=kyc_frontend&client_secret=QhgAmvKgmwODzsEp98dnA4PeUEMMaFHd&grant_type=client_credentials';
|
|
@@ -832,7 +769,6 @@ export const authentification = async () => {
|
|
|
832
769
|
if (!cachedToken) {
|
|
833
770
|
throw new Error("Keycloak returned a success response but no access_token was found.");
|
|
834
771
|
}
|
|
835
|
-
// Calculate exact expiry timestamp (expires_in is usually in seconds)
|
|
836
772
|
const expiresInMs = (res.data.expires_in || 300) * 1000;
|
|
837
773
|
tokenExpiryTime = Date.now() + expiresInMs;
|
|
838
774
|
return cachedToken;
|
|
@@ -841,23 +777,16 @@ export const authentification = async () => {
|
|
|
841
777
|
return finalToken;
|
|
842
778
|
}
|
|
843
779
|
catch (error) {
|
|
844
|
-
cachedToken = null;
|
|
845
|
-
const safeErrorMessage = error?.response?.data?.error_description ||
|
|
846
|
-
error?.response?.data?.error ||
|
|
847
|
-
error?.message ||
|
|
848
|
-
'Unknown network error';
|
|
780
|
+
cachedToken = null;
|
|
781
|
+
const safeErrorMessage = error?.response?.data?.error_description || error?.response?.data?.error || error?.message || 'Unknown network error';
|
|
849
782
|
logger.error('Error authentifying:', safeErrorMessage);
|
|
850
783
|
throw new Error(`Échec de l'authentification: ${safeErrorMessage}`);
|
|
851
784
|
}
|
|
852
785
|
finally {
|
|
853
|
-
// 4. Always clear the lock when the request finishes
|
|
854
786
|
activeAuthRequest = null;
|
|
855
787
|
}
|
|
856
788
|
};
|
|
857
789
|
export const errorMessage = (error) => {
|
|
858
|
-
return error.response?.data?.message ||
|
|
859
|
-
error.response?.data?.detail ||
|
|
860
|
-
error.response?.data?.error ||
|
|
861
|
-
error.response?.data;
|
|
790
|
+
return error.response?.data?.message || error.response?.data?.detail || error.response?.data?.error || error.response?.data;
|
|
862
791
|
};
|
|
863
792
|
//# sourceMappingURL=KYCService.js.map
|