@finos_sdk/sdk-ekyc 1.4.5 → 1.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/finos/sdk/ekyc/EKYCModule.kt +193 -89
- package/dist/EKYCModule.d.ts +16 -6
- package/dist/EKYCModule.js +4 -1
- package/dist/index.d.ts +2 -2
- package/dist/package.json +1 -1
- package/dist/src/modules/FinosEKYCModule.d.ts +12 -6
- package/dist/src/modules/FinosESignModule.d.ts +9 -7
- package/dist/src/types/ekycFaceType.d.ts +9 -0
- package/dist/src/types/ekycLivenessType.d.ts +22 -0
- package/ios/EKYCModule.swift +35 -7
- package/package.json +1 -1
- package/src/modules/FinosEKYCModule.ts +12 -6
- package/src/modules/FinosESignModule.ts +8 -6
- package/src/types/ekycFaceType.ts +10 -0
- package/src/types/ekycLivenessType.ts +26 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
React Native SDK for eKYC (electronic Know Your Customer) and eSign. Features include Vietnamese CCCD NFC reading, OCR, Liveness detection, Face matching, C06 residence verification, SMS OTP verification, and Electronic Signature (eSign) capabilities.
|
|
7
7
|
|
|
8
|
-
**Version**: 1.4.
|
|
8
|
+
**Version**: 1.4.7
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
package/android/build.gradle
CHANGED
|
@@ -65,7 +65,7 @@ dependencies {
|
|
|
65
65
|
implementation 'com.facebook.react:react-android'
|
|
66
66
|
|
|
67
67
|
// Finos eKYC SDK dependencies from GitHub Packages Maven repository
|
|
68
|
-
def sdkVersion = "1.4.
|
|
68
|
+
def sdkVersion = "1.4.7"
|
|
69
69
|
implementation("finos.sdk.ekyc:ekyc:$sdkVersion")
|
|
70
70
|
implementation("finos.sdk.ekyc:ekycui:$sdkVersion")
|
|
71
71
|
implementation("finos.sdk.ekyc:nfc:$sdkVersion")
|
|
@@ -27,6 +27,7 @@ import finos.sdk.core.model.sdk.config.AppKeyConfig
|
|
|
27
27
|
import finos.sdk.core.model.sdk.config.C06Config
|
|
28
28
|
import finos.sdk.core.model.sdk.config.EKYCConfigSDK
|
|
29
29
|
import finos.sdk.core.model.sdk.config.FaceServiceConfig
|
|
30
|
+
import finos.sdk.core.model.sdk.config.LivenessBackConfirmConfig
|
|
30
31
|
import finos.sdk.core.model.sdk.config.LivenessConfig
|
|
31
32
|
import finos.sdk.core.model.sdk.config.NfcConfig
|
|
32
33
|
import finos.sdk.core.model.sdk.config.OcrConfig
|
|
@@ -96,6 +97,46 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
96
97
|
return Pair(eventMap, promiseMap)
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
/** Recursively convert org.json.JSONObject → WritableMap (preserves key names as-is). */
|
|
101
|
+
private fun jsonObjectToWritableMap(jsonObject: JSONObject): WritableMap {
|
|
102
|
+
val map = Arguments.createMap()
|
|
103
|
+
val keys = jsonObject.keys()
|
|
104
|
+
while (keys.hasNext()) {
|
|
105
|
+
val key = keys.next()
|
|
106
|
+
when (val value = jsonObject.get(key)) {
|
|
107
|
+
is JSONObject -> map.putMap(key, jsonObjectToWritableMap(value))
|
|
108
|
+
is JSONArray -> map.putArray(key, jsonArrayToWritableArray(value))
|
|
109
|
+
is Boolean -> map.putBoolean(key, value)
|
|
110
|
+
is Int -> map.putInt(key, value)
|
|
111
|
+
is Long -> map.putDouble(key, value.toDouble())
|
|
112
|
+
is Double -> map.putDouble(key, value)
|
|
113
|
+
is String -> map.putString(key, value)
|
|
114
|
+
JSONObject.NULL -> map.putNull(key)
|
|
115
|
+
else -> map.putString(key, value.toString())
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return map
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Recursively convert org.json.JSONArray → WritableArray. */
|
|
122
|
+
private fun jsonArrayToWritableArray(jsonArray: JSONArray): WritableArray {
|
|
123
|
+
val array = Arguments.createArray()
|
|
124
|
+
for (i in 0 until jsonArray.length()) {
|
|
125
|
+
when (val value = jsonArray.get(i)) {
|
|
126
|
+
is JSONObject -> array.pushMap(jsonObjectToWritableMap(value))
|
|
127
|
+
is JSONArray -> array.pushArray(jsonArrayToWritableArray(value))
|
|
128
|
+
is Boolean -> array.pushBoolean(value)
|
|
129
|
+
is Int -> array.pushInt(value)
|
|
130
|
+
is Long -> array.pushDouble(value.toDouble())
|
|
131
|
+
is Double -> array.pushDouble(value)
|
|
132
|
+
is String -> array.pushString(value)
|
|
133
|
+
JSONObject.NULL -> array.pushNull()
|
|
134
|
+
else -> array.pushString(value.toString())
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return array
|
|
138
|
+
}
|
|
139
|
+
|
|
99
140
|
/** Convert ESignOpenSessionResult to WritableMap for RN. */
|
|
100
141
|
private fun eSignOpenSessionResultToWritableMap(result: ESignModels.ESignOpenSessionResult): WritableMap =
|
|
101
142
|
Arguments.createMap().apply {
|
|
@@ -235,7 +276,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
235
276
|
val eventMap =
|
|
236
277
|
Arguments.createMap().apply {
|
|
237
278
|
putString("event", event.name.toString())
|
|
238
|
-
putString("data",
|
|
279
|
+
putString("data", Gson().toJson(data))
|
|
239
280
|
}
|
|
240
281
|
sendEvent("onNfcScanStart", eventMap)
|
|
241
282
|
}
|
|
@@ -260,7 +301,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
260
301
|
val eventMap =
|
|
261
302
|
Arguments.createMap().apply {
|
|
262
303
|
putString("event", event.name.toString())
|
|
263
|
-
putString("data",
|
|
304
|
+
putString("data", Gson().toJson(data))
|
|
264
305
|
}
|
|
265
306
|
sendEvent("onNfcEvent", eventMap)
|
|
266
307
|
}
|
|
@@ -314,18 +355,19 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
314
355
|
ekycConfigSDK = ekycConfig,
|
|
315
356
|
callbackSuccess = { event, data ->
|
|
316
357
|
Log.d(TAG, "✅ checkC06() success")
|
|
358
|
+
val c06Json = Gson().toJson((data as? SDKEkycResult)?.checkC06Response)
|
|
317
359
|
|
|
318
360
|
// Create separate maps for event and promise
|
|
319
361
|
val eventMap =
|
|
320
362
|
Arguments.createMap().apply {
|
|
321
363
|
putString("event", event.name.toString())
|
|
322
|
-
putString("data",
|
|
364
|
+
putString("data", c06Json)
|
|
323
365
|
}
|
|
324
366
|
|
|
325
367
|
val promiseMap =
|
|
326
368
|
Arguments.createMap().apply {
|
|
327
369
|
putString("event", event.name.toString())
|
|
328
|
-
putString("data",
|
|
370
|
+
putString("data", c06Json)
|
|
329
371
|
}
|
|
330
372
|
sendEvent("onC06Success", eventMap)
|
|
331
373
|
promise.resolve(promiseMap)
|
|
@@ -389,7 +431,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
389
431
|
Log.d(TAG, "✅ startOcr() success")
|
|
390
432
|
val (eventMap, promiseMap) = createSeparateMaps { map ->
|
|
391
433
|
map.putString("event", event.name.toString())
|
|
392
|
-
map.putString("data", (data as SDKEkycResult).ocrResponse
|
|
434
|
+
map.putString("data", Gson().toJson((data as SDKEkycResult).ocrResponse))
|
|
393
435
|
}
|
|
394
436
|
sendEvent("onOcrSuccess", eventMap)
|
|
395
437
|
promise.resolve(promiseMap)
|
|
@@ -428,6 +470,8 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
428
470
|
activeActionCount: Int?,
|
|
429
471
|
forceCaptureTimeout: Double?,
|
|
430
472
|
isActiveLivenessColor: Boolean?,
|
|
473
|
+
isShowBackConfirmation: Boolean?,
|
|
474
|
+
backConfirmConfigJson: String?,
|
|
431
475
|
promise: Promise
|
|
432
476
|
) {
|
|
433
477
|
Log.d(TAG, "▶️ startLiveness() called")
|
|
@@ -461,6 +505,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
461
505
|
null
|
|
462
506
|
}
|
|
463
507
|
|
|
508
|
+
val backConfirmConfig = parseBackConfirmConfig(backConfirmConfigJson)
|
|
464
509
|
val livenessConfig =
|
|
465
510
|
LivenessConfig(
|
|
466
511
|
isActiveLiveness = isActiveLiveness ?: false,
|
|
@@ -475,7 +520,9 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
475
520
|
activeActionCount = activeActionCount ?: 2,
|
|
476
521
|
forceCaptureTimeout = (forceCaptureTimeout ?: 0.0).toLong(),
|
|
477
522
|
selfieImage = imageFile,
|
|
478
|
-
transactionId = transactionId
|
|
523
|
+
transactionId = transactionId,
|
|
524
|
+
isShowBackConfirmation = isShowBackConfirmation ?: false,
|
|
525
|
+
backConfirmConfig = backConfirmConfig,
|
|
479
526
|
)
|
|
480
527
|
val ekycConfig =
|
|
481
528
|
EKYCConfigSDK(
|
|
@@ -486,24 +533,25 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
486
533
|
eKYCFinOSLiveness.startEkyc(
|
|
487
534
|
ekycConfigSDK = ekycConfig,
|
|
488
535
|
callbackSuccess = { event, data ->
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
536
|
+
Log.d(TAG, "🔥 NATIVE CALLBACK (Liveness): event=${event.name}")
|
|
537
|
+
val response = (data as? SDKEkycResult)?.checkLivenessResponse
|
|
538
|
+
val jsonStr = data?.customData?.let { Gson().toJson(it) } ?: Gson().toJson(response ?: data)
|
|
539
|
+
|
|
540
|
+
// Helper to create fresh data map to avoid 'Map already consumed' error
|
|
541
|
+
fun createWritableData() = try { jsonObjectToWritableMap(JSONObject(jsonStr)) } catch (e: Exception) { Arguments.createMap() }
|
|
542
|
+
|
|
543
|
+
// Send the event directly to React Native
|
|
544
|
+
sendEvent("onLivenessSuccess", Arguments.createMap().apply {
|
|
545
|
+
putString("event", event.name)
|
|
546
|
+
putMap("data", createWritableData())
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
// Always attempt to resolve the promise.
|
|
550
|
+
// RN safely ignores subsequent resolves if the native SDK fires multiple events.
|
|
551
|
+
promise.resolve(Arguments.createMap().apply {
|
|
552
|
+
putString("event", event.name)
|
|
553
|
+
putMap("data", createWritableData())
|
|
554
|
+
})
|
|
507
555
|
},
|
|
508
556
|
callbackError = { event, errorResult ->
|
|
509
557
|
Log.e(TAG, "❌ startLiveness() failed - Event: $event, Code: ${errorResult.code}, Message: ${errorResult.message}")
|
|
@@ -570,24 +618,21 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
570
618
|
eKYCFinOSFaceService.startEkyc(
|
|
571
619
|
ekycConfigSDK = ekycConfig,
|
|
572
620
|
callbackSuccess = { event, data ->
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
sendEvent("onFaceCompareSuccess", eventMap)
|
|
590
|
-
promise.resolve(promiseMap)
|
|
621
|
+
Log.d(TAG, "🔥 NATIVE CALLBACK (Face Compare): event=${event.name}")
|
|
622
|
+
val response = (data as? SDKEkycResult)?.checkFaceResponse
|
|
623
|
+
val jsonStr = data?.customData?.let { Gson().toJson(it) } ?: Gson().toJson(response ?: data)
|
|
624
|
+
fun createWritableData() = try { jsonObjectToWritableMap(JSONObject(jsonStr)) } catch (e: Exception) { Arguments.createMap() }
|
|
625
|
+
|
|
626
|
+
sendEvent("onFaceCompareSuccess", Arguments.createMap().apply {
|
|
627
|
+
putString("event", event.name)
|
|
628
|
+
putMap("data", createWritableData())
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
// Always attempt to resolve the promise.
|
|
632
|
+
promise.resolve(Arguments.createMap().apply {
|
|
633
|
+
putString("event", event.name)
|
|
634
|
+
putMap("data", createWritableData())
|
|
635
|
+
})
|
|
591
636
|
},
|
|
592
637
|
callbackError = { event, errorResult ->
|
|
593
638
|
Log.e(TAG, "❌ startFaceCompare() failed - Event: $event, Code: ${errorResult.code}, Message: ${errorResult.message}")
|
|
@@ -783,6 +828,9 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
783
828
|
else -> AppIDType.NONE
|
|
784
829
|
}
|
|
785
830
|
|
|
831
|
+
val isShowBackConfirmation = extractBooleanValue(optionConfigJson, "isShowBackConfirmation") ?: false
|
|
832
|
+
val backConfirmConfig = parseBackConfirmConfig(extractObjectJson(optionConfigJson, "backConfirmConfig"))
|
|
833
|
+
|
|
786
834
|
val livenessConfig = if (finalFlow.contains(SDKType.LIVENESS)) {
|
|
787
835
|
LivenessConfig(
|
|
788
836
|
isActiveLiveness = isActiveLiveness,
|
|
@@ -790,6 +838,8 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
790
838
|
isShowCameraFont = switchFrontCamera,
|
|
791
839
|
customActions = customActionsList?.takeIf { it.isNotEmpty() },
|
|
792
840
|
activeActionCount = activeActionCount,
|
|
841
|
+
isShowBackConfirmation = isShowBackConfirmation,
|
|
842
|
+
backConfirmConfig = backConfirmConfig,
|
|
793
843
|
)
|
|
794
844
|
} else null
|
|
795
845
|
|
|
@@ -807,41 +857,55 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
807
857
|
activity = currentActivity,
|
|
808
858
|
ekycConfigSDK = ekycConfigSDK,
|
|
809
859
|
callbackSuccess = { event, data ->
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
val logDataStr = data?.customData?.let { Gson().toJson(it) } ?: data?.toString() ?: "null"
|
|
813
|
-
Log.d(TAG, "LOG_SUCCESS [EkycUI]: $logDataStr")
|
|
814
|
-
sendEvent("onEkycUISuccess", Arguments.createMap().apply {
|
|
815
|
-
putString("status", "log")
|
|
816
|
-
putString("event", event.name.toString())
|
|
817
|
-
putString("data", logDataStr)
|
|
818
|
-
})
|
|
819
|
-
return@startEkyc
|
|
820
|
-
}
|
|
821
|
-
Log.d(TAG, "✅ startEkycUI() callback - event=${event.name}")
|
|
860
|
+
Log.d(TAG, "🔥 NATIVE CALLBACK (EkycUI): event=${event.name}")
|
|
861
|
+
|
|
822
862
|
val result = data as? SDKEkycResult
|
|
823
|
-
val
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
863
|
+
val response = result?.ekycStateModel
|
|
864
|
+
val jsonStr = data?.customData?.let { Gson().toJson(it) } ?: Gson().toJson(response ?: data)
|
|
865
|
+
|
|
866
|
+
// Helper to create fresh data map to avoid 'Map already consumed' error
|
|
867
|
+
fun createWritableData() = try { jsonObjectToWritableMap(JSONObject(jsonStr)) } catch (e: Exception) { Arguments.createMap() }
|
|
868
|
+
|
|
869
|
+
val statusStr = when (event) {
|
|
870
|
+
EKYCEvent.LOG_SUCCESS -> "log"
|
|
871
|
+
EKYCEvent.SDK_START_SUCCESS -> "started"
|
|
872
|
+
else -> "success"
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Send EVERY event directly to React Native Event Listener (Pass-through)
|
|
876
|
+
sendEvent("onEkycUISuccess", Arguments.createMap().apply {
|
|
877
|
+
putString("status", statusStr)
|
|
878
|
+
putString("event", event.name)
|
|
879
|
+
|
|
880
|
+
if (event == EKYCEvent.SDK_START_SUCCESS && response == null) {
|
|
881
|
+
// Bỏ qua data nếu là SDK_START_SUCCESS (chỉ có event/status) để giống code cũ
|
|
882
|
+
} else {
|
|
883
|
+
putMap("data", createWritableData())
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Retain the convenient root properties cho backward compatibility
|
|
832
887
|
val ekycFiles = result?.ekycStateModel?.eKYCFileModel
|
|
833
888
|
val ekycTransactionId = result?.ekycStateModel?.transactionId ?: ""
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
889
|
+
if (ekycTransactionId.isNotEmpty()) putString("transactionId", ekycTransactionId)
|
|
890
|
+
ekycFiles?.imageFace?.absolutePath?.let { putString("imageFacePath", it) }
|
|
891
|
+
ekycFiles?.imageOcrFront?.absolutePath?.let { putString("imageOcrFrontPath", it) }
|
|
892
|
+
ekycFiles?.imageOcrBack?.absolutePath?.let { putString("imageOcrBackPath", it) }
|
|
893
|
+
})
|
|
894
|
+
|
|
895
|
+
// IMPORTANT: Chỉ resolve promise ở SDK_END_SUCCESS để `await FinosEKYC.startEkycUI()` không bị kết thúc sớm
|
|
896
|
+
if (event == EKYCEvent.SDK_END_SUCCESS) {
|
|
897
|
+
promise.resolve(Arguments.createMap().apply {
|
|
898
|
+
putString("status", "success")
|
|
899
|
+
putString("event", event.name)
|
|
900
|
+
putMap("data", createWritableData())
|
|
901
|
+
|
|
902
|
+
val ekycFiles = result?.ekycStateModel?.eKYCFileModel
|
|
903
|
+
val ekycTransactionId = result?.ekycStateModel?.transactionId ?: ""
|
|
904
|
+
if (ekycTransactionId.isNotEmpty()) putString("transactionId", ekycTransactionId)
|
|
905
|
+
ekycFiles?.imageFace?.absolutePath?.let { putString("imageFacePath", it) }
|
|
906
|
+
ekycFiles?.imageOcrFront?.absolutePath?.let { putString("imageOcrFrontPath", it) }
|
|
907
|
+
ekycFiles?.imageOcrBack?.absolutePath?.let { putString("imageOcrBackPath", it) }
|
|
908
|
+
})
|
|
845
909
|
}
|
|
846
910
|
},
|
|
847
911
|
callbackError = { event, errorResult ->
|
|
@@ -853,14 +917,14 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
853
917
|
putString("customMessage", errorResult.message)
|
|
854
918
|
putString("message", errorResult.message)
|
|
855
919
|
}
|
|
920
|
+
sendEvent("onEkycUIError", errorMap)
|
|
921
|
+
|
|
856
922
|
val promiseErrorMap = Arguments.createMap().apply {
|
|
857
923
|
putString("status", "error")
|
|
858
924
|
putString("event", event.name.toString())
|
|
859
925
|
putString("customCode", errorResult.code)
|
|
860
926
|
putString("customMessage", errorResult.message)
|
|
861
|
-
putString("message", errorResult.message)
|
|
862
927
|
}
|
|
863
|
-
sendEvent("onEkycUIError", errorMap)
|
|
864
928
|
promise.reject(event.name.toString(), errorResult.message, null, promiseErrorMap)
|
|
865
929
|
}
|
|
866
930
|
)
|
|
@@ -895,32 +959,42 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
895
959
|
private fun parseOptionConfig(optionConfigJson: String): OptionConfig {
|
|
896
960
|
return try {
|
|
897
961
|
if (optionConfigJson.isBlank() || optionConfigJson == "{}") {
|
|
898
|
-
|
|
899
|
-
OptionConfig(
|
|
900
|
-
baseUrl = null,
|
|
901
|
-
countMaxRetry = 3,
|
|
902
|
-
language = null
|
|
903
|
-
)
|
|
962
|
+
OptionConfig()
|
|
904
963
|
} else {
|
|
905
|
-
// Simple JSON parsing (in real implementation, use proper JSON library)
|
|
906
964
|
val baseUrl = extractStringValue(optionConfigJson, "baseUrl")
|
|
907
965
|
val countMaxRetry = extractIntValue(optionConfigJson, "countMaxRetry") ?: 3
|
|
908
966
|
val language = extractStringValue(optionConfigJson, "language")
|
|
967
|
+
val networkTimeoutMs = extractLongValue(optionConfigJson, "networkTimeoutMs") ?: 30_000L
|
|
909
968
|
|
|
910
969
|
OptionConfig(
|
|
911
970
|
baseUrl = baseUrl,
|
|
912
971
|
countMaxRetry = countMaxRetry,
|
|
913
|
-
language = language
|
|
972
|
+
language = language,
|
|
973
|
+
networkTimeoutMs = networkTimeoutMs,
|
|
914
974
|
)
|
|
915
975
|
}
|
|
916
976
|
} catch (e: Exception) {
|
|
917
977
|
Log.e(TAG, "Error parsing optionConfig: ${e.message}", e)
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
978
|
+
OptionConfig()
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
private fun parseBackConfirmConfig(json: String?): LivenessBackConfirmConfig? {
|
|
983
|
+
if (json.isNullOrBlank() || json == "{}") return null
|
|
984
|
+
return try {
|
|
985
|
+
LivenessBackConfirmConfig(
|
|
986
|
+
titleVi = extractStringValue(json, "titleVi"),
|
|
987
|
+
titleEn = extractStringValue(json, "titleEn"),
|
|
988
|
+
bodyVi = extractStringValue(json, "bodyVi"),
|
|
989
|
+
bodyEn = extractStringValue(json, "bodyEn"),
|
|
990
|
+
confirmButtonVi = extractStringValue(json, "confirmButtonVi"),
|
|
991
|
+
confirmButtonEn = extractStringValue(json, "confirmButtonEn"),
|
|
992
|
+
cancelButtonVi = extractStringValue(json, "cancelButtonVi"),
|
|
993
|
+
cancelButtonEn = extractStringValue(json, "cancelButtonEn"),
|
|
923
994
|
)
|
|
995
|
+
} catch (e: Exception) {
|
|
996
|
+
Log.e(TAG, "Error parsing backConfirmConfig: ${e.message}", e)
|
|
997
|
+
null
|
|
924
998
|
}
|
|
925
999
|
}
|
|
926
1000
|
|
|
@@ -934,6 +1008,16 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
934
1008
|
}
|
|
935
1009
|
}
|
|
936
1010
|
|
|
1011
|
+
private fun extractLongValue(json: String, key: String): Long? {
|
|
1012
|
+
return try {
|
|
1013
|
+
val pattern = "\"$key\"\\s*:\\s*(-?\\d+)".toRegex()
|
|
1014
|
+
val match = pattern.find(json)
|
|
1015
|
+
match?.groupValues?.get(1)?.toLong()
|
|
1016
|
+
} catch (e: Exception) {
|
|
1017
|
+
null
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
937
1021
|
private fun extractBooleanValue(json: String, key: String): Boolean? {
|
|
938
1022
|
return try {
|
|
939
1023
|
val pattern = "\"$key\"\\s*:\\s*(true|false)".toRegex()
|
|
@@ -1088,6 +1172,17 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
1088
1172
|
}
|
|
1089
1173
|
}
|
|
1090
1174
|
|
|
1175
|
+
/** Extract a nested JSON object `{ ... }` for a given key. Returns the raw JSON string or null. */
|
|
1176
|
+
private fun extractObjectJson(json: String, key: String): String? {
|
|
1177
|
+
return try {
|
|
1178
|
+
val obj = JSONObject(json)
|
|
1179
|
+
val nested = obj.optJSONObject(key) ?: return null
|
|
1180
|
+
nested.toString()
|
|
1181
|
+
} catch (e: Exception) {
|
|
1182
|
+
null
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1091
1186
|
// ==================== SMS OTP Methods ====================
|
|
1092
1187
|
|
|
1093
1188
|
@ReactMethod
|
|
@@ -2115,4 +2210,13 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
2115
2210
|
}
|
|
2116
2211
|
}
|
|
2117
2212
|
|
|
2213
|
+
@ReactMethod
|
|
2214
|
+
fun addListener(eventName: String) {
|
|
2215
|
+
// Keep: Required for RN built-in Event Emitter Calls.
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
@ReactMethod
|
|
2219
|
+
fun removeListeners(count: Int) {
|
|
2220
|
+
// Keep: Required for RN built-in Event Emitter Calls.
|
|
2221
|
+
}
|
|
2118
2222
|
}
|
package/dist/EKYCModule.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { NfcConfig, NfcError } from './src/types/ekycNFCType';
|
|
|
3
3
|
import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, StartEkycUIResult } from './src/types/ekycType';
|
|
4
4
|
import { C06Config } from './src/types/ekycC06Type';
|
|
5
5
|
import { OcrConfig } from './src/types/ekycOCRType';
|
|
6
|
-
import { LivenessConfig, SDKFaceDetectStatus } from './src/types/ekycLivenessType';
|
|
7
|
-
import { FaceServiceConfig } from './src/types/ekycFaceType';
|
|
6
|
+
import { LivenessConfig, SDKFaceDetectStatus, LivenessSuccessEvent } from './src/types/ekycLivenessType';
|
|
7
|
+
import { FaceServiceConfig, FaceCompareSuccessEvent } from './src/types/ekycFaceType';
|
|
8
8
|
import { SmsOtpConfig, SmsOtpResult, SmsOtpError } from './src/types/ekycSmsOtpType';
|
|
9
9
|
import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, AuthorizeInfo, ESignPdfResult, ESignApiResponse } from './src/types/ekycESignType';
|
|
10
10
|
export declare const SDK_VERSION: string;
|
|
@@ -29,8 +29,8 @@ declare class SDKeKYC {
|
|
|
29
29
|
startOcr(config: OcrConfig): Promise<SDKEkycResultStringWithEvent>;
|
|
30
30
|
startNfcScan(config: NfcConfig): Promise<SDKEkycResultStringWithEvent>;
|
|
31
31
|
checkC06(config: C06Config): Promise<SDKEkycResultStringWithEvent>;
|
|
32
|
-
startLiveness(config: LivenessConfig): Promise<
|
|
33
|
-
startFaceCompare(config: FaceServiceConfig): Promise<
|
|
32
|
+
startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent>;
|
|
33
|
+
startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent>;
|
|
34
34
|
onResume(): void;
|
|
35
35
|
onPause(): void;
|
|
36
36
|
onNfcScanStart(callback: (data: SDKEkycResultWithEvent) => void): EmitterSubscription | null;
|
|
@@ -40,9 +40,9 @@ declare class SDKeKYC {
|
|
|
40
40
|
onC06Error(callback: (error: NfcError) => void): EmitterSubscription | null;
|
|
41
41
|
onOcrSuccess(callback: (data: SDKEkycResultStringWithEvent) => void): EmitterSubscription | null;
|
|
42
42
|
onOcrError(callback: (error: any) => void): EmitterSubscription | null;
|
|
43
|
-
onLivenessSuccess(callback: (data:
|
|
43
|
+
onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void): EmitterSubscription | null;
|
|
44
44
|
onLivenessError(callback: (error: any) => void): EmitterSubscription | null;
|
|
45
|
-
onFaceCompareSuccess(callback: (data:
|
|
45
|
+
onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void): EmitterSubscription | null;
|
|
46
46
|
onFaceCompareError(callback: (error: any) => void): EmitterSubscription | null;
|
|
47
47
|
onEkycUISuccess(callback: (data: any) => void): EmitterSubscription | null;
|
|
48
48
|
onEkycUIError(callback: (error: any) => void): EmitterSubscription | null;
|
|
@@ -148,6 +148,16 @@ declare class SDKeKYC {
|
|
|
148
148
|
baseUrl?: string;
|
|
149
149
|
countMaxRetry?: number;
|
|
150
150
|
language?: string;
|
|
151
|
+
networkTimeoutMs?: number;
|
|
152
|
+
switchFrontCamera?: boolean;
|
|
153
|
+
isActiveLiveness?: boolean;
|
|
154
|
+
autoCapture?: boolean;
|
|
155
|
+
forceCaptureTimeout?: number;
|
|
156
|
+
customActions?: string[];
|
|
157
|
+
activeActionCount?: number;
|
|
158
|
+
appIDType?: string;
|
|
159
|
+
isShowBackConfirmation?: boolean;
|
|
160
|
+
backConfirmConfig?: import('./src/types/ekycLivenessType').LivenessBackConfirmConfig;
|
|
151
161
|
}, styleConfig?: {
|
|
152
162
|
textSize?: number;
|
|
153
163
|
textFont?: string;
|
package/dist/EKYCModule.js
CHANGED
|
@@ -213,7 +213,10 @@ class SDKeKYC {
|
|
|
213
213
|
try {
|
|
214
214
|
// Convert SDKFaceDetectStatus enum array to string array
|
|
215
215
|
const customActionsStrings = (_b = (_a = config.customActions) === null || _a === void 0 ? void 0 : _a.map(action => String(action))) !== null && _b !== void 0 ? _b : undefined;
|
|
216
|
-
|
|
216
|
+
const backConfirmConfigJson = config.backConfirmConfig
|
|
217
|
+
? JSON.stringify(config.backConfirmConfig)
|
|
218
|
+
: undefined;
|
|
219
|
+
return await nativeModule.startLiveness(config.appKey, config.selfieImage, (_c = config.transactionId) !== null && _c !== void 0 ? _c : '', config.isActiveLiveness, config.isShowCameraFont, customActionsStrings, config.activeActionCount, config.forceCaptureTimeout, config.isActiveLivenessColor, config.isShowBackConfirmation, backConfirmConfigJson);
|
|
217
220
|
}
|
|
218
221
|
catch (error) {
|
|
219
222
|
console.error('Liveness Error:', error);
|
package/dist/index.d.ts
CHANGED
|
@@ -9,9 +9,9 @@ export { SDKFlowType, SDK_FLOW_OPTIONS, flowToStrings } from './src/types/ekycFl
|
|
|
9
9
|
export type { NfcConfig, NFCData, NfcInfo } from './src/types/ekycNFCType';
|
|
10
10
|
export type { C06Config } from './src/types/ekycC06Type';
|
|
11
11
|
export type { OcrConfig } from './src/types/ekycOCRType';
|
|
12
|
-
export type { LivenessConfig } from './src/types/ekycLivenessType';
|
|
12
|
+
export type { LivenessConfig, LivenessSuccessEvent, CheckLivenessResponse, Metadata, LivenessCheckResult, Details, QualityChecks, QualityCheck, AgeRange, LiveFace } from './src/types/ekycLivenessType';
|
|
13
13
|
export { SDKFaceDetectStatus, SDK_LIVENESS_ACTIONS, customActionsToStrings } from './src/types/ekycLivenessType';
|
|
14
|
-
export type { FaceServiceConfig } from './src/types/ekycFaceType';
|
|
14
|
+
export type { FaceServiceConfig, FaceCompareSuccessEvent, CheckFaceResponse, FaceVerifyResult } from './src/types/ekycFaceType';
|
|
15
15
|
export { EKYCEvent, EKYCErrorEvent } from './src/types/EKYCEvent';
|
|
16
16
|
export type { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, StartEkycUIResult, EKYCError } from './src/types/ekycType';
|
|
17
17
|
export { getEkycError, AppIDType } from './src/types/ekycType';
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finos_sdk/sdk-ekyc",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"description": "React Native SDK for eKYC - Vietnamese CCCD NFC reading, OCR, Liveness detection, Face matching, and C06, eSign, SmsOTP residence verification",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -3,8 +3,8 @@ import { NfcConfig } from '../types/ekycNFCType';
|
|
|
3
3
|
import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, EKYCError, AppIDType } from '../types/ekycType';
|
|
4
4
|
import { C06Config } from '../types/ekycC06Type';
|
|
5
5
|
import { OcrConfig } from '../types/ekycOCRType';
|
|
6
|
-
import { LivenessConfig, SDKFaceDetectStatus } from '../types/ekycLivenessType';
|
|
7
|
-
import { FaceServiceConfig } from '../types/ekycFaceType';
|
|
6
|
+
import { LivenessConfig, SDKFaceDetectStatus, LivenessSuccessEvent } from '../types/ekycLivenessType';
|
|
7
|
+
import { FaceServiceConfig, FaceCompareSuccessEvent } from '../types/ekycFaceType';
|
|
8
8
|
import { SmsOtpConfig, SmsOtpResult } from '../types/ekycSmsOtpType';
|
|
9
9
|
import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType';
|
|
10
10
|
import { SDKFlowType } from '../types/ekycFlowType';
|
|
@@ -77,12 +77,12 @@ export declare class FinosEKYCModule {
|
|
|
77
77
|
* @param config.activeActionCount - Number of random actions (1-10), only used when customActions is null (default: 2)
|
|
78
78
|
* @param config.switchFrontCamera - Use front camera (default: false)
|
|
79
79
|
*/
|
|
80
|
-
startLiveness(config: LivenessConfig): Promise<
|
|
80
|
+
startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent>;
|
|
81
81
|
/**
|
|
82
82
|
* Start face comparison (Face Service)
|
|
83
83
|
* @param config Face service configuration
|
|
84
84
|
*/
|
|
85
|
-
startFaceCompare(config: FaceServiceConfig): Promise<
|
|
85
|
+
startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent>;
|
|
86
86
|
/**
|
|
87
87
|
* Extract NFC data for C06 verification
|
|
88
88
|
* @param nfcResultJson NFC result JSON string
|
|
@@ -134,7 +134,7 @@ export declare class FinosEKYCModule {
|
|
|
134
134
|
/**
|
|
135
135
|
* Listen for liveness success events
|
|
136
136
|
*/
|
|
137
|
-
onLivenessSuccess(callback: (data:
|
|
137
|
+
onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void): import("react-native").EmitterSubscription | null;
|
|
138
138
|
/**
|
|
139
139
|
* Listen for liveness error events
|
|
140
140
|
*/
|
|
@@ -143,7 +143,7 @@ export declare class FinosEKYCModule {
|
|
|
143
143
|
/**
|
|
144
144
|
* Listen for face compare success events
|
|
145
145
|
*/
|
|
146
|
-
onFaceCompareSuccess(callback: (data:
|
|
146
|
+
onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void): import("react-native").EmitterSubscription | null;
|
|
147
147
|
/** Payload là EKYCError: { event, code, message }. */
|
|
148
148
|
onFaceCompareError(callback: (error: EKYCError) => void): import("react-native").EmitterSubscription | null;
|
|
149
149
|
/**
|
|
@@ -308,6 +308,8 @@ export declare class FinosEKYCModule {
|
|
|
308
308
|
baseUrl?: string;
|
|
309
309
|
countMaxRetry?: number;
|
|
310
310
|
language?: string;
|
|
311
|
+
/** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */
|
|
312
|
+
networkTimeoutMs?: number;
|
|
311
313
|
switchFrontCamera?: boolean;
|
|
312
314
|
/** LivenessConfig – SDKeKYCActivity (157-169) */
|
|
313
315
|
isActiveLiveness?: boolean;
|
|
@@ -318,6 +320,10 @@ export declare class FinosEKYCModule {
|
|
|
318
320
|
activeActionCount?: number;
|
|
319
321
|
/** AppIDType: NONE | HD_BANK | VIKKI (default: AppIDType.NONE) */
|
|
320
322
|
appIDType?: AppIDType;
|
|
323
|
+
/** Show confirmation bottom sheet when user presses back in liveness screen (v1.4.6+) */
|
|
324
|
+
isShowBackConfirmation?: boolean;
|
|
325
|
+
/** Text config for the back confirmation bottom sheet (v1.4.6+) */
|
|
326
|
+
backConfirmConfig?: import('../types/ekycLivenessType').LivenessBackConfirmConfig;
|
|
321
327
|
}, styleConfig?: {
|
|
322
328
|
textSize?: number;
|
|
323
329
|
textFont?: string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SDK_VERSION, SDK_NAME } from '../../EKYCModule';
|
|
2
2
|
import { SmsOtpConfig, SmsOtpResult } from '../types/ekycSmsOtpType';
|
|
3
3
|
import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, AuthorizeInfo, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType';
|
|
4
|
-
import { LivenessConfig } from '../types/ekycLivenessType';
|
|
5
|
-
import { FaceServiceConfig } from '../types/ekycFaceType';
|
|
6
|
-
import {
|
|
4
|
+
import { LivenessConfig, LivenessSuccessEvent } from '../types/ekycLivenessType';
|
|
5
|
+
import { FaceServiceConfig, FaceCompareSuccessEvent } from '../types/ekycFaceType';
|
|
6
|
+
import { EKYCError } from '../types/ekycType';
|
|
7
7
|
/**
|
|
8
8
|
* Finos eSign SDK Module
|
|
9
9
|
*
|
|
@@ -255,16 +255,16 @@ export declare class FinosESignModule {
|
|
|
255
255
|
* @param config.activeActionCount - Number of random actions (1-10), only used when customActions is null (default: 2)
|
|
256
256
|
* @param config.switchFrontCamera - Use front camera (default: false)
|
|
257
257
|
*/
|
|
258
|
-
startLiveness(config: LivenessConfig): Promise<
|
|
259
|
-
onLivenessSuccess(callback: (data:
|
|
258
|
+
startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent>;
|
|
259
|
+
onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void): import("react-native").EmitterSubscription | null;
|
|
260
260
|
/** Payload là EKYCError: { event, code, message }. */
|
|
261
261
|
onLivenessError(callback: (error: EKYCError) => void): import("react-native").EmitterSubscription | null;
|
|
262
262
|
/**
|
|
263
263
|
* Face matching (Face Service)
|
|
264
264
|
* @param config Face service configuration
|
|
265
265
|
*/
|
|
266
|
-
startFaceCompare(config: FaceServiceConfig): Promise<
|
|
267
|
-
onFaceCompareSuccess(callback: (data:
|
|
266
|
+
startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent>;
|
|
267
|
+
onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void): import("react-native").EmitterSubscription | null;
|
|
268
268
|
/** Payload là EKYCError: { event, code, message }. */
|
|
269
269
|
onFaceCompareError(callback: (error: EKYCError) => void): import("react-native").EmitterSubscription | null;
|
|
270
270
|
/**
|
|
@@ -286,6 +286,8 @@ export declare class FinosESignModule {
|
|
|
286
286
|
baseUrl?: string;
|
|
287
287
|
countMaxRetry?: number;
|
|
288
288
|
language?: string;
|
|
289
|
+
/** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */
|
|
290
|
+
networkTimeoutMs?: number;
|
|
289
291
|
switchFrontCamera?: boolean;
|
|
290
292
|
}, styleConfig?: {
|
|
291
293
|
textSize?: number;
|
|
@@ -12,6 +12,15 @@ export interface FaceVerifyResult {
|
|
|
12
12
|
matchScore?: number;
|
|
13
13
|
toBeReviewed?: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Payload emitted by onFaceCompareSuccess listener.
|
|
17
|
+
* event = "FACE_SUCCESS" | "LOG_SUCCESS"
|
|
18
|
+
* data = CheckFaceResponse object (bridge serialises individual fields, not a JSON string)
|
|
19
|
+
*/
|
|
20
|
+
export interface FaceCompareSuccessEvent {
|
|
21
|
+
event: string;
|
|
22
|
+
data: CheckFaceResponse;
|
|
23
|
+
}
|
|
15
24
|
export interface FaceServiceConfig {
|
|
16
25
|
/**
|
|
17
26
|
* Backward/forward compatible:
|
|
@@ -20,6 +20,17 @@ export declare enum SDKFaceDetectStatus {
|
|
|
20
20
|
export declare const SDK_LIVENESS_ACTIONS: readonly SDKFaceDetectStatus[];
|
|
21
21
|
/** Chuyển customActions enum[] sang string[] khi gửi xuống native. */
|
|
22
22
|
export declare function customActionsToStrings(actions: SDKFaceDetectStatus[]): string[];
|
|
23
|
+
/** Text config for the back-press confirmation bottom sheet (v1.4.6+). All fields optional – fallback to built-in strings. */
|
|
24
|
+
export interface LivenessBackConfirmConfig {
|
|
25
|
+
titleVi?: string;
|
|
26
|
+
titleEn?: string;
|
|
27
|
+
bodyVi?: string;
|
|
28
|
+
bodyEn?: string;
|
|
29
|
+
confirmButtonVi?: string;
|
|
30
|
+
confirmButtonEn?: string;
|
|
31
|
+
cancelButtonVi?: string;
|
|
32
|
+
cancelButtonEn?: string;
|
|
33
|
+
}
|
|
23
34
|
export interface LivenessConfig {
|
|
24
35
|
appKey: string;
|
|
25
36
|
transactionId?: string;
|
|
@@ -30,6 +41,8 @@ export interface LivenessConfig {
|
|
|
30
41
|
customActions?: SDKFaceDetectStatus[];
|
|
31
42
|
activeActionCount?: number;
|
|
32
43
|
forceCaptureTimeout?: number;
|
|
44
|
+
isShowBackConfirmation?: boolean;
|
|
45
|
+
backConfirmConfig?: LivenessBackConfirmConfig;
|
|
33
46
|
}
|
|
34
47
|
export interface CheckLivenessResponse {
|
|
35
48
|
metadata?: Metadata;
|
|
@@ -84,3 +97,12 @@ export interface QualityCheck {
|
|
|
84
97
|
}
|
|
85
98
|
/** Liveness error – dùng chung EKYCError (event, code, message). */
|
|
86
99
|
export type LivenessError = import('./ekycType').EKYCError;
|
|
100
|
+
/**
|
|
101
|
+
* Payload emitted by onLivenessSuccess listener.
|
|
102
|
+
* event = "LIVENESS_SUCCESS" | "LOG_SUCCESS"
|
|
103
|
+
* data = CheckLivenessResponse object (bridge serialises fields directly, not a JSON string)
|
|
104
|
+
*/
|
|
105
|
+
export interface LivenessSuccessEvent {
|
|
106
|
+
event: string;
|
|
107
|
+
data: CheckLivenessResponse;
|
|
108
|
+
}
|
package/ios/EKYCModule.swift
CHANGED
|
@@ -96,14 +96,14 @@ class EKYCModule: RCTEventEmitter {
|
|
|
96
96
|
callbackSuccess: { event, data in
|
|
97
97
|
switch event {
|
|
98
98
|
case .SCAN_NFC_START:
|
|
99
|
-
self?.emit("onNfcScanStart", body: ["event": event.description, "data":
|
|
99
|
+
self?.emit("onNfcScanStart", body: ["event": event.description, "data": (data as? SDKEkycResult)?.nfcResponse ?? "{}"])
|
|
100
100
|
case .SCAN_NFC_SUCCESS:
|
|
101
101
|
let nfcResponse = (data as? SDKEkycResult)?.nfcResponse ?? ""
|
|
102
102
|
let body: [String: Any] = ["event": event.description, "data": nfcResponse]
|
|
103
103
|
self?.emit("onNfcScanSuccess", body: body)
|
|
104
104
|
resolve(body)
|
|
105
105
|
default:
|
|
106
|
-
self?.emit("onNfcEvent", body: ["event": event.description, "data":
|
|
106
|
+
self?.emit("onNfcEvent", body: ["event": event.description, "data": (data as? SDKEkycResult)?.nfcResponse ?? "{}"])
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
109
|
callbackError: { event, errorResult in
|
|
@@ -123,7 +123,8 @@ class EKYCModule: RCTEventEmitter {
|
|
|
123
123
|
let ekycConfig = EKYCConfigSDK(appKey: AppKeyConfig(appKey: appKey), c06Config: c06Config)
|
|
124
124
|
SdkEkycC06.startEkyc(ekycConfigSDK: ekycConfig,
|
|
125
125
|
callbackSuccess: { [weak self] event, data in
|
|
126
|
-
let
|
|
126
|
+
let c06Response = (data as? SDKEkycResult)?.checkC06Response?.description ?? "{}"
|
|
127
|
+
let body: [String: Any] = ["event": event.description, "data": c06Response]
|
|
127
128
|
self?.emit("onC06Success", body: body)
|
|
128
129
|
resolve(body)
|
|
129
130
|
},
|
|
@@ -183,8 +184,16 @@ class EKYCModule: RCTEventEmitter {
|
|
|
183
184
|
let ekycConfig = EKYCConfigSDK(appKey: AppKeyConfig(appKey: appKey), sdkType: .liveness, livenessConfig: livenessConfig)
|
|
184
185
|
SdkEkycLiveness.startEkyc(from: vc, ekycConfigSDK: ekycConfig,
|
|
185
186
|
callbackSuccess: { event, data in
|
|
186
|
-
let livenessResponse = (data as? SDKEkycResult)?.checkLivenessResponse
|
|
187
|
-
let
|
|
187
|
+
let livenessResponse = (data as? SDKEkycResult)?.checkLivenessResponse
|
|
188
|
+
let livenessDict: Any
|
|
189
|
+
if let resp = livenessResponse,
|
|
190
|
+
let encoded = try? JSONEncoder().encode(resp),
|
|
191
|
+
let dict = try? JSONSerialization.jsonObject(with: encoded) as? [String: Any] {
|
|
192
|
+
livenessDict = dict
|
|
193
|
+
} else {
|
|
194
|
+
livenessDict = [String: Any]()
|
|
195
|
+
}
|
|
196
|
+
let body: [String: Any] = ["event": event.description, "data": livenessDict]
|
|
188
197
|
self?.emit("onLivenessSuccess", body: body)
|
|
189
198
|
resolve(body)
|
|
190
199
|
},
|
|
@@ -210,7 +219,17 @@ class EKYCModule: RCTEventEmitter {
|
|
|
210
219
|
DispatchQueue.main.async { [weak self] in
|
|
211
220
|
SdkEkycFaceService.startEkyc(ekycConfigSDK: ekycConfig,
|
|
212
221
|
callbackSuccess: { event, data in
|
|
213
|
-
let
|
|
222
|
+
let faceResponse = (data as? SDKEkycResult)?.checkFaceResponse
|
|
223
|
+
var faceDict: [String: Any] = [:]
|
|
224
|
+
if let v = faceResponse?.requestId { faceDict["requestId"] = v }
|
|
225
|
+
if let r = faceResponse?.result {
|
|
226
|
+
faceDict["result"] = ["conf": r.conf, "match": r.match, "matchScore": r.matchScore, "toBeReviewed": r.toBeReviewed]
|
|
227
|
+
}
|
|
228
|
+
if let v = faceResponse?.status { faceDict["status"] = v }
|
|
229
|
+
if let v = faceResponse?.statusCode { faceDict["statusCode"] = v }
|
|
230
|
+
if let v = faceResponse?.error { faceDict["error"] = v }
|
|
231
|
+
if let v = faceResponse?.type { faceDict["type"] = v }
|
|
232
|
+
let body: [String: Any] = ["event": event.description, "data": faceDict]
|
|
214
233
|
self?.emit("onFaceCompareSuccess", body: body)
|
|
215
234
|
resolve(body)
|
|
216
235
|
},
|
|
@@ -291,7 +310,16 @@ class EKYCModule: RCTEventEmitter {
|
|
|
291
310
|
return
|
|
292
311
|
}
|
|
293
312
|
let files = result?.ekycStateModel?.eKYCFileModel
|
|
294
|
-
var
|
|
313
|
+
var dataDict: [String: Any] = [:]
|
|
314
|
+
if let v = result?.nfcResponse { dataDict["nfcResponse"] = v }
|
|
315
|
+
if let v = result?.checkC06Response?.description { dataDict["checkC06Response"] = v }
|
|
316
|
+
if let v = result?.ocrResponse?.description { dataDict["ocrResponse"] = v }
|
|
317
|
+
if let v = result?.checkLivenessResponse?.description { dataDict["checkLivenessResponse"] = v }
|
|
318
|
+
if let v = result?.checkFaceResponse?.description { dataDict["checkFaceResponse"] = v }
|
|
319
|
+
if let v = result?.customData { dataDict["customData"] = v }
|
|
320
|
+
let dataJson = (try? JSONSerialization.data(withJSONObject: dataDict))
|
|
321
|
+
.flatMap { String(data: $0, encoding: .utf8) } ?? "{}"
|
|
322
|
+
var body: [String: Any] = ["status": "success", "event": event.description, "data": dataJson, "transactionId": result?.ekycStateModel?.transactionId ?? ""]
|
|
295
323
|
if let facePath = files?.imageFace { body["imageFacePath"] = facePath.path }
|
|
296
324
|
if let frontPath = files?.imageOcrFront { body["imageOcrFrontPath"] = frontPath.path }
|
|
297
325
|
if let backPath = files?.imageOcrBack { body["imageOcrBackPath"] = backPath.path }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finos_sdk/sdk-ekyc",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"description": "React Native SDK for eKYC - Vietnamese CCCD NFC reading, OCR, Liveness detection, Face matching, and C06, eSign, SmsOTP residence verification",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -4,8 +4,8 @@ import { NfcConfig, NfcError } from '../types/ekycNFCType';
|
|
|
4
4
|
import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, EKYCError, getEkycError, AppIDType } from '../types/ekycType';
|
|
5
5
|
import { C06Config } from '../types/ekycC06Type';
|
|
6
6
|
import { OcrConfig, OcrError } from '../types/ekycOCRType';
|
|
7
|
-
import { LivenessConfig, SDKFaceDetectStatus, LivenessError } from '../types/ekycLivenessType';
|
|
8
|
-
import { FaceServiceConfig, FaceCompareError } from '../types/ekycFaceType';
|
|
7
|
+
import { LivenessConfig, SDKFaceDetectStatus, LivenessError, LivenessSuccessEvent } from '../types/ekycLivenessType';
|
|
8
|
+
import { FaceServiceConfig, FaceCompareError, FaceCompareSuccessEvent } from '../types/ekycFaceType';
|
|
9
9
|
import { SmsOtpConfig, SmsOtpResult, SmsOtpError } from '../types/ekycSmsOtpType';
|
|
10
10
|
import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, ESignAuthenticateResult, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType';
|
|
11
11
|
import { SDKFlowType, flowToStrings } from '../types/ekycFlowType';
|
|
@@ -194,7 +194,7 @@ export class FinosEKYCModule {
|
|
|
194
194
|
* @param config.activeActionCount - Number of random actions (1-10), only used when customActions is null (default: 2)
|
|
195
195
|
* @param config.switchFrontCamera - Use front camera (default: false)
|
|
196
196
|
*/
|
|
197
|
-
public async startLiveness(config: LivenessConfig): Promise<
|
|
197
|
+
public async startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent> {
|
|
198
198
|
this.validateSDKReady();
|
|
199
199
|
|
|
200
200
|
try {
|
|
@@ -221,7 +221,7 @@ export class FinosEKYCModule {
|
|
|
221
221
|
* Start face comparison (Face Service)
|
|
222
222
|
* @param config Face service configuration
|
|
223
223
|
*/
|
|
224
|
-
public async startFaceCompare(config: FaceServiceConfig): Promise<
|
|
224
|
+
public async startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent> {
|
|
225
225
|
this.validateSDKReady();
|
|
226
226
|
|
|
227
227
|
const normalized: FaceServiceConfig = {
|
|
@@ -337,7 +337,7 @@ export class FinosEKYCModule {
|
|
|
337
337
|
/**
|
|
338
338
|
* Listen for liveness success events
|
|
339
339
|
*/
|
|
340
|
-
public onLivenessSuccess(callback: (data:
|
|
340
|
+
public onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void) {
|
|
341
341
|
return this.sdk.onLivenessSuccess(callback);
|
|
342
342
|
}
|
|
343
343
|
|
|
@@ -352,7 +352,7 @@ export class FinosEKYCModule {
|
|
|
352
352
|
/**
|
|
353
353
|
* Listen for face compare success events
|
|
354
354
|
*/
|
|
355
|
-
public onFaceCompareSuccess(callback: (data:
|
|
355
|
+
public onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void) {
|
|
356
356
|
return this.sdk.onFaceCompareSuccess(callback);
|
|
357
357
|
}
|
|
358
358
|
|
|
@@ -764,6 +764,8 @@ export class FinosEKYCModule {
|
|
|
764
764
|
baseUrl?: string;
|
|
765
765
|
countMaxRetry?: number;
|
|
766
766
|
language?: string;
|
|
767
|
+
/** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */
|
|
768
|
+
networkTimeoutMs?: number;
|
|
767
769
|
switchFrontCamera?: boolean;
|
|
768
770
|
/** LivenessConfig – SDKeKYCActivity (157-169) */
|
|
769
771
|
isActiveLiveness?: boolean;
|
|
@@ -774,6 +776,10 @@ export class FinosEKYCModule {
|
|
|
774
776
|
activeActionCount?: number;
|
|
775
777
|
/** AppIDType: NONE | HD_BANK | VIKKI (default: AppIDType.NONE) */
|
|
776
778
|
appIDType?: AppIDType;
|
|
779
|
+
/** Show confirmation bottom sheet when user presses back in liveness screen (v1.4.6+) */
|
|
780
|
+
isShowBackConfirmation?: boolean;
|
|
781
|
+
/** Text config for the back confirmation bottom sheet (v1.4.6+) */
|
|
782
|
+
backConfirmConfig?: import('../types/ekycLivenessType').LivenessBackConfirmConfig;
|
|
777
783
|
},
|
|
778
784
|
styleConfig?: {
|
|
779
785
|
textSize?: number;
|
|
@@ -2,8 +2,8 @@ import { Platform } from 'react-native';
|
|
|
2
2
|
import sdkEKYC, { SDKeKYC, SDK_VERSION, SDK_NAME } from '../../EKYCModule';
|
|
3
3
|
import { SmsOtpConfig, SmsOtpResult, SmsOtpError } from '../types/ekycSmsOtpType';
|
|
4
4
|
import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, ESignAuthenticateResult, AuthorizeInfo, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType';
|
|
5
|
-
import { LivenessConfig, LivenessError } from '../types/ekycLivenessType';
|
|
6
|
-
import { FaceServiceConfig, FaceCompareError } from '../types/ekycFaceType';
|
|
5
|
+
import { LivenessConfig, LivenessError, LivenessSuccessEvent } from '../types/ekycLivenessType';
|
|
6
|
+
import { FaceServiceConfig, FaceCompareError, FaceCompareSuccessEvent } from '../types/ekycFaceType';
|
|
7
7
|
import { SDKEkycResultStringWithEvent, SDKEkycResultWithEvent, EKYCError, getEkycError } from '../types/ekycType';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -565,14 +565,14 @@ export class FinosESignModule {
|
|
|
565
565
|
* @param config.activeActionCount - Number of random actions (1-10), only used when customActions is null (default: 2)
|
|
566
566
|
* @param config.switchFrontCamera - Use front camera (default: false)
|
|
567
567
|
*/
|
|
568
|
-
public async startLiveness(config: LivenessConfig): Promise<
|
|
568
|
+
public async startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent> {
|
|
569
569
|
this.validateSDKReady();
|
|
570
570
|
// Pass through to SDK - error handling is done in EKYCModule.ts
|
|
571
571
|
return await this.sdk.startLiveness(config);
|
|
572
572
|
}
|
|
573
573
|
|
|
574
574
|
// Liveness Event Listeners
|
|
575
|
-
public onLivenessSuccess(callback: (data:
|
|
575
|
+
public onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void) {
|
|
576
576
|
return this.sdk.onLivenessSuccess(callback);
|
|
577
577
|
}
|
|
578
578
|
|
|
@@ -587,7 +587,7 @@ export class FinosESignModule {
|
|
|
587
587
|
* Face matching (Face Service)
|
|
588
588
|
* @param config Face service configuration
|
|
589
589
|
*/
|
|
590
|
-
public async startFaceCompare(config: FaceServiceConfig): Promise<
|
|
590
|
+
public async startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent> {
|
|
591
591
|
this.validateSDKReady();
|
|
592
592
|
const normalized: FaceServiceConfig = {
|
|
593
593
|
...config,
|
|
@@ -598,7 +598,7 @@ export class FinosESignModule {
|
|
|
598
598
|
}
|
|
599
599
|
|
|
600
600
|
// Face Compare Event Listeners
|
|
601
|
-
public onFaceCompareSuccess(callback: (data:
|
|
601
|
+
public onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void) {
|
|
602
602
|
return this.sdk.onFaceCompareSuccess(callback);
|
|
603
603
|
}
|
|
604
604
|
|
|
@@ -634,6 +634,8 @@ export class FinosESignModule {
|
|
|
634
634
|
baseUrl?: string;
|
|
635
635
|
countMaxRetry?: number;
|
|
636
636
|
language?: string;
|
|
637
|
+
/** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */
|
|
638
|
+
networkTimeoutMs?: number;
|
|
637
639
|
switchFrontCamera?: boolean;
|
|
638
640
|
},
|
|
639
641
|
styleConfig?: {
|
|
@@ -14,6 +14,16 @@ export interface FaceVerifyResult {
|
|
|
14
14
|
toBeReviewed?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Payload emitted by onFaceCompareSuccess listener.
|
|
19
|
+
* event = "FACE_SUCCESS" | "LOG_SUCCESS"
|
|
20
|
+
* data = CheckFaceResponse object (bridge serialises individual fields, not a JSON string)
|
|
21
|
+
*/
|
|
22
|
+
export interface FaceCompareSuccessEvent {
|
|
23
|
+
event: string;
|
|
24
|
+
data: CheckFaceResponse;
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
export interface FaceServiceConfig {
|
|
18
28
|
/**
|
|
19
29
|
* Backward/forward compatible:
|
|
@@ -38,11 +38,23 @@ export function customActionsToStrings(actions: SDKFaceDetectStatus[]): string[]
|
|
|
38
38
|
return actions.map(a => a as string);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/** Text config for the back-press confirmation bottom sheet (v1.4.6+). All fields optional – fallback to built-in strings. */
|
|
42
|
+
export interface LivenessBackConfirmConfig {
|
|
43
|
+
titleVi?: string;
|
|
44
|
+
titleEn?: string;
|
|
45
|
+
bodyVi?: string;
|
|
46
|
+
bodyEn?: string;
|
|
47
|
+
confirmButtonVi?: string;
|
|
48
|
+
confirmButtonEn?: string;
|
|
49
|
+
cancelButtonVi?: string;
|
|
50
|
+
cancelButtonEn?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
41
53
|
export interface LivenessConfig {
|
|
42
54
|
appKey: string;
|
|
43
55
|
transactionId?: string;
|
|
44
56
|
// old fields usingRandomAction and isStraight removed in 1.3.1
|
|
45
|
-
// usingRandomAction: boolean;
|
|
57
|
+
// usingRandomAction: boolean;
|
|
46
58
|
// isStraight: boolean;
|
|
47
59
|
selfieImage: string;
|
|
48
60
|
// New fields for version 1.3.0
|
|
@@ -52,6 +64,9 @@ export interface LivenessConfig {
|
|
|
52
64
|
customActions?: SDKFaceDetectStatus[];
|
|
53
65
|
activeActionCount?: number;
|
|
54
66
|
forceCaptureTimeout?: number;
|
|
67
|
+
// New fields for version 1.4.6
|
|
68
|
+
isShowBackConfirmation?: boolean;
|
|
69
|
+
backConfirmConfig?: LivenessBackConfirmConfig;
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
export interface CheckLivenessResponse {
|
|
@@ -116,3 +131,13 @@ export interface QualityCheck {
|
|
|
116
131
|
|
|
117
132
|
/** Liveness error – dùng chung EKYCError (event, code, message). */
|
|
118
133
|
export type LivenessError = import('./ekycType').EKYCError;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Payload emitted by onLivenessSuccess listener.
|
|
137
|
+
* event = "LIVENESS_SUCCESS" | "LOG_SUCCESS"
|
|
138
|
+
* data = CheckLivenessResponse object (bridge serialises fields directly, not a JSON string)
|
|
139
|
+
*/
|
|
140
|
+
export interface LivenessSuccessEvent {
|
|
141
|
+
event: string;
|
|
142
|
+
data: CheckLivenessResponse;
|
|
143
|
+
}
|