card reader setup

This commit is contained in:
moon 2026-05-12 15:41:41 +06:30
parent 32c4bf8fb7
commit d3a6ddbc37
41 changed files with 2723 additions and 14 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@ -1,6 +1,8 @@
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
} }
android { android {
@ -44,10 +46,12 @@ dependencies {
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.foundation) implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)
testImplementation(libs.junit) testImplementation(libs.junit)
@ -57,4 +61,18 @@ dependencies {
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.tooling)
} implementation("com.google.dagger:hilt-android:2.59.2")
ksp(libs.hilt.android.compiler)
implementation(libs.rxjava)
implementation(libs.rxandroid)
// local libs
implementation(project(":baselib"))
implementation(project(":mpulib"))
implementation(project(":paylibs"))
implementation(project(":paysdk-lib"))
implementation(project(":qrgen-lib"))
implementation(project(":sunmiui-lib"))
implementation(project(":ecr"))
implementation(project(":xpay"))
implementation(project(":cmhl"))
}

View File

@ -2,7 +2,32 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.sunmi.perm.LED" />
<uses-permission android:name="com.sunmi.perm.MSR" />
<uses-permission android:name="com.sunmi.perm.ICC" />
<uses-permission android:name="com.sunmi.perm.PINPAD" />
<uses-permission android:name="com.sunmi.perm.SECURITY" />
<uses-permission android:name="com.sunmi.perm.CONTACTLESS_CARD" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application <application
android:name="com.mob.utsmyanmar.MyApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@ -12,7 +37,7 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.MOBPOS"> android:theme="@style/Theme.MOBPOS">
<activity <activity
android:name=".MainActivity" android:name="com.mob.utsmyanmar.MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.MOBPOS"> android:theme="@style/Theme.MOBPOS">
@ -24,4 +49,4 @@
</activity> </activity>
</application> </application>
</manifest> </manifest>

View File

@ -7,7 +7,9 @@ import androidx.activity.enableEdgeToEdge
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.mob.utsmyanmar.ui.navigation.AppNavGraph import com.mob.utsmyanmar.ui.navigation.AppNavGraph
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@ -0,0 +1,13 @@
package com.mob.utsmyanmar
import com.mob.utsmyanmar.utils.AppContextHolder
import com.utsmyanmar.baselib.BaseApplication
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApplication : BaseApplication() {
override fun onCreate() {
super.onCreate()
AppContextHolder.init(this)
}
}

View File

@ -0,0 +1,5 @@
package com.mob.utsmyanmar.model
enum class CardTransactionType {
MPU, EMV, MAG, FALLBACK
}

View File

@ -0,0 +1,39 @@
package com.mob.utsmyanmar.model
object ProcessCode {
const val BALANCE_INQUIRY = "31"
const val SALE_PURCHASE = "00"
const val SALE_VOID = "02"
const val PRE_AUTH_SALE = "30"
const val PRE_AUTH_VOID = "30"
const val PRE_AUTH_COMPLETE = "30"
const val PRE_AUTH_COMPLETE_VOID = "30"
const val CASH_ADVANCE = "01" // 01-MPU 17-TTIP
const val FUND_TRANSFER = "61"
const val PIN_CHANGE = "70"
const val SETTLEMENT = "92"
const val SIGN_ON = "95"
const val CASH_DEPOSIT = "21"
const val REFUND = "20"
const val SMART = "00"
const val SAVING = "10"
const val CURRENT = "20"
const val TO_ACCOUNT = "00"
}

View File

@ -0,0 +1,6 @@
package com.mob.utsmyanmar.model
enum class SettlementType {
NORMAL,
CUT_OVER
}

View File

@ -0,0 +1,29 @@
package com.mob.utsmyanmar.model
enum class TransResultStatus {
CLICK_CONFIRM,
SUCCESS,
FAIL,
OFFLINE_SUCCESS,
OFFLINE_FAILURE,
SECONDARY,
REVERSAL_PROCESS,
REVERSAL_PREPARE,
REVERSAL_SECONDARY,
REVERSAL_THIRD,
REVERSAL_FAIL,
REVERSAL_SUCCESS,
BEFORE_REVERSAL,
PIN_PAD_CANCEL,
PIN_PAD_CONFIRM,
PIN_PAD_ERROR,
PIN_MISMATCH,
PIN_MISMATCH_END,
REMOVED_CARD,
EMV_ERROR,
ERROR,
RETRY_AGAIN,
NEXT_SCREEN,
EMPTY_PIN,
NETWORK_ERROR
}

View File

@ -0,0 +1,7 @@
package com.mob.utsmyanmar.model.ecr
enum class ECRResultStatus {
USER_CANCEL,
TIME_OUT,
RESPONSE_RECEIVED
}

View File

