pin pad init oki

This commit is contained in:
moon 2026-05-12 22:24:54 +06:30
parent d44163b601
commit b5e2ec8e01
9 changed files with 361 additions and 62 deletions

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

View File

@ -1,13 +1,16 @@
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

@ -1,16 +1,43 @@
package com.mob.utsmyanmar.config
import com.sunmi.pay.hardware.aidl.security.SecurityOpt
import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2
import com.sunmi.pay.hardware.aidlv2.readcard.ReadCardOptV2
import com.sunmi.pay.hardware.aidlv2.security.SecurityOptV2
import jakarta.inject.Inject
import jakarta.inject.Singleton
import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2
import com.utsmyanmar.baselib.BaseApplication
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SunmiPayManager @Inject constructor() {
var pinPadOptV2: PinPadOptV2? = null
var securityOptV2: SecurityOptV2? = null
private val app: BaseApplication?
get() = BaseApplication.getInstance()
val pinPadOptV2: PinPadOptV2?
get() = app?.mPinPadOptV2
val securityOptV2: SecurityOptV2?
get() = app?.mSecurityOptV2
val readCardOptV2: ReadCardOptV2?
get() = app?.mReadCardOptV2
val emvOptV2: EMVOptV2?
get() = app?.mEMVOptV2
val basicOptV2: BasicOptV2?
get() = BaseApplication.basicOptV2
val securityOpt: SecurityOpt?
get() = app?.securityOpt
fun isReady(): Boolean {
return pinPadOptV2 != null && securityOptV2 != null
return pinPadOptV2 != null &&
securityOptV2 != null &&
readCardOptV2 != null &&
emvOptV2 != null &&
basicOptV2 != null
}
}

View File

@ -15,11 +15,12 @@ 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.pinpad.PinPadRoute
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.EmvTransactionProcessViewModel
import com.mob.utsmyanmar.viewmodel.PinPadViewModel
import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel
import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
@ -124,7 +125,9 @@ fun AppNavGraph(
ProcessingCardRoute(
viewModel = processingCardViewModel,
onNavigatePinPad = {},
onNavigatePinPad = {
navController.navigate(Routes.PinPad.route)
},
onNavigateInputAmount = { navController.popBackStack(Routes.Amount.route, false) },
onNavigateProcessing = {},
onNavigateEmvTransaction = {},
@ -133,5 +136,16 @@ fun AppNavGraph(
onShowDecline = {}
)
}
composable(Routes.PinPad.route) {
val pinPadViewModel: PinPadViewModel = hiltViewModel(activity)
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
PinPadRoute(
pinPadViewModel = pinPadViewModel,
transProcessViewModel = transProcessViewModel,
onBack = { navController.popBackStack() }
)
}
}
}

View File

@ -7,4 +7,5 @@ sealed class Routes(val route: String) {
}
data object CardWaiting : Routes("card_waiting")
data object ProcessingCard : Routes("processing_card")
data object PinPad : Routes("pin_pad")
}

View File

@ -0,0 +1,71 @@
package com.mob.utsmyanmar.ui.pinpad
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.mob.utsmyanmar.model.PinPadStatus
import com.mob.utsmyanmar.model.TransResultStatus
import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
@Composable
fun PinPadRoute(
pinPadViewModel: PinPadViewModel,
transProcessViewModel: TransProcessViewModel,
onBack: () -> Unit
) {
val pinText by pinPadViewModel.pinText.collectAsStateWithLifecycle()
val alertMsg by pinPadViewModel.alertMsg.collectAsStateWithLifecycle()
val pinStatus by pinPadViewModel.pinStatus.collectAsStateWithLifecycle()
val transStatus by transProcessViewModel.transResultStatus.collectAsStateWithLifecycle()
LaunchedEffect(pinStatus) {
when (pinStatus) {
PinPadStatus.ON_CONFIRM,
PinPadStatus.ON_NEXT_SCREEN,
PinPadStatus.ON_EMPTY -> {
transProcessViewModel.startOnlineProcess()
}
PinPadStatus.ON_CANCEL,
PinPadStatus.ON_TIMEOUT,
PinPadStatus.ON_CARD_REMOVED -> {
onBack()
}
else -> {}
}
}
LaunchedEffect(transStatus) {
when (transStatus) {
TransResultStatus.SUCCESS,
TransResultStatus.FAIL,
TransResultStatus.NETWORK_ERROR,
TransResultStatus.ERROR,
TransResultStatus.REVERSAL_FAIL,
TransResultStatus.REVERSAL_SUCCESS -> {
onBack()
}
else -> {}
}
}
DisposableEffect(Unit) {
onDispose {
pinPadViewModel.cancelPinPad()
}
}
PinPadScreen(
pinText = pinText,
alertMessage = alertMsg,
onBack = onBack,
onKeyboardReady = { keyboard ->
pinPadViewModel.startPinPadProcess(keyboard)
}
)
}

View File

