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