@ -137,7 +137,7 @@ fun AmountScreen(
) )
) { ) {
Text( Text(
text = "Next", text = "Enter",
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )

View File

@ -0,0 +1,9 @@
package com.mob.utsmyanmar.ui.cardwaiting
sealed interface CardWaitingEvent {
data object GoManualEntry : CardWaitingEvent
data object GoProcessingCard : CardWaitingEvent
data object GoTimeout : CardWaitingEvent
data object GoMain : CardWaitingEvent
data object GoBack : CardWaitingEvent
}

View File

@ -0,0 +1,93 @@
package com.mob.utsmyanmar.ui.cardwaiting
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.ui.theme.White
@Composable
fun CardWaitingScreen(
viewModel: CardWaitingViewModel,
onManualEntry: () -> Unit,
onProcessingCard: () -> Unit,
onTimeout: () -> Unit,
onBack: () -> Unit,
onMain: () -> Unit
) {
val uiState by viewModel.uiState.collectAsState()
LaunchedEffect(viewModel) {
viewModel.events.collect { event ->
when (event) {
CardWaitingEvent.GoManualEntry -> onManualEntry()
CardWaitingEvent.GoProcessingCard -> onProcessingCard()
CardWaitingEvent.GoTimeout -> onTimeout()
CardWaitingEvent.GoMain -> onMain()
CardWaitingEvent.GoBack -> onBack()
}
}
}
LaunchedEffect(viewModel) {
viewModel.onScreenResume()
}
DisposableEffect(viewModel) {
onDispose {
viewModel.onScreenPause()
}
}
BackHandler {
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()
) {
Text("Manual Entry")
}
}
}

View File

@ -0,0 +1,7 @@
package com.mob.utsmyanmar.ui.cardwaiting
data class CardWaitingUiState(
val alertMessage: String = "Please insert, tap, or swipe card",
val isLoading: Boolean = false,
val isFallback: Boolean = false
)

View File

@ -0,0 +1,293 @@
package com.mob.utsmyanmar.ui.cardwaiting
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.mob.utsmyanmar.model.CardTransactionType
import com.mob.utsmyanmar.model.ecr.ECRResultStatus
import com.mob.utsmyanmar.utils.CoreUtils
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.sunmi.pay.hardware.aidl.AidlConstants
import com.utsmyanmar.checkxread.checkcard.CheckCardResultX
import com.utsmyanmar.checkxread.util.CardTypeX
import com.utsmyanmar.ecr.ECRHelper
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class CardWaitingViewModel(
private val cardReadViewModel: CardReaderViewModel,
private val sharedViewModel: SharedViewModel
) : ViewModel() {
companion object {
fun provideFactory(
cardReadViewModel: CardReaderViewModel,
sharedViewModel: SharedViewModel
): ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CardWaitingViewModel(
cardReadViewModel = cardReadViewModel,
sharedViewModel = sharedViewModel
) as T
}
}
}
}
private val _uiState = MutableStateFlow(CardWaitingUiState())
val uiState = _uiState.asStateFlow()
private val _events = Channel<CardWaitingEvent>()
val events = _events.receiveAsFlow()
private var retryCounter = 0
private var fallbackCounter = 0
private var fallbackEnabled = false
private var readerInitJob: Job? = null
fun onScreenResume() {
retryCounter = 0
fallbackCounter = SystemParamsOperation.getInstance().fallbackCounter
fallbackEnabled = SystemParamsOperation.getInstance().fallbackEnabled
if (sharedViewModel.transactionsType.value == TransactionsType.REFUND) {
sharedViewModel.enableCardStatusIcon(false, false, false, false)
} else {
sharedViewModel.enableCardStatusIcon(true, true, true, false)
}
val isFallback = sharedViewModel.getIsFallback().value == true
if (isFallback) {
_uiState.update {
it.copy(
alertMessage = "Fallback!\nPlease stripe!",
isFallback = true
)
}
}
startCardReadWhenReady(isFallback)
}
fun onScreenPause() {
readerInitJob?.cancel()
stopCardReading()
}
fun onBackPressed() {
if (sharedViewModel.isEcr.value == true) {
sharedViewModel.isEcr.postValue(false)
CoreUtils.getInstance(sharedViewModel).responseRejectMsg("Transaction cancelled")
sharedViewModel.isEcrFinished.postValue(true)
}
stopCardReading()
viewModelScope.launch {
_events.send(CardWaitingEvent.GoBack)
}
}
fun onManualEntryClick() {
viewModelScope.launch {
_events.send(CardWaitingEvent.GoManualEntry)
}
}
private fun startCardReadWhenReady(isFallback: Boolean) {
readerInitJob?.cancel()
readerInitJob = viewModelScope.launch {
if (cardReadViewModel.isReaderReady()) {
setupCardReadProcess(isFallback)
return@launch
}
_uiState.update {
it.copy(
alertMessage = "Initializing card reader...",
isLoading = true
)
}
repeat(10) {
delay(300)
if (cardReadViewModel.isReaderReady()) {
_uiState.update { state ->
state.copy(
alertMessage = if (isFallback) {
"Fallback!\nPlease stripe!"
} else {
"Please insert, tap, or swipe card"
},
isLoading = false
)
}
setupCardReadProcess(isFallback)
return@launch
}
}
_uiState.update {
it.copy(
alertMessage = "Card reader unavailable.\nPlease wait and try again.",
isLoading = false
)
}
}
}
private fun setupCardReadProcess(isFallback: Boolean) {
initCheckCard(isFallback)
}
private fun initCheckCard(isFallback: Boolean) {
var allType =
AidlConstants.CardType.NFC.value or
AidlConstants.CardType.IC.value or
AidlConstants.CardType.MAGNETIC.value
if (isFallback) {
allType = AidlConstants.CardType.MAGNETIC.value
} else if (
SystemParamsOperation.getInstance().isMagStripeEnabled &&
!SystemParamsOperation.getInstance().isNfcEnabled
) {
allType =
AidlConstants.CardType.IC.value or
AidlConstants.CardType.MAGNETIC.value
}
cardReadViewModel.startCheckXProcess(
allType,
65,
object : CheckCardResultX {
override fun onSuccess(cardType: CardTypeX, isMPU: Boolean) {
when {
!isFallback && cardType == CardTypeX.MAG -> {
if (SystemParamsOperation.getInstance().isMagStripeEnabled) {
cardReadViewModel.setCardTransactionType(CardTransactionType.MAG)
} else {
_uiState.update {
it.copy(alertMessage = "Mag stripe not allowed")
}
setupCardReadProcess(false)
return
}
}
isFallback && cardType == CardTypeX.MAG -> {
sharedViewModel.setEmvTrans(false)
cardReadViewModel.setCardTransactionType(CardTransactionType.FALLBACK)
}
cardType == CardTypeX.IC || cardType == CardTypeX.NFC -> {
if (isMPU) {
sharedViewModel.setEmvTrans(false)
cardReadViewModel.setCardTransactionType(CardTransactionType.MPU)
} else {
cardReadViewModel.setCardData(cardType.value)
cardReadViewModel.setCardTransactionType(CardTransactionType.EMV)
}
}
}
viewModelScope.launch {
_events.send(CardWaitingEvent.GoProcessingCard)
}
}
override fun onError(code: Int, message: String) {
ecrActionCancel("Transaction cancelled")
viewModelScope.launch {
_uiState.update {
it.copy(
alertMessage = message,
isLoading = false
)
}
_events.send(CardWaitingEvent.GoMain)
}
}
override fun onCommError() {
if (fallbackEnabled && retryCounter < fallbackCounter) {
_uiState.update {
it.copy(
alertMessage = "Card not detected!\nRemain Attempt - ${fallbackCounter - retryCounter}"
)
}
retryCounter++
setupCardReadProcess(false)
return
}
if (retryCounter == fallbackCounter) {
_uiState.update {
it.copy(alertMessage = "Fallback!\nPlease stripe!")
}
setupCardReadProcess(true)
return
}
ecrActionCancel("Transaction cancelled")
viewModelScope.launch {
_uiState.update {
it.copy(
alertMessage = "Chip not detected!",
isLoading = false
)
}
_events.send(CardWaitingEvent.GoMain)
}
}
}
)
}
private fun ecrActionCancel(msg: String) {
if (sharedViewModel.isEcr.value != true) {
return
}
sharedViewModel.isEcr.postValue(false)
sharedViewModel.isEcrFinished.postValue(true)
if (SystemParamsOperation.getInstance().isCMHLEnabled) {
ECRHelper.send(
CoreUtils.getInstance(sharedViewModel)
.generateCMHLResponse(ECRResultStatus.USER_CANCEL)
)
CoreUtils.getInstance(sharedViewModel).responseACKCMHL()
return
}
CoreUtils.getInstance(sharedViewModel).responseRejectMsg(msg)
}
private fun stopCardReading() {
sharedViewModel.setIsFallback(false)
cardReadViewModel.cancelCheckCard()
cardReadViewModel.resetOneTimeFlag()
cardReadViewModel.cancelCheckXProcess()
}
override fun onCleared() {
super.onCleared()
readerInitJob?.cancel()
stopCardReading()
}
}

