@finos_sdk/sdk-ekyc 1.3.2 → 1.3.4
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/android/build.gradle +1 -1
- package/android/src/main/java/finos/sdk/ekyc/EKYCModule.kt +281 -96
- package/dist/EKYCModule.d.ts +17 -2
- package/dist/EKYCModule.js +41 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8 -1
- package/dist/package.json +1 -1
- package/dist/src/modules/FinosEKYCModule.d.ts +13 -5
- package/dist/src/modules/FinosEKYCModule.js +10 -8
- package/dist/src/modules/FinosESignModule.d.ts +13 -1
- package/dist/src/modules/FinosESignModule.js +23 -4
- package/dist/src/types/ekycFaceType.d.ts +7 -0
- package/dist/src/types/ekycFlowType.d.ts +17 -0
- package/dist/src/types/ekycFlowType.js +28 -0
- package/dist/src/types/ekycLivenessType.d.ts +16 -0
- package/dist/src/types/ekycLivenessType.js +32 -1
- package/dist/src/types/ekycType.d.ts +30 -0
- package/package.json +1 -1
- package/src/modules/FinosEKYCModule.ts +23 -10
- package/src/modules/FinosESignModule.ts +32 -5
- package/src/modules/README.md +1 -1
- package/src/types/ekycFaceType.ts +7 -0
- package/src/types/ekycFlowType.ts +25 -0
- package/src/types/ekycLivenessType.ts +33 -1
- package/src/types/ekycType.ts +31 -0
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.3.
|
|
68
|
+
def sdkVersion = "1.3.3.1"
|
|
69
69
|
implementation("finos.sdk.ekyc:ekyc:$sdkVersion")
|
|
70
70
|
implementation("finos.sdk.ekyc:ekycui:$sdkVersion")
|
|
71
71
|
implementation("finos.sdk.ekyc:nfc:$sdkVersion")
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
package finos.sdk.ekyc
|
|
2
2
|
|
|
3
3
|
import android.R
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
4
7
|
import android.util.Base64
|
|
5
8
|
import android.util.Log
|
|
6
9
|
import com.facebook.react.bridge.Arguments
|
|
@@ -38,9 +41,12 @@ import finos.sdk.smsotp.SdkSmsOtpService
|
|
|
38
41
|
import finos.sdk.core.model.sdk.config.SmsOtpConfig
|
|
39
42
|
import vn.softdreams.easyca.sdk.SdkeSign
|
|
40
43
|
|
|
44
|
+
import org.json.JSONArray
|
|
41
45
|
import org.json.JSONObject
|
|
42
46
|
import java.io.File
|
|
43
47
|
import java.io.FileOutputStream
|
|
48
|
+
import java.io.FileInputStream
|
|
49
|
+
import java.io.InputStream
|
|
44
50
|
import java.util.Date
|
|
45
51
|
|
|
46
52
|
@ReactModule(name = EKYCModule.NAME)
|
|
@@ -102,8 +108,20 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
102
108
|
promise.resolve(true)
|
|
103
109
|
}
|
|
104
110
|
} catch (e: Exception) {
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
val msg = e.message?.lowercase() ?: ""
|
|
112
|
+
// SDK đã được khởi tạo (Koin/DI already started) → coi là thành công, cho icon pass xanh / text xanh
|
|
113
|
+
if (msg.contains("already been started") || msg.contains("already started") || msg.contains("already initialized")) {
|
|
114
|
+
Log.d(TAG, "✅ initSdkEkyc() – SDK đã khởi tạo, trả success (không coi là lỗi)")
|
|
115
|
+
val params = Arguments.createMap().apply {
|
|
116
|
+
putString("status", "success")
|
|
117
|
+
putString("message", "SDK EKYC already initialized")
|
|
118
|
+
}
|
|
119
|
+
sendEvent("EKYCInitEvent", params)
|
|
120
|
+
promise.resolve(true)
|
|
121
|
+
} else {
|
|
122
|
+
Log.e(TAG, "❌ initSdkEkyc() failed: ${e.message}", e)
|
|
123
|
+
promise.reject("INIT_ERROR", "Failed to initialize SDK EKYC: ${e.message}")
|
|
124
|
+
}
|
|
107
125
|
}
|
|
108
126
|
}
|
|
109
127
|
|
|
@@ -433,48 +451,58 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
433
451
|
) {
|
|
434
452
|
Log.d(TAG, "▶️ startFaceCompare() called")
|
|
435
453
|
try {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
454
|
+
// React Native có thể gọi khi currentActivity = null (background / app state).
|
|
455
|
+
// FaceService không cần Activity, nên không block ở đây.
|
|
456
|
+
val selfieFile = resolveImageInputToFile(selfieImage, "selfie")
|
|
457
|
+
val idImageFile = resolveImageInputToFile(idImage, "id")
|
|
458
|
+
|
|
459
|
+
if (!selfieFile.exists() || selfieFile.length() <= 0L) {
|
|
460
|
+
promise.reject("SELFIE_IMAGE_INVALID", "Selfie image is invalid or not found")
|
|
440
461
|
return
|
|
441
462
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
promise.reject("FILE_NOT_FOUND", "Image file does not exist")
|
|
463
|
+
if (!idImageFile.exists() || idImageFile.length() <= 0L) {
|
|
464
|
+
promise.reject("ID_IMAGE_INVALID", "ID image is invalid or not found")
|
|
445
465
|
return
|
|
446
466
|
}
|
|
447
|
-
val idImageFile = base64ToImageFile(idImage)
|
|
448
467
|
|
|
449
468
|
val faceServiceConfig =
|
|
450
469
|
FaceServiceConfig(
|
|
451
470
|
transactionId = transactionId,
|
|
452
|
-
selfieImage =
|
|
471
|
+
selfieImage = selfieFile,
|
|
453
472
|
idImage = idImageFile
|
|
454
473
|
)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
val (eventMap, promiseMap) = createSeparateMaps { map ->
|
|
461
|
-
map.putString("event", event.name.toString())
|
|
462
|
-
map.putString("data", data.toString())
|
|
463
|
-
}
|
|
464
|
-
sendEvent("onFaceCompareSuccess", eventMap)
|
|
465
|
-
promise.resolve(promiseMap)
|
|
466
|
-
},
|
|
467
|
-
callbackError = { event, message ->
|
|
468
|
-
Log.e(TAG, "❌ startFaceCompare() failed - Event: $event, Message: $message")
|
|
469
|
-
val errorMap =
|
|
470
|
-
Arguments.createMap().apply {
|
|
471
|
-
putString("event", event.name.toString())
|
|
472
|
-
putString("message", message?.toString() ?: "Unknown error")
|
|
473
|
-
}
|
|
474
|
-
sendEvent("onFaceCompareError", errorMap)
|
|
475
|
-
promise.reject(event.name.toString(), message?.toString() ?: "Unknown FaceService error")
|
|
476
|
-
}
|
|
474
|
+
// FaceService cần appKeyFaceService. Trước đây RN chỉ truyền 1 key string,
|
|
475
|
+
// nên map key này vào appKeyFaceService (và set appKey luôn để tương thích).
|
|
476
|
+
val ekycConfig = EKYCConfigSDK(
|
|
477
|
+
appKey = AppKeyConfig(appKey = appKey, appKeyFaceService = appKey),
|
|
478
|
+
faceServiceConfig = faceServiceConfig
|
|
477
479
|
)
|
|
480
|
+
|
|
481
|
+
// Đảm bảo chạy trên main thread để tránh crash trên một số devices/SDK versions.
|
|
482
|
+
Handler(Looper.getMainLooper()).post {
|
|
483
|
+
SdkEkycFaceService.startEkyc(
|
|
484
|
+
ekycConfigSDK = ekycConfig,
|
|
485
|
+
callbackSuccess = { event, data ->
|
|
486
|
+
Log.d(TAG, "✅ startFaceCompare() success")
|
|
487
|
+
val (eventMap, promiseMap) = createSeparateMaps { map ->
|
|
488
|
+
map.putString("event", event.name.toString())
|
|
489
|
+
map.putString("data", data.toString())
|
|
490
|
+
}
|
|
491
|
+
sendEvent("onFaceCompareSuccess", eventMap)
|
|
492
|
+
promise.resolve(promiseMap)
|
|
493
|
+
},
|
|
494
|
+
callbackError = { event, message ->
|
|
495
|
+
Log.e(TAG, "❌ startFaceCompare() failed - Event: $event, Message: $message")
|
|
496
|
+
val errorMap =
|
|
497
|
+
Arguments.createMap().apply {
|
|
498
|
+
putString("event", event.name.toString())
|
|
499
|
+
putString("message", message?.toString() ?: "Unknown error")
|
|
500
|
+
}
|
|
501
|
+
sendEvent("onFaceCompareError", errorMap)
|
|
502
|
+
promise.reject(event.name.toString(), message?.toString() ?: "Unknown FaceService error")
|
|
503
|
+
}
|
|
504
|
+
)
|
|
505
|
+
}
|
|
478
506
|
} catch (e: Exception) {
|
|
479
507
|
Log.e(TAG, "❌ startFaceCompare() exception: ${e.message}", e)
|
|
480
508
|
promise.reject("FACE_COMPARE_ERROR", e.message)
|
|
@@ -502,7 +530,56 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
502
530
|
}
|
|
503
531
|
}
|
|
504
532
|
|
|
505
|
-
|
|
533
|
+
/**
|
|
534
|
+
* Resolve input image string to a local file.
|
|
535
|
+
* - Supports: file path (`/...`), file URI (`file://...`), content URI (`content://...`), base64 (with/without data URI).
|
|
536
|
+
*/
|
|
537
|
+
private fun resolveImageInputToFile(input: String, prefix: String): File {
|
|
538
|
+
val value = input.trim()
|
|
539
|
+
if (value.isEmpty()) {
|
|
540
|
+
// Return empty file to trigger validation error upstream
|
|
541
|
+
return File(reactApplicationContext.cacheDir, "empty_${prefix}_${Date().time}.jpg").apply {
|
|
542
|
+
createNewFile()
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// file://...
|
|
547
|
+
if (value.startsWith("file://")) {
|
|
548
|
+
val file = File(value.removePrefix("file://"))
|
|
549
|
+
if (file.exists()) return file
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// content://...
|
|
553
|
+
if (value.startsWith("content://")) {
|
|
554
|
+
val copied = copyContentUriToCacheFile(value, prefix)
|
|
555
|
+
if (copied != null && copied.exists()) return copied
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// raw path
|
|
559
|
+
val rawFile = File(value)
|
|
560
|
+
if (rawFile.exists()) return rawFile
|
|
561
|
+
|
|
562
|
+
// fallback: treat as base64
|
|
563
|
+
return base64ToImageFile(value, prefix)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private fun copyContentUriToCacheFile(uriString: String, prefix: String): File? {
|
|
567
|
+
return try {
|
|
568
|
+
val uri = Uri.parse(uriString)
|
|
569
|
+
val resolver = reactApplicationContext.contentResolver
|
|
570
|
+
val inputStream: InputStream = resolver.openInputStream(uri) ?: return null
|
|
571
|
+
val outFile = File(reactApplicationContext.cacheDir, "${prefix}_${Date().time}.jpg")
|
|
572
|
+
FileOutputStream(outFile).use { out ->
|
|
573
|
+
inputStream.use { ins -> ins.copyTo(out) }
|
|
574
|
+
}
|
|
575
|
+
outFile
|
|
576
|
+
} catch (e: Exception) {
|
|
577
|
+
Log.w(TAG, "copyContentUriToCacheFile failed: ${e.message}")
|
|
578
|
+
null
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
private fun base64ToImageFile(base64Image: String, prefix: String = "image"): File {
|
|
506
583
|
try {
|
|
507
584
|
// Remove data URI prefix if present (like "data:image/png;base64,")
|
|
508
585
|
var processedBase64 = base64Image
|
|
@@ -515,7 +592,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
515
592
|
|
|
516
593
|
// Create a unique filename with timestamp
|
|
517
594
|
val timestamp = Date().time.toString()
|
|
518
|
-
val fileName = "
|
|
595
|
+
val fileName = "${prefix}_$timestamp.jpg"
|
|
519
596
|
|
|
520
597
|
// Get temporary directory and create file path
|
|
521
598
|
val directory = reactApplicationContext.cacheDir
|
|
@@ -527,14 +604,28 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
527
604
|
|
|
528
605
|
return imageFile
|
|
529
606
|
} catch (e: Exception) {
|
|
530
|
-
Log.e(
|
|
607
|
+
Log.e(TAG, "Error converting base64 to image file: ${e.message}", e)
|
|
531
608
|
// In case of error, create an empty file to avoid null returns
|
|
532
|
-
val errorFile = File(reactApplicationContext.cacheDir, "
|
|
609
|
+
val errorFile = File(reactApplicationContext.cacheDir, "error_${prefix}_${Date().time}.jpg")
|
|
533
610
|
errorFile.createNewFile()
|
|
534
611
|
return errorFile
|
|
535
612
|
}
|
|
536
613
|
}
|
|
537
614
|
|
|
615
|
+
/**
|
|
616
|
+
* Convert File to base64 string for passing to React Native.
|
|
617
|
+
* Mirrors Android SDK: data?.ekycStateModel?.eKYCFileModel?.imageFace
|
|
618
|
+
*/
|
|
619
|
+
private fun fileToBase64(file: File?): String? {
|
|
620
|
+
if (file == null || !file.exists()) return null
|
|
621
|
+
return try {
|
|
622
|
+
Base64.encodeToString(file.readBytes(), Base64.NO_WRAP)
|
|
623
|
+
} catch (e: Exception) {
|
|
624
|
+
Log.e(TAG, "fileToBase64 error: ${e.message}", e)
|
|
625
|
+
null
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
538
629
|
@ReactMethod
|
|
539
630
|
fun startEkycUI(
|
|
540
631
|
appKey: String,
|
|
@@ -574,15 +665,26 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
574
665
|
// Parse StyleConfig JSON from React Native
|
|
575
666
|
val styleConfig = parseStyleConfig(styleConfigJson)
|
|
576
667
|
|
|
577
|
-
//
|
|
668
|
+
// LivenessConfig theo SDKeKYCActivity (157-169): isActiveLiveness, autoCapture, forceCaptureTimeout, isShowCameraFont, customActions, activeActionCount
|
|
578
669
|
val switchFrontCamera = extractBooleanValue(optionConfigJson, "switchFrontCamera") ?: false
|
|
670
|
+
val isActiveLiveness = extractBooleanValue(optionConfigJson, "isActiveLiveness") ?: true
|
|
671
|
+
val autoCapture = extractBooleanValue(optionConfigJson, "autoCapture") ?: true
|
|
672
|
+
val forceCaptureTimeoutSec = extractIntValue(optionConfigJson, "forceCaptureTimeout") ?: 30
|
|
673
|
+
val customActionsList = parseCustomActionsFromJson(optionConfigJson)
|
|
674
|
+
val activeActionCount = extractIntValue(optionConfigJson, "activeActionCount") ?: customActionsList?.size ?: 2
|
|
579
675
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
676
|
+
val livenessConfig = if (finalFlow.contains(SDKType.LIVENESS)) {
|
|
677
|
+
LivenessConfig(
|
|
678
|
+
isActiveLiveness = isActiveLiveness,
|
|
679
|
+
autoCapture = autoCapture,
|
|
680
|
+
forceCaptureTimeout = (forceCaptureTimeoutSec * 1000L).coerceAtLeast(0),
|
|
681
|
+
isShowCameraFont = switchFrontCamera,
|
|
682
|
+
customActions = customActionsList?.takeIf { it.isNotEmpty() },
|
|
683
|
+
activeActionCount = activeActionCount,
|
|
684
|
+
)
|
|
583
685
|
} else null
|
|
584
686
|
|
|
585
|
-
// Create EKYCConfigSDK like MainActivity.kt
|
|
687
|
+
// Create EKYCConfigSDK like SDKeKYCActivity / MainActivity.kt
|
|
586
688
|
val ekycConfigSDK = EKYCConfigSDK(
|
|
587
689
|
appKey = appKeyConfig,
|
|
588
690
|
optionConfig = optionConfig.copy(language = if (language == "en") "en" else "vi"),
|
|
@@ -596,14 +698,31 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
596
698
|
activity = currentActivity,
|
|
597
699
|
ekycConfigSDK = ekycConfigSDK,
|
|
598
700
|
callbackSuccess = { event, data ->
|
|
599
|
-
Log.d(TAG, "✅ startEkycUI()
|
|
600
|
-
val
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
701
|
+
Log.d(TAG, "✅ startEkycUI() callback - event=${event.name}")
|
|
702
|
+
val result = data as? SDKEkycResult
|
|
703
|
+
val hasRealData = result?.ekycStateModel != null
|
|
704
|
+
// Chỉ resolve promise khi flow hoàn thành có data (SDK_END_SUCCESS); SDK_START_SUCCESS = activity vừa mở, bỏ qua
|
|
705
|
+
if (event == EKYCEvent.SDK_START_SUCCESS && !hasRealData) {
|
|
706
|
+
Log.d(TAG, "⏳ startEkycUI() SDK_START_SUCCESS – chờ SDK_END_SUCCESS")
|
|
707
|
+
sendEvent("onEkycUISuccess", Arguments.createMap().apply {
|
|
708
|
+
putString("status", "started")
|
|
709
|
+
putString("event", event.name.toString())
|
|
710
|
+
})
|
|
711
|
+
} else {
|
|
712
|
+
val ekycFiles = result?.ekycStateModel?.eKYCFileModel
|
|
713
|
+
val ekycTransactionId = result?.ekycStateModel?.transactionId ?: ""
|
|
714
|
+
val (eventMap, promiseMap) = createSeparateMaps { map ->
|
|
715
|
+
map.putString("status", "success")
|
|
716
|
+
map.putString("event", event.name.toString())
|
|
717
|
+
map.putString("data", data.toString())
|
|
718
|
+
map.putString("transactionId", ekycTransactionId)
|
|
719
|
+
ekycFiles?.imageFace?.absolutePath?.let { map.putString("imageFacePath", it) }
|
|
720
|
+
ekycFiles?.imageOcrFront?.absolutePath?.let { map.putString("imageOcrFrontPath", it) }
|
|
721
|
+
ekycFiles?.imageOcrBack?.absolutePath?.let { map.putString("imageOcrBackPath", it) }
|
|
722
|
+
}
|
|
723
|
+
sendEvent("onEkycUISuccess", eventMap)
|
|
724
|
+
promise.resolve(promiseMap)
|
|
604
725
|
}
|
|
605
|
-
sendEvent("onEkycUISuccess", eventMap)
|
|
606
|
-
promise.resolve(promiseMap)
|
|
607
726
|
},
|
|
608
727
|
callbackError = { event, errorResult ->
|
|
609
728
|
Log.e(TAG, "❌ startEkycUI() failed - Event: $event, Message: ${errorResult.message}")
|
|
@@ -703,6 +822,37 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
703
822
|
}
|
|
704
823
|
}
|
|
705
824
|
|
|
825
|
+
/** Parse customActions array from optionConfig JSON. Maps như SDKeKYCActivity getSelectedCustomActions. */
|
|
826
|
+
private fun parseCustomActionsFromJson(optionConfigJson: String): List<SDKFaceDetectStatus>? {
|
|
827
|
+
return try {
|
|
828
|
+
val obj = JSONObject(optionConfigJson)
|
|
829
|
+
val arr = obj.optJSONArray("customActions") ?: return null
|
|
830
|
+
if (arr.length() == 0) return null
|
|
831
|
+
val actions = mutableListOf<SDKFaceDetectStatus>()
|
|
832
|
+
for (i in 0 until arr.length()) {
|
|
833
|
+
val s = arr.optString(i, "")
|
|
834
|
+
when (s) {
|
|
835
|
+
"LEFT" -> actions.add(SDKFaceDetectStatus.LEFT)
|
|
836
|
+
"RIGHT" -> actions.add(SDKFaceDetectStatus.RIGHT)
|
|
837
|
+
"UP" -> actions.add(SDKFaceDetectStatus.UP)
|
|
838
|
+
"DOWN" -> actions.add(SDKFaceDetectStatus.DOWN)
|
|
839
|
+
"SMILE" -> actions.add(SDKFaceDetectStatus.SMILE)
|
|
840
|
+
"BLINK" -> actions.add(SDKFaceDetectStatus.BLINK)
|
|
841
|
+
"TILT_LEFT" -> actions.add(SDKFaceDetectStatus.TILT_LEFT)
|
|
842
|
+
"TILT_RIGHT" -> actions.add(SDKFaceDetectStatus.TILT_RIGHT)
|
|
843
|
+
"WINK_LEFT" -> actions.add(SDKFaceDetectStatus.WINK_LEFT)
|
|
844
|
+
"WINK_RIGHT" -> actions.add(SDKFaceDetectStatus.WINK_RIGHT)
|
|
845
|
+
"STRAIGHT" -> actions.add(SDKFaceDetectStatus.STRAIGHT)
|
|
846
|
+
else -> if (s.isNotEmpty()) Log.w(TAG, "parseCustomActionsFromJson: unknown action=$s")
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (actions.isNotEmpty()) actions else null
|
|
850
|
+
} catch (e: Exception) {
|
|
851
|
+
Log.w(TAG, "parseCustomActionsFromJson: ${e.message}")
|
|
852
|
+
null
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
706
856
|
private fun parseAppKeyConfig(appKeyConfigJson: String): AppKeyConfig {
|
|
707
857
|
return try {
|
|
708
858
|
if (appKeyConfigJson.isBlank() || appKeyConfigJson == "{}") {
|
|
@@ -1019,14 +1169,26 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
1019
1169
|
promise.resolve(promiseMap)
|
|
1020
1170
|
},
|
|
1021
1171
|
callbackError = { event, error ->
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1172
|
+
val msg = (error.message ?: "").lowercase()
|
|
1173
|
+
// eSign đã được khởi tạo → coi là thành công, cho icon pass xanh / text xanh
|
|
1174
|
+
if (msg.contains("đã được khởi tạo") || msg.contains("already") || msg.contains("already initialized")) {
|
|
1175
|
+
Log.d(TAG, "✅ initializeESign() – eSign đã khởi tạo, trả success (không coi là lỗi)")
|
|
1176
|
+
val (eventMap, promiseMap) = createSeparateMaps { map ->
|
|
1177
|
+
map.putString("code", "0")
|
|
1178
|
+
map.putString("message", "eSign already initialized")
|
|
1179
|
+
}
|
|
1180
|
+
sendEvent("onESignInitSuccess", eventMap)
|
|
1181
|
+
promise.resolve(promiseMap)
|
|
1182
|
+
} else {
|
|
1183
|
+
Log.e(TAG, "❌ initializeESign() failed - Event: $event, Message: ${error.message}")
|
|
1184
|
+
val errorMap = Arguments.createMap().apply {
|
|
1185
|
+
putString("event", event.name.toString())
|
|
1186
|
+
putString("message", error.message ?: "")
|
|
1187
|
+
putString("code", error.code.toString())
|
|
1188
|
+
}
|
|
1189
|
+
sendEvent("onESignError", errorMap)
|
|
1190
|
+
promise.reject(event.name.toString(), error.message ?: "Unknown Error")
|
|
1027
1191
|
}
|
|
1028
|
-
sendEvent("onESignError", errorMap)
|
|
1029
|
-
promise.reject(event.name.toString(), error.message ?: "Unknown Error")
|
|
1030
1192
|
}
|
|
1031
1193
|
)
|
|
1032
1194
|
} catch (e: Exception) {
|
|
@@ -1053,9 +1215,14 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
1053
1215
|
promise.resolve(token)
|
|
1054
1216
|
},
|
|
1055
1217
|
callbackError = { event, error ->
|
|
1056
|
-
Log.e(TAG, "❌ getSdkToken() failed: $error")
|
|
1057
|
-
|
|
1058
|
-
|
|
1218
|
+
Log.e(TAG, "❌ getSdkToken() failed - Event: $event, Message: ${error.message}")
|
|
1219
|
+
val errorMap = Arguments.createMap().apply {
|
|
1220
|
+
putString("event", event.name.toString())
|
|
1221
|
+
putString("message", error.message ?: "")
|
|
1222
|
+
putString("code", error.code.toString())
|
|
1223
|
+
}
|
|
1224
|
+
sendEvent("onESignError", errorMap)
|
|
1225
|
+
promise.reject(event.name.toString(), error.message ?: "Get SDK Token failed")
|
|
1059
1226
|
}
|
|
1060
1227
|
)
|
|
1061
1228
|
} catch (e: Exception) {
|
|
@@ -1420,26 +1587,14 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
1420
1587
|
callbackError = { event, error ->
|
|
1421
1588
|
val errorMessage = error.message ?: ""
|
|
1422
1589
|
val errorCode = error.code.toString()
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
map.putString("response", "{\"status\":1,\"msg\":\"$errorMessage\"}")
|
|
1429
|
-
map.putString("message", errorMessage)
|
|
1430
|
-
}
|
|
1431
|
-
sendEvent("onESignRegisterRemoteSigningSuccess", eventMap)
|
|
1432
|
-
promise.resolve(promiseMap)
|
|
1433
|
-
} else {
|
|
1434
|
-
Log.e(TAG, "❌ registerRemoteSigning() failed - Event: $event, Message: $errorMessage")
|
|
1435
|
-
val errorMap = Arguments.createMap().apply {
|
|
1436
|
-
putString("event", event.name.toString())
|
|
1437
|
-
putString("message", errorMessage)
|
|
1438
|
-
putString("code", errorCode)
|
|
1439
|
-
}
|
|
1440
|
-
sendEvent("onESignError", errorMap)
|
|
1441
|
-
promise.reject(event.name.toString(), errorMessage)
|
|
1590
|
+
Log.e(TAG, "❌ registerRemoteSigning() failed - Event: $event, Message: $errorMessage")
|
|
1591
|
+
val errorMap = Arguments.createMap().apply {
|
|
1592
|
+
putString("event", event.name.toString())
|
|
1593
|
+
putString("message", errorMessage)
|
|
1594
|
+
putString("code", errorCode)
|
|
1442
1595
|
}
|
|
1596
|
+
sendEvent("onESignError", errorMap)
|
|
1597
|
+
promise.reject(event.name.toString(), errorMessage)
|
|
1443
1598
|
}
|
|
1444
1599
|
)
|
|
1445
1600
|
} catch (e: Exception) {
|
|
@@ -1448,6 +1603,48 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
1448
1603
|
}
|
|
1449
1604
|
}
|
|
1450
1605
|
|
|
1606
|
+
/**
|
|
1607
|
+
* Composite API: Register Remote Signing + Send Confirmation Document
|
|
1608
|
+
* Align với SdkeSignImpl.registerAndConfirm: gọi registerRemoteSigning -> parse sessionId -> sendConfirmationDocument
|
|
1609
|
+
*/
|
|
1610
|
+
@ReactMethod
|
|
1611
|
+
fun registerAndConfirm(
|
|
1612
|
+
requestJson: String,
|
|
1613
|
+
confirmationDocBase64: String,
|
|
1614
|
+
promise: Promise
|
|
1615
|
+
) {
|
|
1616
|
+
Log.d(TAG, "▶️ registerAndConfirm() called")
|
|
1617
|
+
try {
|
|
1618
|
+
SdkeSign.registerAndConfirm(
|
|
1619
|
+
requestJson = requestJson,
|
|
1620
|
+
confirmationDocBase64 = confirmationDocBase64,
|
|
1621
|
+
callbackSuccess = { rawResponse ->
|
|
1622
|
+
Log.d(TAG, "✅ registerAndConfirm() success")
|
|
1623
|
+
val (eventMap, promiseMap) = createSeparateMaps { map ->
|
|
1624
|
+
map.putString("response", rawResponse)
|
|
1625
|
+
}
|
|
1626
|
+
sendEvent("onESignRegisterAndConfirmSuccess", eventMap)
|
|
1627
|
+
promise.resolve(promiseMap)
|
|
1628
|
+
},
|
|
1629
|
+
callbackError = { event, error ->
|
|
1630
|
+
val errorMessage = error.message ?: ""
|
|
1631
|
+
val errorCode = error.code.toString()
|
|
1632
|
+
Log.e(TAG, "❌ registerAndConfirm() failed - Event: $event, Message: $errorMessage")
|
|
1633
|
+
val errorMap = Arguments.createMap().apply {
|
|
1634
|
+
putString("event", event.name.toString())
|
|
1635
|
+
putString("message", errorMessage)
|
|
1636
|
+
putString("code", errorCode)
|
|
1637
|
+
}
|
|
1638
|
+
sendEvent("onESignError", errorMap)
|
|
1639
|
+
promise.reject(event.name.toString(), errorMessage)
|
|
1640
|
+
}
|
|
1641
|
+
)
|
|
1642
|
+
} catch (e: Exception) {
|
|
1643
|
+
Log.e(TAG, "❌ registerAndConfirm() exception: ${e.message}", e)
|
|
1644
|
+
promise.reject("ESIGN_EXCEPTION", e.message, e)
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1451
1648
|
@ReactMethod
|
|
1452
1649
|
fun signPdf(
|
|
1453
1650
|
requestJson: String,
|
|
@@ -1514,25 +1711,13 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
1514
1711
|
callbackError = { event, error ->
|
|
1515
1712
|
val errorMessage = error.message ?: ""
|
|
1516
1713
|
val errorCode = error.code.toString()
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
val (eventMap, promiseMap) = createSeparateMaps { map ->
|
|
1522
|
-
map.putString("response", "{\"status\":1,\"msg\":\"$errorMessage\"}")
|
|
1523
|
-
map.putString("message", errorMessage)
|
|
1524
|
-
}
|
|
1525
|
-
sendEvent("onESignSendConfirmationDocumentSuccess", eventMap)
|
|
1526
|
-
promise.resolve(promiseMap)
|
|
1527
|
-
} else {
|
|
1528
|
-
val errorMap = Arguments.createMap().apply {
|
|
1529
|
-
putString("event", event.name.toString())
|
|
1530
|
-
putString("message", errorMessage)
|
|
1531
|
-
putString("code", errorCode)
|
|
1532
|
-
}
|
|
1533
|
-
sendEvent("onESignError", errorMap)
|
|
1534
|
-
promise.reject(event.name.toString(), errorMessage)
|
|
1714
|
+
val errorMap = Arguments.createMap().apply {
|
|
1715
|
+
putString("event", event.name.toString())
|
|
1716
|
+
putString("message", errorMessage)
|
|
1717
|
+
putString("code", errorCode)
|
|
1535
1718
|
}
|
|
1719
|
+
sendEvent("onESignError", errorMap)
|
|
1720
|
+
promise.reject(event.name.toString(), errorMessage)
|
|
1536
1721
|
}
|
|
1537
1722
|
)
|
|
1538
1723
|
} catch (e: Exception) {
|
package/dist/EKYCModule.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EmitterSubscription } from 'react-native';
|
|
2
2
|
import { NfcConfig, NfcError } from './src/types/ekycNFCType';
|
|
3
|
-
import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent } from './src/types/ekycType';
|
|
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
6
|
import { LivenessConfig, SDKFaceDetectStatus } from './src/types/ekycLivenessType';
|
|
@@ -82,6 +82,13 @@ declare class SDKeKYC {
|
|
|
82
82
|
sendConfirmationDocument(requestJson: string): Promise<{
|
|
83
83
|
response: string;
|
|
84
84
|
}>;
|
|
85
|
+
/**
|
|
86
|
+
* Composite API: Register Remote Signing + Send Confirmation Document
|
|
87
|
+
* Align với SdkeSignImpl.registerAndConfirm
|
|
88
|
+
*/
|
|
89
|
+
registerAndConfirm(requestJson: string, confirmationDocBase64: string): Promise<{
|
|
90
|
+
response: string;
|
|
91
|
+
}>;
|
|
85
92
|
onESignInitSuccess(callback: (data: ESignInitResult) => void): EmitterSubscription | null;
|
|
86
93
|
onESignOpenSessionSuccess(callback: (data: ESignOpenSessionResult) => void): EmitterSubscription | null;
|
|
87
94
|
onESignRegisterDeviceSuccess(callback: (data: {
|
|
@@ -111,7 +118,15 @@ declare class SDKeKYC {
|
|
|
111
118
|
onESignSendConfirmationDocumentSuccess(callback: (data: {
|
|
112
119
|
response: string;
|
|
113
120
|
}) => void): EmitterSubscription | null;
|
|
121
|
+
onESignRegisterAndConfirmSuccess(callback: (data: {
|
|
122
|
+
response: string;
|
|
123
|
+
}) => void): EmitterSubscription | null;
|
|
114
124
|
onESignError(callback: (error: ESignError) => void): EmitterSubscription | null;
|
|
125
|
+
/**
|
|
126
|
+
* Start eKYC UI flow. On success, result contains the same data as Android:
|
|
127
|
+
* data.ekycStateModel.eKYCFileModel.imageFace → result.imageFace (base64),
|
|
128
|
+
* result.imageFacePath (file path), result.imageOcrFront / imageOcrBack, result.transactionId.
|
|
129
|
+
*/
|
|
115
130
|
startEkycUI(appKey: string, flowSDK: string[], language: string, transactionId: string, appKeyConfig: {
|
|
116
131
|
appKey: string;
|
|
117
132
|
appKeyNfc: string;
|
|
@@ -159,7 +174,7 @@ declare class SDKeKYC {
|
|
|
159
174
|
textFont?: string;
|
|
160
175
|
textColor?: number;
|
|
161
176
|
};
|
|
162
|
-
}): Promise<
|
|
177
|
+
}): Promise<StartEkycUIResult>;
|
|
163
178
|
}
|
|
164
179
|
declare const sdkEKYC: SDKeKYC;
|
|
165
180
|
export { SDKeKYC };
|
package/dist/EKYCModule.js
CHANGED
|
@@ -221,10 +221,12 @@ class SDKeKYC {
|
|
|
221
221
|
}
|
|
222
222
|
async startFaceCompare(config) {
|
|
223
223
|
var _a;
|
|
224
|
-
|
|
224
|
+
// Support both `appKey` and docs' `appKeyFaceService`
|
|
225
|
+
const normalized = Object.assign(Object.assign({}, config), { appKey: (config.appKeyFaceService || config.appKey) });
|
|
226
|
+
this.validateConfig(normalized, ['appKey', 'selfieImage', 'idImage']);
|
|
225
227
|
const nativeModule = this.ensureNativeModule();
|
|
226
228
|
try {
|
|
227
|
-
return await nativeModule.startFaceCompare(
|
|
229
|
+
return await nativeModule.startFaceCompare(normalized.appKey, (_a = normalized.transactionId) !== null && _a !== void 0 ? _a : '', normalized.selfieImage, normalized.idImage);
|
|
228
230
|
}
|
|
229
231
|
catch (error) {
|
|
230
232
|
console.error('Face Compare Error:', error);
|
|
@@ -700,6 +702,21 @@ class SDKeKYC {
|
|
|
700
702
|
throw error;
|
|
701
703
|
}
|
|
702
704
|
}
|
|
705
|
+
/**
|
|
706
|
+
* Composite API: Register Remote Signing + Send Confirmation Document
|
|
707
|
+
* Align với SdkeSignImpl.registerAndConfirm
|
|
708
|
+
*/
|
|
709
|
+
async registerAndConfirm(requestJson, confirmationDocBase64) {
|
|
710
|
+
const nativeModule = this.ensureNativeModule();
|
|
711
|
+
try {
|
|
712
|
+
const result = await nativeModule.registerAndConfirm(requestJson, confirmationDocBase64);
|
|
713
|
+
return result;
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
console.error('eSign Register And Confirm Error:', error);
|
|
717
|
+
throw error;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
703
720
|
// eSign Event Listeners
|
|
704
721
|
onESignInitSuccess(callback) {
|
|
705
722
|
try {
|
|
@@ -861,6 +878,22 @@ class SDKeKYC {
|
|
|
861
878
|
return null;
|
|
862
879
|
}
|
|
863
880
|
}
|
|
881
|
+
onESignRegisterAndConfirmSuccess(callback) {
|
|
882
|
+
try {
|
|
883
|
+
const emitter = this.ensureEventEmitter();
|
|
884
|
+
if (!emitter) {
|
|
885
|
+
console.error('❌ Event emitter not available for onESignRegisterAndConfirmSuccess');
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
const listener = emitter.addListener('onESignRegisterAndConfirmSuccess', callback);
|
|
889
|
+
this.listeners.push(listener);
|
|
890
|
+
return listener;
|
|
891
|
+
}
|
|
892
|
+
catch (error) {
|
|
893
|
+
console.error('Failed to add onESignRegisterAndConfirmSuccess listener:', error);
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
864
897
|
onESignError(callback) {
|
|
865
898
|
try {
|
|
866
899
|
const emitter = this.ensureEventEmitter();
|
|
@@ -877,7 +910,11 @@ class SDKeKYC {
|
|
|
877
910
|
return null;
|
|
878
911
|
}
|
|
879
912
|
}
|
|
880
|
-
|
|
913
|
+
/**
|
|
914
|
+
* Start eKYC UI flow. On success, result contains the same data as Android:
|
|
915
|
+
* data.ekycStateModel.eKYCFileModel.imageFace → result.imageFace (base64),
|
|
916
|
+
* result.imageFacePath (file path), result.imageOcrFront / imageOcrBack, result.transactionId.
|
|
917
|
+
*/
|
|
881
918
|
async startEkycUI(appKey, flowSDK, language, transactionId, appKeyConfig, optionConfig, styleConfig) {
|
|
882
919
|
if (!this.isInitialized) {
|
|
883
920
|
throw new Error('SDK is not initialized. Please call initSdkEkyc() first.');
|
|
@@ -892,7 +929,7 @@ class SDKeKYC {
|
|
|
892
929
|
const appKeyConfigJson = JSON.stringify(appKeyConfig);
|
|
893
930
|
const styleConfigJson = styleConfig ? JSON.stringify(styleConfig) : '{}';
|
|
894
931
|
const nativeModule = this.ensureNativeModule();
|
|
895
|
-
const result = await nativeModule.startEkycUI(appKey, flowSDKJson, language, transactionId, optionConfigJson, appKeyConfigJson, styleConfigJson);
|
|
932
|
+
const result = (await nativeModule.startEkycUI(appKey, flowSDKJson, language, transactionId, optionConfigJson, appKeyConfigJson, styleConfigJson));
|
|
896
933
|
console.log('✅ SdkEkycUI started successfully');
|
|
897
934
|
return result;
|
|
898
935
|
}
|