melhorando aditmSdkService

This commit is contained in:
Ighor Moura 2025-07-23 16:23:44 -04:00
parent 31ab97af95
commit 427c75b93c
7 changed files with 330 additions and 16 deletions

View File

@ -0,0 +1,61 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

View File

@ -21,6 +21,7 @@ android {
buildTypes { buildTypes {
debug { debug {
buildConfigField ("String", "ACTIVATION_CODE", "\"436596309\"")
buildConfigField ("String", "APPLICATION_TOKEN", "\"mk_MbQ92RRHEOcGFRIf9/R1A\"") buildConfigField ("String", "APPLICATION_TOKEN", "\"mk_MbQ92RRHEOcGFRIf9/R1A\"")
} }
release { release {

View File

@ -1,25 +1,35 @@
package com.example.mypos package com.example.mypos
import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.mypos.models.PaymentViewModel
import com.example.mypos.screen.PaymentScreen
import com.example.mypos.services.AditumSdkService
import com.example.mypos.services.PaymentApplication
import com.example.mypos.ui.theme.POSTheme import com.example.mypos.ui.theme.POSTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() val context = this
var paymentApplication = application as PaymentApplication
var aditumSdkService = AditumSdkService(paymentApplication)
var paymentViewModel = ViewModelProvider(
this,
PaymentViewModel.provideFactory(paymentApplication, context, aditumSdkService)
)[PaymentViewModel::class.java]
setContent { setContent {
POSTheme { POSTheme {
PaymentScreen(paymentViewModel, aditumSdkService)
} }
} }
} }

View File

@ -0,0 +1,12 @@
package com.example.mypos.data
import br.com.aditum.data.v2.enums.AbecsCommands
import br.com.aditum.data.v2.enums.TransactionStatus
interface PaymentRegister {
fun notification(
message: String?,
transactionStatus: TransactionStatus?,
command: AbecsCommands?
)
}

View File

@ -2,7 +2,12 @@ package com.example.mypos.models
import android.content.Context import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import br.com.aditum.data.v2.enums.AbecsCommands
import br.com.aditum.data.v2.enums.TransactionStatus
import com.example.mypos.data.PaymentRegister
import com.example.mypos.services.AditumSdkService
import com.example.mypos.services.PaymentApplication import com.example.mypos.services.PaymentApplication
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -11,9 +16,10 @@ import kotlinx.coroutines.launch
class PaymentViewModel( class PaymentViewModel(
private val application: PaymentApplication, private val application: PaymentApplication,
private val context: Context private val context: Context,
private val aditumSdkService: AditumSdkService
) : ViewModel() { ) : ViewModel() {
private val paymentApplication = application as PaymentApplication private val paymentApplication = application
private val _isServiceConnected = MutableStateFlow(false) private val _isServiceConnected = MutableStateFlow(false)
val isServiceConnected: StateFlow<Boolean> = _isServiceConnected.asStateFlow() val isServiceConnected: StateFlow<Boolean> = _isServiceConnected.asStateFlow()
@ -22,10 +28,37 @@ class PaymentViewModel(
paymentApplication.isServiceConnectedFlow.collect { isConnected -> paymentApplication.isServiceConnectedFlow.collect { isConnected ->
_isServiceConnected.value = isConnected _isServiceConnected.value = isConnected
} }
aditumSdkService.register(
listener = object : PaymentRegister {
override fun notification(
message: String?,
transactionStatus: TransactionStatus?,
command: AbecsCommands?
) {
TODO("Not yet implemented")
}
}
)
} }
} }
fun startService() { fun startService() {
paymentApplication.startAditumSdkService() paymentApplication.startAditumSdkService()
} }
companion object {
fun provideFactory(
paymentApplication: PaymentApplication,
context: Context,
aditumSdkService: AditumSdkService
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PaymentViewModel::class.java)) {
return PaymentViewModel(paymentApplication, context, aditumSdkService) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
}
} }

View File

