Compare commits
No commits in common. "d44163b6011d1e5c3b17ab33fcf240aa65203fb5" and "d3a6ddbc379d8352eacede9b49dcbbc8106c8409" have entirely different histories.
d44163b601
...
d3a6ddbc37
@ -1,13 +0,0 @@
|
|||||||
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"
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
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,28 +1,15 @@
|
|||||||
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
|
||||||
@ -30,17 +17,12 @@ 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,
|
||||||
@ -78,158 +60,34 @@ fun CardWaitingScreen(
|
|||||||
viewModel.onBackPressed()
|
viewModel.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Column(
|
||||||
topBar = {
|
modifier = Modifier
|
||||||
CenterAlignedTopAppBar(
|
.fillMaxSize()
|
||||||
title = {
|
.background(White)
|
||||||
Text(
|
.padding(24.dp),
|
||||||
text = "CARD CAPTURE",
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
color = White,
|
) {
|
||||||
fontSize = 16.sp,
|
Text(
|
||||||
fontWeight = FontWeight.SemiBold
|
text = "Card Capture",
|
||||||
)
|
fontSize = 22.sp,
|
||||||
},
|
fontWeight = FontWeight.Bold
|
||||||
navigationIcon = {
|
)
|
||||||
IconButton(onClick = onBack) {
|
|
||||||
Icon(
|
Spacer(modifier = Modifier.height(40.dp))
|
||||||
painter = painterResource(R.drawable.ic_left_arrow),
|
|
||||||
contentDescription = "Back",
|
Text(
|
||||||
tint = White
|
text = uiState.alertMessage,
|
||||||
)
|
fontSize = 20.sp,
|
||||||
}
|
textAlign = TextAlign.Center
|
||||||
},
|
)
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
|
||||||
containerColor = Primary
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
)
|
|
||||||
)
|
Button(
|
||||||
},
|
onClick = viewModel::onManualEntryClick,
|
||||||
containerColor = White
|
modifier = Modifier.fillMaxWidth()
|
||||||
) { 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,6 +1,5 @@
|
|||||||
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
|
||||||
@ -174,7 +173,6 @@ 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) {
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
package com.mob.utsmyanmar.ui.navigation
|
package com.mob.utsmyanmar.ui.navigation
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
@ -10,26 +8,17 @@ import androidx.navigation.NavType
|
|||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.mob.utsmyanmar.model.ProcessCode
|
|
||||||
import com.mob.utsmyanmar.ui.amount.AmountScreen
|
import com.mob.utsmyanmar.ui.amount.AmountScreen
|
||||||
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen
|
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen
|
||||||
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel
|
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel
|
||||||
import com.mob.utsmyanmar.ui.dashboard.DashboardScreen
|
import com.mob.utsmyanmar.ui.dashboard.DashboardScreen
|
||||||
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardRoute
|
|
||||||
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel
|
|
||||||
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
|
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
|
||||||
import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel
|
|
||||||
import com.mob.utsmyanmar.viewmodel.PinPadViewModel
|
|
||||||
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
|
|
||||||
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavGraph(
|
fun AppNavGraph(
|
||||||
navController: NavHostController
|
navController: NavHostController
|
||||||
) {
|
) {
|
||||||
val activity = LocalContext.current as ComponentActivity
|
|
||||||
|
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = Routes.Dashboard.route
|
startDestination = Routes.Dashboard.route
|
||||||
@ -57,29 +46,19 @@ fun AppNavGraph(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
) { backStackEntry ->
|
) { backStackEntry ->
|
||||||
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
|
||||||
|
|
||||||
AmountScreen(
|
AmountScreen(
|
||||||
action = backStackEntry.arguments?.getString("action").orEmpty(),
|
action = backStackEntry.arguments?.getString("action").orEmpty(),
|
||||||
onBackClick = { navController.popBackStack() },
|
onBackClick = { navController.popBackStack() },
|
||||||
onCancelClick = { navController.popBackStack() },
|
onCancelClick = { navController.popBackStack() },
|
||||||
onNextClick = { amount ->
|
onNextClick = {
|
||||||
sharedViewModel.amount.value = amount
|
|
||||||
sharedViewModel.setAmountExist(true)
|
|
||||||
sharedViewModel.setCardDataExist(false)
|
|
||||||
sharedViewModel.setTransMenu(null)
|
|
||||||
sharedViewModel.transactionsType.value = TransactionsType.SALE
|
|
||||||
sharedViewModel.processCode.value =
|
|
||||||
ProcessCode.SALE_PURCHASE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
|
|
||||||
|
|
||||||
navController.navigate(Routes.CardWaiting.route)
|
navController.navigate(Routes.CardWaiting.route)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(Routes.CardWaiting.route) {
|
composable(Routes.CardWaiting.route) {
|
||||||
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
val sharedViewModel: SharedViewModel = hiltViewModel()
|
||||||
val cardReaderViewModel: CardReaderViewModel = hiltViewModel(activity)
|
val cardReaderViewModel: CardReaderViewModel = hiltViewModel()
|
||||||
val cardWaitingViewModel: CardWaitingViewModel = viewModel(
|
val cardWaitingViewModel: CardWaitingViewModel = viewModel(
|
||||||
factory = CardWaitingViewModel.provideFactory(
|
factory = CardWaitingViewModel.provideFactory(
|
||||||
cardReadViewModel = cardReaderViewModel,
|
cardReadViewModel = cardReaderViewModel,
|
||||||
@ -90,9 +69,7 @@ fun AppNavGraph(
|
|||||||
CardWaitingScreen(
|
CardWaitingScreen(
|
||||||
viewModel = cardWaitingViewModel,
|
viewModel = cardWaitingViewModel,
|
||||||
onManualEntry = {},
|
onManualEntry = {},
|
||||||
onProcessingCard = {
|
onProcessingCard = {},
|
||||||
navController.navigate(Routes.ProcessingCard.route)
|
|
||||||
},
|
|
||||||
onTimeout = { navController.popBackStack() },
|
onTimeout = { navController.popBackStack() },
|
||||||
onBack = { navController.popBackStack() },
|
onBack = { navController.popBackStack() },
|
||||||
onMain = {
|
onMain = {
|
||||||
@ -105,33 +82,5 @@ fun AppNavGraph(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(Routes.ProcessingCard.route) {
|
|
||||||
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
|
||||||
val cardReaderViewModel: CardReaderViewModel = hiltViewModel(activity)
|
|
||||||
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
|
|
||||||
val pinPadViewModel: PinPadViewModel = hiltViewModel(activity)
|
|
||||||
val emvTransactionViewModel: EmvTransactionProcessViewModel = hiltViewModel(activity)
|
|
||||||
val processingCardViewModel: ProcessingCardViewModel = viewModel(
|
|
||||||
factory = ProcessingCardViewModel.provideFactory(
|
|
||||||
cardReadViewModel = cardReaderViewModel,
|
|
||||||
sharedViewModel = sharedViewModel,
|
|
||||||
transProcessViewModel = transProcessViewModel,
|
|
||||||
pinPadViewModel = pinPadViewModel,
|
|
||||||
emvTransactionViewModel = emvTransactionViewModel
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
ProcessingCardRoute(
|
|
||||||
viewModel = processingCardViewModel,
|
|
||||||
onNavigatePinPad = {},
|
|
||||||
onNavigateInputAmount = { navController.popBackStack(Routes.Amount.route, false) },
|
|
||||||
onNavigateProcessing = {},
|
|
||||||
onNavigateEmvTransaction = {},
|
|
||||||
onNavigateError = {},
|
|
||||||
onBack = { navController.popBackStack() },
|
|
||||||
onShowDecline = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,5 +6,4 @@ sealed class Routes(val route: String) {
|
|||||||
fun createRoute(action: String): String = "amount/$action"
|
fun createRoute(action: String): String = "amount/$action"
|
||||||
}
|
}
|
||||||
data object CardWaiting : Routes("card_waiting")
|
data object CardWaiting : Routes("card_waiting")
|
||||||
data object ProcessingCard : Routes("processing_card")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
package com.mob.utsmyanmar.ui.processing_card
|
|
||||||
|
|
||||||
sealed interface ProcessingCardEvent {
|
|
||||||
data object StartProcess : ProcessingCardEvent
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
package com.mob.utsmyanmar.ui.processing_card
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ProcessingCardRoute(
|
|
||||||
viewModel: ProcessingCardViewModel,
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package com.mob.utsmyanmar.ui.processing_card
|
|
||||||
|
|
||||||
data class ProcessingCardState(
|
|
||||||
val title: String = "Processing Card",
|
|
||||||
val message: String = "Please wait...",
|
|
||||||
val isLoading: Boolean = true
|
|
||||||
)
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@ -1,285 +0,0 @@
|
|||||||
package com.mob.utsmyanmar.ui.processing_card
|
|
||||||
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
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 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
|
|
||||||
|
|
||||||
class ProcessingCardViewModel(
|
|
||||||
private val cardReadViewModel: CardReaderViewModel,
|
|
||||||
private val sharedViewModel: SharedViewModel,
|
|
||||||
private val transProcessViewModel: TransProcessViewModel,
|
|
||||||
private val pinPadViewModel: PinPadViewModel,
|
|
||||||
private val emvTransactionViewModel: EmvTransactionProcessViewModel
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = ProcessingCardViewModel::class.java.simpleName
|
|
||||||
|
|
||||||
fun provideFactory(
|
|
||||||
cardReadViewModel: CardReaderViewModel,
|
|
||||||
sharedViewModel: SharedViewModel,
|
|
||||||
transProcessViewModel: TransProcessViewModel,
|
|
||||||
pinPadViewModel: PinPadViewModel,
|
|
||||||
emvTransactionViewModel: EmvTransactionProcessViewModel
|
|
||||||
): ViewModelProvider.Factory {
|
|
||||||
return object : ViewModelProvider.Factory {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
return ProcessingCardViewModel(
|
|
||||||
cardReadViewModel = cardReadViewModel,
|
|
||||||
sharedViewModel = sharedViewModel,
|
|
||||||
transProcessViewModel = transProcessViewModel,
|
|
||||||
pinPadViewModel = pinPadViewModel,
|
|
||||||
emvTransactionViewModel = emvTransactionViewModel
|
|
||||||
) as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,429 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,585 +0,0 @@
|
|||||||
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,13 +52,11 @@ class TransProcessViewModel @Inject constructor(
|
|||||||
val transResultStatus =
|
val transResultStatus =
|
||||||
_transResultStatus.asStateFlow()
|
_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 =
|
private val _printStatus =
|
||||||
MutableStateFlow(PrintStatus.FIRST_PRINT)
|
MutableStateFlow(PrintStatus.FIRST_PRINT)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user