View File

@ -1,13 +1,19 @@
package com.mob.utsmyanmar.ui.navigation package com.mob.utsmyanmar.ui.navigation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType 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.ui.amount.AmountScreen import com.mob.utsmyanmar.ui.amount.AmountScreen
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel
import com.mob.utsmyanmar.ui.dashboard.DashboardScreen import com.mob.utsmyanmar.ui.dashboard.DashboardScreen
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
import com.mob.utsmyanmar.viewmodel.SharedViewModel
@Composable @Composable
fun AppNavGraph( fun AppNavGraph(
@ -42,7 +48,38 @@ fun AppNavGraph(
) { backStackEntry -> ) { backStackEntry ->
AmountScreen( AmountScreen(
action = backStackEntry.arguments?.getString("action").orEmpty(), action = backStackEntry.arguments?.getString("action").orEmpty(),
onBackClick = { navController.popBackStack() } onBackClick = { navController.popBackStack() },
onCancelClick = { navController.popBackStack() },
onNextClick = {
navController.navigate(Routes.CardWaiting.route)
}
)
}
composable(Routes.CardWaiting.route) {
val sharedViewModel: SharedViewModel = hiltViewModel()
val cardReaderViewModel: CardReaderViewModel = hiltViewModel()
val cardWaitingViewModel: CardWaitingViewModel = viewModel(
factory = CardWaitingViewModel.provideFactory(
cardReadViewModel = cardReaderViewModel,
sharedViewModel = sharedViewModel
)
)
CardWaitingScreen(
viewModel = cardWaitingViewModel,
onManualEntry = {},
onProcessingCard = {},
onTimeout = { navController.popBackStack() },
onBack = { navController.popBackStack() },
onMain = {
navController.navigate(Routes.Dashboard.route) {
popUpTo(Routes.Dashboard.route) {
inclusive = false
}
launchSingleTop = true
}
}
) )
} }
} }

View File

@ -5,4 +5,5 @@ sealed class Routes(val route: String) {
data object Amount : Routes("amount/{action}") { data object Amount : Routes("amount/{action}") {
fun createRoute(action: String): String = "amount/$action" fun createRoute(action: String): String = "amount/$action"
} }
data object CardWaiting : Routes("card_waiting")
} }

View File

