Tích hợp React Native
Yêu cầu
- Yêu cầu iOS >= 13.4
- Yêu cầu Android: minSdkVersion = 21, compileSdkVersion = 34
Cài đặt cho Android
Bước 1: Tải SDK và cấu hình project
- Tải phiên bản SDK mới nhất ở link: https://github.com/VNPT-SmartCA/vnpt_smartca_sdk_android/releases/
- Sau khi giải nén, copy các file .aar và thư mục repo vào thư mục
android/app/libs
của project Android (nếu chưa có thư mụclibs
thì tạo mới)
Lưu ý
Ứng dụng viết bằng Flutter hoặc tích hợp Flutter module không copy thư mục repo
- Trong
android/build.gradle
, thêm cấu hình như sau
buildscript {
// ...
repositories {
// ...
jcenter()
flatDir {
dirs 'libs'
}
maven {
url 'https://jitpack.io'
}
}
//...
}
String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
allprojects {
repositories {
google()
mavenCentral()
jcenter()
maven {
url '../app/libs/repo'
}
maven {
url "$storageUrl/download.flutter.io"
}
maven { url "https://jitpack.io" }
}
}
// ...
- Trong
android/gradle.properties
, enablenewArchEnabled
cho project
newArchEnabled=true
- Trong
android/app/proguard-rules.pro
, thêm cấu hình
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontwarn android.support.**
-dontwarn com.squareup.**
-dontwarn com.google.android.**
-verbose
# -keepattributes *Annotation*
-dontwarn lombok.**
-dontwarn io.realm.**
-dontwarn java.awt.**
# okhttp3
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontwarn org.conscrypt.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Application classes that will be serialized/deserialized over Gson
-keep class vnpt.it3.econtract.data.model.** { *; }
-keep class vnpt.it3.econtract.data.** { *; }
# Java 8
-dontwarn java.lang.invoke.*
-dontwarn **$$Lambda$*
# Retrofit 2
-dontnote retrofit2.Platform
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
-dontwarn retrofit2.Platform$Java8
-keepattributes Signature
-keepattributes Exceptions
-keepclassmembernames,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# pdf lib
-keep class com.shockwave.**
#To remove debug logs:
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** w(...);
public static *** i(...);
}
-keep public enum vnpt.it3.econtract.data.**{
*;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
# Glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
-keep class com.vnptit.innovation.sample.model.** { *; }
-keep class ai.icenter.face3d.native_lib.Face3DConfig { *; }
-keep class ai.icenter.face3d.native_lib.CardConfig { *; }
- Trong
android/app/build.gradle
, thêm các cấu hình sau
android {
// ...
defaultConfig {
// ...
multiDexEnabled true
ndk {
// abiFilters 'armeabi-v7a', 'arm64-v8a','x86_64'
debugSymbolLevel 'FULL'
}
}
// ...
packagingOptions {
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
}
aaptOptions {
noCompress "bic"
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.aar"])
// ...
implementation 'com.vnpt.smartca.module.vnpt_smartca_module:flutter_release:1.0'
implementation files('libs/vnpt_smartca_sdk_lib-release-v1.0.aar')
implementation files('libs/ekyc_sdk-release-v3.2.7.aar')
implementation files('libs/eContract-v3.6.12.aar')
implementation 'com.auth0.android:jwtdecode:2.0.0'
implementation 'com.github.gcacace:signature-pad:1.3.1'
implementation "androidx.multidex:multidex:2.0.1"
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.test.espresso:espresso-idling-resource:3.5.1'
implementation 'com.github.mhiew:android-pdf-viewer:3.2.0-beta.3'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'com.karumi:dexter:6.0.0'
implementation 'com.android.support:exifinterface:28.0.0'
implementation 'com.google.code.gson:gson:2.10'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.12'
}
- Trong
android/app/src/main/AndroidManifest.xml
thêm các cấu hình sau
// Thêm quyền
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
// Thêm vào thẻ <application>
tools:replace="android:allowBackup"
tools:targetApi="m"
android:allowBackup="false"
android:fullBackupContent="false"
// Thêm setup 2 activity
<activity android:name="io.flutter.embedding.android.FlutterFragmentActivity"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:exported="true"/>
<activity android:name="io.flutter.embedding.android.FlutterActivity"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:exported="true"/>
Bước 2: Tạo Native Modules
(https://reactnative.dev/docs/native-modules-android)
- Tạo file
SmartCAModule.kt
ngang hàng vớiMainApplication.kt
như sau
package com.reactnativeapp
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.util.Log
import android.widget.Toast
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.vnpt.smartca.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
class SmartCAModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName() = "SmartCAModule"
private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap?) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
@ReactMethod
fun addListener(type: String?) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
fun removeListeners(type: Int?) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod(isBlockingSynchronousMethod = true)
private fun createAccount() {
currentActivity?.runOnUiThread {
try {
VNPTSmartCA.createAccount { result ->
when (result.status) {
SmartCAResultCode.SUCCESS_CODE -> {
val obj: CallbackResult = Json.decodeFromString(
CallbackResult.serializer(), result.data.toString()
)
// SDK trả lại token, credential của khách hàng
// Đối tác tạo transaction cho khách hàng để lấy transId, sau đó gọi getWaitingTransaction
val token = obj.accessToken
val credentialId = obj.credentialId
val params = Arguments.createMap().apply {
putInt("code", 0)
putString("token", token)
putString("credentialId", credentialId)
}
sendEvent(this.reactApplicationContext, "EventReminder", params)
}
else -> {
val params = Arguments.createMap().apply {
putInt("code", 1)
putString("token", result.status.toString())
putString("credentialId", result.statusDesc)
}
//
sendEvent(this.reactApplicationContext, "EventReminder", params)
}
}
}
} catch (ex: Exception) {
throw ex;
}
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
private fun getAuth() {
currentActivity?.runOnUiThread {
try {
VNPTSmartCA.getAuthentication { result ->
when (result.status) {
SmartCAResultCode.SUCCESS_CODE -> {
val obj: CallbackResult = Json.decodeFromString(
CallbackResult.serializer(), result.data.toString()
)
// SDK trả lại token, credential của khách hàng
// Đối tác tạo transaction cho khách hàng để lấy transId, sau đó gọi getWaitingTransaction
val token = obj.accessToken
val credentialId = obj.credentialId
val serial = obj.serial
val params = Arguments.createMap().apply {
putInt("code", 0)
putString("token", token)
putString("credentialId", credentialId)
putString("serial", serial)
}
sendEvent(this.reactApplicationContext, "EventReminder", params)
}
else -> {
// Xử lý lỗi
val params = Arguments.createMap().apply {
putInt("code", 1)
putString("token", result.status.toString())
putString("credentialId", result.statusDesc)
}
//
sendEvent(this.reactApplicationContext, "EventReminder", params)
}
}
}
} catch (ex: Exception) {
throw ex;
}
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
private fun getMainInfo() {
currentActivity?.runOnUiThread {
try {
VNPTSmartCA.getMainInfo { result ->
when (result.status) {
SmartCAResultCode.SUCCESS_CODE -> {
// Xử lý khi confirm thành công
}
else -> {
// Xử lý khi confirm thất bại
}
}
}
} catch (ex: java.lang.Exception) {
throw ex;
}
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
private fun getWaitingTransaction(transId: String) {
currentActivity?.runOnUiThread {
try {
if (transId.isNullOrEmpty()) {
return;
}
VNPTSmartCA.getWaitingTransaction(transId) { result ->
val params = Arguments.createMap().apply {
putInt("code", 0)
}
sendEvent(this.reactApplicationContext, "EventReminder", params)
when (result.status) {
SmartCAResultCode.SUCCESS_CODE -> {
// Xử lý khi thành công
}
else -> {
// Xử lý khi thất bại
}
}
}
} catch (ex: Exception) {
throw ex;
}
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
private fun signOut() {
currentActivity?.runOnUiThread {
try {
VNPTSmartCA.signOut { result ->
when (result.status) {
SmartCAResultCode.SUCCESS_CODE -> {
val params = Arguments.createMap().apply {
putInt("code", 0)
}
sendEvent(this.reactApplicationContext, "EventReminder", params)
}
else -> {
val params = Arguments.createMap().apply {
putInt("code", 1)
}
sendEvent(this.reactApplicationContext, "EventReminder", params)
}
}
}
} catch (ex: Exception) {
throw ex;
}
}
}
companion object {
private var VNPTSmartCA = VNPTSmartCASDK()
@SuppressLint("StaticFieldLeak")
private lateinit var context: Context
fun init(context: Context) {
this.context = context
var customParams = CustomParams(
customerId = "", // Số CCCD, giấy tờ của KH đăng nhập SDK, để trống KH tự nhập
borderRadiusBtn = 999.0, // Border radius của button
colorSecondBtn = "", // Màu background nút phụ ví dụ: #FFFFFF
colorPrimaryBtn = "", // Màu background nút chính ví dụ: #4788FF
featuresLink = "", // Đường dẫn tới trang hướng dẫn sử dụng các tính năng sdk
customerPhone = "", // Số ĐT của KH
packageDefault = "", // Gói mặc định hiển thị khi mua Chứng thư số, ví dụ: PS0
password = "", // Mật khẩu mặc định khi tạo tài khoản
logoCustom = "", // Logo của đối tác theo mã hoá Base64 dạng "iVBORw0KGgoAAAANSUhEUgAAANgAAA......"
backgroundLogin = "" // Background của đối tác theo mã hoá Base64 dạng "iVBORw0KGgoAAAANSUhEUgAAANgAAA......"
)
var config = ConfigSDK(
clientId = "", // clientId tương ứng với môi trường được cấp qua email
clientSecret = "", // clientSecret tương ứng với môi trường được cấp qua email
env = SmartCAEnvironment.DEMO_ENV, // Môi trường kết nối DEMO/PROD
customParams = customParams,
lang = SmartCALanguage.VI, // Ngôn ngữ vi/en
isFlutter = true, // true nếu ứng dụng phát triển bằng Flutter, hoặc tích hợp Flutter module
)
VNPTSmartCA.initSDK(context, config)
val x = mutableListOf<Int>()
x.add(Intent.FLAG_ACTIVITY_NEW_TASK)
VNPTSmartCA.initCustomIntentFlag(x)
}
fun onDestroy() {
// super.onDestroy()
VNPTSmartCA.destroySDK();
}
}
}
@Serializable
data class CallbackResult(
val credentialId: String,
val accessToken: String,
val serial: String,
) : java.io.Serializable
- Tạo file
MyAppPackage.kt
ngang hàng vớiMainApplication.kt
import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
class MyAppPackage : ReactPackage {
override fun createViewManagers(
reactContext: ReactApplicationContext
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
override fun createNativeModules(
reactContext: ReactApplicationContext
): MutableList<NativeModule> = listOf(SmartCAModule(reactContext)).toMutableList()
}
- Kết nối
MyAppPackage
vào functiongetPackages
trongMainApplication
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
add(MyAppPackage())
}
- Trong
MainActivity
thêm hai function sau
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
SmartCAModule.init(this)
}
override fun onDestroy() {
super.onDestroy()
SmartCAModule.onDestroy()
}
Bước 3: Chi tiết các hàm chính
Cài đặt cho iOS
Bước 1: Tải SDK và cấu hình Project
Tải phiên bản SDK mới nhất từ link: https://github.com/VNPT-SmartCA/ios_vnptsmartca_sdk/releases
Kéo thả toàn bộ file .xcframework và .framework vào trong project. Đi tới Targets Project -> General -> Frameworks, Libraries, and Embedded Content
Lưu ýỨng dụng viết bằng Flutter hoặc tích hợp Flutter module chỉ kéo thả file ở thư mục eContract-eKYC
Bổ sung quyền truy cập Camera, FaceID trong Info.plist
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) requires access to your phone’s camera</string>
<key>NSFaceIDUsageDescription</key>
<string>$(PRODUCT_NAME) Authentication with TouchId or FaceID</string>
Bước 2: Tạo Native Modules
(https://reactnative.dev/docs/native-modules-ios)
- Sử dụng
xcode
để tạo và mở project ios (*.xcworkspace) - Tạo file swift với tên là
SmartCAModule.swift
, lúc này xcode sẽ gợi ý tạo file đểconfigure an Objective-C Bridging Header
, chọnCreate Bridging Header
import Foundation
import SmartCASDK
import FlutterPluginRegistrant
@objc(SmartCAModule)
class SmartCAModule: RCTEventEmitter {
private var manager: SmartCAManager?
private var hasListeners = false
override init() {
super.init()
self.manager = SmartCAManager.shared
}
override func startObserving() {
hasListeners = true
}
override func stopObserving() {
hasListeners = false
}
@objc func createAccount() {
DispatchQueue.main.async {
self.manager?.vnptSmartCASDK?.createAccount(callback: { result in
print(result)
})
}
}
@objc func signOut() {
DispatchQueue.main.async {
self.manager?.vnptSmartCASDK?.signOut(callback: { result in
if result.status == SmartCAResultCode.SUCCESS_CODE {
// Xử lý khi thành công
if self.hasListeners {
self.sendEvent(withName: "EventReminder", body: ["code": 0, "token": "Thông báo", "credentialId": "Đăng xuất thành công"])
}
} else {
// Xử lý khi thất bại
}
});
}
}
@objc func getAuth() {
DispatchQueue.main.async {
// SDK tự động xử lý các trường hợp về token: Hết hạn, chưa kích hoạt...
self.manager?.vnptSmartCASDK?.getAuthentication(callback: { result in
if result.status == SmartCAResultCode.SUCCESS_CODE {
// Xử lý khi thành công
if self.hasListeners {
self.sendEvent(withName: "EventReminder", body: ["code": 0, "token": result.data, "credentialId": ""])
}
} else {
// Xử lý khi thất bại
}
});
}
}
@objc func getMainInfo(){
DispatchQueue.main.async {
self.manager?.vnptSmartCASDK?.getMainInfo(callback: { result in
})
}
}
@objc func getWaitingTransaction(_ tranId: String) {
DispatchQueue.main.async {
self.manager?.vnptSmartCASDK?.getWaitingTransaction(tranId: tranId, callback: { result in
if result.status == SmartCAResultCode.SUCCESS_CODE {
print("Giao dịch thành công: \(result.status) - \(result.statusDesc) - \(result.data)");
if self.hasListeners {
self.sendEvent(withName: "EventReminder", body: ["code": 0, "token": result.status, "credentialId": result.statusDesc])
}
} else {
if self.hasListeners {
self.sendEvent(withName: "EventReminder", body: ["code": 1, "token": result.status, "credentialId": result.statusDesc])
}
}
});
}
}
override static func requiresMainQueueSetup() -> Bool {
return true
}
}
class SmartCAManager: NSObject {
static let shared = SmartCAManager()
@objc class func getInstance() -> SmartCAManager {
return SmartCAManager.shared
}
var vnptSmartCASDK: VNPTSmartCASDK?
@objc
func initSDK() {
if let ab = RCTPresentedViewController() {
let customParams = CustomParams(
customerId: "", // Số CCCD, giấy tờ của KH đăng nhập SDK, để trống KH tự nhập
borderRadiusBtn: 0, // Border radius của button
colorSecondBtn: "", // Màu background nút phụ vd: #FFFFFF
colorPrimaryBtn: "", // Màu background nút chính vd: #4788FF
featuresLink: "", // Đường dẫn tới trang hướng dẫn sử dụng các tính năng sdk
customerPhone: "", // Số ĐT mặc định của KH
packageDefault: "", // Gói mặc định hiển thị khi mua Chứng thư số
password: "", // Mật khẩu mặc định khi tạo tài khoản
logoCustom: "", // Logo của đối tác theo mã hoá Base64
backgroundLogin: "" // Background của đối tác theo mã hoá Base64
)
let config = SDKConfig(
clientId: "", // clientId tương ứng với môi trường được cấp qua email
clientSecret: "", // clientSecret tương ứng với môi trường được cấp qua email
environment: ENVIRONMENT.DEMO, // Môi trường kết nối DEMO/PROD
lang: LANG.VI, // Ngôn ngữ vi/en
isFlutterApp: false, // true nếu ứng dụng phát triển bằng Flutter, hoặc tích hợp Flutter module
customParams: customParams
)
self.vnptSmartCASDK = VNPTSmartCASDK(viewController: ab, config: config)
GeneratedPluginRegistrant.register(with: self.vnptSmartCASDK?.flutterEngine as! FlutterPluginRegistry);
}
}
@objc
func destroySDK() {
self.vnptSmartCASDK?.destroySDK();
}
}
- Cấu hình file
*-Bridging-Header.h
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
#import <React/RCTUtils.h>
- Tạo file
SmartCAModule.m
// RCTSmartCAModule.m
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
@interface RCT_EXTERN_MODULE(SmartCAModule, RCTEventEmitter)
RCT_EXTERN_METHOD(createAccount)
RCT_EXTERN_METHOD(getAuth)
RCT_EXTERN_METHOD(getMainInfo)
RCT_EXTERN_METHOD(getWaitingTransaction: String: String)
RCT_EXTERN_METHOD(signOut)
@end
Sử dụng trong React Native
- Import native module
import {NativeModules} from 'react-native';
- Khởi tạo
const {SmartCAModule} = NativeModules;
- Viết hàm lắng nghe sự kiện
EventReminder
(có thể tạo nhiều sự kiện)
useEffect(() => {
const eventEmitter = new NativeEventEmitter(SmartCAModule)
let eventListener = eventEmitter.addListener('EventReminder', event => {
let code = event.code
let statusCode = event.statusCode
let statusDesc = event.statusDesc
let data = event.data
let testIOSEvent = eventEmitter.addListener('onIncrement', event => {
console.log('onIncrement event', event);
});
// Removes the listener once unmounted
return () => {
eventListener.remove();
testIOSEvent.remove();
};
}, []);
- Sử dụng các hàm chính
// create account
SmartCAModule.createAccount()
// get auth
SmartCAModule.getAuth()
// get main info
SmartCAModule.getMainInfo()
// get waiting transaction
// Lấy accessToken từ `SmartCAModule.getAuth()`
let transactionId = 'xxx'
SmartCAModule.getWaitingTransaction(transactionId)
// sign out
SmartCAModule.signOut()