new ViewModels added
This commit is contained in:
parent
d3a6ddbc37
commit
37a1a2e38d
13
app/src/main/java/com/mob/utsmyanmar/config/Constants.kt
Normal file
13
app/src/main/java/com/mob/utsmyanmar/config/Constants.kt
Normal 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"
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
12
app/src/main/java/com/mob/utsmyanmar/model/PinPadStatus.kt
Normal file
12
app/src/main/java/com/mob/utsmyanmar/model/PinPadStatus.kt
Normal 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,
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
package com.mob.utsmyanmar.ui.processing_card
|
||||
|
||||
sealed interface ProcessingCardEvent {
|
||||
data object StartProcess : ProcessingCardEvent
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
120
app/src/main/java/com/mob/utsmyanmar/utils/TransactionUtil.kt
Normal file
120
app/src/main/java/com/mob/utsmyanmar/utils/TransactionUtil.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -52,11 +52,13 @@ class TransProcessViewModel @Inject constructor(
|
||||
val transResultStatus =
|
||||
_transResultStatus.asStateFlow()
|
||||
|
||||
private val _transType =
|
||||
MutableStateFlow<TransactionsType?>(null)
|
||||
private val _transType = MutableStateFlow<TransactionsType?>(null)
|
||||
|
||||
val transType =
|
||||
_transType.asStateFlow()
|
||||
val transType =_transType.asStateFlow()
|
||||
|
||||
fun setTransType(type: TransactionsType?) {
|
||||
_transType.value = type
|
||||
}
|
||||
|
||||
private val _printStatus =
|
||||
MutableStateFlow(PrintStatus.FIRST_PRINT)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user