diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639..b2c751a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/src/main/java/com/mob/utsmyanmar/MyApplication.kt b/app/src/main/java/com/mob/utsmyanmar/MyApplication.kt index d9dd889..6cda045 100644 --- a/app/src/main/java/com/mob/utsmyanmar/MyApplication.kt +++ b/app/src/main/java/com/mob/utsmyanmar/MyApplication.kt @@ -1,13 +1,16 @@ package com.mob.utsmyanmar + import com.mob.utsmyanmar.utils.AppContextHolder import com.utsmyanmar.baselib.BaseApplication import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp class MyApplication : BaseApplication() { + override fun onCreate() { super.onCreate() AppContextHolder.init(this) } + } diff --git a/app/src/main/java/com/mob/utsmyanmar/config/SunmiPayManager.kt b/app/src/main/java/com/mob/utsmyanmar/config/SunmiPayManager.kt index 9f9c763..0cb3ff8 100644 --- a/app/src/main/java/com/mob/utsmyanmar/config/SunmiPayManager.kt +++ b/app/src/main/java/com/mob/utsmyanmar/config/SunmiPayManager.kt @@ -1,16 +1,43 @@ package com.mob.utsmyanmar.config +import com.sunmi.pay.hardware.aidl.security.SecurityOpt +import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2 import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2 +import com.sunmi.pay.hardware.aidlv2.readcard.ReadCardOptV2 import com.sunmi.pay.hardware.aidlv2.security.SecurityOptV2 -import jakarta.inject.Inject -import jakarta.inject.Singleton +import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2 +import com.utsmyanmar.baselib.BaseApplication +import javax.inject.Inject +import javax.inject.Singleton @Singleton -class SunmiPayManager @Inject constructor(){ - var pinPadOptV2: PinPadOptV2? = null - var securityOptV2: SecurityOptV2? = null - +class SunmiPayManager @Inject constructor() { + private val app: BaseApplication? + get() = BaseApplication.getInstance() + + val pinPadOptV2: PinPadOptV2? + get() = app?.mPinPadOptV2 + + val securityOptV2: SecurityOptV2? + get() = app?.mSecurityOptV2 + + val readCardOptV2: ReadCardOptV2? + get() = app?.mReadCardOptV2 + + val emvOptV2: EMVOptV2? + get() = app?.mEMVOptV2 + + val basicOptV2: BasicOptV2? + get() = BaseApplication.basicOptV2 + + val securityOpt: SecurityOpt? + get() = app?.securityOpt + fun isReady(): Boolean { - return pinPadOptV2 != null && securityOptV2 != null + return pinPadOptV2 != null && + securityOptV2 != null && + readCardOptV2 != null && + emvOptV2 != null && + basicOptV2 != null } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt index f5094fa..317e74b 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt @@ -15,11 +15,12 @@ import com.mob.utsmyanmar.ui.amount.AmountScreen import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel import com.mob.utsmyanmar.ui.dashboard.DashboardScreen +import com.mob.utsmyanmar.ui.pinpad.PinPadRoute import com.mob.utsmyanmar.ui.processing_card.ProcessingCardRoute import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel import com.mob.utsmyanmar.viewmodel.CardReaderViewModel import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel -import com.mob.utsmyanmar.viewmodel.PinPadViewModel +import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType @@ -124,7 +125,9 @@ fun AppNavGraph( ProcessingCardRoute( viewModel = processingCardViewModel, - onNavigatePinPad = {}, + onNavigatePinPad = { + navController.navigate(Routes.PinPad.route) + }, onNavigateInputAmount = { navController.popBackStack(Routes.Amount.route, false) }, onNavigateProcessing = {}, onNavigateEmvTransaction = {}, @@ -133,5 +136,16 @@ fun AppNavGraph( onShowDecline = {} ) } + + composable(Routes.PinPad.route) { + val pinPadViewModel: PinPadViewModel = hiltViewModel(activity) + val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity) + + PinPadRoute( + pinPadViewModel = pinPadViewModel, + transProcessViewModel = transProcessViewModel, + onBack = { navController.popBackStack() } + ) + } } } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt index e7b2a31..d459005 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt @@ -7,4 +7,5 @@ sealed class Routes(val route: String) { } data object CardWaiting : Routes("card_waiting") data object ProcessingCard : Routes("processing_card") + data object PinPad : Routes("pin_pad") } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadRoute.kt new file mode 100644 index 0000000..13caf2d --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadRoute.kt @@ -0,0 +1,71 @@ +package com.mob.utsmyanmar.ui.pinpad + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.mob.utsmyanmar.model.PinPadStatus +import com.mob.utsmyanmar.model.TransResultStatus +import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel +import com.mob.utsmyanmar.viewmodel.TransProcessViewModel + +@Composable +fun PinPadRoute( + pinPadViewModel: PinPadViewModel, + transProcessViewModel: TransProcessViewModel, + onBack: () -> Unit +) { + val pinText by pinPadViewModel.pinText.collectAsStateWithLifecycle() + val alertMsg by pinPadViewModel.alertMsg.collectAsStateWithLifecycle() + val pinStatus by pinPadViewModel.pinStatus.collectAsStateWithLifecycle() + val transStatus by transProcessViewModel.transResultStatus.collectAsStateWithLifecycle() + + LaunchedEffect(pinStatus) { + when (pinStatus) { + PinPadStatus.ON_CONFIRM, + PinPadStatus.ON_NEXT_SCREEN, + PinPadStatus.ON_EMPTY -> { + transProcessViewModel.startOnlineProcess() + } + + PinPadStatus.ON_CANCEL, + PinPadStatus.ON_TIMEOUT, + PinPadStatus.ON_CARD_REMOVED -> { + onBack() + } + + else -> {} + } + } + + LaunchedEffect(transStatus) { + when (transStatus) { + TransResultStatus.SUCCESS, + TransResultStatus.FAIL, + TransResultStatus.NETWORK_ERROR, + TransResultStatus.ERROR, + TransResultStatus.REVERSAL_FAIL, + TransResultStatus.REVERSAL_SUCCESS -> { + onBack() + } + + else -> {} + } + } + + DisposableEffect(Unit) { + onDispose { + pinPadViewModel.cancelPinPad() + } + } + + PinPadScreen( + pinText = pinText, + alertMessage = alertMsg, + onBack = onBack, + onKeyboardReady = { keyboard -> + pinPadViewModel.startPinPadProcess(keyboard) + } + ) +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadScreen.kt new file mode 100644 index 0000000..344b839 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadScreen.kt @@ -0,0 +1,174 @@ +package com.mob.utsmyanmar.ui.pinpad + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import com.mob.utsmyanmar.R +import com.mob.utsmyanmar.ui.theme.Black +import com.mob.utsmyanmar.ui.theme.Primary +import com.mob.utsmyanmar.ui.theme.White +import com.utsmyanmar.baselib.ui.CustomPinPadKeyboard + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PinPadScreen( + pinText: String, + alertMessage: String?, + onBack: () -> Unit, + onKeyboardReady: (CustomPinPadKeyboard) -> Unit +) { + var keyboardView by remember { mutableStateOf(null) } + var isStarted by remember { mutableStateOf(false) } + + LaunchedEffect(keyboardView) { + val keyboard = keyboardView ?: return@LaunchedEffect + if (!isStarted) { + isStarted = true + onKeyboardReady(keyboard) + } + } + + Scaffold( + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = "PIN ENTRY", + color = White, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold + ) + }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + painter = painterResource(R.drawable.ic_left_arrow), + contentDescription = "Back", + tint = White + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Primary + ) + ) + }, + containerColor = White + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .background(White) + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + color = Primary, + shape = RoundedCornerShape(24.dp) + ) + .padding(horizontal = 24.dp, vertical = 28.dp), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = "Please Input PIN", + color = White, + fontSize = 22.sp, + fontWeight = FontWeight.Bold + ) + + Spacer(modifier = Modifier.height(14.dp)) + + Text( + text = if (pinText.isBlank()) "------" else pinText, + color = White, + fontSize = 30.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 4.sp + ) + + if (!alertMessage.isNullOrBlank()) { + Spacer(modifier = Modifier.height(12.dp)) + Text( + text = alertMessage, + color = White, + fontSize = 15.sp, + textAlign = TextAlign.Center + ) + } + } + } + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = "Enter your PIN on the secured keypad below.", + color = Black, + fontSize = 16.sp, + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + AndroidView( + factory = { context -> + CustomPinPadKeyboard(context).also { + keyboardView = it + } + } + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "Cancel on device or use back to exit.", + color = Black, + fontSize = 14.sp, + textAlign = TextAlign.Center + ) + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/viewmodel/PinPadViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadViewModel.kt similarity index 88% rename from app/src/main/java/com/mob/utsmyanmar/viewmodel/PinPadViewModel.kt rename to app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadViewModel.kt index 24aeeb1..10be5cb 100644 --- a/app/src/main/java/com/mob/utsmyanmar/viewmodel/PinPadViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadViewModel.kt @@ -1,4 +1,4 @@ -package com.mob.utsmyanmar.viewmodel +package com.mob.utsmyanmar.ui.pinpad import android.os.Handler import android.os.Looper @@ -8,15 +8,15 @@ import android.view.View import android.view.ViewTreeObserver import android.widget.TextView import androidx.lifecycle.ViewModel +import com.mob.utsmyanmar.config.Constants +import com.mob.utsmyanmar.config.SunmiPayManager +import com.mob.utsmyanmar.model.PinPadStatus +import com.sunmi.pay.hardware.aidl.AidlConstants import com.sunmi.pay.hardware.aidlv2.AidlErrorCodeV2 import com.sunmi.pay.hardware.aidlv2.bean.PinPadConfigV2 import com.sunmi.pay.hardware.aidlv2.bean.PinPadDataV2 import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadListenerV2 import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2 -import com.mob.utsmyanmar.config.Constants -import com.mob.utsmyanmar.config.SunmiPayManager -import com.mob.utsmyanmar.model.PinPadStatus -import com.sunmi.pay.hardware.aidl.AidlConstants import com.utsmyanmar.baselib.ui.CustomPinPadKeyboard import com.utsmyanmar.checkxread.sdk.SunmiSDK import com.utsmyanmar.paylibs.model.PayDetail @@ -56,6 +56,7 @@ class PinPadViewModel @Inject constructor( private var mCancelHeight = 112 private var mCancelCoordinate = intArrayOf(0, 48) private var dukptIndex = 0 + private var tmkIndex = 9 private var tradeData: TradeData? = null private var payDetail: PayDetail? = null private var pan: String = "" @@ -120,19 +121,18 @@ class PinPadViewModel @Inject constructor( fun startPinPadProcess( customPinPadKeyboard: CustomPinPadKeyboard ) { + +// dukptIndex = SystemParamsOperation.getInstance().tmkIndex.toInt() + if(!sunmiPayManager.isReady()){ + _alertMsg.value = + "Sunmi Pay SDK is not ready" - dukptIndex = - SystemParamsOperation.getInstance() - .tmkIndex - .toInt() - - initData() - - if(mPinPadOptV2 == null){ - _alertMsg.value = "PinPad service is not ready!" + _pinStatus.value = + PinPadStatus.ON_ERROR + + return } - - getKSN() + initData() initPinPad(customPinPadKeyboard) } @@ -182,7 +182,7 @@ class PinPadViewModel @Inject constructor( try { val securityOptV2 = sunmiPayManager.securityOptV2; - if(securityOptV2 == null){ + if (securityOptV2 == null) { Log.d(TAG, "Security service is not ready!") return; } @@ -209,7 +209,7 @@ class PinPadViewModel @Inject constructor( try { val securityOptV2 = sunmiPayManager.securityOptV2; - if(securityOptV2 == null){ + if (securityOptV2 == null) { Log.d(TAG, "Security service is not ready!") return; } @@ -217,7 +217,7 @@ class PinPadViewModel @Inject constructor( val dataOut = ByteArray(10) val result = securityOptV2.dukptCurrentKSN(dukptIndex, dataOut) - + if (result == 0) { val ksnStr = @@ -245,7 +245,6 @@ class PinPadViewModel @Inject constructor( e.printStackTrace() } } - /* * Init Pin Pad */ @@ -253,58 +252,56 @@ class PinPadViewModel @Inject constructor( private fun initPinPad( customPinPadKeyboard: CustomPinPadKeyboard ) { - LogUtil.e(TAG, "Init Pin Pad PAN:$pan") - val pikIndex = dukptIndex + var timeout = Constants.PIN_PAD_TIMEOUT - var pinPadOrder = true - pinPadOrder = !SystemParamsOperation.getInstance().isRandomPinPad + val pinPadOrder = !SystemParamsOperation.getInstance().isRandomPinPad + if (SunmiSDK.getInstance().checkCardExist() != 2) { timeout = Constants.TIMEOUT } + try { + val config = PinPadConfigV2().apply { + maxInput = 6 + minInput = 0 + pinPadType = 1 // custom keyboard + algorithmType = AidlConstants.Security.KEY_ALG_TYPE_3DES + pinType = mPinType + this.timeout = timeout * 1000 + isOrderNumKey = pinPadOrder - val config = PinPadConfigV2() - config.maxInput = 6 - config.minInput = 0 - config.pinPadType = 1 - config.algorithmType = 0 - config.pinType = mPinType - config.timeout = timeout * 1000 - config.isOrderNumKey = pinPadOrder - config.keySystem = 1 - config.pinKeyIndex = pikIndex - config.pinblockFormat = 0 + keySystem = AidlConstants.Security.SEC_MKSK + pinKeyIndex = tmkIndex - val length = pan.length - - val panBytes = pan.substring(length - 13, length - 1).toByteArray(StandardCharsets.US_ASCII) - - config.pan = panBytes + pinblockFormat = AidlConstants.PinBlockFormat.SEC_PIN_BLK_ISO_FMT0 + this.pan = getPanBytes(this@PinPadViewModel.pan) + } val result = mPinPadOptV2?.initPinPad(config, mPinPadListener) - LogUtil.e(TAG, "result:$result") + LogUtil.e(TAG, "pinpad result:$result") - result?.let { - getKeyboardCoordinate( - it, - customPinPadKeyboard - ) + if (result.isNullOrEmpty()) { + _alertMsg.value = "PinPad init failed" + _pinStatus.value = PinPadStatus.ON_ERROR + return } + getKeyboardCoordinate(result, customPinPadKeyboard) + _pinText.value = "" - customPinPadKeyboard.keepScreenOn = true - customPinPadKeyboard.setKeyBoard(result) - customPinPadKeyboard.visibility = View.VISIBLE } catch (e: Exception) { e.printStackTrace() + _alertMsg.value = e.message + _pinStatus.value = PinPadStatus.ON_ERROR } } + /* * Keyboard @@ -436,10 +433,12 @@ class PinPadViewModel @Inject constructor( ON_NUMBER_CLICK -> { showPasswordView(msg.arg1) } + ON_CONFIRM_CLICK -> { LogUtil.d(TAG, "ON CLICK CONFIRM") _pinStatus.value = PinPadStatus.ON_CONFIRM } + ON_CANCEL_CLICK -> { LogUtil.d(TAG, "ON CLICK CANCEL") _pinStatus.value = PinPadStatus.ON_CANCEL @@ -503,7 +502,7 @@ class PinPadViewModel @Inject constructor( if ( SunmiSDK.getInstance().checkCardExist() == 2 || - payDetail?.cardType == AidlConstants.CardType.MAGNETIC.value || + payDetail?.cardType == AidlConstants.CardType.MAGNETIC.getValue() || payDetail?.cardType == -9 ) { @@ -582,4 +581,15 @@ class PinPadViewModel @Inject constructor( } } } + + private fun getPanBytes(pan: String): ByteArray { + + if (pan.length < 13) { + return ByteArray(0) + } + + return pan + .substring(pan.length - 13, pan.length - 1) + .toByteArray(StandardCharsets.US_ASCII) + } } \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt index 39a180b..59f4949 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt @@ -9,7 +9,7 @@ import com.mob.utsmyanmar.model.CardTransactionType import com.mob.utsmyanmar.utils.TransactionUtil import com.mob.utsmyanmar.viewmodel.CardReaderViewModel import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel -import com.mob.utsmyanmar.viewmodel.PinPadViewModel +import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel import com.utsmyanmar.checkxread.model.CardDataX