react-native-kalapa-ekyc 1.2.9 → 1.3.0

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 (40) hide show
  1. package/README.md +274 -273
  2. package/android/build.gradle +3 -3
  3. package/android/src/main/java/com/reactnativekalapaekyc/KalapaEkycModule.java +53 -43
  4. package/ios/KalapaEkyc.m +33 -15
  5. package/lib/typescript/src/KalapaResult.d.ts +107 -0
  6. package/lib/typescript/src/index.d.ts +26 -0
  7. package/package.json +7 -7
  8. package/src/KalapaResult.ts +10 -19
  9. package/src/index.tsx +75 -34
  10. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  11. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  12. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  13. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  14. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  15. package/android/.gradle/8.9/gc.properties +0 -0
  16. package/android/.gradle/9.0-milestone-1/checksums/checksums.lock +0 -0
  17. package/android/.gradle/9.0-milestone-1/checksums/sha1-checksums.bin +0 -0
  18. package/android/.gradle/9.0-milestone-1/fileChanges/last-build.bin +0 -0
  19. package/android/.gradle/9.0-milestone-1/fileHashes/fileHashes.lock +0 -0
  20. package/android/.gradle/9.0-milestone-1/gc.properties +0 -0
  21. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  22. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  23. package/android/.gradle/config.properties +0 -2
  24. package/android/.gradle/vcs-1/gc.properties +0 -0
  25. package/android/.idea/AndroidProjectSystem.xml +0 -6
  26. package/android/.idea/caches/deviceStreaming.xml +0 -1510
  27. package/android/.idea/gradle.xml +0 -12
  28. package/android/.idea/material_theme_project_new.xml +0 -10
  29. package/android/.idea/migrations.xml +0 -10
  30. package/android/.idea/misc.xml +0 -10
  31. package/android/.idea/runConfigurations.xml +0 -17
  32. package/android/.idea/vcs.xml +0 -6
  33. package/android/local.properties +0 -8
  34. package/android/src/main/java/com/reactnativekalapaekyc/kUtils.java +0 -66
  35. package/ios/KalapaEkyc.xcodeproj/project.pbxproj +0 -167
  36. package/ios/KalapaEkyc.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -4
  37. package/ios/KalapaEkyc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  38. package/ios/KalapaEkyc.xcodeproj/project.xcworkspace/xcuserdata/iosdev.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  39. package/ios/KalapaEkyc.xcodeproj/project.xcworkspace/xcuserdata/leo.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  40. package/ios/KalapaEkyc.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
package/README.md CHANGED
@@ -1,37 +1,62 @@
1
1
  # Kalapa eKYC React Native SDK Documentation
2
2
 
3
3
  Complete guide for integrating Kalapa eKYC functionality into your React Native applications.
4
+
4
5
  ---
6
+
5
7
  ## Changelog
8
+
9
+ ### 1.3.0
10
+ - **TypeScript**: Fix default export being inferred as `any`. Spreading the native module (`...NativeModules.KalapaEkyc`) into the exported object previously erased all typing, so `KalapaEkyc.start()` and its result fell back to `any` in consumer code. The module is now declared via a `KalapaEkycModule` interface, so `start()` is fully typed and returns `Promise<KalapaEkycResponse>` (`{ kalapa_result: KalapaResult; session?: string }`) — no casting needed to access `kalapa_result` or its methods.
11
+ - **Android**: Update to latest version of Kalapa eKYC to use latest version of Android Camera X 1.6.1.
12
+ ### 1.2.10
13
+ - **Android**: Fix switch fall-through in `onError` callback — prevents Promise being rejected multiple times
14
+ - **Android**: Fix `getCurrentActivity() == null` case now rejects Promise with `ACTIVITY_NULL` instead of leaving it pending forever
15
+ - **Android**: Fix native crash when optional config keys are missing — all boolean/int fields now use safe `hasKey` guards
16
+ - **Android**: SDK now runs on UI thread to safely open Activity
17
+ - **Android**: Bump `compileSdkVersion` and `targetSdkVersion` to 35
18
+ - **iOS**: Fix `handleResult` crash when `ocr_data.data.fields` is nil (e.g. `nfc_only` flow)
19
+ - **iOS**: Fix `qr_code` response format — now correctly wrapped as `{ data: { decoded_text } }` to match TypeScript parser
20
+ - **iOS**: Unify `EventSessionExpired` error code to `EXPIRED` (was `UNAUTHORIZED`)
21
+ - **TypeScript**: `config` parameter is now fully typed (`KalapaEkycConfig`), no more `any`
22
+ - **TypeScript**: `flow` parameter is now typed as `KalapaEkycFlow` union
23
+ - **TypeScript**: Added `KalapaEkycErrorCode` union type for all error codes
24
+ - **TypeScript**: Native module linkage guard — throws descriptive error with fix instructions if autolinking fails
25
+ - **TypeScript**: `nfc_data` parser now handles camelCase field names from Android (`idNumber`, `dateOfBirth`, etc.)
26
+
6
27
  ### 1.2.9
7
28
  - **React-Native**: Add `customer_language` into config.
8
- - **Android**: Fix obfuscation error
29
+ - **Android**: Fix obfuscation error
30
+
9
31
  ### 1.2.8
10
32
  - **Android**: Update stable version for NFC step.
33
+
11
34
  ### 1.2.7
12
35
  - **Android**: Update config to scan QR code or not.