@ -0,0 +1,15 @@
package com.mob.utsmyanmar.utils
import android.content.Context
object AppContextHolder {
private lateinit var appContext: Context
fun init(context: Context) {
appContext = context.applicationContext
}
fun get(): Context {
return appContext
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
package com.mob.utsmyanmar.utils
import com.utsmyanmar.ecr.data.model.Transactions
interface ECRSetups {
fun setUpECREchoTest()
fun setUpECRSale(trans: Transactions): Boolean
fun setUpECRQR(trans: Transactions): Boolean
fun setUpECRVoid(trans: Transactions): Boolean
fun setUpECRCashAdvance(trans: Transactions): Boolean
fun setUpECRPreAuth(trans: Transactions): Boolean
fun setUpECRPreAuthVoid(trans: Transactions): Boolean
fun setUpECRPreAuthComplete(trans: Transactions): Boolean
fun setUpECRPreAuthCompleteVoid(trans: Transactions): Boolean
fun setUpECRSettlement()
fun setUpECRRefund(trans: Transactions): Boolean
}

View File

@ -0,0 +1,24 @@
package com.mob.utsmyanmar.utils
import com.kizzy.cmhl.models.PingRequest
import com.kizzy.cmhl.models.PreAuthCancellationRequest
import com.kizzy.cmhl.models.PreAuthCompletionRequest
import com.kizzy.cmhl.models.PreAuthRequest
import com.kizzy.cmhl.models.QrPaymentRequest
import com.kizzy.cmhl.models.SaleRequest
import com.kizzy.cmhl.models.SettlementRequest
import com.kizzy.cmhl.models.VoidQrPaymentRequest
import com.kizzy.cmhl.models.VoidRequest
interface ECRSetupsCMHL {
fun setUpECREchoTest()
fun setupPingRequest(trans: PingRequest)
fun setUpECRSale(trans: SaleRequest): Boolean
fun setUpECRQR(trans: QrPaymentRequest): Boolean
fun setUpECRQRVoid(trans: VoidQrPaymentRequest): Boolean
fun setUpECRVoid(trans: VoidRequest): Boolean
fun setUpECRPreAuth(trans: PreAuthRequest): Boolean
fun setUpECRPreAuthVoid(trans: PreAuthCancellationRequest): Boolean
fun setUpECRPreAuthComplete(trans: PreAuthCompletionRequest): Boolean
fun setUpECRSettlement(trans: SettlementRequest)
}

View File

@ -0,0 +1,14 @@
package com.mob.utsmyanmar.utils
interface OldECRSetups {
fun setUpECRTest()
fun setUpECRSale(msg: String): Boolean
fun setUpECRVoid(msg: String): Boolean
fun setUpECRCashAdvance(msg: String): Boolean
fun setUpECRPreAuth(msg: String): Boolean
fun setUpECRPreAuthVoid(msg: String): Boolean
fun setUpECRPreAuthComplete(msg: String): Boolean
fun setUpECRPreAuthCompleteVoid(msg: String): Boolean
fun setUpECRSettlement()
fun setUpECRRefund(msg: String): Boolean
}

View File

@ -0,0 +1,152 @@
package com.mob.utsmyanmar.viewmodel
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.ViewModel
import com.mob.utsmyanmar.model.CardTransactionType
import com.utsmyanmar.checkxread.CheckXRead
import com.utsmyanmar.checkxread.checkcard.CheckCardResultX
import com.utsmyanmar.checkxread.model.CardDataX
import com.utsmyanmar.checkxread.readcard.ReadCardResultX
import com.utsmyanmar.checkxread.readcard.ReadCardX
import com.utsmyanmar.checkxread.sdk.SunmiSDK
import com.utsmyanmar.paylibs.model.PayDetail
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
@HiltViewModel
class CardReaderViewModel @Inject constructor() : ViewModel() {
companion object {
private val TAG = CardReaderViewModel::class.java.simpleName
}
private val mainThreadHandler = Handler(Looper.getMainLooper())
private var oneTimeFlag = false
private var cardTransactionType: CardTransactionType? = null
/*
* UI States
*/
private val _errorCode = MutableStateFlow("")
val errorCode = _errorCode.asStateFlow()
private val _cardData = MutableStateFlow<CardDataX?>(null)
val cardData = _cardData.asStateFlow()
private var _cardTypeData = MutableStateFlow<Int?>(null)
val cardTypeData = _cardTypeData.asStateFlow()
private val _payDetail = MutableStateFlow<PayDetail?>(null)
val payDetail = _payDetail.asStateFlow()
private val _checkCardAlertMsg =
MutableStateFlow<String?>(null)
val checkCardAlertMsg =
_checkCardAlertMsg.asStateFlow()
/*
* Transaction Type
*/
fun setCardTransactionType(
cardTransactionType: CardTransactionType
) {
this.cardTransactionType = cardTransactionType
}
fun getCardTransactionType(): CardTransactionType? {
return cardTransactionType
}
/*
* Check Card Process
*/
fun startCheckXProcess(
allType: Int,
timeOut: Int,
cardResultX: CheckCardResultX
) {
CheckXRead.getInstance()
.startCheckXProcess(
allType,
timeOut,
cardResultX
)
}
fun isReaderReady(): Boolean {
return SunmiSDK.getInstance().readCardOptV2 != null
}
fun cancelCheckXProcess() {
CheckXRead.getInstance()
.cancelCheckXProcess()
}
/*
* Read Card Process
*/
fun startReadXProcess(
readCardX: ReadCardX,
readCardResultX: ReadCardResultX
) {
CheckXRead.getInstance()
.startReadXProcess(
readCardX,
readCardResultX
)
}
/*
* Alert Message
*/
fun setCheckCardAlertMsg(
msg: String,
isAutoHide: Boolean
) {
_checkCardAlertMsg.value = msg
if (isAutoHide) {
Handler(Looper.getMainLooper()).postDelayed({
_checkCardAlertMsg.value = null
}, 5000)
}
}
fun resetUI() {
_checkCardAlertMsg.value = null
}
/*
* One Time Flag
*/
fun resetOneTimeFlag() {
oneTimeFlag = false
}
/*
* Cancel Card Checking
*/
fun cancelCheckCard() {
if (isReaderReady()) {
SunmiSDK.getInstance()
.cancelCheckCard()
}
}
fun setCardData(value: Int){
_cardTypeData.value = value
}
}

View File

@ -0,0 +1,25 @@
package com.mob.utsmyanmar.viewmodel
import com.mob.utsmyanmar.model.TransResultStatus
import com.utsmyanmar.paylibs.model.PayDetail
import com.utsmyanmar.paylibs.system.SingleLiveEvent
interface ProcessingTransaction {
fun resetTransactionStatus()
fun getTransStatus(): SingleLiveEvent<TransResultStatus>
fun startOnlineProcess()
fun insertDB(payResult: PayDetail)
fun processVoidDB(payResult: PayDetail)
fun processPreVoidDb(payResult: PayDetail)
fun processPreCompDb(payResult: PayDetail)
fun processPreCompVoidDb(payResult: PayDetail)
fun processRefundDB(payResult: PayDetail)
}

View File

@ -0,0 +1,476 @@
package com.mob.utsmyanmar.viewmodel
import android.graphics.Bitmap
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.mob.utsmyanmar.model.SettlementType
import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest
import com.utsmyanmar.baselib.network.model.sirius.SiriusResponse
import com.utsmyanmar.baselib.repo.Repository
import com.utsmyanmar.ecr.data.TransType
import com.utsmyanmar.ecr.data.model.Transactions
import com.utsmyanmar.paylibs.model.PayDetail
import com.utsmyanmar.paylibs.print.printx.PrintXReceipt
import com.utsmyanmar.paylibs.print.printx.PrintXStatus
import com.utsmyanmar.paylibs.system.SingleLiveEvent
import com.utsmyanmar.paylibs.utils.AccountType
import com.utsmyanmar.paylibs.utils.PrintStatus
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import com.utsmyanmar.paylibs.utils.enums.HostType
import com.utsmyanmar.paylibs.utils.enums.TransMenu
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.rxjava3.core.Observable
import sunmi.sunmiui.utils.LogUtil
import javax.inject.Inject
@HiltViewModel
class SharedViewModel @Inject constructor(
private val repository: Repository
) : ViewModel() {
companion object {
private const val TAG = "SharedViewModel"
}
val transactionsType = SingleLiveEvent<TransactionsType>()
val settlementType = SingleLiveEvent<SettlementType>()
val accountType = SingleLiveEvent<AccountType>()
val amount = SingleLiveEvent<String>()
val totalAmount = SingleLiveEvent<Long>()
val cardNo = SingleLiveEvent<String>()
val processCode = SingleLiveEvent<String>()
val payDetail = SingleLiveEvent<PayDetail>()
val printStatus = SingleLiveEvent<PrintStatus>()
val merchantName = SingleLiveEvent<String>()
val transactionName = SingleLiveEvent<String>()
val payDetailList = SingleLiveEvent<List<PayDetail>>()
val isEcr = SingleLiveEvent<Boolean>()
val traceNo = SingleLiveEvent<String>()
val rrNNo = SingleLiveEvent<String>()
val approvalCode = SingleLiveEvent<String>()
val isEcrFinished = SingleLiveEvent<Boolean>()
val isEmv = SingleLiveEvent<Boolean>()
private val settlementStatus = MutableLiveData<Boolean>()
private val wavePayStatus = MutableLiveData<Boolean>()
val sendMsg = SingleLiveEvent<String>()
val qrData = SingleLiveEvent<String>()
val qrRefNum = SingleLiveEvent<String>()
val ecrCMD = SingleLiveEvent<TransType>()
val _transMenu = SingleLiveEvent<TransMenu?>()
val twoBtnLayout = MutableLiveData(0)
val oneBtnLayout = MutableLiveData(8)
private val reprintBtnLayout = MutableLiveData(8)
val isReprint = SingleLiveEvent<Boolean>()
var signBitmap: Bitmap? = null
val hostType = SingleLiveEvent<HostType>()
private val printReceiptButtons = MutableLiveData(0)
val printReceiptMsg = SingleLiveEvent<String>()
val reprintTransTypeMsg = SingleLiveEvent<String>()
val ecrTrans = SingleLiveEvent<Transactions>()
val _manualEntryStatus = MutableLiveData<Boolean>()
val fullVoidPreauthStatus = MutableLiveData<Boolean>()
val partialVoidPreauthStatus = MutableLiveData<Boolean>()
val printXStatus = SingleLiveEvent<PrintStatus>()
private val _errorFragmentMsg = SingleLiveEvent<String>()
private val _successFragmentMsg = SingleLiveEvent<String>()
private val _currencyText = SingleLiveEvent<String>()
val tapCardStatus = MutableLiveData(1)
val tapDeviceStatus = MutableLiveData(1)
val insertCardStatus = MutableLiveData(1)
val swipeCardStatus = MutableLiveData(0)
val countDownTxt = MutableLiveData<String>()
val mmqrLoading = MutableLiveData<Int>()
val isMMPay = MutableLiveData<Int>()
val isWavePay = MutableLiveData<Int>()
val mockData = SingleLiveEvent<String>()
val qrPayVisibility = MutableLiveData<Int>()
val loadingView = MutableLiveData(8)
val loadingMsg = SingleLiveEvent<String>()
private val isFallback = SingleLiveEvent<Boolean>()
private val _isCardDataExist = SingleLiveEvent<Boolean>()
private val _isAmountExist = SingleLiveEvent<Boolean>()
private var mPayDetail = PayDetail()
init {
setPrintStatus(PrintStatus.FIRST_PRINT)
isReprint.value = false
cardNo.value = ""
}
fun setSettlementStatus(status: Boolean) {
settlementStatus.value = status
}
fun getSettlementStatus(): MutableLiveData<Boolean> {
return settlementStatus
}
fun setWavePayStatus(status: Boolean) {
wavePayStatus.value = status
}
fun getWavePayStatus(): MutableLiveData<Boolean> {
return wavePayStatus
}
fun setEmvTrans(status: Boolean) {
isEmv.value = status
}
fun isEmvTrans(): SingleLiveEvent<Boolean> {
return isEmv
}
private fun getPayDetail(): PayDetail {
return mPayDetail
}
private fun cachePayDetail(payDetail: PayDetail) {
mPayDetail = payDetail
}
fun setPrintReceiptButtons(visible: Boolean) {
printReceiptButtons.value = if (visible) 0 else 8
}
fun postPrintReceiptButtons(visible: Boolean) {
printReceiptButtons.postValue(if (visible) 0 else 8)
}
fun getPrintReceiptButtons(): MutableLiveData<Int> {
return printReceiptButtons
}
fun getReprintBtnLayout(): MutableLiveData<Int> {
return reprintBtnLayout
}
fun setReprintBtnLayout(visible: Boolean) {
reprintBtnLayout.value = if (visible) 0 else 8
}
fun setPrintReceiptMsg(msg: String) {
printReceiptMsg.value = msg
}
fun postPrintReceiptMsg(msg: String) {
printReceiptMsg.postValue(msg)
}
fun setPrintStatus(printStatus: PrintStatus) {
this.printStatus.value = printStatus
}
fun postPrintStatus(printStatus: PrintStatus) {
this.printStatus.postValue(printStatus)
}
fun getPrintStatusEvent(): SingleLiveEvent<PrintStatus> {
return printStatus
}
fun setIsFallback(status: Boolean) {
isFallback.value = status
}
fun getIsFallback(): SingleLiveEvent<Boolean> {
return isFallback
}
fun setCardDataExist(exist: Boolean) {
_isCardDataExist.value = exist
}
fun getCardDataExist(): SingleLiveEvent<Boolean> {
return _isCardDataExist
}
fun setAmountExist(exist: Boolean) {
_isAmountExist.value = exist
}
fun getAmountExist(): SingleLiveEvent<Boolean> {
return _isAmountExist
}
fun getManualEntryStatus(): MutableLiveData<Boolean> {
return _manualEntryStatus
}
fun setManualEntryStatus(status: Boolean) {
_manualEntryStatus.value = status
}
fun getErrorFragmentMsg(): SingleLiveEvent<String> {
return _errorFragmentMsg
}
fun set_errorFragmentMsg(msg: String) {
_errorFragmentMsg.value = msg
}
fun getSuccessFragmentMsg(): SingleLiveEvent<String> {
return _successFragmentMsg
}
fun set_currencyText(msg: String) {
_currencyText.value = msg
}
fun get_currencyText(): SingleLiveEvent<String> {
return _currencyText
}
fun set_successFragmentMsg(msg: String) {
_successFragmentMsg.value = msg
}
fun getTransMenu(): SingleLiveEvent<TransMenu?> {
return _transMenu
}
fun setTransMenu(transMenu: TransMenu?) {
_transMenu.value = transMenu
}
fun loadingMsg(msg: String) {
loadingView.value = 0
loadingMsg.value = msg
}
fun dismissLoadingMsg() {
loadingView.value = 8
loadingMsg.value = ""
}
fun getParams(siriusRequest: SiriusRequest): Observable<SiriusResponse> {
return repository.getParams(siriusRequest)
}
private fun printReceipt(isMerchantCopy: Boolean) {
PrintXReceipt.getInstance()
.printSmileReceipt(payDetail.value, isMerchantCopy, object : PrintXStatus {
override fun onSuccess() {
if (isMerchantCopy) {
if (!SystemParamsOperation.getInstance().demoStatus) {
if (SystemParamsOperation.getInstance().printISOStatus) {
PrintXReceipt.getInstance()
.printSmileISoReceipt(payDetail.value)
}
}
postPrintStatus(PrintStatus.FIRST_PRINT_DONE)
} else {
setPrintStatus(PrintStatus.SECOND_PRINT_DONE)
}
}
override fun onFailure() {
LogUtil.d(TAG, "Print Status Result Failure!")
printXStatus.postValue(PrintStatus.EMPTY_PAPER_ROLL)
postPrintReceiptButtons(true)
}
})
}
fun startPrintReceipt(isFirstPrint: Boolean) {
payDetail.value?.let {
cachePayDetail(it)
}
PrintXReceipt.getInstance()
.printSmileReceipt(getPayDetail(), isFirstPrint, object : PrintXStatus {
override fun onSuccess() {
if (isFirstPrint) {
if (!SystemParamsOperation.getInstance().demoStatus) {
if (SystemParamsOperation.getInstance().printISOStatus) {
PrintXReceipt.getInstance()
.printSmileISoReceipt(getPayDetail())
}
}
postPrintStatus(PrintStatus.FIRST_PRINT_DONE)
if (isEcr.value == true &&
SystemParamsOperation.getInstance()
.isAutoPrintCustomerCopy
) {
printReceiptButtons.postValue(8)
} else {
printReceiptButtons.postValue(0)
}
} else {
postPrintStatus(PrintStatus.SECOND_PRINT_DONE)
}
}
override fun onFailure() {
LogUtil.d(TAG, "Print Status Result Failure!")
if (isFirstPrint) {
setPrintStatus(PrintStatus.EMPTY_PAPER_ROLL_FIRST)
} else {
setPrintStatus(PrintStatus.EMPTY_PAPER_ROLL_SECOND)
}
postPrintReceiptButtons(true)
}
})
}
fun startPrintProcess() {
LogUtil.d(TAG, "Print status : ${getPrintStatusEvent().value}")
when (getPrintStatusEvent().value) {
PrintStatus.FIRST_PRINT -> {
printReceipt(true)
postPrintReceiptMsg("Printing Receipt for Merchant")
postPrintReceiptButtons(false)
}
PrintStatus.SECOND_PRINT -> {
printReceipt(false)
postPrintReceiptMsg("Printing Receipt for Customer")
}
PrintStatus.NOT_PRINT -> {
}
else -> {}
}
}
fun startPrintProcessSettlement() {
val detail = payDetail.value ?: return
PrintXReceipt.getInstance()
.printSmileSettlementReport(detail, object : PrintXStatus {
override fun onSuccess() {
}
override fun onFailure() {
}
})
}
fun getLastThreeTransactions(): LiveData<List<PayDetail>> {
return repository.getLastThreeTransactions()
}
fun getReversalTransaction(voucherNo: String): LiveData<PayDetail> {
return repository.getReversalTransaction(voucherNo)
}
fun updatePayDetail(payDetail: PayDetail) {
repository.updatePayDetail(payDetail)
}
fun insertPayDetail(payDetail: PayDetail) {
repository.insertPayDetail(payDetail)
}
fun enableCardStatusIcon(
tapCard: Boolean,
tapDevice: Boolean,
insertCard: Boolean,
swipeCard: Boolean
) {
tapCardStatus.value = if (tapCard) 1 else 0
tapDeviceStatus.value = if (tapDevice) 1 else 0
insertCardStatus.value = if (insertCard) 1 else 0
swipeCardStatus.value = if (swipeCard) 1 else 0
}
fun resetParamsMain() {
isEcrFinished.postValue(true)
isEcr.postValue(false)
setAmountExist(false)
setCardDataExist(false)
setTransMenu(null)
}
fun updateButtonStatus() {
setSettlementStatus(
SystemParamsOperation.getInstance().settlementStatus
)
setWavePayStatus(
SystemParamsOperation.getInstance().wavePayStatus
)
}
}

View File

@ -0,0 +1,346 @@
package com.mob.utsmyanmar.viewmodel
import android.text.TextUtils
import androidx.lifecycle.ViewModel
import com.mob.utsmyanmar.model.TransResultStatus
import com.utsmyanmar.baselib.repo.Repository
import com.utsmyanmar.paylibs.model.PayDetail
import com.utsmyanmar.paylibs.model.TradeData
import com.utsmyanmar.paylibs.network.ISOSocket
import com.utsmyanmar.paylibs.reversal.ReversalAction
import com.utsmyanmar.paylibs.reversal.ReversalListener
import com.utsmyanmar.paylibs.system.SystemDateTime
import com.utsmyanmar.paylibs.transactions.TransactionsOperation
import com.utsmyanmar.paylibs.transactions.TransactionsOperationListener
import com.utsmyanmar.paylibs.utils.PrintStatus
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 javax.inject.Inject
@HiltViewModel
class TransProcessViewModel @Inject constructor(
private val repository: Repository
) : ViewModel(), ProcessingTransaction {
companion object {
private const val RC_APPROVED_V1 = "00"
private const val RC_APPROVED_V2 = "000"
private const val TRY_SECONDARY = "TRY_SECONDARY"
}
private var pan: String = ""
private lateinit var tradeData: TradeData
var payDetail: PayDetail? = null
private var oldTransPayDetail: PayDetail? = null
private var isSecondCall = false
private var isThirdCall = false
/*
* States
*/
private val _transResultStatus =
MutableStateFlow<TransResultStatus?>(null)
val transResultStatus =
_transResultStatus.asStateFlow()
private val _transType =
MutableStateFlow<TransactionsType?>(null)
val transType =
_transType.asStateFlow()
private val _printStatus =
MutableStateFlow(PrintStatus.FIRST_PRINT)
val printStatus =
_printStatus.asStateFlow()
private val _payDetailResult =
MutableStateFlow<PayDetail?>(null)
val payDetailResult =
_payDetailResult.asStateFlow()
private val _errorMessage =
MutableStateFlow<String?>(null)
val errorMessage =
_errorMessage.asStateFlow()
/*
* Setup
*/
fun setTradeData(tradeData: TradeData) {
this.tradeData = tradeData
payDetail = tradeData.payDetail
pan = payDetail?.cardNo ?: ""
}
fun getTradeData(): TradeData {
return tradeData
}
fun updatePayDetail(payDetail: PayDetail) {
this.payDetail = payDetail
tradeData = TradeData().apply {
this.payDetail = payDetail
}
}
fun setOldTransPayDetail(payDetail: PayDetail) {
oldTransPayDetail = payDetail
}
/*
* Transaction
*/
override fun startOnlineProcess() {
TransactionsOperation.getInstance()
.getStartOperation(
tradeData,
transType.value
)
.checkOperation(object : TransactionsOperationListener {
override fun onSuccess(tradeData: TradeData) {
val payDetailRes =
tradeData.payDetail
if (
TextUtils.equals(
payDetailRes.tradeAnswerCode,
RC_APPROVED_V1
) ||
TextUtils.equals(
payDetailRes.tradeAnswerCode,
RC_APPROVED_V2
)
) {
payDetailRes.invoiceNo =
SystemParamsOperation
.getInstance()
.incrementInvoiceNum
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
_transResultStatus.value =
TransResultStatus.SUCCESS
} else {
_payDetailResult.value =
payDetailRes
_transResultStatus.value =
TransResultStatus.FAIL
}
}
override fun onReversal(
tradeData: TradeData
) {
_transResultStatus.value =
TransResultStatus.REVERSAL_PREPARE
callReversal(tradeData)
}
override fun onError(message: String) {
if (message == TRY_SECONDARY) {
_transResultStatus.value =
TransResultStatus.SECONDARY
startOnlineProcess()
} else {
_errorMessage.value = message
_transResultStatus.value =
TransResultStatus.ERROR
}
}
})
}
/*
* Database
*/
override fun insertDB(payResult: PayDetail) {
payDetail?.pinCipher = ""
repository.insertPayDetail(
payDetail
)
}
override fun processVoidDB(payResult: PayDetail) {
payDetail?.isCanceled = true
payDetail?.let {
repository.updatePayDetail(it)
}
repository.insertPayDetail(
updateCurrentDateAndTime(payResult)
)
}
override fun processRefundDB(payResult: PayDetail) {
oldTransPayDetail?.apply {
isReturnGood = true
isCanceled = true
repository.updatePayDetail(this)
}
repository.insertPayDetail(
updateCurrentDateAndTime(payResult)
)
}
override fun processPreVoidDb(payResult: PayDetail) {}
override fun processPreCompDb(payResult: PayDetail) {}
override fun processPreCompVoidDb(payResult: PayDetail) {}
/*
* Reversal
*/
private fun callReversal(tradeData: TradeData) {
payDetail = tradeData.payDetail
ReversalAction.getInstance()
.setData(tradeData)
.enqueue()
.startReversal(object : ReversalListener {
override fun onSuccessReversal() {
_transResultStatus.value =
TransResultStatus.REVERSAL_SUCCESS
payDetail?.let {
repository.insertPayDetail(it)
}
}
override fun onNetworkFail(msg: String) {
SystemParamsOperation
.getInstance()
.setSecondHostEnable(true)
if (
SystemParamsOperation
.getInstance()
.isSecondHostEnabled
) {
if (!isSecondCall) {
_transResultStatus.value =
TransResultStatus.REVERSAL_SECONDARY
ISOSocket.getInstance()
.switchIp()
isSecondCall = true
callReversal(tradeData)
} else {
_transResultStatus.value =
TransResultStatus.REVERSAL_FAIL
}
} else {
_transResultStatus.value =
TransResultStatus.REVERSAL_FAIL
}
}
override fun onFailReversal(msg: String) {
_transResultStatus.value =
TransResultStatus.REVERSAL_FAIL
}
})
}
/*
* Utils
*/
private fun updateCurrentDateAndTime(
payDetail: PayDetail
): PayDetail {
payDetail.tradeDate =
SystemDateTime.getMMDD()
payDetail.tradeTime =
SystemDateTime.getHHmmss()
return payDetail
}
override fun resetTransactionStatus() {
_transResultStatus.value = null
}
override fun getTransStatus() = TODO()
}

View File

@ -98,13 +98,16 @@ public class BaseApplication extends Application {
mPinPadOptV2 = sunmiPayKernel.mPinPadOptV2; mPinPadOptV2 = sunmiPayKernel.mPinPadOptV2;
mReadCardOptV2 = sunmiPayKernel.mReadCardOptV2; mReadCardOptV2 = sunmiPayKernel.mReadCardOptV2;
mSecurityOptV2 = sunmiPayKernel.mSecurityOptV2; mSecurityOptV2 = sunmiPayKernel.mSecurityOptV2;
//init
initTerminal();
PayLibsUtils.getInstance().initLib(mSecurityOptV2,mEMVOptV2,securityOpt,mReadCardOptV2); PayLibsUtils.getInstance().initLib(mSecurityOptV2,mEMVOptV2,securityOpt,mReadCardOptV2);
SunmiSDK.getInstance().initSDK(mReadCardOptV2,basicOptV2); SunmiSDK.getInstance().initSDK(mReadCardOptV2,basicOptV2);
try {
initTerminal();
} catch (Exception terminalInitException) {
terminalInitException.printStackTrace();
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -4,7 +4,7 @@
<color name="colorTextTitle">#222222</color> <color name="colorTextTitle">#222222</color>
<color name="colorTextContent">#666666</color> <color name="colorTextContent">#666666</color>
<color name="colorTextHelp">#999999</color> <color name="colorTextHelp">#999999</color>
<color name="colorLineColor"># </color> <color name="colorLineColor">#DDDDDD</color>
<color name="yellow">#FEE135</color> <color name="yellow">#FEE135</color>
<color name="red">#D0312D</color> <color name="red">#D0312D</color>
<color name="FD5A52">#FD5A52</color> <color name="FD5A52">#FD5A52</color>
@ -19,4 +19,4 @@
<color name="CDDDDDD">#dddddd</color> <color name="CDDDDDD">#dddddd</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
</resources> </resources>

View File

@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.android.library) apply false alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.hilt.android) apply false
alias(libs.plugins.kotlin.kapt) apply false alias(libs.plugins.kotlin.kapt) apply false
id("com.google.dagger.hilt.android") version "2.59.2" apply false
id("com.google.devtools.ksp") version "2.3.4" apply false
} }

