diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..7061a0d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,61 @@ + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 64e8382..594a34b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,6 +21,7 @@ android { buildTypes { debug { + buildConfigField ("String", "ACTIVATION_CODE", "\"436596309\"") buildConfigField ("String", "APPLICATION_TOKEN", "\"mk_MbQ92RRHEOcGFRIf9/R1A\"") } release { diff --git a/app/src/main/java/com/example/mypos/MainActivity.kt b/app/src/main/java/com/example/mypos/MainActivity.kt index 9b5e255..e018939 100644 --- a/app/src/main/java/com/example/mypos/MainActivity.kt +++ b/app/src/main/java/com/example/mypos/MainActivity.kt @@ -1,25 +1,35 @@ package com.example.mypos +import android.content.Context import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent 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.ui.Modifier +import androidx.compose.ui.platform.LocalContext 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 class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { 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 { POSTheme { - + PaymentScreen(paymentViewModel, aditumSdkService) } } } diff --git a/app/src/main/java/com/example/mypos/data/PaymentRegister.kt b/app/src/main/java/com/example/mypos/data/PaymentRegister.kt new file mode 100644 index 0000000..c3b13c6 --- /dev/null +++ b/app/src/main/java/com/example/mypos/data/PaymentRegister.kt @@ -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? + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mypos/models/PaymentViewModel.kt b/app/src/main/java/com/example/mypos/models/PaymentViewModel.kt index 61a27e8..f50980a 100644 --- a/app/src/main/java/com/example/mypos/models/PaymentViewModel.kt +++ b/app/src/main/java/com/example/mypos/models/PaymentViewModel.kt @@ -2,7 +2,12 @@ package com.example.mypos.models import android.content.Context import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider 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 kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -11,9 +16,10 @@ import kotlinx.coroutines.launch class PaymentViewModel( private val application: PaymentApplication, - private val context: Context + private val context: Context, + private val aditumSdkService: AditumSdkService ) : ViewModel() { - private val paymentApplication = application as PaymentApplication + private val paymentApplication = application private val _isServiceConnected = MutableStateFlow(false) val isServiceConnected: StateFlow = _isServiceConnected.asStateFlow() @@ -22,10 +28,37 @@ class PaymentViewModel( paymentApplication.isServiceConnectedFlow.collect { isConnected -> _isServiceConnected.value = isConnected } + aditumSdkService.register( + listener = object : PaymentRegister { + override fun notification( + message: String?, + transactionStatus: TransactionStatus?, + command: AbecsCommands? + ) { + TODO("Not yet implemented") + } + } + ) } } fun startService() { paymentApplication.startAditumSdkService() } + + companion object { + fun provideFactory( + paymentApplication: PaymentApplication, + context: Context, + aditumSdkService: AditumSdkService + ): ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(PaymentViewModel::class.java)) { + return PaymentViewModel(paymentApplication, context, aditumSdkService) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mypos/screen/PaymentScreen.kt b/app/src/main/java/com/example/mypos/screen/PaymentScreen.kt new file mode 100644 index 0000000..721af53 --- /dev/null +++ b/app/src/main/java/com/example/mypos/screen/PaymentScreen.kt @@ -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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mypos/services/AditumSdkService.kt b/app/src/main/java/com/example/mypos/services/AditumSdkService.kt index c859e90..821aa21 100644 --- a/app/src/main/java/com/example/mypos/services/AditumSdkService.kt +++ b/app/src/main/java/com/example/mypos/services/AditumSdkService.kt @@ -28,6 +28,7 @@ import br.com.aditum.data.v2.model.transactions.ConfirmTransactionCallback import br.com.aditum.data.v2.model.transactions.PendingTransactionsCallback import com.example.mypos.BuildConfig import com.example.mypos.data.AditumError +import com.example.mypos.data.PaymentRegister import com.google.gson.Gson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -52,7 +53,7 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) { private val _pinLengthFlow = MutableStateFlow(0) val pinLengthFlow: StateFlow = _pinLengthFlow.asStateFlow() - fun register() { + fun register(listener: PaymentRegister) { coroutineScope.launch { val callback = object : IPaymentCallback.Stub() { override fun notification( @@ -60,9 +61,15 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) { transactionStatus: TransactionStatus?, command: AbecsCommands? ) { + Log.d(TAG, "\nnotification - ${ message ?: "" }") if (message != null) { _messageFlow.value = message.replace("\\s+".toRegex(), " ").trim() } + listener.notification( + message?.replace("\\s+".toRegex(), " ")?.trim(), + transactionStatus, + command + ) } 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.applicationName = applicationName this.applicationVersion = applicationVersion + this.useOnlySdk = true this.applicationToken = BuildConfig.APPLICATION_TOKEN } @@ -161,7 +167,7 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) { fun pay( amount: Long, - installments: Int = 0, + installments: Int? = null, paymentType: PaymentType, allowContactless: Boolean = true, amountSeasoning: ((Long) -> Long)? = null, @@ -188,8 +194,13 @@ class AditumSdkService(private val paymentApplication: PaymentApplication) { this.amount = amount this.allowContactless = allowContactless manualEntry = false - installmentType = if (paymentType == PaymentType.Debit) InstallmentType.None else InstallmentType.Merchant - installmentNumber = installments + installmentType = if (paymentType == PaymentType.Debit) InstallmentType.Undefined else InstallmentType.Merchant + 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() {