@ -0,0 +1,186 @@
package com.example.mypos.screen
import androidx.compose.foundation.text.KeyboardOptions
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import br.com.aditum.data.v2.enums.PaymentType
import com.example.mypos.BuildConfig
import com.example.mypos.models.PaymentViewModel
import com.example.mypos.services.AditumSdkService
import com.example.mypos.services.PaymentApplication
import kotlinx.coroutines.flow.collectLatest
@Composable
fun PaymentScreen(viewModel: PaymentViewModel, aditumSdkService: AditumSdkService) {
val context = LocalContext.current
var amount by remember { mutableStateOf("") }
var paymentType by remember { mutableStateOf(PaymentType.Credit) }
var isServiceConnected by remember { mutableStateOf(false) }
var message by remember { mutableStateOf("") }
var pinLength by remember { mutableStateOf(0) }
var isInitializing by remember { mutableStateOf(false) }
var isPaying by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
viewModel.isServiceConnected.collectLatest { connected ->
isServiceConnected = connected
}
}
LaunchedEffect(Unit) {
aditumSdkService.messageFlow.collectLatest { msg ->
message = msg
}
}
LaunchedEffect(Unit) {
aditumSdkService.pinLengthFlow.collectLatest { length ->
pinLength = length
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Pagamento",
style = MaterialTheme.typography.headlineMedium
)
Text(
text = if (isServiceConnected) "Serviço Conectado" else "Serviço Desconectado",
color = if (isServiceConnected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
)
Button(
onClick = {
if (!isServiceConnected) {
viewModel.startService()
Toast.makeText(context, "Tentando conectar ao serviço...", Toast.LENGTH_SHORT).show()
return@Button
}
if (!isInitializing) {
isInitializing = true
aditumSdkService.initAditumSdk(
applicationName = "MyPOS",
applicationVersion = "1.0.0",
activationCode = BuildConfig.ACTIVATION_CODE,
resolve = { success ->
isInitializing = false
Toast.makeText(
context,
if (success) "SDK inicializado com sucesso!" else "Falha na inicialização.",
Toast.LENGTH_LONG
).show()
},
reject = { errorCode, errorMessage ->
isInitializing = false
Toast.makeText(context, "Erro: $errorMessage", Toast.LENGTH_LONG).show()
}
)
}
},
enabled = !isInitializing,
modifier = Modifier.fillMaxWidth()
) {
Text(if (isInitializing) "Inicializando..." else "Inicializar SDK")
}
OutlinedTextField(
value = amount,
onValueChange = { amount = it.filter { char -> char.isDigit() || char == '.' } },
label = { Text("Valor (R$)") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
modifier = Modifier.fillMaxWidth()
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(
selected = paymentType == PaymentType.Credit,
onClick = { paymentType = PaymentType.Credit }
)
Text("Crédito")
}
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(
selected = paymentType == PaymentType.Debit,
onClick = { paymentType = PaymentType.Debit }
)
Text("Débito")
}
}
Button(
onClick = {
val amountValue = amount.toDoubleOrNull()?.times(100)?.toLong()
if (amountValue == null || amountValue <= 0) {
Toast.makeText(context, "Insira um valor válido", Toast.LENGTH_SHORT).show()
return@Button
}
if (!isServiceConnected) {
Toast.makeText(context, "Serviço não conectado", Toast.LENGTH_SHORT).show()
return@Button
}
if (!isPaying) {
isPaying = true
aditumSdkService.pay(
amount = amountValue,
paymentType = paymentType,
allowContactless = true,
resolve = { response ->
isPaying = false
Toast.makeText(context, "Pagamento realizado com sucesso!", Toast.LENGTH_LONG).show()
},
reject = { errorCode, errorMessage ->
isPaying = false
Toast.makeText(context, "Erro no pagamento: $errorMessage", Toast.LENGTH_LONG).show()
}
)
}
},
enabled = !isPaying,
modifier = Modifier.fillMaxWidth()
) {
Text(if (isPaying) "Processando Pagamento..." else "Realizar Pagamento")
}
if (message.isNotEmpty()) {
Text(
text = "Mensagem do PIN pad: $message",
style = MaterialTheme.typography.bodyMedium
)
}
if (pinLength > 0) {
Text(
text = "Comprimento do PIN: $pinLength",
style = MaterialTheme.typography.bodyMedium
)
}
}
}

