new ViewModels added

This commit is contained in:
moon 2026-05-12 19:17:21 +06:30
parent d3a6ddbc37
commit 37a1a2e38d
15 changed files with 1711 additions and 31 deletions

View File

@ -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"
}

View File

@ -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
}
}

View File

@ -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,
}

View File

@ -1,15 +1,28 @@
package com.mob.utsmyanmar.ui.cardwaiting package com.mob.utsmyanmar.ui.cardwaiting
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
import androidx.compose.foundation.background 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.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding 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.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.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -17,12 +30,17 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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 import com.mob.utsmyanmar.ui.theme.White
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CardWaitingScreen( fun CardWaitingScreen(
viewModel: CardWaitingViewModel, viewModel: CardWaitingViewModel,
@ -60,34 +78,158 @@ fun CardWaitingScreen(
viewModel.onBackPressed() viewModel.onBackPressed()
} }
Column( Scaffold(
modifier = Modifier topBar = {
.fillMaxSize() CenterAlignedTopAppBar(
.background(White) title = {
.padding(24.dp), Text(
horizontalAlignment = Alignment.CenterHorizontally text = "CARD CAPTURE",
) { color = White,
Text( fontSize = 16.sp,
text = "Card Capture", fontWeight = FontWeight.SemiBold
fontSize = 22.sp, )
fontWeight = FontWeight.Bold },
) navigationIcon = {
IconButton(onClick = onBack) {
Spacer(modifier = Modifier.height(40.dp)) Icon(
painter = painterResource(R.drawable.ic_left_arrow),
Text( contentDescription = "Back",
text = uiState.alertMessage, tint = White
fontSize = 20.sp, )
textAlign = TextAlign.Center }
) },
colors = TopAppBarDefaults.topAppBarColors(
Spacer(modifier = Modifier.weight(1f)) containerColor = Primary
)
Button( )
onClick = viewModel::onManualEntryClick, },
modifier = Modifier.fillMaxWidth() 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
)
}
}
}
} }
} }
} }

View File

@ -1,5 +1,6 @@
package com.mob.utsmyanmar.ui.cardwaiting package com.mob.utsmyanmar.ui.cardwaiting
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -173,6 +174,7 @@ class CardWaitingViewModel(
65, 65,
object : CheckCardResultX { object : CheckCardResultX {
override fun onSuccess(cardType: CardTypeX, isMPU: Boolean) { override fun onSuccess(cardType: CardTypeX, isMPU: Boolean) {
Log.d("CardWaitingViewModel", "on success cardType=$cardType and isMpu=$isMPU")
when { when {
!isFallback && cardType == CardTypeX.MAG -> { !isFallback && cardType == CardTypeX.MAG -> {
if (SystemParamsOperation.getInstance().isMagStripeEnabled) { if (SystemParamsOperation.getInstance().isMagStripeEnabled) {

View File

@ -0,0 +1,5 @@
package com.mob.utsmyanmar.ui.processing_card
sealed interface ProcessingCardEvent {
data object StartProcess : ProcessingCardEvent
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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
)

View File

@ -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
}

View File

@ -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<ProcessingCardUiEvent>()
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
}
}

View File

@ -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
}
}

View File

@ -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<TransResultStatus>()
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<String>
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<TransResultStatus> {
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"
}
}

View File

@ -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<String?>(null)
val alertMsg = _alertMsg.asStateFlow()
private val _errorCode = MutableStateFlow<Int?>(null)
val errorCode = _errorCode.asStateFlow()
private val _transType = MutableStateFlow<TransactionsType?>(null)
val transType = _transType.asStateFlow()
private val _pinStatus =
MutableStateFlow<PinPadStatus?>(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()
}
}
}
}
}

View File

@ -52,11 +52,13 @@ class TransProcessViewModel @Inject constructor(
val transResultStatus = val transResultStatus =
_transResultStatus.asStateFlow() _transResultStatus.asStateFlow()
private val _transType = private val _transType = MutableStateFlow<TransactionsType?>(null)
MutableStateFlow<TransactionsType?>(null)
val transType = val transType =_transType.asStateFlow()
_transType.asStateFlow()
fun setTransType(type: TransactionsType?) {
_transType.value = type
}
private val _printStatus = private val _printStatus =
MutableStateFlow(PrintStatus.FIRST_PRINT) MutableStateFlow(PrintStatus.FIRST_PRINT)