@ -0,0 +1,174 @@
package com.mob.utsmyanmar.ui.pinpad
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.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
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.utsmyanmar.baselib.ui.CustomPinPadKeyboard
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PinPadScreen(
pinText: String,
alertMessage: String?,
onBack: () -> Unit,
onKeyboardReady: (CustomPinPadKeyboard) -> Unit
) {
var keyboardView by remember { mutableStateOf<CustomPinPadKeyboard?>(null) }
var isStarted by remember { mutableStateOf(false) }
LaunchedEffect(keyboardView) {
val keyboard = keyboardView ?: return@LaunchedEffect
if (!isStarted) {
isStarted = true
onKeyboardReady(keyboard)
}
}
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
text = "PIN ENTRY",
color = White,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold
)
},
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
painter = painterResource(R.drawable.ic_left_arrow),
contentDescription = "Back",
tint = White
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Primary
)
)
},
containerColor = White
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.background(White)
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(
color = Primary,
shape = RoundedCornerShape(24.dp)
)
.padding(horizontal = 24.dp, vertical = 28.dp),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "Please Input PIN",
color = White,
fontSize = 22.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(14.dp))
Text(
text = if (pinText.isBlank()) "------" else pinText,
color = White,
fontSize = 30.sp,
fontWeight = FontWeight.Bold,
letterSpacing = 4.sp
)
if (!alertMessage.isNullOrBlank()) {
Spacer(modifier = Modifier.height(12.dp))
Text(
text = alertMessage,
color = White,
fontSize = 15.sp,
textAlign = TextAlign.Center
)
}
}
}
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "Enter your PIN on the secured keypad below.",
color = Black,
fontSize = 16.sp,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(24.dp))
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
AndroidView(
factory = { context ->
CustomPinPadKeyboard(context).also {
keyboardView = it
}
}
)
}
Spacer(modifier = Modifier.weight(1f))
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Cancel on device or use back to exit.",
color = Black,
fontSize = 14.sp,
textAlign = TextAlign.Center
)
}
}
}
}

View File

@ -1,4 +1,4 @@
package com.mob.utsmyanmar.viewmodel
package com.mob.utsmyanmar.ui.pinpad
import android.os.Handler
import android.os.Looper
@ -8,15 +8,15 @@ import android.view.View
import android.view.ViewTreeObserver
import android.widget.TextView
import androidx.lifecycle.ViewModel
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.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
@ -56,6 +56,7 @@ class PinPadViewModel @Inject constructor(
private var mCancelHeight = 112
private var mCancelCoordinate = intArrayOf(0, 48)
private var dukptIndex = 0
private var tmkIndex = 9
private var tradeData: TradeData? = null
private var payDetail: PayDetail? = null
private var pan: String = ""
@ -121,18 +122,17 @@ class PinPadViewModel @Inject constructor(
customPinPadKeyboard: CustomPinPadKeyboard
) {
dukptIndex =
SystemParamsOperation.getInstance()
.tmkIndex
.toInt()
// dukptIndex = SystemParamsOperation.getInstance().tmkIndex.toInt()
if(!sunmiPayManager.isReady()){
_alertMsg.value =
"Sunmi Pay SDK is not ready"
initData()
_pinStatus.value =
PinPadStatus.ON_ERROR
if(mPinPadOptV2 == null){
_alertMsg.value = "PinPad service is not ready!"
return
}
getKSN()
initData()
initPinPad(customPinPadKeyboard)
}
@ -245,7 +245,6 @@ class PinPadViewModel @Inject constructor(
e.printStackTrace()
}
}
/*
* Init Pin Pad
*/
@ -253,59 +252,57 @@ class PinPadViewModel @Inject constructor(
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
val pinPadOrder = !SystemParamsOperation.getInstance().isRandomPinPad
if (SunmiSDK.getInstance().checkCardExist() != 2) {
timeout = Constants.TIMEOUT
}
try {
val config = PinPadConfigV2().apply {
maxInput = 6
minInput = 0
pinPadType = 1 // custom keyboard
algorithmType = AidlConstants.Security.KEY_ALG_TYPE_3DES
pinType = mPinType
this.timeout = timeout * 1000
isOrderNumKey = pinPadOrder
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
keySystem = AidlConstants.Security.SEC_MKSK
pinKeyIndex = tmkIndex
val length = pan.length
val panBytes = pan.substring(length - 13, length - 1).toByteArray(StandardCharsets.US_ASCII)
config.pan = panBytes
pinblockFormat = AidlConstants.PinBlockFormat.SEC_PIN_BLK_ISO_FMT0
this.pan = getPanBytes(this@PinPadViewModel.pan)
}
val result = mPinPadOptV2?.initPinPad(config, mPinPadListener)
LogUtil.e(TAG, "result:$result")
LogUtil.e(TAG, "pinpad result:$result")
result?.let {
getKeyboardCoordinate(
it,
customPinPadKeyboard
)
if (result.isNullOrEmpty()) {
_alertMsg.value = "PinPad init failed"
_pinStatus.value = PinPadStatus.ON_ERROR
return
}
getKeyboardCoordinate(result, customPinPadKeyboard)
_pinText.value = ""
customPinPadKeyboard.keepScreenOn = true
customPinPadKeyboard.setKeyBoard(result)
customPinPadKeyboard.visibility = View.VISIBLE
} catch (e: Exception) {
e.printStackTrace()
_alertMsg.value = e.message
_pinStatus.value = PinPadStatus.ON_ERROR
}
}
/*
* Keyboard
*/
@ -436,10 +433,12 @@ class PinPadViewModel @Inject constructor(
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
@ -503,7 +502,7 @@ class PinPadViewModel @Inject constructor(
if (
SunmiSDK.getInstance().checkCardExist() == 2 ||
payDetail?.cardType == AidlConstants.CardType.MAGNETIC.value ||
payDetail?.cardType == AidlConstants.CardType.MAGNETIC.getValue() ||
payDetail?.cardType == -9
) {
@ -582,4 +581,15 @@ class PinPadViewModel @Inject constructor(
}
}
}
private fun getPanBytes(pan: String): ByteArray {
if (pan.length < 13) {
return ByteArray(0)
}
return pan
.substring(pan.length - 13, pan.length - 1)
.toByteArray(StandardCharsets.US_ASCII)
}
}

View File

@ -9,7 +9,7 @@ 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.ui.pinpad.PinPadViewModel
import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import com.utsmyanmar.checkxread.model.CardDataX