View File

@ -1,6 +1,7 @@
[versions] [versions]
agp = "9.2.0" agp = "9.2.0"
coreKtx = "1.18.0" coreKtx = "1.18.0"
hiltAndroidCompiler = "2.59.2"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.3.0" junitVersion = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
@ -11,10 +12,15 @@ composeBom = "2026.02.01"
navigationCompose = "2.9.8" navigationCompose = "2.9.8"
hilt = "2.57.1" hilt = "2.57.1"
foundation = "1.11.1" foundation = "1.11.1"
runtime = "1.11.1"
rxandroid = "3.0.2"
rxjava = "3.1.12"
hiltNavigationCompose = "1.2.0"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroidCompiler" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@ -31,11 +37,14 @@ androidx-compose-material3 = { group = "androidx.compose.material3", name = "mat
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" }
rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" }
rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }

View File

@ -0,0 +1 @@
o/mpulib-1.2-runtime

View File

@ -0,0 +1 @@
o/PayLib-release-1.4.64-runtime

View File

@ -0,0 +1 @@
o/PayLib-release-1.4.64-runtime

View File

@ -0,0 +1 @@
o/sunmiui-1.1.27-runtime

View File

@ -0,0 +1 @@
o/sunmiui-1.1.27-runtime

View File

@ -2,6 +2,7 @@ package com.kizzy.xpay.util
import java.security.MessageDigest import java.security.MessageDigest
import java.util.Locale import java.util.Locale
import java.util.Locale.getDefault
object Sign { object Sign {
@ -13,7 +14,7 @@ object Sign {
val sorted = filtered.toSortedMap() val sorted = filtered.toSortedMap()
val stringA = sorted.entries.joinToString("&") {"${it.key}=${it.value}"} val stringA = sorted.entries.joinToString("&") {"${it.key}=${it.value}"}
val stringToSign = "$stringA&key=$appKey" val stringToSign = "$stringA&key=$appKey"
val hash = stringToSign.hashedWithSha256().toUpperCase() val hash = stringToSign.hashedWithSha256().uppercase(getDefault())
return hash; return hash;
} }