View File

@ -28,6 +28,7 @@ import br.com.aditum.data.v2.model.transactions.ConfirmTransactionCallback
import br.com.aditum.data.v2.model.transactions.PendingTransactionsCallback import br.com.aditum.data.v2.model.transactions.PendingTransactionsCallback
import com.example.mypos.BuildConfig import com.example.mypos.BuildConfig
import com.example.mypos.data.AditumError import com.example.mypos.data.AditumError
import com.example.mypos.data.PaymentRegister
import com.google.gson.Gson import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -52,7 +53,7 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) {
private val _pinLengthFlow = MutableStateFlow(0) private val _pinLengthFlow = MutableStateFlow(0)
val pinLengthFlow: StateFlow<Int> = _pinLengthFlow.asStateFlow() val pinLengthFlow: StateFlow<Int> = _pinLengthFlow.asStateFlow()
fun register() { fun register(listener: PaymentRegister) {
coroutineScope.launch { coroutineScope.launch {
val callback = object : IPaymentCallback.Stub() { val callback = object : IPaymentCallback.Stub() {
override fun notification( override fun notification(
@ -60,9 +61,15 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) {
transactionStatus: TransactionStatus?, transactionStatus: TransactionStatus?,
command: AbecsCommands? command: AbecsCommands?
) { ) {
Log.d(TAG, "\nnotification - ${ message ?: "" }")
if (message != null) { if (message != null) {
_messageFlow.value = message.replace("\\s+".toRegex(), " ").trim() _messageFlow.value = message.replace("\\s+".toRegex(), " ").trim()
} }
listener.notification(
message?.replace("\\s+".toRegex(), " ")?.trim(),
transactionStatus,
command
)
} }
override fun pinNotification(message: String?, length: Int) { override fun pinNotification(message: String?, length: Int) {
@ -88,9 +95,7 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) {
} }
} }
paymentApplication.communicationService?.registerPaymentCallback(callback) ?: run { paymentApplication.communicationService?.registerPaymentCallback(callback)
}
} }
} }
@ -117,6 +122,7 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) {
this.activationCode = if (BuildConfig.DEBUG) BuildConfig.ACTIVATION_CODE else activationCode this.activationCode = if (BuildConfig.DEBUG) BuildConfig.ACTIVATION_CODE else activationCode
this.applicationName = applicationName this.applicationName = applicationName
this.applicationVersion = applicationVersion this.applicationVersion = applicationVersion
this.useOnlySdk = true
this.applicationToken = BuildConfig.APPLICATION_TOKEN this.applicationToken = BuildConfig.APPLICATION_TOKEN
} }
@ -161,7 +167,7 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) {
fun pay( fun pay(
amount: Long, amount: Long,
installments: Int = 0, installments: Int? = null,
paymentType: PaymentType, paymentType: PaymentType,
allowContactless: Boolean = true, allowContactless: Boolean = true,
amountSeasoning: ((Long) -> Long)? = null, amountSeasoning: ((Long) -> Long)? = null,
@ -188,8 +194,13 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) {
this.amount = amount this.amount = amount
this.allowContactless = allowContactless this.allowContactless = allowContactless
manualEntry = false manualEntry = false
installmentType = if (paymentType == PaymentType.Debit) InstallmentType.None else InstallmentType.Merchant installmentType = if (paymentType == PaymentType.Debit) InstallmentType.Undefined else InstallmentType.Merchant
installmentNumber = installments this.installmentNumber = when {
installments == null -> if (paymentType == PaymentType.Credit) 1 else 0
installments > 1 -> installments
paymentType == PaymentType.Debit -> 0
else -> 1
}
} }
val callback = object : PaymentResponseCallback.Stub() { val callback = object : PaymentResponseCallback.Stub() {