13
- - **React-Native**: Introduce config require_qr in AppConfig
36
+ - **React-Native**: Introduce config `require_qr` in AppConfig
37
+
14
38
  ### 1.2.6
15
39
  - **Android**: Minor update, UI / UX optimize. Stable version
40
+
16
41
  ### 1.2.5
17
42
  - **Android**: Fix NFC step error UNKNOWN rarely happens on some device like Samsung Note 10
43
+
18
44
  ### 1.2.4
19
45
  - **Android**: Optimize UI / UX in NFC step
20
- - **React-Native**: show to skip / show confirm screen.
46
+ - **React-Native**: show to skip / show confirm screen.
47
+
21
48
  ### 1.2.3
22
49
  - **Android**: Optimize UI / UX in NFC step
50
+
23
51
  ### 1.2.2
24
52
  - **iOS**: Maintain UI config
25
53
  - **Android**: Maintain / Optimize NFC step
54
+
26
55
  ### 1.2.1
27
56
  - **successColor / failureColor**: Now can be configured via sdkConfig
57
+
28
58
  ### 1.2.0
29
59
  - **Documentation**: Complete overhaul of KalapaResult class documentation
30
- - **Documentation**: Added comprehensive property documentation for all root-level fields
31
- - **Documentation**: Added AddressEntities, MrzData, and QrCode object documentation
32
- - **Documentation**: Enhanced usage examples with all available properties
33
- - **Documentation**: Fixed package name references throughout documentation
34
- - **Documentation**: Added examples for accessing MRZ data, QR code data, and address entities
35
60
 
36
61
  ### 1.1.3
37
62
  - **Android**: Removed GIF dependencies to satisfy Google Play requirement (16KB pages size)
@@ -58,18 +83,23 @@ Complete guide for integrating Kalapa eKYC functionality into your React Native
58
83
 
59
84
  ## Requirements
60
85
 
61
- ### React Native
86
+ ### Version Matrix
62
87
 
63
- ```json
64
- "react": "17.0.2"
65
- "react-native": "0.66.3"
66
- ```
88
+ | Dependency | Version |
89
+ |---|---|
90
+ | React Native | >= 0.66 |
91
+ | Android `minSdkVersion` | 24 |
92
+ | Android `compileSdkVersion` | 35 |
93
+ | Android `targetSdkVersion` | 35 |
94
+ | iOS Deployment Target | >= 13.0 |
95
+ | Kalapa Android SDK | 2.11.8 |
67
96
 
68
97
  ### Android
69
98
 
70
99
  **Minimum SDK Requirements:**
71
100
  - `minSdkVersion = 24`
72
- - `targetSdkVersion = 33`
101
+ - `compileSdkVersion = 35`
102
+ - `targetSdkVersion = 35`
73
103
  - `android.useAndroidX = true`
74
104
  - `Kotlin = 1.8.0+`
75
105
 
