From 37a1a2e38de87457cc3282785f82894d771a0cc2 Mon Sep 17 00:00:00 2001 From: moon <56061215+MgKyawLay@users.noreply.github.com> Date: Tue, 12 May 2026 19:17:21 +0630 Subject: [PATCH] new ViewModels added --- .../com/mob/utsmyanmar/config/Constants.kt | 13 + .../mob/utsmyanmar/config/SunmiPayManager.kt | 16 + .../com/mob/utsmyanmar/model/PinPadStatus.kt | 12 + .../ui/cardwaiting/CardWaitingScreen.kt | 196 +++++- .../ui/cardwaiting/CardWaitingViewModel.kt | 2 + .../ui/processing_card/ProcessingCardEvent.kt | 5 + .../ui/processing_card/ProcessingCardRoute.kt | 44 ++ .../processing_card/ProcessingCardScreen.kt | 27 + .../ui/processing_card/ProcessingCardState.kt | 7 + .../processing_card/ProcessingCardUiEvent.kt | 11 + .../ProcessingCardViewModel.kt | 265 ++++++++ .../mob/utsmyanmar/utils/TransactionUtil.kt | 120 ++++ .../EmvTransactionProcessViewModel.kt | 429 +++++++++++++ .../utsmyanmar/viewmodel/PinPadViewModel.kt | 585 ++++++++++++++++++ .../viewmodel/TransProcessViewModel.kt | 10 +- 15 files changed, 1711 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/com/mob/utsmyanmar/config/Constants.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/config/SunmiPayManager.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/model/PinPadStatus.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardEvent.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardRoute.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardScreen.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardState.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardUiEvent.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/utils/TransactionUtil.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/viewmodel/EmvTransactionProcessViewModel.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/viewmodel/PinPadViewModel.kt diff --git a/app/src/main/java/com/mob/utsmyanmar/config/Constants.kt b/app/src/main/java/com/mob/utsmyanmar/config/Constants.kt new file mode 100644 index 0000000..10854fa --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/config/Constants.kt @@ -0,0 +1,13 @@ +package com.mob.utsmyanmar.config + +import com.mob.utsmyanmar.R + +object Constants { + const val WALLET = "WALLET" + const val MPU_CARD_SCHEME = "MPU" + const val TIMEOUT = 30 + const val PROCESSING_TIMEOUT = 180 + const val PIN_PAD_TIMEOUT = 60 + const val REVERSAL = "REVERSAL" + +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/config/SunmiPayManager.kt b/app/src/main/java/com/mob/utsmyanmar/config/SunmiPayManager.kt new file mode 100644 index 0000000..9f9c763 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/config/SunmiPayManager.kt @@ -0,0 +1,16 @@ +package com.mob.utsmyanmar.config + +import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2 +import com.sunmi.pay.hardware.aidlv2.security.SecurityOptV2 +import jakarta.inject.Inject +import jakarta.inject.Singleton + +@Singleton +class SunmiPayManager @Inject constructor(){ + var pinPadOptV2: PinPadOptV2? = null + var securityOptV2: SecurityOptV2? = null + + fun isReady(): Boolean { + return pinPadOptV2 != null && securityOptV2 != null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/model/PinPadStatus.kt b/app/src/main/java/com/mob/utsmyanmar/model/PinPadStatus.kt new file mode 100644 index 0000000..42d0793 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/model/PinPadStatus.kt @@ -0,0 +1,12 @@ +package com.mob.utsmyanmar.model + +enum class PinPadStatus { + ON_CONFIRM, + ON_CANCEL , + ON_EMPTY, + ON_ERROR, + ON_TIMEOUT, + ON_NEXT_SCREEN, + ON_CARD_REMOVED, + ON_ERROR_DUKPT, +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingScreen.kt index 6d41a0a..7ef9980 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingScreen.kt @@ -1,15 +1,28 @@ package com.mob.utsmyanmar.ui.cardwaiting import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image 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.Row 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.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +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.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -17,12 +30,17 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue 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 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 +@OptIn(ExperimentalMaterial3Api::class) @Composable fun CardWaitingScreen( viewModel: CardWaitingViewModel, @@ -60,34 +78,158 @@ fun CardWaitingScreen( viewModel.onBackPressed() } - Column( - modifier = Modifier - .fillMaxSize() - .background(White) - .padding(24.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = "Card Capture", - fontSize = 22.sp, - fontWeight = FontWeight.Bold - ) - - Spacer(modifier = Modifier.height(40.dp)) - - Text( - text = uiState.alertMessage, - fontSize = 20.sp, - textAlign = TextAlign.Center - ) - - Spacer(modifier = Modifier.weight(1f)) - - Button( - onClick = viewModel::onManualEntryClick, - modifier = Modifier.fillMaxWidth() + Scaffold( + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = "CARD CAPTURE", + 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) ) { - Text("Manual Entry") + + Box( + modifier = Modifier + .fillMaxSize() + .background( + color = Primary, + shape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) + ) + .padding(18.dp) + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + color = White, + shape = RoundedCornerShape(24.dp) + ) + .padding(horizontal = 22.dp, vertical = 28.dp) + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .size(92.dp) + .background( + color = Primary.copy(alpha = 0.08f), + shape = RoundedCornerShape(28.dp) + ), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(R.drawable.buy_ecommerce_finance_payment_pos_shop_svgrepo_com), + contentDescription = "Card reader" + ) + } + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = if (uiState.isFallback) { + "Swipe Card" + } else { + "Present Card" + }, + color = Black, + fontSize = 24.sp, + fontWeight = FontWeight.Bold + ) + + Spacer(modifier = Modifier.height(10.dp)) + + Text( + text = uiState.alertMessage, + color = Black, + fontSize = 18.sp, + lineHeight = 26.sp, + textAlign = TextAlign.Center + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .background( + color = White.copy(alpha = 0.12f), + shape = RoundedCornerShape(22.dp) + ) + .padding(horizontal = 18.dp, vertical = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = if (uiState.isLoading) { + "Reader status" + } else { + "Ready for card" + }, + color = White, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold + ) + + Text( + text = if (uiState.isLoading) "Initializing" else "Waiting", + color = White, + fontSize = 15.sp + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + Button( + onClick = viewModel::onManualEntryClick, + modifier = Modifier + .fillMaxWidth() + .height(64.dp), + shape = RoundedCornerShape(18.dp), + colors = ButtonDefaults.buttonColors( + containerColor = White, + contentColor = Primary + ) + ) { + Text( + text = "Manual Entry", + fontSize = 20.sp, + fontWeight = FontWeight.Bold + ) + } + } + } } } } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt index 3f63ef9..882abcd 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt @@ -1,5 +1,6 @@ package com.mob.utsmyanmar.ui.cardwaiting +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope @@ -173,6 +174,7 @@ class CardWaitingViewModel( 65, object : CheckCardResultX { override fun onSuccess(cardType: CardTypeX, isMPU: Boolean) { + Log.d("CardWaitingViewModel", "on success cardType=$cardType and isMpu=$isMPU") when { !isFallback && cardType == CardTypeX.MAG -> { if (SystemParamsOperation.getInstance().isMagStripeEnabled) { diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardEvent.kt b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardEvent.kt new file mode 100644 index 0000000..1937fa8 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardEvent.kt @@ -0,0 +1,5 @@ +package com.mob.utsmyanmar.ui.processing_card + +sealed interface ProcessingCardEvent { + data object StartProcess : ProcessingCardEvent +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardRoute.kt new file mode 100644 index 0000000..41252ec --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardRoute.kt @@ -0,0 +1,44 @@ +package com.mob.utsmyanmar.ui.processing_card + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle + +@Composable +fun ProcessingCardRoute( + viewModel: ProcessingCardViewModel = hiltViewModel(), + onNavigatePinPad: () -> Unit, + onNavigateInputAmount: () -> Unit, + onNavigateProcessing: () -> Unit, + onNavigateEmvTransaction: () -> Unit, + onNavigateError: (String) -> Unit, + onBack: () -> Unit, + onShowDecline: (String) -> Unit +) { + val state by viewModel.state.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewModel.onEvent(ProcessingCardEvent.StartProcess) + } + + LaunchedEffect(Unit) { + viewModel.uiEvent.collect { event -> + when (event) { + ProcessingCardUiEvent.NavigateToPinPad -> onNavigatePinPad() + ProcessingCardUiEvent.NavigateToInputAmount -> onNavigateInputAmount() + ProcessingCardUiEvent.NavigateToProcessing -> onNavigateProcessing() + ProcessingCardUiEvent.NavigateToEmvTransaction -> onNavigateEmvTransaction() + is ProcessingCardUiEvent.NavigateToError -> onNavigateError(event.message) + is ProcessingCardUiEvent.ShowDeclineAndBack -> { + onShowDecline(event.message) + onBack() + } + ProcessingCardUiEvent.Back -> onBack() + } + } + } + + ProcessingCardScreen(state = state) +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardScreen.kt new file mode 100644 index 0000000..fdd0707 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardScreen.kt @@ -0,0 +1,27 @@ +package com.mob.utsmyanmar.ui.processing_card + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun ProcessingCardScreen( + state: ProcessingCardState +) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + if (state.isLoading) { + CircularProgressIndicator() + } + + Text(text = state.message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardState.kt b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardState.kt new file mode 100644 index 0000000..3a148a4 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardState.kt @@ -0,0 +1,7 @@ +package com.mob.utsmyanmar.ui.processing_card + +data class ProcessingCardState( + val title: String = "Processing Card", + val message: String = "Please wait...", + val isLoading: Boolean = true +) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardUiEvent.kt b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardUiEvent.kt new file mode 100644 index 0000000..ff25f2c --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardUiEvent.kt @@ -0,0 +1,11 @@ +package com.mob.utsmyanmar.ui.processing_card + +sealed interface ProcessingCardUiEvent { + data object NavigateToPinPad : ProcessingCardUiEvent + data object NavigateToInputAmount : ProcessingCardUiEvent + data object NavigateToProcessing : ProcessingCardUiEvent + data object NavigateToEmvTransaction : ProcessingCardUiEvent + data class NavigateToError(val message: String) : ProcessingCardUiEvent + data class ShowDeclineAndBack(val message: String) : ProcessingCardUiEvent + data object Back : ProcessingCardUiEvent +} \ 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 new file mode 100644 index 0000000..2188987 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt @@ -0,0 +1,265 @@ +package com.mob.utsmyanmar.ui.processing_card + +import android.text.TextUtils +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +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.viewmodel.SharedViewModel +import com.mob.utsmyanmar.viewmodel.TransProcessViewModel +import com.utsmyanmar.checkxread.model.CardDataX +import com.utsmyanmar.checkxread.readcard.MAGXReadCard +import com.utsmyanmar.checkxread.readcard.MPUXReadCard +import com.utsmyanmar.checkxread.readcard.ReadCardResultX +import com.utsmyanmar.checkxread.util.CardTypeX +import com.utsmyanmar.paylibs.model.PayDetail +import com.utsmyanmar.paylibs.model.TradeData +import com.utsmyanmar.paylibs.model.enums.TransCVM +import com.utsmyanmar.paylibs.utils.POSUtil +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation +import com.utsmyanmar.paylibs.utils.enums.TransMenu +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import com.utsmyanmar.paylibs.utils.params.Params +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import sunmi.sunmiui.utils.LogUtil +import javax.inject.Inject + +@HiltViewModel +class ProcessingCardViewModel @Inject constructor( + private val cardReadViewModel: CardReaderViewModel, + private val sharedViewModel: SharedViewModel, + private val transProcessViewModel: TransProcessViewModel, + private val pinPadViewModel: PinPadViewModel, + private val emvTransactionViewModel: EmvTransactionProcessViewModel +) : ViewModel() { + + private val _state = MutableStateFlow(ProcessingCardState()) + val state = _state.asStateFlow() + + private val _uiEvent = Channel() + val uiEvent = _uiEvent.receiveAsFlow() + + fun onEvent(event: ProcessingCardEvent) { + when (event) { + ProcessingCardEvent.StartProcess -> { + resetEmvTransResult() + checkCardTransactionType() + } + } + } + + private fun resetEmvTransResult() { + emvTransactionViewModel.resetTransactionStatus() + } + + private fun checkCardTransactionType() { + when (cardReadViewModel.getCardTransactionType()) { + CardTransactionType.MPU -> readMPUCard() + CardTransactionType.EMV -> handlePreEmvProcess() + CardTransactionType.FALLBACK -> readMAGStripe( + isFallback = true, + isNeedPin = true + ) + + CardTransactionType.MAG -> readMAGStripe( + isFallback = false, + isNeedPin = true + ) + + else -> { + Log.d( + "ProcessingCardViewModel", + "error on CardTransactionType : ${cardReadViewModel.getCardTransactionType()}" + ) + } + } + } + + private fun readMPUCard() { + cardReadViewModel.startReadXProcess( + MPUXReadCard.getInstance(), + object : ReadCardResultX { + override fun onSuccess(cardDataX: CardDataX) { + transProcessViewModel.setTransType(sharedViewModel.transactionsType.value) + pinPadViewModel.setTransType(sharedViewModel.transactionsType.value) + val tradeData = TransactionUtil.initMPUTransaction(cardDataX, CardTypeX.IC) + transProcessViewModel.setTradeData(tradeData) + pinPadViewModel.setTradeData(tradeData) + + when { + sharedViewModel._transMenu.value == TransMenu.PRE_AUTH_PARTIAL_VOID -> { + sendUiEvent( + ProcessingCardUiEvent.NavigateToError( + "Function not supported" + ) + ) + return + } + + sharedViewModel.transactionsType.value == TransactionsType.REFUND -> { + sendUiEvent( + ProcessingCardUiEvent.NavigateToError( + "Card not supported" + ) + ) + return + } + } + + sharedViewModel.setCardDataExist(true) + + if (sharedViewModel.getAmountExist().value == false) { + sendUiEvent(ProcessingCardUiEvent.NavigateToInputAmount) + } else { + sendUiEvent(ProcessingCardUiEvent.NavigateToPinPad) + } + } + + override fun onError(code: Int, message: String) { + LogUtil.d(TAG, "Failure at $code message: $message") + sharedViewModel.setCardDataExist(false) + + sendUiEvent( + ProcessingCardUiEvent.ShowDeclineAndBack( + "FAILURE :$message" + ) + ) + } + } + ) + } + + private fun readMAGStripe( + isFallback: Boolean, + isNeedPin: Boolean + ) { + cardReadViewModel.startReadXProcess( + MAGXReadCard.getInstance(), + object : ReadCardResultX { + override fun onSuccess(cardDataX: CardDataX) { + sharedViewModel.setEmvTrans(false) + + if (isNeedPin) { + transProcessViewModel.setTransType(sharedViewModel.transactionsType.value) + pinPadViewModel.setTransType(sharedViewModel.transactionsType.value) + + val tradeData = TransactionUtil.initMagStripeTransaction(cardDataX, isFallback) + + transProcessViewModel.setTradeData(tradeData) + pinPadViewModel.setTradeData(tradeData) + } else { + transProcessViewModel.setTransType(sharedViewModel.transactionsType.value) + + val tradeData = TransactionUtil.initMagStripeTransaction(cardDataX, isFallback) + val processCode = sharedViewModel.processCode.value + val amount = sharedViewModel.amount.value + val payDetail: PayDetail = tradeData.payDetail + + payDetail.amount = POSUtil.getInstance().convertAmount(amount) + + if (!TextUtils.equals(processCode, "")) { + payDetail.processCode = processCode + } + + payDetail.transCVM = TransCVM.SIGNATURE + transProcessViewModel.setTradeData(tradeData) + } + + when { + sharedViewModel.getTransMenu().value == TransMenu.PRE_AUTH_PARTIAL_VOID -> { + sharedViewModel.setTransMenu(null) + sendUiEvent( + ProcessingCardUiEvent.NavigateToError( + "Function not supported" + ) + ) + return + } + + sharedViewModel.transactionsType.value == TransactionsType.REFUND -> { + sendUiEvent( + ProcessingCardUiEvent.NavigateToError( + "Card not supported" + ) + ) + return + } + } + + sharedViewModel.setCardDataExist(true) + + if ( + sharedViewModel.getTransMenu().value != TransMenu.PRE_AUTH_FULL_VOID && + sharedViewModel.getAmountExist().value == false + ) { + sendUiEvent(ProcessingCardUiEvent.NavigateToInputAmount) + } else { + if (isNeedPin) { + sendUiEvent(ProcessingCardUiEvent.NavigateToPinPad) + } else { + sendUiEvent(ProcessingCardUiEvent.NavigateToProcessing) + } + } + } + + override fun onError(code: Int, message: String) { + LogUtil.d(TAG, "Failure at $code message: $message") + + sendUiEvent( + ProcessingCardUiEvent.ShowDeclineAndBack( + "FAILURE :$message" + ) + ) + } + } + ) + } + + private fun handlePreEmvProcess() { + emvTransactionViewModel.transType.value = + sharedViewModel.transactionsType.value + + if (SystemParamsOperation.getInstance().isEmvEnabled) { + prepareEmvTransaction() + sendUiEvent(ProcessingCardUiEvent.NavigateToEmvTransaction) + } else { + sendUiEvent( + ProcessingCardUiEvent.NavigateToError( + "Please enable EMV" + ) + ) + } + } + + private fun prepareEmvTransaction() { + sharedViewModel.setEmvTrans(true) + + val cardType = cardReadViewModel.cardTypeData.value ?: return + + val tradeData: TradeData = Params.newTrade(false) + val payDetail = tradeData.payDetail + + payDetail.cardType = cardType + + emvTransactionViewModel.setTradeData(tradeData) + } + + private fun sendUiEvent(event: ProcessingCardUiEvent) { + viewModelScope.launch { + _uiEvent.send(event) + } + } + + companion object { + private val TAG = ProcessingCardViewModel::class.java.simpleName + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/utils/TransactionUtil.kt b/app/src/main/java/com/mob/utsmyanmar/utils/TransactionUtil.kt new file mode 100644 index 0000000..c5b1894 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/utils/TransactionUtil.kt @@ -0,0 +1,120 @@ +package com.mob.utsmyanmar.utils + +import com.mob.utsmyanmar.config.Constants +import com.utsmyanmar.checkxread.model.CardDataX +import com.utsmyanmar.checkxread.util.CardTypeX +import com.utsmyanmar.paylibs.model.CardInfo +import com.utsmyanmar.paylibs.model.MAGCardInfo +import com.utsmyanmar.paylibs.model.PayDetail +import com.utsmyanmar.paylibs.model.TradeData +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import com.utsmyanmar.paylibs.utils.params.Params +import sunmi.sunmiui.utils.LogUtil + +object TransactionUtil { + private const val TAG = "TransactionUtil" + private const val MPU_CARD_SCHEME = "MPU" + private const val VISA_CARD_SCHEME = "VISA" + private const val MASTER_CARD_SCHEME = "MASTER" + private const val UPI_CARD_SCHEME = "UPI" + private val qrTerminalId: String + get() = SystemParamsOperation.getInstance().secHostTerminalId + + private val qrMerchantId: String + get() = SystemParamsOperation.getInstance().secHostMerchantId + + fun getQRMerchantId(): String { + return qrMerchantId + } + + fun getQRTerminalId(): String { + return qrTerminalId + } + + fun initMPUTransaction( + cardDataX: CardDataX, + cardTypeX: CardTypeX + ): TradeData { + + LogUtil.d(TAG, "CardDataX : $cardDataX") + + val tradeData = Params.newTrade(false) + val payDetail = tradeData.payDetail + + payDetail.cardNo = cardDataX.pan + payDetail.expDate = cardDataX.exp + payDetail.cardType = cardTypeX.value + payDetail.accountType = MPU_CARD_SCHEME + payDetail.cardHolderName = cardDataX.cardHolderName + + val cardInfo = CardInfo() + + val magCardInfo = MAGCardInfo() + magCardInfo.track2Cipher = cardDataX.track2 + + cardInfo.magCardInfo = magCardInfo + + payDetail.cardInfo = cardInfo + + return tradeData + } + + fun initMagStripeTransaction( + cardDataX: CardDataX, + isFallback: Boolean + ): TradeData { + + LogUtil.d(TAG, "CardDataX : $cardDataX") + + val tradeData = Params.newTrade(false) + val payDetail = tradeData.payDetail + + payDetail.cardNo = cardDataX.pan + payDetail.expDate = cardDataX.exp + + payDetail.accountType = when { + cardDataX.pan.startsWith("4") -> VISA_CARD_SCHEME + cardDataX.pan.startsWith("5") -> MASTER_CARD_SCHEME + cardDataX.pan.startsWith("6") -> UPI_CARD_SCHEME + else -> MPU_CARD_SCHEME + } + + payDetail.cardType = if (isFallback) { + -9 + } else { + CardTypeX.MAG.value + } + + payDetail.cardHolderName = cardDataX.cardHolderName + + val cardInfo = CardInfo() + + val magCardInfo = MAGCardInfo() + magCardInfo.track2Cipher = cardDataX.track2 + + cardInfo.magCardInfo = magCardInfo + + payDetail.cardInfo = cardInfo + + return tradeData + } + + fun initWalletTransaction( + transactionType: TransactionsType + ): PayDetail { + + val tradeData = Params.newTrade(true) + val payDetail = tradeData.payDetail + + payDetail.accountType = Constants.WALLET + payDetail.transType = transactionType.name + payDetail.transactionType = transactionType.value + payDetail.terminalNo = qrTerminalId + payDetail.merchantNo = qrMerchantId + payDetail.currencyCode = "104" + payDetail.isCanceled = false + + return payDetail + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/viewmodel/EmvTransactionProcessViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/viewmodel/EmvTransactionProcessViewModel.kt new file mode 100644 index 0000000..79b3941 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/viewmodel/EmvTransactionProcessViewModel.kt @@ -0,0 +1,429 @@ +package com.mob.utsmyanmar.viewmodel + +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.text.TextUtils +import com.utsmyanmar.baselib.emv.EmvParamOperation +import com.utsmyanmar.baselib.repo.Repository +import com.utsmyanmar.baselib.util.enums.EmvResultStatus +import com.utsmyanmar.baselib.viewModel.EmvBaseViewModel +import com.utsmyanmar.checkxread.util.CardTypeX +import com.utsmyanmar.paylibs.model.PayDetail +import com.utsmyanmar.paylibs.model.TradeData +import com.utsmyanmar.paylibs.model.enums.TransCVM +import com.utsmyanmar.paylibs.network.ISOSocket +import com.utsmyanmar.paylibs.reversal.ReversalAction +import com.utsmyanmar.paylibs.reversal.ReversalListener +import com.utsmyanmar.paylibs.system.SingleLiveEvent +import com.utsmyanmar.paylibs.transactions.TransactionsOperation +import com.utsmyanmar.paylibs.transactions.TransactionsOperationListener +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionType +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import dagger.hilt.android.lifecycle.HiltViewModel +import sunmi.sunmiui.utils.LogUtil +import javax.inject.Inject +import com.mob.utsmyanmar.model.TransResultStatus + +@HiltViewModel +class EmvTransactionProcessViewModel @Inject constructor( + private val repository: Repository, + emvParamOperation: EmvParamOperation +) : EmvBaseViewModel(repository, emvParamOperation), ProcessingTransaction { + + private val transResult = SingleLiveEvent() + + override fun handleMsg(msg: Message) { + when (msg.what) { + + PIN_CLICK_NUMBER -> { + showPasswordView(msg.arg1) + } + + PIN_CLICK_CONFIRM -> { + if (!mPayDetail.PINCipher.isNullOrEmpty()) { + importPinInputStatus(0) + } else if (isOfflinePinEntered) { + importPinInputStatus(0) + } else { + importPinInputStatus(2) + } + + if ( + transType.value == TransactionsType.PRE_AUTH_COMPLETE || + transType.value == TransactionsType.PRE_AUTH_VOID || + transType.value == TransactionsType.REFUND + ) { + emvResultStatus.postValue(EmvResultStatus.ON_NEXT_SCREEN) + } + } + + PIN_CLICK_CANCEL -> { + importPinInputStatus(1) + } + + PIN_ERROR -> { + increasedKSN() + importPinInputStatus(3) + handleTransactionFail(msg.arg1, PIN_PAD_FAILED) + } + + PIN_CLICK_EMPTY -> { + emvResultStatus.postValue(EmvResultStatus.PIN_EMPTY) + } + + EMV_APP_SELECT -> { + val candiNames = msg.obj as Array + LogUtil.d(TAG, "CandiNames size:${candiNames.size}") + candiList.value = candiNames + emvResultStatus.postValue(EmvResultStatus.SELECT_APP) + } + + EMV_FINAL_APP_SELECT -> { + importFinalAppSelectStatus(0) + } + + EMV_CONFIRM_CARD_NO -> { + importCardNoStatus(0) + } + + EMV_CERT_VERIFY -> { + importCertStatus(0) + } + + EMV_SIGNATURE -> { + mPayDetail.setIsFreeSign(false) + mPayDetail.transCVM = TransCVM.SIGNATURE + importSignatureStatus(0) + } + + EMV_SHOW_PIN_PAD -> { + startPinProcess() + } + + EMV_ONLINE_PROCESS -> { + handleTransactionProcess() + } + + EMV_ERROR -> { + handleTransactionFail(msg.arg1, msg.obj.toString()) + } + + EMV_TRY_AGAIN -> { + emvResultStatus.postValue(EmvResultStatus.TRY_AGAIN) + } + + EMV_CONFIRM_CODE_VERIFY -> { + emvResultStatus.postValue(EmvResultStatus.CONFIRM_CODE_VERIFY) + } + + EMV_SUCCESS_ONLINE -> { + transResult.postValue(TransResultStatus.SUCCESS) + } + + EMV_FAILURE_ONLINE -> { + transResult.postValue(TransResultStatus.FAIL) + } + + EMV_SUCCESS_OFFLINE -> { + transResult.postValue(TransResultStatus.OFFLINE_SUCCESS) + } + + EMV_FAILURE_OFFLINE -> { + transResult.postValue(TransResultStatus.OFFLINE_FAILURE) + } + } + } + + fun onCancelEmvTransaction() { + LogUtil.e(TAG, "Terminate Transaction : $mProcessStep") + + when (mProcessStep) { + EMV_APP_SELECT -> importAppSelect(-1) + EMV_FINAL_APP_SELECT -> importFinalAppSelectStatus(-1) + EMV_CONFIRM_CARD_NO -> importCardNoStatus(1) + EMV_CERT_VERIFY -> importCertStatus(1) + PIN_ERROR -> importPinInputStatus(3) + EMV_ONLINE_PROCESS -> importOnlineProcessStatus(1) + EMV_SIGNATURE -> importSignatureStatus(1) + EMV_SHOW_PIN_PAD -> importPinInputStatus(1) + } + } + + fun setPayDetail(payDetail: PayDetail) { + mPayDetail = payDetail + mTradeData = TradeData().apply { + setPayDetail(payDetail) + } + } + + fun getPayDetail(): PayDetail { + return mPayDetail + } + + fun resetProcessStepCount() { + mProcessStep = 0 + } + + override fun resetTransactionStatus() { + emvResultStatus.clear() + transResult.clear() + } + + override fun getTransStatus(): SingleLiveEvent { + return transResult + } + + private fun isCardLessTrans(): Boolean { + return transType.value == TransactionsType.VOID || + transType.value == TransactionsType.PRE_AUTH_COMPLETE_VOID || + transType.value == TransactionsType.PRE_AUTH_VOID || + transType.value == TransactionsType.REFUND + } + + fun setPinPadCancel() { + importPinInputStatus(1) + } + + override fun startOnlineProcess() { + isReversal = false + + TransactionsOperation.getInstance() + .getStartOperation(mTradeData, transType.value) + .checkOperation(object : TransactionsOperationListener { + + override fun onSuccess(tradeData: TradeData) { + val payDetailRes = tradeData.payDetail + + payDetailRes.invoiceNo = + SystemParamsOperation.getInstance().incrementInvoiceNum + + LogUtil.d(TAG, "Transaction Operation Success: ${payDetailRes.tradeAnswerCode}") + LogUtil.d(TAG, "Transaction Type: ${payDetailRes.transactionType}") + + if ( + TextUtils.equals(payDetailRes.tradeAnswerCode, RC_APPROVED_V1) || + TextUtils.equals(payDetailRes.tradeAnswerCode, RC_APPROVED_V2) + ) { + when (transType.value) { + TransactionsType.VOID -> processVoidDB(payDetailRes) + TransactionsType.REFUND -> processRefundDB(payDetailRes) + TransactionsType.PRE_AUTH_VOID -> processPreVoidDb(payDetailRes) + TransactionsType.PRE_AUTH_COMPLETE -> processPreCompDb(payDetailRes) + TransactionsType.PRE_AUTH_COMPLETE_VOID -> processPreCompVoidDb(payDetailRes) + else -> insertDB(payDetailRes) + } + + payDetailResult.value = payDetailRes + + if (isCardLessTrans()) { + transResult.value = TransResultStatus.SUCCESS + } else if (payDetailRes.cardType == CardTypeX.MANUAL.value) { + transResult.value = TransResultStatus.SUCCESS + } + + } else { + payDetailResult.value = payDetailRes + + if (isCardLessTrans()) { + transResult.value = TransResultStatus.FAIL + } else if (payDetailRes.cardType == CardTypeX.MANUAL.value) { + transResult.value = TransResultStatus.FAIL + } + } + } + + override fun onReversal(tradeData: TradeData) { + LogUtil.d(TAG, "<<<< On Reversal >>>>") + + isReversal = true + transResult.postValue(TransResultStatus.REVERSAL_PREPARE) + + var reversalDelay = 15000 + + val delayValue = SystemParamsOperation.getInstance().reversalDelay + if (!delayValue.isNullOrEmpty()) { + reversalDelay = "${delayValue}000".toInt() + } + + Handler(Looper.getMainLooper()).postDelayed({ + val payDetailReversal = tradeData.payDetail + + if ( + payDetailReversal.transactionType == TransactionType.SALE || + payDetailReversal.transactionType == TransactionType.VOID || + payDetailReversal.transactionType == TransactionType.REFUND || + payDetailReversal.transactionType == TransactionType.PRE_SALE || + payDetailReversal.transactionType == TransactionType.CASH_ADVANCE || + payDetailReversal.transactionType == TransactionType.PRE_SALE_CANCEL || + payDetailReversal.transactionType == TransactionType.PRE_SALE_COMPLETE || + payDetailReversal.transactionType == TransactionType.PRE_SALE_COMPLETE_VOID + ) { + emvResultStatus.postValue(EmvResultStatus.REVERSAL_PROCESS) + transResult.postValue(TransResultStatus.REVERSAL_PROCESS) + callReversal(tradeData) + } else { + emvResultStatus.postValue(EmvResultStatus.REVERSAL_FAIL) + transResult.postValue(TransResultStatus.REVERSAL_FAIL) + } + }, reversalDelay.toLong()) + } + + override fun onError(message: String) { + LogUtil.e(TAG, "<<<< On Error >>>>") + LogUtil.e(TAG, "error message:$message") + + if (TextUtils.equals(message, TRY_SECONDARY)) { + emvResultStatus.postValue(EmvResultStatus.SECONDARY) + transResult.postValue(TransResultStatus.SECONDARY) + startOnlineProcess() + } else { + importOnlineProcessStatus(1) + setNetWorkErrorDesc(message) + errorCodeMsg.value = message + emvResultStatus.postValue(EmvResultStatus.NETWORK_ERROR) + transResult.postValue(TransResultStatus.NETWORK_ERROR) + } + } + }) + } + + override fun insertDB(payResult: PayDetail) { + payResult.PINCipher = "" + repository.insertPayDetail(payResult) + } + + override fun processVoidDB(payResult: PayDetail) { + mPayDetail.setIsCanceled(true) + updatePayDetail(mPayDetail) + repository.insertPayDetail(updateCurrentDateAndTime(payResult)) + } + + override fun processPreVoidDb(payResult: PayDetail) { + oldTransPayDetail.setIsCanceled(true) + updatePayDetail(oldTransPayDetail) + repository.insertPayDetail(updateCurrentDateAndTime(payResult)) + } + + override fun processPreCompDb(payResult: PayDetail) { + if (oldTransPayDetail.amount == payResult.amount) { + oldTransPayDetail.setIsCanceled(true) + updatePayDetail(oldTransPayDetail) + } else { + oldTransPayDetail.setIsCanceled(false) + updatePayDetail(oldTransPayDetail) + } + + repository.insertPayDetail(updateCurrentDateAndTime(payResult)) + } + + override fun processPreCompVoidDb(payResult: PayDetail) { + mPayDetail.setIsCanceled(true) + updatePayDetail(mPayDetail) + repository.insertPayDetail(updateCurrentDateAndTime(payResult)) + } + + override fun processRefundDB(payResult: PayDetail) { + mPayDetail.setReturnGood(true) + updatePayDetail(mPayDetail) + repository.insertPayDetail(updateCurrentDateAndTime(payResult)) + } + + private fun handleTransactionFail(code: Int, description: String) { + Handler(Looper.getMainLooper()).postDelayed({ + LogUtil.d(TAG, "On handleTransactionFail ::") + + if (isReversal) { + transResult.value = TransResultStatus.REVERSAL_PREPARE + } else { + emvResultStatus.postValue(EmvResultStatus.ERROR) + transResult.value = TransResultStatus.FAIL + errorCodeMsg.postValue("$code:$description") + } + }, 500) + } + + private fun handleTransactionProcess() { + val cardNo = mPayDetail.cardNo + + if (cardNo.isNullOrEmpty()) { + getCardInfo() + } + + if ( + transType.value == TransactionsType.PRE_AUTH_COMPLETE || + transType.value == TransactionsType.PRE_AUTH_VOID || + transType.value == TransactionsType.REFUND + ) { + emvResultStatus.postValue(EmvResultStatus.ON_NEXT_SCREEN) + } else { + emvResultStatus.postValue(EmvResultStatus.SUCCESS) + } + } + + protected fun callReversal(tradeData: TradeData) { + if (transType.value == TransactionsType.VOID) { + tradeData.payDetail.icC55 = "" + } + + ReversalAction.getInstance() + .setData(tradeData) + .enqueue() + .startReversal(object : ReversalListener { + + override fun onSuccessReversal() { + importOnlineProcessStatus(0) + emvResultStatus.postValue(EmvResultStatus.REVERSAL_SUCCESS) + transResult.postValue(TransResultStatus.REVERSAL_SUCCESS) + } + + override fun onNetworkFail(msg: String) { + importOnlineProcessStatus(0) + } + + override fun onFailReversal(msg: String) { + importOnlineProcessStatus(0) + + if (!isSecondCall) { + if (!TextUtils.equals(msg, REVERSAL)) { + emvResultStatus.postValue(EmvResultStatus.REVERSAL_SECONDARY) + transResult.postValue(TransResultStatus.REVERSAL_SECONDARY) + + ISOSocket.getInstance().switchIp() + isSecondCall = true + + callReversal(tradeData) + } else { + saveNeedReversal() + } + } else { + saveNeedReversal() + } + } + }) + } + + private fun saveNeedReversal() { + mPayDetail.setNeedReversal(true) + mPayDetail.transactionType = TransactionType.REVERSAL + mPayDetail.transType = REVERSAL + mPayDetail.pid = null + + repository.insertPayDetail(mPayDetail) + + emvResultStatus.postValue(EmvResultStatus.REVERSAL_FAIL) + transResult.postValue(TransResultStatus.REVERSAL_FAIL) + + isSecondCall = false + } + + companion object { + private val TAG = EmvTransactionProcessViewModel::class.java.simpleName + private const val TRY_SECONDARY = "TRY_SECONDARY" + private const val PIN_PAD_FAILED = "Pin pad failed" + private const val RC_APPROVED_V1 = "00" + private const val RC_APPROVED_V2 = "000" + private const val REVERSAL = "REVERSAL" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/viewmodel/PinPadViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/viewmodel/PinPadViewModel.kt new file mode 100644 index 0000000..24aeeb1 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/viewmodel/PinPadViewModel.kt @@ -0,0 +1,585 @@ +package com.mob.utsmyanmar.viewmodel + +import android.os.Handler +import android.os.Looper +import android.os.RemoteException +import android.util.Log +import android.view.View +import android.view.ViewTreeObserver +import android.widget.TextView +import androidx.lifecycle.ViewModel +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 +import com.utsmyanmar.paylibs.model.TradeData +import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import sunmi.sunmiui.utils.LogUtil +import java.nio.charset.StandardCharsets +import javax.inject.Inject + +@HiltViewModel +class PinPadViewModel @Inject constructor( + private val sunmiPayManager: SunmiPayManager +) : ViewModel() { + companion object { + private const val TAG = "PinPadViewModel" + private const val ON_CONFIRM_CLICK = 2 + private const val ON_CANCEL_CLICK = 3 + private const val ON_ERROR_PIN_PAD = 4 + private const val ON_NUMBER_CLICK = 5 + private const val ON_EMPTY_PIN_BLOCK = 6 + private const val ON_TIMEOUT_PIN_PAD = 7 + private const val ON_ERROR_DUKPT = 8 + } + + private var mPinPadOptV2: PinPadOptV2? = null + private var mPinType = 0 + private var mWidth = 239 + private var mHeight = 130 + private var mInterval = 1 + private var mKeyboardCoordinate = intArrayOf(0, 661) + private var mCancelWidth = 112 + private var mCancelHeight = 112 + private var mCancelCoordinate = intArrayOf(0, 48) + private var dukptIndex = 0 + private var tradeData: TradeData? = null + private var payDetail: PayDetail? = null + private var pan: String = "" + + /* + * UI States + */ + + private val _pinText = MutableStateFlow("") + val pinText = _pinText.asStateFlow() + + private val _alertMsg = MutableStateFlow(null) + val alertMsg = _alertMsg.asStateFlow() + + private val _errorCode = MutableStateFlow(null) + val errorCode = _errorCode.asStateFlow() + + private val _transType = MutableStateFlow(null) + val transType = _transType.asStateFlow() + + private val _pinStatus = + MutableStateFlow(null) + + val pinStatus = _pinStatus.asStateFlow() + + /* + * Trade Data + */ + + fun setTradeData(tradeData: TradeData) { + this.tradeData = tradeData + + payDetail = tradeData.payDetail + + pan = payDetail?.cardNo ?: "" + } + + fun getTradeData(): TradeData? { + return tradeData + } + + fun setPayDetail(payDetail: PayDetail) { + this.payDetail = payDetail + + tradeData = TradeData().apply { + this.payDetail = payDetail + } + } + + fun getPayDetail(): PayDetail? { + return payDetail + } + + fun setTransType(type: TransactionsType?) { + _transType.value = type + } + + /* + * Pin Pad + */ + + fun startPinPadProcess( + customPinPadKeyboard: CustomPinPadKeyboard + ) { + + dukptIndex = + SystemParamsOperation.getInstance() + .tmkIndex + .toInt() + + initData() + + if(mPinPadOptV2 == null){ + _alertMsg.value = "PinPad service is not ready!" + } + + getKSN() + initPinPad(customPinPadKeyboard) + } + + private fun initData() { + + mPinPadOptV2 = sunmiPayManager.pinPadOptV2 + + if (mPinPadOptV2 == null) { + Log.d(TAG, "PinPad service are not ready!") + return; + } + + try { + + val result = mPinPadOptV2?.setAntiExhaustiveProtectionMode(3) + + if ((result ?: -1) >= 0) { + LogUtil.d( + TAG, + "Pin anti exhaustive result:$result" + ) + } else { + LogUtil.d( + TAG, "Pin Anti Exhaustive failed" + ) + } + + } catch (e: RemoteException) { + e.printStackTrace() + } + } + + fun cancelPinPad() { + try { + mPinPadOptV2?.cancelInputPin() + } catch (e: RemoteException) { + throw RuntimeException(e) + } + } + + /* + * KSN + */ + + private fun increasedKSN() { + + try { + + val securityOptV2 = sunmiPayManager.securityOptV2; + if(securityOptV2 == null){ + Log.d(TAG, "Security service is not ready!") + return; + } + val result = securityOptV2.dukptIncreaseKSN(dukptIndex); + + if (result == 0) { + LogUtil.d( + TAG, + "--------KSN increased Success-----" + ) + } else { + LogUtil.d( + TAG, + "--------KSN result:$result-----" + ) + } + + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun getKSN() { + + try { + val securityOptV2 = sunmiPayManager.securityOptV2; + if(securityOptV2 == null){ + Log.d(TAG, "Security service is not ready!") + return; + } + + val dataOut = ByteArray(10) + + val result = securityOptV2.dukptCurrentKSN(dukptIndex, dataOut) + + if (result == 0) { + + val ksnStr = + ByteUtil.bytes2HexStr(dataOut) + + LogUtil.d(TAG, "ksnStr: $ksnStr") + + payDetail?.reference = ksnStr + payDetail?.tempKSN = ksnStr + + LogUtil.d( + TAG, + "final ksnStr: $ksnStr" + ) + + } else { + + LogUtil.e( + TAG, + "Result code for current KSN:$result" + ) + } + + } catch (e: Exception) { + e.printStackTrace() + } + } + + /* + * Init Pin Pad + */ + + 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 + if (SunmiSDK.getInstance().checkCardExist() != 2) { + timeout = Constants.TIMEOUT + } + try { + + 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 + + val length = pan.length + + val panBytes = pan.substring(length - 13, length - 1).toByteArray(StandardCharsets.US_ASCII) + + config.pan = panBytes + + val result = mPinPadOptV2?.initPinPad(config, mPinPadListener) + + LogUtil.e(TAG, "result:$result") + + result?.let { + getKeyboardCoordinate( + it, + customPinPadKeyboard + ) + } + + _pinText.value = "" + + customPinPadKeyboard.keepScreenOn = true + + customPinPadKeyboard.setKeyBoard(result) + + customPinPadKeyboard.visibility = View.VISIBLE + + } catch (e: Exception) { + e.printStackTrace() + } + } + + /* + * Keyboard + */ + + private fun getKeyboardCoordinate( + keyBoardText: String, + customPinPadKeyboard: CustomPinPadKeyboard + ) { + + customPinPadKeyboard + .viewTreeObserver + .addOnGlobalLayoutListener( + object : ViewTreeObserver.OnGlobalLayoutListener { + + override fun onGlobalLayout() { + + customPinPadKeyboard + .viewTreeObserver + .removeOnGlobalLayoutListener(this) + + val textView: TextView = + customPinPadKeyboard.key_0 + + textView.getLocationOnScreen( + mKeyboardCoordinate + ) + + mWidth = textView.width + mHeight = textView.height + + mInterval = 1 + + importPinPadData(keyBoardText) + } + } + ) + } + + private fun importPinPadData(text: String) { + + val pinPadData = PinPadDataV2() + + pinPadData.numX = mKeyboardCoordinate[0] + pinPadData.numY = mKeyboardCoordinate[1] + + pinPadData.numW = mWidth + pinPadData.numH = mHeight + + pinPadData.lineW = mInterval + + pinPadData.cancelX = mCancelCoordinate[0] + pinPadData.cancelY = mCancelCoordinate[1] + + pinPadData.cancelW = mCancelWidth + pinPadData.cancelH = mCancelHeight + + pinPadData.lineW = 0 + + pinPadData.rows = 5 + pinPadData.clos = 3 + + keyMap(text, pinPadData) + + try { + mPinPadOptV2?.importPinPadData(pinPadData) + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun keyMap( + str: String, + data: PinPadDataV2 + ) { + + data.keyMap = ByteArray(64) + + var j = 0 + + for (i in 0 until 15) { + + when (i) { + + 9, 12 -> { + data.keyMap[i] = 0x1B + } + + 13 -> { + data.keyMap[i] = 0x0C + } + + 11, 14 -> { + data.keyMap[i] = 0x0D + } + + else -> { + data.keyMap[i] = + str[j].code.toByte() + + j++ + } + } + } + } + + /* + * Password View + */ + + private fun showPasswordView(len: Int) { + + val sb = StringBuilder() + + repeat(len) { + sb.append("*") + } + + _pinText.value = sb.toString() + } + + /* + * Handler + */ + + private val handler = + Handler(Looper.getMainLooper()) { msg -> + when (msg.what) { + 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 + } + + ON_ERROR_PIN_PAD -> { + LogUtil.d(TAG, "ON ERROR CODE: ${msg.arg1}") + _errorCode.value = msg.arg1 + _pinStatus.value = PinPadStatus.ON_ERROR + } + + ON_EMPTY_PIN_BLOCK -> { + _pinStatus.value = PinPadStatus.ON_EMPTY + } + + ON_TIMEOUT_PIN_PAD -> { + _pinStatus.value = PinPadStatus.ON_TIMEOUT + } + + ON_ERROR_DUKPT -> { + increasedKSN() + _alertMsg.value = "Try Again!" + _pinStatus.value = PinPadStatus.ON_ERROR_DUKPT + } + } + + true + } + + /* + * Listener + */ + + private val mPinPadListener = + object : PinPadListenerV2.Stub() { + + override fun onPinLength(len: Int) { + handler.obtainMessage( + ON_NUMBER_CLICK, + len, + 0 + ).sendToTarget() + } + + override fun onConfirm( + status: Int, + pinBlock: ByteArray? + ) { + + LogUtil.e( + TAG, + "onConfirm status:$status" + ) + + if (pinBlock != null) { + + increasedKSN() + + val hexStr = + ByteUtil.bytes2HexStr(pinBlock) + + if ( + SunmiSDK.getInstance().checkCardExist() == 2 || + payDetail?.cardType == AidlConstants.CardType.MAGNETIC.value || + payDetail?.cardType == -9 + ) { + + payDetail?.pinCipher = hexStr + + if ( + transType.value == TransactionsType.PRE_AUTH_COMPLETE || + transType.value == TransactionsType.PRE_AUTH_VOID || + transType.value == TransactionsType.REFUND + ) { + + _pinStatus.value = + PinPadStatus.ON_NEXT_SCREEN + + } else { + + handler.obtainMessage( + ON_CONFIRM_CLICK + ).sendToTarget() + } + + } else { + + _pinStatus.value = + PinPadStatus.ON_CARD_REMOVED + } + + } else { + + handler.obtainMessage( + ON_EMPTY_PIN_BLOCK + ).sendToTarget() + } + } + + override fun onCancel() { + handler.obtainMessage( + ON_CANCEL_CLICK + ).sendToTarget() + } + + override fun onError(code: Int) { + + val msg = + AidlErrorCodeV2 + .valueOf(code) + .msg + + LogUtil.d( + TAG, + "error code:$code - message :$msg" + ) + + when (code) { + + -60001 -> { + handler.obtainMessage( + ON_TIMEOUT_PIN_PAD + ).sendToTarget() + } + + -3025 -> { + handler.obtainMessage( + ON_ERROR_DUKPT + ).sendToTarget() + } + + else -> { + handler.obtainMessage( + ON_ERROR_PIN_PAD, + code, + code, + code + ).sendToTarget() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt index 16b1edd..af6c241 100644 --- a/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt @@ -52,11 +52,13 @@ class TransProcessViewModel @Inject constructor( val transResultStatus = _transResultStatus.asStateFlow() - private val _transType = - MutableStateFlow(null) + private val _transType = MutableStateFlow(null) - val transType = - _transType.asStateFlow() + val transType =_transType.asStateFlow() + + fun setTransType(type: TransactionsType?) { + _transType.value = type + } private val _printStatus = MutableStateFlow(PrintStatus.FIRST_PRINT)