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"> <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="jbr-21" project-jdk-type="JavaSDK"> <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 package com.mob.utsmyanmar
import com.mob.utsmyanmar.utils.AppContextHolder import com.mob.utsmyanmar.utils.AppContextHolder
import com.utsmyanmar.baselib.BaseApplication import com.utsmyanmar.baselib.BaseApplication
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp @HiltAndroidApp
class MyApplication : BaseApplication() { class MyApplication : BaseApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
AppContextHolder.init(this) AppContextHolder.init(this)
} }
} }

View File

@ -1,16 +1,43 @@
package com.mob.utsmyanmar.config 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.pinpad.PinPadOptV2
import com.sunmi.pay.hardware.aidlv2.readcard.ReadCardOptV2
import com.sunmi.pay.hardware.aidlv2.security.SecurityOptV2 import com.sunmi.pay.hardware.aidlv2.security.SecurityOptV2
import jakarta.inject.Inject import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2
import jakarta.inject.Singleton import com.utsmyanmar.baselib.BaseApplication
import javax.inject.Inject
import javax.inject.Singleton
@Singleton @Singleton
class SunmiPayManager @Inject constructor(){ class SunmiPayManager @Inject constructor() {
var pinPadOptV2: PinPadOptV2? = null private val app: BaseApplication?
var securityOptV2: SecurityOptV2? = null 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 { 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.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.pinpad.PinPadRoute
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardRoute import com.mob.utsmyanmar.ui.processing_card.ProcessingCardRoute
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel 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.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.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
@ -124,7 +125,9 @@ fun AppNavGraph(
ProcessingCardRoute( ProcessingCardRoute(
viewModel = processingCardViewModel, viewModel = processingCardViewModel,
onNavigatePinPad = {}, onNavigatePinPad = {
navController.navigate(Routes.PinPad.route)
},
onNavigateInputAmount = { navController.popBackStack(Routes.Amount.route, false) }, onNavigateInputAmount = { navController.popBackStack(Routes.Amount.route, false) },
onNavigateProcessing = {}, onNavigateProcessing = {},
onNavigateEmvTransaction = {}, onNavigateEmvTransaction = {},
@ -133,5 +136,16 @@ fun AppNavGraph(
onShowDecline = {} 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 CardWaiting : Routes("card_waiting")
data object ProcessingCard : Routes("processing_card") 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.Handler
import android.os.Looper import android.os.Looper
@ -8,15 +8,15 @@ import android.view.View
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.TextView import android.widget.TextView
import androidx.lifecycle.ViewModel 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.AidlErrorCodeV2
import com.sunmi.pay.hardware.aidlv2.bean.PinPadConfigV2 import com.sunmi.pay.hardware.aidlv2.bean.PinPadConfigV2
import com.sunmi.pay.hardware.aidlv2.bean.PinPadDataV2 import com.sunmi.pay.hardware.aidlv2.bean.PinPadDataV2
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadListenerV2 import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadListenerV2
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2 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.baselib.ui.CustomPinPadKeyboard
import com.utsmyanmar.checkxread.sdk.SunmiSDK import com.utsmyanmar.checkxread.sdk.SunmiSDK
import com.utsmyanmar.paylibs.model.PayDetail import com.utsmyanmar.paylibs.model.PayDetail
@ -56,6 +56,7 @@ class PinPadViewModel @Inject constructor(
private var mCancelHeight = 112 private var mCancelHeight = 112
private var mCancelCoordinate = intArrayOf(0, 48) private var mCancelCoordinate = intArrayOf(0, 48)
private var dukptIndex = 0 private var dukptIndex = 0
private var tmkIndex = 9
private var tradeData: TradeData? = null private var tradeData: TradeData? = null
private var payDetail: PayDetail? = null private var payDetail: PayDetail? = null
private var pan: String = "" private var pan: String = ""
@ -120,19 +121,18 @@ class PinPadViewModel @Inject constructor(
fun startPinPadProcess( fun startPinPadProcess(
customPinPadKeyboard: CustomPinPadKeyboard customPinPadKeyboard: CustomPinPadKeyboard
) { ) {
// dukptIndex = SystemParamsOperation.getInstance().tmkIndex.toInt()
if(!sunmiPayManager.isReady()){
_alertMsg.value =
"Sunmi Pay SDK is not ready"
dukptIndex = _pinStatus.value =
SystemParamsOperation.getInstance() PinPadStatus.ON_ERROR
.tmkIndex
.toInt() return
initData()
if(mPinPadOptV2 == null){
_alertMsg.value = "PinPad service is not ready!"
} }
initData()
getKSN()
initPinPad(customPinPadKeyboard) initPinPad(customPinPadKeyboard)
} }
@ -182,7 +182,7 @@ class PinPadViewModel @Inject constructor(
try { try {
val securityOptV2 = sunmiPayManager.securityOptV2; val securityOptV2 = sunmiPayManager.securityOptV2;
if(securityOptV2 == null){ if (securityOptV2 == null) {
Log.d(TAG, "Security service is not ready!") Log.d(TAG, "Security service is not ready!")
return; return;
} }
@ -209,7 +209,7 @@ class PinPadViewModel @Inject constructor(
try { try {
val securityOptV2 = sunmiPayManager.securityOptV2; val securityOptV2 = sunmiPayManager.securityOptV2;
if(securityOptV2 == null){ if (securityOptV2 == null) {
Log.d(TAG, "Security service is not ready!") Log.d(TAG, "Security service is not ready!")
return; return;
} }
@ -217,7 +217,7 @@ class PinPadViewModel @Inject constructor(
val dataOut = ByteArray(10) val dataOut = ByteArray(10)
val result = securityOptV2.dukptCurrentKSN(dukptIndex, dataOut) val result = securityOptV2.dukptCurrentKSN(dukptIndex, dataOut)
if (result == 0) { if (result == 0) {
val ksnStr = val ksnStr =
@ -245,7 +245,6 @@ class PinPadViewModel @Inject constructor(
e.printStackTrace() e.printStackTrace()
} }
} }
/* /*
* Init Pin Pad * Init Pin Pad
*/ */
@ -253,58 +252,56 @@ class PinPadViewModel @Inject constructor(
private fun initPinPad( private fun initPinPad(
customPinPadKeyboard: CustomPinPadKeyboard customPinPadKeyboard: CustomPinPadKeyboard
) { ) {
LogUtil.e(TAG, "Init Pin Pad PAN:$pan") LogUtil.e(TAG, "Init Pin Pad PAN:$pan")
val pikIndex = dukptIndex
var timeout = Constants.PIN_PAD_TIMEOUT var timeout = Constants.PIN_PAD_TIMEOUT
var pinPadOrder = true val pinPadOrder = !SystemParamsOperation.getInstance().isRandomPinPad
pinPadOrder = !SystemParamsOperation.getInstance().isRandomPinPad
if (SunmiSDK.getInstance().checkCardExist() != 2) { if (SunmiSDK.getInstance().checkCardExist() != 2) {
timeout = Constants.TIMEOUT timeout = Constants.TIMEOUT
} }
try { 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() keySystem = AidlConstants.Security.SEC_MKSK
config.maxInput = 6 pinKeyIndex = tmkIndex
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 pinblockFormat = AidlConstants.PinBlockFormat.SEC_PIN_BLK_ISO_FMT0
this.pan = getPanBytes(this@PinPadViewModel.pan)
val panBytes = pan.substring(length - 13, length - 1).toByteArray(StandardCharsets.US_ASCII) }
config.pan = panBytes
val result = mPinPadOptV2?.initPinPad(config, mPinPadListener) val result = mPinPadOptV2?.initPinPad(config, mPinPadListener)
LogUtil.e(TAG, "result:$result") LogUtil.e(TAG, "pinpad result:$result")
result?.let { if (result.isNullOrEmpty()) {
getKeyboardCoordinate( _alertMsg.value = "PinPad init failed"
it, _pinStatus.value = PinPadStatus.ON_ERROR
customPinPadKeyboard return
)
} }
getKeyboardCoordinate(result, customPinPadKeyboard)
_pinText.value = "" _pinText.value = ""
customPinPadKeyboard.keepScreenOn = true customPinPadKeyboard.keepScreenOn = true
customPinPadKeyboard.setKeyBoard(result) customPinPadKeyboard.setKeyBoard(result)
customPinPadKeyboard.visibility = View.VISIBLE customPinPadKeyboard.visibility = View.VISIBLE
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
_alertMsg.value = e.message
_pinStatus.value = PinPadStatus.ON_ERROR
} }
} }
/* /*
* Keyboard * Keyboard
@ -436,10 +433,12 @@ class PinPadViewModel @Inject constructor(
ON_NUMBER_CLICK -> { ON_NUMBER_CLICK -> {
showPasswordView(msg.arg1) showPasswordView(msg.arg1)
} }
ON_CONFIRM_CLICK -> { ON_CONFIRM_CLICK -> {
LogUtil.d(TAG, "ON CLICK CONFIRM") LogUtil.d(TAG, "ON CLICK CONFIRM")
_pinStatus.value = PinPadStatus.ON_CONFIRM _pinStatus.value = PinPadStatus.ON_CONFIRM
} }
ON_CANCEL_CLICK -> { ON_CANCEL_CLICK -> {
LogUtil.d(TAG, "ON CLICK CANCEL") LogUtil.d(TAG, "ON CLICK CANCEL")
_pinStatus.value = PinPadStatus.ON_CANCEL _pinStatus.value = PinPadStatus.ON_CANCEL
@ -503,7 +502,7 @@ class PinPadViewModel @Inject constructor(
if ( if (
SunmiSDK.getInstance().checkCardExist() == 2 || SunmiSDK.getInstance().checkCardExist() == 2 ||
payDetail?.cardType == AidlConstants.CardType.MAGNETIC.value || payDetail?.cardType == AidlConstants.CardType.MAGNETIC.getValue() ||
payDetail?.cardType == -9 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.utils.TransactionUtil
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel 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.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import com.utsmyanmar.checkxread.model.CardDataX import com.utsmyanmar.checkxread.model.CardDataX