@@ -83,7 +113,7 @@ compileOptions {
83
113
 
84
114
  ### iOS
85
115
 
86
- - iOS Development Target >= 13.4
116
+ - iOS Deployment Target >= 13.0
87
117
 
88
118
  ---
89
119
 
@@ -105,11 +135,11 @@ npm install react-native-kalapa-ekyc
105
135
 
106
136
  #### Manual Installation
107
137
 
108
- Add to your `app/package.json`:
138
+ Add to your `package.json`:
109
139
 
110
140
  ```json
111
141
  "dependencies": {
112
- "react-native-kalapa-ekyc": "^1.2.9"
142
+ "react-native-kalapa-ekyc": "^1.2.10"
113
143
  }
114
144
  ```
115
145
 
@@ -151,7 +181,7 @@ Add the **Near Field Communication Tag Reading** capability to your project targ
151
181
  Each eKYC profile requires a unique session ID (JWT access token). The SDK uses this session ID to perform all eKYC steps.
152
182
 
153
183
  **Important Notes:**
154
- - Failure to provide a valid session ID results in an unauthorized error
184
+ - Failure to provide a valid session ID results in an `EXPIRED` error
155
185
  - Sessions are valid for 10 minutes by default (adjustable)
156
186
  - Each session ID should be used for a single eKYC flow
157
187
 
@@ -161,63 +191,57 @@ Check the [API documentation](https://www.notion.so/Init-session-820a772b8402499
161
191
 
162
192
  ### 2. Define SDK Configuration
163
193
 
164
- #### Basic Configuration Structure
165
-
166
- ```jsx
167
- let configInfo = {
168
- domain: <BASE_URL>,
169
- main_color: <MAIN_COLOR>,
170
- main_text_color: <MAIN_TEXT_COLOR>,
171
- btn_text_color: <BTN_TEXT_COLOR>,
172
- background_color: <BG_COLOR>,
173
- success_color: <SUCCESS_COLOR>,
174
- failure_color: <FAILURE_COLOR>,
175
- language: <LANGUAGE>,
176
- liveness_version: <LIVENESS_VERSION>,
177
- face_data: <FACE_DATA>,
178
- mrz: <INPUT_MRZ>,
179
- qr_code: <INPUT_QR_CODE>,
180
- allow_mrz_rescan_on_nfc_mismatch: <ALLOW_MRZ_RESCAN_ON_NFC_MISMATCH>,
181
- with_confirm_screen: <WITH_CONFIRM_SCREEN>,
182
- require_qr: <REQUIRE_QR>,
183
- customer_language: <CUSTOMER_LANGUAGE>
184
- }
194
+ #### TypeScript Interface
195
+
196
+ ```typescript
197
+ import KalapaEkyc, { KalapaEkycConfig, KalapaEkycFlow } from 'react-native-kalapa-ekyc';
198
+
199
+ const configInfo: KalapaEkycConfig = {
200
+ domain: 'https://ekyc-sdk.kalapa.vn',
201
+ main_color: '#1F69E6',
202
+ main_text_color: '#000000',
203
+ btn_text_color: '#FFFFFF',
204
+ background_color: '#FFFFFF',
205
+ success_color: '#388E3C',
206
+ failure_color: '#F44336',
207
+ language: 'vi',
208
+ liveness_version: 3,
209
+ with_confirm_screen: true,
210
+ require_qr: false,
211
+ allow_mrz_rescan_on_nfc_mismatch: true,
212
+ };
185
213
  ```
186
214
 
187
215
  #### Configuration Parameters
188
216
 
189
- | Parameter | Type | Description |
190
- |-----------|------|-------------|
191
- | `BASE_URL` | String | Base URL for SDK API requests. Example endpoints: `<BASE_URL>/api/kyc/scan-front`, `<BASE_URL>/api/kyc/scan-back` |
192
- | `BG_COLOR` | String | Hex color code for SDK background |
193
- | `MAIN_COLOR` | String | Hex color code for buttons and functional texts |
194
- | `MAIN_TEXT_COLOR` | String | Hex color code for main text elements |
195
- | `BTN_TEXT_COLOR` | String | Hex color code for text inside filled buttons |
196
- | `SUCCESS_COLOR` | String | Hex color code for text and view that indicated for success state |
197
- | `FAILURE_COLOR` | String | Hex color code for text and view that indicated for failure state |
198
- | `LIVENESS_VERSION` | Integer | Liveness detection version:<br/>• `3` - Requires 3 random actions (turn left/right/up/down, tilt left/right)<br/>• `2` - Move face towards camera<br/>• `1` - No action required |
199
- | `LANGUAGE` | String | UI language: `"vi"` (Vietnamese) or `"en"` (English) |
200
- | `FACE_DATA` | String | Base64 face data. If valid, skips liveness step and uses this for comparison with NFC portrait or document portrait |
201
- | `INPUT_MRZ` | String | Pre-filled MRZ data. If valid, skips MRZ scan step. Invalid input throws `INVALID_MRZ` error |
202
- | `INPUT_QR_CODE` | String | Pre-filled QR code. If valid, skips QR scan step. Invalid input throws `INVALID_QR` error |
203
- | `ALLOW_MRZ_RESCAN_ON_NFC_MISMATCH` | Boolean | If `true`, allows user to rescan MRZ when chip data doesn't match input MRZ |
204
- | `WITH_CONFIRM_SCREEN` | Boolean | If `true`, allows user to enter confirm step |
205
- | `REQUIRE_QR` | Boolean | If `true`, SDK will scan for QR Code and may reduce the MRZ step when enter the NFC step. |
206
- | `CUSTOMER_LANGUAGE` | String | Default "", If you want to custom your own language, contact Kalapa in order to use this feature. |
217
+ | Parameter | Type | Default | Description |
218
+ |-----------|------|---------|-------------|
219
+ | `domain` | string | `https://ekyc-sdk.kalapa.vn` | Base URL for SDK API requests |
220
+ | `main_color` | string | `#1F69E6` | Hex color for buttons and primary UI elements |
221
+ | `main_text_color` | string | `#000000` | Hex color for main text |
222
+ | `btn_text_color` | string | `#FFFFFF` | Hex color for text inside filled buttons |
223
+ | `background_color` | string | `#FFFFFF` | Hex color for SDK background |
224
+ | `success_color` | string | `#388E3C` | Hex color for success state indicators |
225
+ | `failure_color` | string | `#F44336` | Hex color for failure state indicators |
226
+ | `language` | `'vi'` \| `'en'` | `'en'` | SDK UI language |
227
+ | `customer_language` | string | `''` | Custom language key contact Kalapa to enable |
228
+ | `liveness_version` | `1` \| `2` \| `3` | `0` | Liveness detection version: `3` = 3 random actions, `2` = move face toward camera, `1` = no action |
229
+ | `face_data` | string | | Base64 face data. If valid, skips liveness step |
230
+ | `mrz` | string | — | Pre-filled MRZ. If valid, skips MRZ scan. Invalid input throws `MRZ_INVALID` |
231
+ | `qr_code` | string | | Pre-filled QR code. If valid, skips QR scan |
232
+ | `allow_mrz_rescan_on_nfc_mismatch` | boolean | `false` | Allow user to rescan MRZ when NFC chip data doesn't match |
233
+ | `with_confirm_screen` | boolean | `false` | Show confirm screen before submitting |
234
+ | `require_qr` | boolean | `false` | Require QR code scan may reduce the MRZ step in NFC flow |
235
+ | `session_id` | string | — | Resume a previous leftover session |
236
+
207
237
  ---
208
238
 
209
239
  ### 3. Define the SDK Flow
210
240
 
211
- #### Available eKYC Steps
212
-
213
- 1. Scan the document
214
- 2. Scan the face
215
- 3. Scan the NFC chip
216
-
217
241
  #### Flow Types
218
242
 
219
- | Flow Type | Scan Document | Scan Face | Scan NFC Chip |
220
- |-----------|---------------|-----------|---------------|
243
+ | Flow | Scan Document | Scan Face | Scan NFC Chip |
244
+ |------|:---:|:---:|:---:|
221
245
  | `ekyc` | ✓ | ✓ | ✗ |
222
246
  | `nfc_ekyc` | ✓ | ✓ | ✓ |
223
247
  | `nfc_only` | ✗ | ✓ | ✓ |
@@ -226,11 +250,8 @@ let configInfo = {
226
250
 
227
251
  The SDK flow typically matches the flow set during session creation. However, there are special use cases:
228
252
 
229
- **Example Use Case for `nfc_only`:**
230
- If users completed the basic `ekyc` flow but failed to scan the NFC chip, you can:
231
- - Reuse the same session ID
232
- - Set flow to `nfc_only`
233
- - Allow NFC chip scanning without re-scanning document and face
253
+ **Example `nfc_only` retry:**
254
+ If a user completed `ekyc` but failed the NFC scan, you can reuse the same session ID with `nfc_only` to allow NFC scanning without re-scanning the document and face.
234
255
 
235
256
  **Best Practice:** Use separate sessions for different flows to simplify data management.
236
257
 
@@ -238,53 +259,64 @@ If users completed the basic `ekyc` flow but failed to scan the NFC chip, you ca
238
259
 
239
260
  ### 4. Start the SDK
240
261
 
241
- ```jsx
242
- import KalapaEkyc, { KalapaResult } from 'react-native-kalapa-ekyc';
262
+ ```typescript
263
+ import KalapaEkyc, { KalapaResult, KalapaEkycConfig, KalapaEkycFlow } from 'react-native-kalapa-ekyc';
264
+
265
+ const configInfo: KalapaEkycConfig = {
266
+ domain: 'https://ekyc-sdk.kalapa.vn',
267
+ language: 'vi',
268
+ liveness_version: 3,
269
+ with_confirm_screen: true,
270
+ };
243
271
 
244
- let sessionId = yourSessionInitFunc();
245
- let sdkFlow = "<YOUR_SDK_FLOW>"; // ekyc / nfc_ekyc / nfc_only
246
- let configInfo = {...};
247
-
248
- KalapaEkyc.start(sessionId, sdkFlow, configInfo)
249
- .then((rawResult) => {
250
- try {
251
- // You can use either the constructor or static method
252
- const result = KalapaResult.fromRawResult(rawResult);
253
- // Or: const result = new KalapaResult(rawResult);
254
- handleSuccessfulResult(result);
255
- } catch (error) {
256
- console.error("Result parsing error:", error);
272
+ const flow: KalapaEkycFlow = 'nfc_ekyc';
273
+
274
+ KalapaEkyc.start(sessionId, flow, configInfo)
275
+ .then(({ kalapa_result }) => {
276
+ const result: KalapaResult = kalapa_result;
277
+
278
+ if (result.isApproved()) {
279
+ // Store or display non-sensitive fields
280
+ saveVerificationResult({
281
+ decision: result.decision,
282
+ session: result.session,
283
+ idNumberMasked: result.id_number.replace(/.(?=.{4})/g, '*'),
284
+ });
285
+ } else if (result.isManualReview()) {
286
+ // Notify backend for manual review
257
287
  }
258
288
  })
259
289
  .catch((error) => {
260
290
  switch (error.code) {
261
- case "EXPIRED":
262
- console.warn("Session expired:", error.message);
291
+ case 'EXPIRED':
292
+ // Prompt user to restart — session timed out
293
+ break;
294
+ case 'CANCELED':
295
+ // User backed out — no action needed
263
296
  break;
264
- case "CANCELED":
265
- console.warn("User canceled:", error.message);
297
+ case 'PERMISSION_DENIED':
298
+ // Guide user to grant camera/NFC permission in Settings
266
299
  break;
267
- case "PERMISSION_DENIED":
268
- console.error("Permission denied:", error.message);
300
+ case 'DEVICE_NOT_ACCEPTABLE':
301
+ // Block flow — emulator, rooted, or jailbroken device
269
302
  break;
270
- case "DEVICE_NOT_ACCEPTABLE":
271
- console.error("Device not acceptable:", error.message);
303
+ case 'MRZ_INVALID':
304
+ // Invalid pre-filled MRZ was passed in config
272
305
  break;
273
- case "MRZ_INVALID":
274
- console.error("Invalid MRZ:", error.message);
306
+ case 'NFC_NOT_MATCH':
307
+ // NFC chip data doesn't match scanned document
275
308
  break;
276
- case "NFC_NOT_MATCH":
277
- console.error("NFC mismatch:", error.message);
309
+ case 'UNSUPPORTED':
310
+ // Device doesn't support NFC
278
311
  break;
279
- case "UNSUPPORTED":
280
- console.error("Unsupported device:", error.message);
312
+ case 'CONFIG_ERROR':
313
+ // Invalid domain, session, or SDK config
281
314
  break;
282
- case "CONFIG_ERROR":
283
- console.error("Configuration error:", error.message);
315
+ case 'ACTIVITY_NULL':
316
+ // Android only — app was backgrounded when SDK launched
284
317
  break;
285
- case "OTHER":
318
+ case 'OTHER':
286
319
  default:
287
- console.error("Unknown error:", error.message);
288
320
  break;
289
321
  }
290
322
  });
@@ -296,17 +328,18 @@ KalapaEkyc.start(sessionId, sdkFlow, configInfo)
296
328
 
297
329
  ### Error Codes
298
330
 
299
- | Error Code | Description |
300
- |------------|-------------|
301
- | `EXPIRED` | Session expired (10 minutes timeout) |
302
- | `CANCELED` | User canceled the eKYC process |
303
- | `PERMISSION_DENIED` | Camera or NFC permission denied |
304
- | `DEVICE_NOT_ACCEPTABLE` | Emulator, rooted, or jailbroken device detected |
305
- | `MRZ_INVALID` | Input MRZ is invalid |
306
- | `NFC_NOT_MATCH` | MRZ doesn't match NFC chip data |
307
- | `UNSUPPORTED` | Device doesn't support required features (usually NFC) |
308
- | `CONFIG_ERROR` | Configuration error |
309
- | `OTHER` | Unexpected error |
331
+ | Error Code | Platform | Description |
332
+ |------------|----------|-------------|
333
+ | `EXPIRED` | Android, iOS | Session expired (default 10-minute timeout) |
334
+ | `CANCELED` | Android, iOS | User canceled the eKYC process |
335
+ | `PERMISSION_DENIED` | Android | Camera or NFC permission denied |
336
+ | `DEVICE_NOT_ACCEPTABLE` | Android | Emulator, rooted, or virtual camera detected |
337
+ | `MRZ_INVALID` | Android | Pre-filled MRZ input is invalid |
338
+ | `NFC_NOT_MATCH` | Android | MRZ does not match NFC chip data |
339
+ | `UNSUPPORTED` | Android, iOS | Device doesn't support required features (NFC) |
340
+ | `CONFIG_ERROR` | Android, iOS | Domain, session, or SDK configuration error |
341
+ | `ACTIVITY_NULL` | Android | App Activity was null when SDK tried to launch |
342
+ | `OTHER` | Android, iOS | Unexpected error |
310
343
 
311
344
  ---
312
345
 
@@ -314,103 +347,79 @@ KalapaEkyc.start(sessionId, sdkFlow, configInfo)
314
347
 
315
348
  ### KalapaResult Class
316
349
 
317
- The SDK provides a `KalapaResult` class for type-safe data access:
350
+ The SDK returns a `KalapaResult` instance in the resolved value of `start()`. No manual parsing is needed.
318
351
 
319
352
  ```typescript
320
- import { KalapaResult } from 'react-native-kalapa-ekyc';
321
-
322
- // Create instance using static method (recommended)
323
- const result = KalapaResult.fromRawResult(rawResult);
353
+ import KalapaEkyc, { KalapaResult } from 'react-native-kalapa-ekyc';
324
354
 
325
- // Or use constructor directly
326
- const result = new KalapaResult(rawResult);
355
+ const { kalapa_result } = await KalapaEkyc.start(sessionId, flow, config);
356
+ // kalapa_result is already a KalapaResult instance
327
357
  ```
328
358
 
359
+ > **Security note:** `KalapaResult` contains sensitive personal data — CCCD number, date of birth, MRZ, and a base64 face image from the NFC chip. Do **not** log these fields directly. Use `result.id_number.replace(/.(?=.{4})/g, '*')` or equivalent masking before any logging. Do not store `nfc_data.face_image` unless legally required and properly secured.
360
+
329
361
  #### Core Properties
330
362
 
331
363
  | Property | Type | Description |
332
364
  |----------|------|-------------|
333
- | `decision` | DecisionType | Decision result: `"APPROVED"`, `"MANUAL"`, `"REJECTED"`, or `"UNKNOWN"` |
334
- | `selfie_data` | SelfieData | Face matching results |
335
- | `nfc_data` | NfcData | NFC chip data |
365
+ | `decision` | `DecisionType` | `"APPROVED"`, `"MANUAL"`, `"REJECTED"`, or `"UNKNOWN"` |
336
366
  | `session` | string | Session JWT token |
367
+ | `selfie_data` | `SelfieData` | Face matching results |
368
+ | `nfc_data` | `NfcData` | NFC chip data |
369
+ | `mrz_data` | `MrzData \| null` | Parsed MRZ fields |
370
+ | `qr_code` | `QrCode \| null` | QR code data from document |
337
371
  | `rawResult` | any | Original unprocessed response |
338
372
 
339
373
  #### Root-Level Personal Information
340
374
 
341
375
  | Property | Type | Description |
342
376
  |----------|------|-------------|
343
- | `name` | string | Full name from the verification result |
377
+ | `name` | string | Full name |
344
378
  | `id_number` | string | ID card number |
345
379
  | `birthday` | string | Birth date |
346
- | `gender` | string | Gender information |
347
- | `country` | string | Country information |
380
+ | `gender` | string | Gender |
381
+ | `country` | string | Country |
348
382
  | `national` | string | Nationality |
349
- | `ethnicity` | string | Ethnicity information |
350
- | `religion` | string | Religious information |
351
- | `features` | string | Personal identification marks or features |
383
+ | `ethnicity` | string | Ethnicity |
384
+ | `religion` | string | Religion |
385
+ | `features` | string | Personal identification marks |
352
386
  | `poi` | string | Place of issue |
353
-
354
- #### Date Information
355
-
356
- | Property | Type | Description |
357
- |----------|------|-------------|
358
387
  | `doe` | string | Date of expiry |
359
388
  | `doi` | string | Date of issuance |
360
-
361
- #### Address Information
362
-
363
- | Property | Type | Description |
364
- |----------|------|-------------|
365
389
  | `home` | string | Home address (full text) |
366
- | `home_entities` | AddressEntities | Parsed home address entities (district, ward, province, unknown) |
390
+ | `home_entities` | `AddressEntities` | Parsed home address |
367
391
  | `resident` | string | Residential address (full text) |
368
- | `resident_entities` | AddressEntities | Parsed residential address entities (district, ward, province, unknown) |
369
-
370
- #### Document Data
371
-
372
- | Property | Type | Description |
373
- |----------|------|-------------|
374
- | `mrz_data` | MrzData \| null | Machine Readable Zone data with parsed fields |
375
- | `qr_code` | QrCode \| null | QR code data from document |
392
+ | `resident_entities` | `AddressEntities` | Parsed residential address |
376
393
  | `type` | string | Document type |
377
394
 
378
395
  ---
379
396
 
380
397
  ### SelfieData Object
381
398
 
382
- #### Properties
383
-
384
399
  | Property | Type | Description |
385
400
  |----------|------|-------------|
386
- | `is_matched` | boolean | Face matching result |
387
- | `matching_score` | number | Matching confidence score (0-100) |
388
-
389
- #### Usage Example
390
-
391
- ```typescript
392
- if (result.selfie_data.is_matched) {
393
- console.log(`Face matched with ${result.selfie_data.matching_score}% confidence`);
394
- }
395
- ```
401
+ | `is_matched` | boolean | Whether face matched the document |
402
+ | `matching_score` | number | Matching confidence score (0100) |
396
403
 
397
404
  ---
398
405
 
399
406
  ### NfcData Object
400
407
 
408
+ > **Security note:** `face_image` is a base64-encoded portrait from the NFC chip. Treat it as biometric data — do not log, cache, or transmit without explicit user consent and appropriate security controls.
409
+
401
410
  #### Personal Information
402
411
 
403
412
  | Property | Type | Description |
404
413
  |----------|------|-------------|
405
- | `name` | string | Full name from NFC chip |
414
+ | `name` | string | Full name |
406
415
  | `id_number` | string | ID card number |
407
416
  | `old_id_number` | string | Previous ID number (if any) |
408
- | `date_of_birth` | string | Birth date in DD/MM/YYYY format |
409
- | `date_of_expiry` | string | ID expiry date in DD/MM/YYYY format |
410
- | `date_of_issuance` | string | ID issuance date in DD/MM/YYYY format |
411
- | `gender` | string | Gender information |
417
+ | `date_of_birth` | string | Birth date (DD/MM/YYYY) |
418
+ | `date_of_expiry` | string | Expiry date (DD/MM/YYYY) |
419
+ | `date_of_issuance` | string | Issuance date (DD/MM/YYYY) |
420
+ | `gender` | string | Gender |
412
421
  | `nationality` | string | Nationality |
413
- | `nation` | string | Nation/ethnicity information |
422
+ | `nation` | string | Nation/ethnicity |
414
423
 
415
424
  #### Address Information
416
425
 
@@ -431,22 +440,15 @@ if (result.selfie_data.is_matched) {
431
440
 
432
441
  | Property | Type | Description |
433
442
  |----------|------|-------------|
434
- | `religion` | string | Religious information |
435
- | `personal_identification` | string | Personal identification marks or features |
436
-
437
- #### Technical Data
438
-
439
- | Property | Type | Description |
440
- |----------|------|-------------|
441
- | `face_image` | string | Base64 encoded face image from NFC chip |
442
- | `mrz` | string | Machine Readable Zone data (raw string) |
443
+ | `religion` | string | Religion |
444
+ | `personal_identification` | string | Personal identification marks |
445
+ | `face_image` | string | Base64 face image from NFC chip — handle as biometric data |
446
+ | `mrz` | string | Raw MRZ string |
443
447
 
444
448
  ---
445
449
 
446
450
  ### AddressEntities Object
447
451
 
448
- Represents parsed address components:
449
-
450
452
  | Property | Type | Description |
451
453
  |----------|------|-------------|
452
454
  | `district` | string | District name |
@@ -458,48 +460,27 @@ Represents parsed address components:
458
460
 
459
461
  ### MrzData Object
460
462
 
461
- Contains MRZ (Machine Readable Zone) information:
462
-
463
- | Property | Type | Description |
464
- |----------|------|-------------|
465
- | `data` | MrzDataFields \| undefined | Parsed MRZ fields |
466
- | `error` | MrzDataError \| undefined | Error information if parsing failed |
467
-
468
- #### MrzDataFields
469
-
470
463
  | Property | Type | Description |
471
464
  |----------|------|-------------|
472
- | `birthday` | string \| undefined | Birth date from MRZ |
473
- | `doe` | string \| undefined | Date of expiry from MRZ |
474
- | `gender` | string \| undefined | Gender from MRZ |
475
- | `id_number` | string \| undefined | ID number from MRZ |
476
- | `name` | string \| undefined | Name from MRZ |
477
- | `raw_mrz` | string \| undefined | Raw MRZ string |
478
-
479
- #### MrzDataError
480
-
481
- | Property | Type | Description |
482
- |----------|------|-------------|
483
- | `code` | number | Error code |
484
- | `message` | string | Error message |
465
+ | `data.fields.birthday` | string \| undefined | Birth date from MRZ |
466
+ | `data.fields.doe` | string \| undefined | Date of expiry from MRZ |
467
+ | `data.fields.gender` | string \| undefined | Gender from MRZ |
468
+ | `data.fields.id_number` | string \| undefined | ID number from MRZ |
469
+ | `data.fields.name` | string \| undefined | Name from MRZ |
470
+ | `data.raw_mrz` | string \| undefined | Raw MRZ string |
471
+ | `error.code` | number \| undefined | Error code if parsing failed |
472
+ | `error.message` | string \| undefined | Error message if parsing failed |
485
473
 
486
474
  ---
487
475
 
488
476
  ### QrCode Object
489
477
 
490
- Contains QR code information:
491
-
492
478
  | Property | Type | Description |
493
479
  |----------|------|-------------|
494
- | `data` | QrCodeData \| undefined | Decoded QR code data |
495
- | `error` | { code: number, message: string } \| undefined | Error information if decoding failed |
496
-
497
- #### QrCodeData
498
-
499
- | Property | Type | Description |
500
- |----------|------|-------------|
501
- | `decoded_text` | string \| undefined | Decoded QR code text |
502
- | `stage` | number \| undefined | Processing stage |
480
+ | `data.decoded_text` | string \| undefined | Decoded QR code text |
481
+ | `data.stage` | number \| undefined | Processing stage |
482
+ | `error.code` | number \| undefined | Error code if decoding failed |
483
+ | `error.message` | string \| undefined | Error message if decoding failed |
503
484
 
504
485
  ---
505
486
 
@@ -509,92 +490,112 @@ Contains QR code information:
509
490
 
510
491
  | Method | Returns | Description |
511
492
  |--------|---------|-------------|
512
- | `isApproved()` | boolean | Returns `true` if decision is `"APPROVED"` |
513
- | `isManualReview()` | boolean | Returns `true` if decision is `"MANUAL"` |
514
- | `isRejected()` | boolean | Returns `true` if decision is `"REJECTED"` |
493
+ | `isApproved()` | boolean | `true` if decision is `"APPROVED"` |
494
+ | `isManualReview()` | boolean | `true` if decision is `"MANUAL"` |
495
+ | `isRejected()` | boolean | `true` if decision is `"REJECTED"` |
515
496
 
516
497
  ### Face Matching Methods
517
498
 
518
499
  | Method | Returns | Description |
519
500
  |--------|---------|-------------|
520
- | `isFaceMatched()` | boolean | Returns `true` if face matched successfully |
521
- | `getFaceMatchingScore()` | number | Returns face matching confidence score |
501
+ | `isFaceMatched()` | boolean | `true` if face matched successfully |
502
+ | `getFaceMatchingScore()` | number | Face matching confidence score |
522
503
 
523
504
  ### Formatted Getters
524
505
 
525
506
  | Method | Returns | Description |
526
507
  |--------|---------|-------------|
527
- | `getDisplayName()` | string | Returns formatted name or `"N/A"` if empty |
528
- | `getIdNumber()` | string | Returns formatted ID number or `"N/A"` if empty |
529
- | `getDateOfBirth()` | string | Returns formatted birth date or `"N/A"` if empty |
530
- | `getFaceImage()` | string | Returns base64 face image from NFC chip |
508
+ | `getDisplayName()` | string | Name from `nfc_data`, or `"N/A"` |
509
+ | `getIdNumber()` | string | ID number from `nfc_data`, or `"N/A"` |
510
+ | `getDateOfBirth()` | string | Birth date from `nfc_data`, or `"N/A"` |
511
+ | `getFaceImage()` | string | Base64 face image from NFC chip |
531
512
 
532
513
  ### Utility Methods
533
514
 
534
515
  | Method | Returns | Description |
535
516
  |--------|---------|-------------|
536
- | `toJSON()` | object | Returns clean object for storage/transmission |
537
- | `fromRawResult(rawResult)` | KalapaResult | Static method to create KalapaResult from raw response |
517
+ | `toJSON()` | object | Plain object suitable for storage or network transmission |
518
+ | `fromRawResult(raw)` | KalapaResult | Static factory creates a `KalapaResult` from a raw object |
538
519
 
539
520
  ---
540
521
 
541
522
  ## Complete Usage Example
542
523
 
543
524
  ```typescript
544
- import KalapaEkyc, { KalapaResult } from 'react-native-kalapa-ekyc';
525
+ import KalapaEkyc, { KalapaResult, KalapaEkycConfig, KalapaEkycFlow } from 'react-native-kalapa-ekyc';
526
+
527
+ const runEkyc = async (sessionId: string) => {
528
+ const config: KalapaEkycConfig = {
529
+ domain: 'https://ekyc-sdk.kalapa.vn',
530
+ language: 'vi',
531
+ liveness_version: 3,
532
+ with_confirm_screen: true,
533
+ allow_mrz_rescan_on_nfc_mismatch: true,
534
+ };
535
+
536
+ const flow: KalapaEkycFlow = 'nfc_ekyc';
545
537
 
546
- const handleEkyc = async () => {
547
538
  try {
548
- const rawResult = await KalapaEkyc.start(sessionId, "nfc_only", configInfo);
549
- const result = KalapaResult.fromRawResult(rawResult);
550
-
551
- // Check decision
539
+ const { kalapa_result: result } = await KalapaEkyc.start(sessionId, flow, config);
540
+
541
+ // Decision
552
542
  if (result.isApproved()) {
553
- console.log("eKYC Approved");
543
+ console.log('Approved');
554
544
  } else if (result.isManualReview()) {
555
- console.log("Manual review required");
545
+ console.log('Manual review required');
546
+ } else if (result.isRejected()) {
547
+ console.log('Rejected');
556
548
  }
557
-
558
- // Check face matching
549
+
550
+ // Face matching
559
551
  if (result.isFaceMatched()) {
560
- console.log(`Face matched: ${result.getFaceMatchingScore()}%`);
552
+ console.log(`Face matched — score: ${result.getFaceMatchingScore()}`);
561
553
  }
562
-
563
- // Access root-level data
564
- console.log(`Name: ${result.name}`);
565
- console.log(`ID Number: ${result.id_number}`);
566
- console.log(`Birthday: ${result.birthday}`);
567
-
568
- // Access NFC data
569
- const userData = {
570
- name: result.nfc_data.name,
571
- idNumber: result.nfc_data.id_number,
572
- birthDate: result.nfc_data.date_of_birth,
573
- faceImage: result.nfc_data.face_image
574
- };
575
-
576
- // Access address entities
554
+
555
+ // Address entities
577
556
  if (result.home_entities) {
578
- console.log(`Home: ${result.home_entities.province}, ${result.home_entities.district}`);
557
+ const { province, district, ward } = result.home_entities;
558
+ console.log(`Home: ${ward}, ${district}, ${province}`);
579
559
  }
580
-
581
- // Access MRZ data if available
582
- if (result.mrz_data?.data) {
583
- console.log(`MRZ Name: ${result.mrz_data.data.name}`);
584
- console.log(`Raw MRZ: ${result.mrz_data.data.raw_mrz}`);
560
+
561
+ // MRZ data
562
+ if (result.mrz_data?.data?.fields) {
563
+ const { id_number, name } = result.mrz_data.data.fields;
564
+ console.log(`MRZ parsed — name: ${name}`); // avoid logging id_number directly
585
565
  }
586
-
587
- // Access QR code data if available
588
- if (result.qr_code?.data) {
589
- console.log(`QR Code: ${result.qr_code.data.decoded_text}`);
566
+
567
+ // QR code data
568
+ if (result.qr_code?.data?.decoded_text) {
569
+ console.log('QR decoded');
570
+ }
571
+
572
+ // Export for backend — avoid logging the full object as it contains PII
573
+ const payload = result.toJSON();
574
+ await submitToBackend(payload);
575
+
576
+ } catch (error: any) {
577
+ switch (error.code) {
578
+ case 'EXPIRED':
579
+ showAlert('Session expired', 'Please start over.');
580
+ break;
581
+ case 'CANCELED':
582
+ // User backed out — no UI needed
583
+ break;
584
+ case 'PERMISSION_DENIED':
585
+ showAlert('Permission required', 'Enable camera and NFC in Settings.');
586
+ break;
587
+ case 'DEVICE_NOT_ACCEPTABLE':
588
+ showAlert('Unsupported device', 'Please use a physical device.');
589
+ break;
590
+ case 'NFC_NOT_MATCH':
591
+ showAlert('NFC mismatch', 'Please rescan your document.');
592
+ break;
593
+ case 'ACTIVITY_NULL':
594
+ // Android: app was backgrounded — ask user to retry
595
+ break;
596
+ default:
597
+ showAlert('Error', error.message ?? 'An unexpected error occurred.');
590
598
  }
591
-
592
- // Export to JSON for storage/transmission
593
- const jsonData = result.toJSON();
594
- await saveUserData(jsonData);
595
-
596
- } catch (error) {
597
- handleEkycError(error);
598
599
  }
599
600
  };
600
601
  ```
@@ -603,13 +604,13 @@ const handleEkyc = async () => {
603
604
 
604
605
  ## Summary
605
606
 
606
- The Kalapa eKYC React Native SDK provides comprehensive identity verification with document scanning, face matching, and NFC chip reading capabilities. Key features include:
607
+ The Kalapa eKYC React Native SDK provides identity verification with document scanning, face matching, and NFC chip reading. Key features:
607
608
 
608
- - Three flexible flow types for different verification needs
609
- - Customizable UI with color and language options
610
- - Multiple liveness detection versions
611
- - Type-safe result objects with convenience methods
612
- - Robust error handling with detailed error codes
613
- - Support for pre-filled data to skip certain steps
609
+ - Three flexible flow types (`ekyc`, `nfc_ekyc`, `nfc_only`)
610
+ - Fully typed TypeScript API `KalapaEkycConfig`, `KalapaEkycFlow`, `KalapaEkycErrorCode`
611
+ - Customizable UI (colors, language, confirm screen)
612
+ - Three liveness detection versions
613
+ - Type-safe `KalapaResult` with convenience methods
614
+ - Unified error codes across Android and iOS
614
615
 
615
- For API documentation and session creation, refer to the [Kalapa API documentation](https://kalapa-no.notion.site/eKYC-API-SDK-document-2a2a4ccfa1184a789cb4513fade351e4?pvs=74)
616
+ For API documentation and session creation, refer to the [Kalapa API documentation](https://kalapa-no.notion.site/eKYC-API-SDK-document-2a2a4ccfa1184a789cb4513fade351e4?pvs=74).