tentando passagem de context para paymentApplication

This commit is contained in:
Ighor Moura 2025-08-21 17:51:01 -04:00
parent 0abcf81d79
commit a5f0139307
7 changed files with 837 additions and 317 deletions

View File

@ -12,19 +12,29 @@ android {
applicationId = "com.example.mypos" applicationId = "com.example.mypos"
minSdk = 23 minSdk = 23
targetSdk = 36 targetSdk = 36
versionCode = 1 versionCode = 2
versionName = "1.0" versionName = "1.0"
buildConfigField ("String", "ACTIVATION_CODE", "\"\"") buildConfigField ("String", "ACTIVATION_CODE", "\"\"")
buildConfigField ("String", "APPLICATION_TOKEN", "\"\"") buildConfigField ("String", "APPLICATION_TOKEN", "\"\"")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
signingConfigs {
create("release") {
storeFile = file("mypos.jks")
storePassword = "app644223"
keyAlias = "pos"
keyPassword = "app644223"
}
}
buildTypes { buildTypes {
debug { debug {
buildConfigField ("String", "ACTIVATION_CODE", "\"436596309\"") buildConfigField ("String", "ACTIVATION_CODE", "\"436596309\"")
buildConfigField ("String", "APPLICATION_TOKEN", "\"mk_MbQ92RRHEOcGFRIf9/R1A\"") buildConfigField ("String", "APPLICATION_TOKEN", "\"mk_MbQ92RRHEOcGFRIf9/R1A\"")
} }
release { release {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = false isMinifyEnabled = false
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
@ -47,7 +57,8 @@ android {
} }
dependencies { dependencies {
implementation(files("libs/AditumSdkIntegration-2.3.4.860743-release.aar")) // implementation(files("libs/AditumSdkIntegration-2.3.4.860743-release.aar"))
implementation(files("libs/AditumSdkIntegration-2.3.7-App2App.763915-release.aar"))
implementation("com.google.code.gson:gson:2.13.1") implementation("com.google.code.gson:gson:2.13.1")
implementation("androidx.datastore:datastore-preferences:1.1.7") implementation("androidx.datastore:datastore-preferences:1.1.7")
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)

BIN
app/mypos.jks Normal file

Binary file not shown.

View File

@ -1,8 +1,8 @@
package com.example.mypos.models package com.example.mypos.models
import android.content.Context import android.content.Context
import android.nfc.Tag
import android.util.Log import android.util.Log
import androidx.datastore.preferences.core.edit
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -33,13 +33,28 @@ 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.example.mypos.preferences.PrefKeys
import com.example.mypos.preferences.dataStore
import com.example.mypos.services.PaymentApplication import com.example.mypos.services.PaymentApplication
import com.google.gson.Gson import com.google.gson.Gson
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
enum class SdkState {
NOT_INITIALIZED,
INITIALIZING,
IDLE,
PROCESSING,
PAYING,
CANCELING
}
class PaymentViewModel( class PaymentViewModel(
private val application: PaymentApplication, private val application: PaymentApplication,
@ -48,65 +63,265 @@ class PaymentViewModel(
private val paymentApplication = application private val paymentApplication = application
private val gson = Gson() private val gson = Gson()
private val TAG = "PaymentViewModel" private val TAG = "PaymentViewModel"
// Estado do SDK
private val _sdkState = MutableStateFlow(SdkState.NOT_INITIALIZED)
val sdkState: StateFlow<SdkState> = _sdkState.asStateFlow()
// Estado de conexão do serviço
private val _isServiceConnected = MutableStateFlow(false) private val _isServiceConnected = MutableStateFlow(false)
val isServiceConnected: StateFlow<Boolean> = _isServiceConnected.asStateFlow() val isServiceConnected: StateFlow<Boolean> = _isServiceConnected.asStateFlow()
// Mensagens e dados do PIN pad
private val _messageFlow = MutableStateFlow("") private val _messageFlow = MutableStateFlow("")
val messageFlow: StateFlow<String> = _messageFlow.asStateFlow() val messageFlow: StateFlow<String> = _messageFlow.asStateFlow()
private val _displayFlow = MutableStateFlow("")
val displayFlow: StateFlow<String> = _displayFlow.asStateFlow()
private val _pinLengthFlow = MutableStateFlow(0) private val _pinLengthFlow = MutableStateFlow(0)
val pinLengthFlow: StateFlow<Int> = _pinLengthFlow.asStateFlow() val pinLengthFlow: StateFlow<Int> = _pinLengthFlow.asStateFlow()
// Estado de erro
private val _errorFlow = MutableStateFlow<Pair<String, String?>?>(null)
val errorFlow: StateFlow<Pair<String, String?>?> = _errorFlow.asStateFlow()
init { init {
viewModelScope.launch { viewModelScope.launch {
paymentApplication.isServiceConnectedFlow.collect { isConnected -> paymentApplication.isServiceConnectedFlow.collect { isConnected ->
_isServiceConnected.value = isConnected _isServiceConnected.value = isConnected
if (isConnected) { if (isConnected) {
try { try {
paymentApplication.communicationService?.registerPaymentCallback(paymentCallback) paymentApplication.communicationService?.registerPaymentCallback(notificationCallback)
?: Log.e(TAG, "Failed to register payment callback: communicationService is null") ?: Log.e(TAG, "Failed to register payment callback: communicationService is null")
// Tentar inicializar automaticamente quando o serviço se conectar
autoInitialize()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error registering payment callback: ${e.message}") Log.e(TAG, "Error registering payment callback: ${e.message}")
setError(AditumError.SERVICE_NULL, e.message)
}
} else {
// Quando o serviço desconecta, voltar para NOT_INITIALIZED
if (_sdkState.value != SdkState.NOT_INITIALIZED) {
_sdkState.value = SdkState.NOT_INITIALIZED
} }
} }
} }
} }
} }
fun startService() { private val notificationCallback = object : IPaymentCallback.Stub() {
paymentApplication.startAditumSdkService()
}
val paymentCallback = object : IPaymentCallback.Stub() {
override fun notification( override fun notification(
message: String?, message: String,
transactionStatus: TransactionStatus?, transactionStatus: TransactionStatus,
command: AbecsCommands? command: AbecsCommands
) { ) {
Log.i(TAG, "\nnotification - ${ message ?: "" }") val filteredMessage = message.replace("\\s+".toRegex(), " ").trim()
if (message != null) { viewModelScope.launch {
_messageFlow.value = message.replace("\\s+".toRegex(), " ").trim() if (command == AbecsCommands.Display && filteredMessage != "Callback Added") {
_displayFlow.emit(filteredMessage)
}
_messageFlow.emit(filteredMessage)
} }
} }
override fun pinNotification(message: String?, length: Int) { override fun pinNotification(message: String, length: Int) {
_pinLengthFlow.value = length viewModelScope.launch {
_pinLengthFlow.emit(length)
_messageFlow.emit("PIN entrada: $length dígitos")
}
} }
override fun startGetClearData( override fun startGetClearData(
clearDataRequest: GetClearDataRequest?, clearDataRequest: GetClearDataRequest?,
finished: GetClearDataFinishedCallback? finished: GetClearDataFinishedCallback?
) { ) {
TODO("Not yet implemented") if (clearDataRequest != null) {
Log.d(TAG, "clearDataRequest: ${clearDataRequest.title} ${clearDataRequest.alphaNumeric} ${clearDataRequest.timeout}")
}
} }
override fun startGetMenuSelection( override fun startGetMenuSelection(
menuSelectionRequest: GetMenuSelectionRequest?, clearDataRequest: GetMenuSelectionRequest?,
finished: GetMenuSelectionFinishedCallback? finished: GetMenuSelectionFinishedCallback?
) { ) {
TODO("Not yet implemented") // Implementação customizada se necessário
} }
override fun qrCodeGenerated(qrCode: String?, expirationTime: Int) { override fun qrCodeGenerated(qrCode: String, expirationTime: Int) {
Log.d(TAG, "\nqrCode - ${ qrCode ?: "" }\nexpirationTime - $expirationTime") viewModelScope.launch {
_messageFlow.emit("QR Code gerado - Expira em: $expirationTime segundos")
}
}
}
private fun setError(errorCode: String, errorMessage: String?) {
_errorFlow.value = Pair(errorCode, errorMessage)
}
private fun clearError() {
_errorFlow.value = null
}
private suspend fun getActivationCode(): String? {
return context.dataStore.data.map { preferences ->
preferences[PrefKeys.ACTIVATION_CODE]
}.firstOrNull()
}
private suspend fun getNewActivationCode(): String? {
return context.dataStore.data.map { preferences ->
preferences[PrefKeys.NEW_ACTIVATION_CODE]
}.firstOrNull()
}
fun saveActivationCode(code: String?) {
viewModelScope.launch {
context.dataStore.edit { preferences ->
if (code != null) {
preferences[PrefKeys.ACTIVATION_CODE] = code
} else {
preferences.remove(PrefKeys.ACTIVATION_CODE)
}
}
}
}
fun saveNewActivationCode(code: String?) {
viewModelScope.launch {
context.dataStore.edit { preferences ->
if (code != null) {
preferences[PrefKeys.NEW_ACTIVATION_CODE] = code
} else {
preferences.remove(PrefKeys.NEW_ACTIVATION_CODE)
}
}
}
}
private suspend fun clearNewActivationCode() {
context.dataStore.edit { preferences ->
preferences.remove(PrefKeys.NEW_ACTIVATION_CODE)
}
}
private suspend fun autoInitialize() {
Log.d(TAG, "autoInitialize - Iniciando inicialização automática")
try {
val activationCode = getActivationCode()
val newActivationCode = getNewActivationCode()
Log.d(TAG, "autoInitialize - activation_code: $activationCode, new_activation_code: $newActivationCode")
if (activationCode == null && newActivationCode == null) {
Log.d(TAG, "autoInitialize - Nenhum código de ativação encontrado")
return
}
// Se os códigos são iguais, não precisa fazer nada
if (activationCode != null && newActivationCode != null && activationCode == newActivationCode) {
Log.d(TAG, "autoInitialize - Códigos são iguais, limpando new_activation_code")
clearNewActivationCode()
return
}
// Se existe um novo código diferente do atual, fazer a troca
if (newActivationCode != null && activationCode != null && activationCode != newActivationCode) {
Log.d(TAG, "autoInitialize - Desativando código antigo")
val deactivationSuccess = suspendCancellableCoroutine<Boolean> { continuation ->
deactivateInternal(
resolve = { success -> continuation.resume(success) },
reject = { error, message ->
Log.e(TAG, "Erro na desativação: $error - $message")
continuation.resume(false)
}
)
}
if (!deactivationSuccess) {
Log.e(TAG, "autoInitialize - Falha ao desativar código antigo")
setError(AditumError.DEACTIVATE_ERROR, "Falha ao desativar para usar novo código")
return
}
}
// Usar o código apropriado para inicialização
val codeToUse = newActivationCode ?: activationCode
Log.d(TAG, "autoInitialize - Inicializando com código: $codeToUse")
initAditumSdkInternal(
applicationName = "MyPOS",
applicationVersion = BuildConfig.VERSION_NAME,
activationCode = codeToUse,
resolve = { success ->
if (success && newActivationCode != null) {
// Se usou o novo código com sucesso, salvar como atual e limpar o novo
viewModelScope.launch {
saveActivationCode(newActivationCode)
clearNewActivationCode()
Log.d(TAG, "autoInitialize - Novo código salvo como atual")
}
}
},
reject = { error, message ->
Log.e(TAG, "autoInitialize - Erro na inicialização: $error - $message")
setError(error, message)
}
)
} catch (e: Exception) {
Log.e(TAG, "autoInitialize - Exceção: ${e.message}")
setError(AditumError.INIT_ERROR, e.message)
}
}
private suspend fun ensureServiceReady(): Boolean {
if (!isServiceConnected.value) {
Log.d(TAG, "ensureServiceReady - Serviço não conectado, tentando conectar")
val connected = paymentApplication.startAditumSdkService(context)
if (!connected) {
setError(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
return false
}
}
// Verificar se o SDK está em estado apropriado
when (_sdkState.value) {
SdkState.NOT_INITIALIZED -> {
Log.d(TAG, "ensureServiceReady - SDK não inicializado, tentando inicializar")
return initializeIfNeeded()
}
SdkState.INITIALIZING, SdkState.PROCESSING, SdkState.PAYING, SdkState.CANCELING -> {
setError(AditumError.SERVICE_NULL, "SDK ocupado com outra operação")
return false
}
SdkState.IDLE -> {
return true
}
}
return false
}
private suspend fun initializeIfNeeded(): Boolean = suspendCancellableCoroutine { continuation ->
val activationCode = viewModelScope.launch {
val currentCode = getActivationCode()
val newCode = getNewActivationCode()
val codeToUse = newCode ?: currentCode
if (codeToUse == null) {
setError(AditumError.INIT_ERROR, "Nenhum código de ativação disponível")
continuation.resume(false)
return@launch
}
initAditumSdkInternal(
applicationName = "MyPOS",
applicationVersion = BuildConfig.VERSION_NAME,
activationCode = codeToUse,
resolve = { success -> continuation.resume(success) },
reject = { error, message ->
setError(error, message)
continuation.resume(false)
}
)
} }
} }
@ -116,13 +331,35 @@ class PaymentViewModel(
activationCode: String?, activationCode: String?,
resolve: (Boolean) -> Unit = {}, resolve: (Boolean) -> Unit = {},
reject: (String, String?) -> Unit = { _, _ -> } reject: (String, String?) -> Unit = { _, _ -> }
) {
if (_sdkState.value == SdkState.INITIALIZING) {
reject(AditumError.INIT_ERROR, "SDK já está inicializando")
return
}
initAditumSdkInternal(applicationName, applicationVersion, activationCode, resolve, reject)
}
private fun initAditumSdkInternal(
applicationName: String,
applicationVersion: String,
activationCode: String?,
resolve: (Boolean) -> Unit = {},
reject: (String, String?) -> Unit = { _, _ -> }
) { ) {
viewModelScope.launch { viewModelScope.launch {
try { try {
if (!paymentApplication.ensureServiceConnected()) { _sdkState.value = SdkState.INITIALIZING
clearError()
if (!isServiceConnected.value) {
val connected = paymentApplication.startAditumSdkService(context)
if (!connected) {
_sdkState.value = SdkState.NOT_INITIALIZED
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE) reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
return@launch return@launch
} }
}
val pinpadMessages = PinpadMessages().apply { val pinpadMessages = PinpadMessages().apply {
mainMessage = applicationName mainMessage = applicationName
@ -139,43 +376,37 @@ class PaymentViewModel(
val callback = object : InitResponseCallback.Stub() { val callback = object : InitResponseCallback.Stub() {
override fun onResponse(initResponse: InitResponse?) { override fun onResponse(initResponse: InitResponse?) {
if (initResponse != null) { viewModelScope.launch {
resolve(initResponse.initialized) if (initResponse != null && initResponse.initialized) {
_sdkState.value = SdkState.IDLE
if (activationCode != null) {
saveActivationCode(activationCode)
clearNewActivationCode()
}
resolve(true)
} else { } else {
Log.e(TAG, "onResponse - initResponse is null") _sdkState.value = SdkState.NOT_INITIALIZED
reject(AditumError.INIT_RESPONSE_NULL, AditumError.INIT_RESPONSE_NULL_MESSAGE) val errorMsg = "Falha na inicialização do SDK"
setError(AditumError.INIT_ERROR, errorMsg)
reject(AditumError.INIT_ERROR, errorMsg)
}
} }
} }
} }
paymentApplication.communicationService?.init(initRequest, callback) paymentApplication.communicationService?.init(initRequest, callback)
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE) ?: run {
_sdkState.value = SdkState.NOT_INITIALIZED
reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
}
} catch (e: Exception) { } catch (e: Exception) {
_sdkState.value = SdkState.NOT_INITIALIZED
setError(AditumError.INIT_ERROR, "${AditumError.INIT_ERROR_MESSAGE}: ${e.message}")
reject(AditumError.INIT_ERROR, "${AditumError.INIT_ERROR_MESSAGE}: ${e.message}") reject(AditumError.INIT_ERROR, "${AditumError.INIT_ERROR_MESSAGE}: ${e.message}")
} }
} }
} }
fun getMerchantData(resolve: (MerchantData) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
viewModelScope.launch {
try {
if (!paymentApplication.ensureServiceConnected()) {
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
return@launch
}
val merchantData: MerchantData? = paymentApplication.communicationService?.merchantData
if (merchantData != null) {
resolve(merchantData)
} else {
reject(AditumError.MERCHANT_DATA_NULL, AditumError.MERCHANT_DATA_NULL_MESSAGE)
}
} catch (e: Exception) {
reject(AditumError.MERCHANT_DATA_ERROR, "${AditumError.MERCHANT_DATA_ERROR_MESSAGE}: ${e.message}")
}
}
}
fun pay( fun pay(
amount: Long, amount: Long,
installments: Int? = null, installments: Int? = null,
@ -187,13 +418,17 @@ class PaymentViewModel(
) { ) {
viewModelScope.launch { viewModelScope.launch {
try { try {
if (!paymentApplication.ensureServiceConnected()) { if (!ensureServiceReady()) {
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
return@launch return@launch
} }
_sdkState.value = SdkState.PAYING
clearError()
val adjustedAmount = amountSeasoning?.invoke(amount) ?: amount val adjustedAmount = amountSeasoning?.invoke(amount) ?: amount
if (adjustedAmount <= 0) { if (adjustedAmount <= 0) {
_sdkState.value = SdkState.IDLE
setError(AditumError.INVALID_AMOUNT, AditumError.INVALID_AMOUNT_MESSAGE)
reject(AditumError.INVALID_AMOUNT, AditumError.INVALID_AMOUNT_MESSAGE) reject(AditumError.INVALID_AMOUNT, AditumError.INVALID_AMOUNT_MESSAGE)
return@launch return@launch
} }
@ -216,20 +451,30 @@ class PaymentViewModel(
val callback = object : PaymentResponseCallback.Stub() { val callback = object : PaymentResponseCallback.Stub() {
override fun onResponse(paymentResponse: PaymentResponse?) { override fun onResponse(paymentResponse: PaymentResponse?) {
viewModelScope.launch {
_sdkState.value = SdkState.IDLE
if (paymentResponse != null) { if (paymentResponse != null) {
val json = gson.toJson(paymentResponse) val json = gson.toJson(paymentResponse)
Log.d(TAG, "onResponse - $json") Log.d(TAG, "Payment response: $json")
resolve(paymentResponse) resolve(paymentResponse)
} else { } else {
Log.e(TAG, "onResponse - paymentResponse is null") val errorMsg = "Resposta de pagamento nula"
reject(AditumError.PAYMENT_RESPONSE_NULL, AditumError.PAYMENT_RESPONSE_NULL_MESSAGE) setError(AditumError.PAYMENT_RESPONSE_NULL, errorMsg)
reject(AditumError.PAYMENT_RESPONSE_NULL, errorMsg)
}
} }
} }
} }
paymentApplication.communicationService?.pay(paymentRequest, callback) paymentApplication.communicationService?.pay(paymentRequest, callback)
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE) ?: run {
_sdkState.value = SdkState.IDLE
setError(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
}
} catch (e: Exception) { } catch (e: Exception) {
_sdkState.value = SdkState.IDLE
setError(AditumError.PAYMENT_ERROR, "${AditumError.PAYMENT_ERROR_MESSAGE}: ${e.message}")
reject(AditumError.PAYMENT_ERROR, "${AditumError.PAYMENT_ERROR_MESSAGE}: ${e.message}") reject(AditumError.PAYMENT_ERROR, "${AditumError.PAYMENT_ERROR_MESSAGE}: ${e.message}")
} }
} }
@ -238,20 +483,31 @@ class PaymentViewModel(
fun confirm(nsu: String, resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) { fun confirm(nsu: String, resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
viewModelScope.launch { viewModelScope.launch {
try { try {
if (!paymentApplication.ensureServiceConnected()) { if (!ensureServiceReady()) {
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
return@launch return@launch
} }
_sdkState.value = SdkState.PROCESSING
clearError()
val callback = object : ConfirmTransactionCallback.Stub() { val callback = object : ConfirmTransactionCallback.Stub() {
override fun onResponse(confirmed: Boolean) { override fun onResponse(confirmed: Boolean) {
viewModelScope.launch {
_sdkState.value = SdkState.IDLE
resolve(confirmed) resolve(confirmed)
} }
} }
}
paymentApplication.communicationService?.confirmTransaction(nsu, callback) paymentApplication.communicationService?.confirmTransaction(nsu, callback)
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE) ?: run {
_sdkState.value = SdkState.IDLE
setError(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
}
} catch (e: Exception) { } catch (e: Exception) {
_sdkState.value = SdkState.IDLE
setError(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}")
reject(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}") reject(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}")
} }
} }
@ -260,91 +516,152 @@ class PaymentViewModel(
fun cancel(nsu: String, isReversal: Boolean, resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) { fun cancel(nsu: String, isReversal: Boolean, resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
viewModelScope.launch { viewModelScope.launch {
try { try {
if (!paymentApplication.ensureServiceConnected()) { if (!ensureServiceReady()) {
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
return@launch return@launch
} }
_sdkState.value = SdkState.CANCELING
clearError()
val cancelationRequest = CancelationRequest(nsu, isReversal) val cancelationRequest = CancelationRequest(nsu, isReversal)
val callback = object : CancelationResponseCallback.Stub() { val callback = object : CancelationResponseCallback.Stub() {
override fun onResponse(cancelationResponse: CancelationResponse?) { override fun onResponse(cancelationResponse: CancelationResponse?) {
viewModelScope.launch {
_sdkState.value = SdkState.IDLE
if (cancelationResponse != null) { if (cancelationResponse != null) {
resolve(cancelationResponse.canceled) resolve(cancelationResponse.canceled)
} else { } else {
Log.e(TAG, "onResponse - cancelationResponse is null") val errorMsg = "Resposta de cancelamento nula"
reject(AditumError.CANCEL_RESPONSE_NULL, AditumError.CANCEL_RESPONSE_NULL_MESSAGE) setError(AditumError.CANCEL_RESPONSE_NULL, errorMsg)
reject(AditumError.CANCEL_RESPONSE_NULL, errorMsg)
}
} }
} }
} }
paymentApplication.communicationService?.cancel(cancelationRequest, callback) paymentApplication.communicationService?.cancel(cancelationRequest, callback)
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE) ?: run {
_sdkState.value = SdkState.IDLE
setError(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
}
} catch (e: Exception) { } catch (e: Exception) {
_sdkState.value = SdkState.IDLE
setError(AditumError.CANCEL_ERROR, "${AditumError.CANCEL_ERROR_MESSAGE}: ${e.message}")
reject(AditumError.CANCEL_ERROR, "${AditumError.CANCEL_ERROR_MESSAGE}: ${e.message}") reject(AditumError.CANCEL_ERROR, "${AditumError.CANCEL_ERROR_MESSAGE}: ${e.message}")
} }
} }
} }
fun aboutOperation(resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) { fun abortOperation(resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
viewModelScope.launch { viewModelScope.launch {
if (!paymentApplication.ensureServiceConnected()) { try {
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE) if (!ensureServiceReady()) {
return@launch return@launch
} }
paymentApplication.communicationService?.abortOperation() paymentApplication.communicationService?.abortOperation()
_sdkState.value = SdkState.IDLE
resolve(true)
} catch (e: Exception) {
setError(AditumError.SERVICE_NULL, "${AditumError.SERVICE_NULL_MESSAGE}: ${e.message}")
reject(AditumError.SERVICE_NULL, "${AditumError.SERVICE_NULL_MESSAGE}: ${e.message}")
}
} }
} }
fun pendingTransactions(resolve: (List<Charge?>?) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) { fun pendingTransactions(resolve: (List<Charge?>?) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
viewModelScope.launch { viewModelScope.launch {
try { try {
if (!paymentApplication.ensureServiceConnected()) { if (!ensureServiceReady()) {
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
return@launch return@launch
} }
_sdkState.value = SdkState.PROCESSING
val callback = object : PendingTransactionsCallback.Stub() { val callback = object : PendingTransactionsCallback.Stub() {
override fun onResponse(results: List<Charge?>?) { override fun onResponse(results: List<Charge?>?) {
viewModelScope.launch {
_sdkState.value = SdkState.IDLE
val json = gson.toJson(results) val json = gson.toJson(results)
Log.e(TAG, "onResponse - $json") Log.d(TAG, "Pending transactions: $json")
resolve(results) resolve(results)
} }
} }
}
paymentApplication.communicationService?.pendingTransactions(callback) paymentApplication.communicationService?.pendingTransactions(callback)
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE) ?: run {
_sdkState.value = SdkState.IDLE
setError(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
}
} catch (e: Exception) { } catch (e: Exception) {
_sdkState.value = SdkState.IDLE
setError(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}")
reject(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}") reject(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}")
} }
} }
} }
fun deactivate(resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) { fun deactivate(resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
deactivateInternal(resolve, reject)
}
private fun deactivateInternal(resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
viewModelScope.launch { viewModelScope.launch {
try { try {
if (!paymentApplication.ensureServiceConnected()) { if (!isServiceConnected.value) {
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE) reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
return@launch return@launch
} }
val callback = object : DeactivationResponseCallback.Stub() { val callback = object : DeactivationResponseCallback.Stub() {
override fun onResponse(status: Boolean) { override fun onResponse(status: Boolean) {
Log.d(TAG, "onDeactivationResponse - deactivationResponse: $status") viewModelScope.launch {
_sdkState.value = SdkState.NOT_INITIALIZED
Log.d(TAG, "Deactivation response: $status")
if (status) {
// Limpar os códigos de ativação quando desativar
saveActivationCode(null)
clearNewActivationCode()
}
resolve(status) resolve(status)
} }
} }
}
paymentApplication.communicationService?.deactivate(callback) paymentApplication.communicationService?.deactivate(callback)
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE) ?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
} catch (e: Exception) { } catch (e: Exception) {
setError(AditumError.DEACTIVATE_ERROR, "${AditumError.DEACTIVATE_ERROR_MESSAGE}: ${e.message}")
reject(AditumError.DEACTIVATE_ERROR, "${AditumError.DEACTIVATE_ERROR_MESSAGE}: ${e.message}") reject(AditumError.DEACTIVATE_ERROR, "${AditumError.DEACTIVATE_ERROR_MESSAGE}: ${e.message}")
} }
} }
} }
fun getMerchantData(resolve: (MerchantData) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
viewModelScope.launch {
try {
if (!ensureServiceReady()) {
return@launch
}
val merchantData: MerchantData? = paymentApplication.communicationService?.merchantData
if (merchantData != null) {
resolve(merchantData)
} else {
setError(AditumError.MERCHANT_DATA_NULL, AditumError.MERCHANT_DATA_NULL_MESSAGE)
reject(AditumError.MERCHANT_DATA_NULL, AditumError.MERCHANT_DATA_NULL_MESSAGE)
}
} catch (e: Exception) {
setError(AditumError.MERCHANT_DATA_ERROR, "${AditumError.MERCHANT_DATA_ERROR_MESSAGE}: ${e.message}")
reject(AditumError.MERCHANT_DATA_ERROR, "${AditumError.MERCHANT_DATA_ERROR_MESSAGE}: ${e.message}")
}
}
}
companion object { companion object {
fun provideFactory( fun provideFactory(
paymentApplication: PaymentApplication, paymentApplication: PaymentApplication,

View File

@ -11,4 +11,5 @@ val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "my
object PrefKeys { object PrefKeys {
val SERIAL_NUMBER = stringPreferencesKey("session_serial_number") val SERIAL_NUMBER = stringPreferencesKey("session_serial_number")
val ACTIVATION_CODE = stringPreferencesKey("session_activation_code") val ACTIVATION_CODE = stringPreferencesKey("session_activation_code")
val NEW_ACTIVATION_CODE = stringPreferencesKey("new_session_activation_code")
} }

View File

@ -1,21 +1,26 @@
package com.example.mypos.screen package com.example.mypos.screen
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import br.com.aditum.data.v2.enums.PaymentType import br.com.aditum.data.v2.enums.PaymentType
import com.example.mypos.BuildConfig import com.example.mypos.BuildConfig
import com.example.mypos.models.PaymentViewModel import com.example.mypos.models.PaymentViewModel
import kotlinx.coroutines.flow.collectLatest import com.example.mypos.models.SdkState
@Composable @Composable
fun PaymentScreen(viewModel: PaymentViewModel) { fun PaymentScreen(viewModel: PaymentViewModel) {
@ -23,99 +28,242 @@ fun PaymentScreen(viewModel: PaymentViewModel) {
var amount by remember { mutableStateOf("") } var amount by remember { mutableStateOf("") }
var installments by remember { mutableStateOf("1") } var installments by remember { mutableStateOf("1") }
var paymentType by remember { mutableStateOf(PaymentType.Credit) } var paymentType by remember { mutableStateOf(PaymentType.Credit) }
var activationCode by remember { mutableStateOf(if (BuildConfig.DEBUG) BuildConfig.ACTIVATION_CODE else "") }
var newActivationCode by remember { mutableStateOf(if (BuildConfig.DEBUG) BuildConfig.ACTIVATION_CODE else "") }
val isServiceConnected by viewModel.isServiceConnected.collectAsState() val isServiceConnected by viewModel.isServiceConnected.collectAsState()
val sdkState by viewModel.sdkState.collectAsState()
val message by viewModel.messageFlow.collectAsState() val message by viewModel.messageFlow.collectAsState()
val pinLength by viewModel.pinLengthFlow.collectAsState() val pinLength by viewModel.pinLengthFlow.collectAsState()
var isInitializing by remember { mutableStateOf(false) } val error by viewModel.errorFlow.collectAsState()
var isPaying by remember { mutableStateOf(false) }
var showLoading by remember { mutableStateOf(false) }
val scrollState = rememberScrollState()
val handler = remember { Handler(Looper.getMainLooper()) } val handler = remember { Handler(Looper.getMainLooper()) }
if (showLoading) {
LoadingScreen( LaunchedEffect(error) {
message = message, error?.let { (errorCode, errorMessage) ->
onDismiss = { showLoading = false } handler.post {
) Toast.makeText(context, "Erro: $errorMessage", Toast.LENGTH_LONG).show()
}
}
} }
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp), .padding(16.dp)
.verticalScroll(scrollState),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Text( Text(
text = "Pagamento", text = "Sistema de Pagamento POS",
style = MaterialTheme.typography.headlineMedium style = MaterialTheme.typography.headlineMedium
) )
Text(
text = if (isServiceConnected) "Serviço Conectado" else "Serviço Desconectado", Card(
color = if (isServiceConnected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = when {
!isServiceConnected -> MaterialTheme.colorScheme.errorContainer
sdkState == SdkState.IDLE -> MaterialTheme.colorScheme.primaryContainer
sdkState == SdkState.NOT_INITIALIZED -> MaterialTheme.colorScheme.secondaryContainer
else -> MaterialTheme.colorScheme.tertiaryContainer
}
) )
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Status do Sistema",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = if (isServiceConnected) "Serviço: Conectado" else "Serviço: Desconectado",
style = MaterialTheme.typography.bodyMedium
)
Text(
text = "SDK: ${getSdkStateText(sdkState)}",
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium
)
if (message.isNotEmpty()) {
Spacer(modifier = Modifier.height(4.dp))
Text(
text = message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
if (pinLength > 0) {
Text(
text = "PIN: ${"*".repeat(pinLength)}",
style = MaterialTheme.typography.bodySmall,
fontWeight = FontWeight.Bold
)
}
}
}
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Códigos de Ativação",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = activationCode,
onValueChange = { activationCode = it },
label = { Text("Código de Ativação Atual") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
modifier = Modifier.fillMaxWidth(),
enabled = sdkState != SdkState.INITIALIZING
)
Spacer(modifier = Modifier.height(8.dp))
Button( Button(
onClick = { onClick = {
if (!isServiceConnected) { if (activationCode.isBlank()) {
viewModel.startService()
handler.post { handler.post {
Toast.makeText(context, "Tentando conectar ao serviço...", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Código de ativação não pode estar vazio", Toast.LENGTH_SHORT).show()
} }
return@Button return@Button
} }
if (!isInitializing) { viewModel.saveActivationCode(activationCode)
isInitializing = true handler.post {
showLoading = true Toast.makeText(context, "Código de ativação salvo com sucesso", Toast.LENGTH_SHORT).show()
}
},
enabled = sdkState != SdkState.INITIALIZING,
modifier = Modifier.fillMaxWidth()
) {
Text("Salvar Código Atual")
}
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField(
value = newActivationCode,
onValueChange = { newActivationCode = it },
label = { Text("Novo Código de Ativação") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
modifier = Modifier.fillMaxWidth(),
enabled = sdkState != SdkState.INITIALIZING
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
if (newActivationCode.isBlank()) {
handler.post {
Toast.makeText(context, "Novo código de ativação não pode estar vazio", Toast.LENGTH_SHORT).show()
}
return@Button
}
viewModel.saveNewActivationCode(newActivationCode)
handler.post {
Toast.makeText(context, "Novo código de ativação salvo com sucesso", Toast.LENGTH_SHORT).show()
}
},
enabled = sdkState != SdkState.INITIALIZING,
modifier = Modifier.fillMaxWidth()
) {
Text("Salvar Novo Código")
}
}
}
Button(
onClick = {
val codeToUse = if (newActivationCode.isNotBlank()) newActivationCode else activationCode
if (codeToUse.isBlank()) {
handler.post {
Toast.makeText(context, "Insira um código de ativação", Toast.LENGTH_SHORT).show()
}
return@Button
}
viewModel.initAditumSdk( viewModel.initAditumSdk(
applicationName = "MyPOS", applicationName = "MyPOS",
applicationVersion = "1.0.0", applicationVersion = BuildConfig.VERSION_NAME,
activationCode = BuildConfig.ACTIVATION_CODE, activationCode = codeToUse,
resolve = { success -> resolve = { success ->
isInitializing = false
showLoading = false
handler.post { handler.post {
Toast.makeText( Toast.makeText(
context, context,
if (success) "SDK inicializado com sucesso!" else "Falha na inicialização.", if (success) "SDK inicializado com sucesso!" else "Falha na inicialização do SDK",
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
} }
}, },
reject = { errorCode, errorMessage -> reject = { errorCode, errorMessage ->
isInitializing = false
showLoading = false
handler.post { handler.post {
Toast.makeText(context, "Erro: $errorMessage", Toast.LENGTH_LONG).show() Toast.makeText(context, "Erro: $errorMessage", Toast.LENGTH_LONG).show()
} }
} }
) )
}
}, },
enabled = !isInitializing, enabled = sdkState !in listOf(SdkState.INITIALIZING, SdkState.PROCESSING, SdkState.PAYING, SdkState.CANCELING),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text(if (isInitializing) "Inicializando..." else "Inicializar SDK") Text(
when (sdkState) {
SdkState.INITIALIZING -> "Inicializando SDK..."
SdkState.IDLE -> "Reinicializar SDK"
else -> "Inicializar SDK"
} }
)
}
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Realizar Pagamento",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( OutlinedTextField(
value = amount, value = amount,
onValueChange = { amount = it.filter { char -> char.isDigit() || char == '.' } }, onValueChange = { amount = it.filter { char -> char.isDigit() || char == '.' } },
label = { Text("Valor (R$)") }, label = { Text("Valor (R$)") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth(),
enabled = sdkState == SdkState.IDLE
) )
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( OutlinedTextField(
value = installments, value = installments,
onValueChange = { installments = it.filter { char -> char.isDigit() } }, onValueChange = { installments = it.filter { char -> char.isDigit() } },
label = { Text("Quantidade de Parcelas") }, label = { Text("Quantidade de Parcelas") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
enabled = paymentType == PaymentType.Credit enabled = paymentType == PaymentType.Credit && sdkState == SdkState.IDLE
) )
Spacer(modifier = Modifier.height(12.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly horizontalArrangement = Arrangement.SpaceEvenly
@ -123,102 +271,166 @@ fun PaymentScreen(viewModel: PaymentViewModel) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton( RadioButton(
selected = paymentType == PaymentType.Credit, selected = paymentType == PaymentType.Credit,
onClick = { paymentType = PaymentType.Credit } onClick = { if (sdkState == SdkState.IDLE) paymentType = PaymentType.Credit },
enabled = sdkState == SdkState.IDLE
) )
Text("Crédito") Text("Crédito")
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton( RadioButton(
selected = paymentType == PaymentType.Debit, selected = paymentType == PaymentType.Debit,
onClick = { paymentType = PaymentType.Debit } onClick = { if (sdkState == SdkState.IDLE) paymentType = PaymentType.Debit },
enabled = sdkState == SdkState.IDLE
) )
Text("Débito") Text("Débito")
} }
} }
Spacer(modifier = Modifier.height(12.dp))
Button( Button(
onClick = { onClick = {
val amountValue = amount.toDoubleOrNull()?.times(100)?.toLong() val amountValue = amount.toDoubleOrNull()?.times(100)?.toLong()
val installmentCount = installments.toIntOrNull() ?: 1 val installmentCount = installments.toIntOrNull() ?: 1
if (amountValue == null || amountValue <= 0) { if (amountValue == null || amountValue <= 0) {
handler.post { handler.post {
Toast.makeText(context, "Insira um valor válido", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Insira um valor válido", Toast.LENGTH_SHORT).show()
} }
return@Button return@Button
} }
if (!isServiceConnected) {
handler.post {
Toast.makeText(context, "Serviço não conectado", Toast.LENGTH_SHORT).show()
}
return@Button
}
if (!isPaying) {
isPaying = true
showLoading = true
viewModel.pay( viewModel.pay(
amount = amountValue, amount = amountValue,
installments = if (paymentType == PaymentType.Credit) installmentCount else null, installments = if (paymentType == PaymentType.Credit) installmentCount else null,
paymentType = paymentType, paymentType = paymentType,
allowContactless = true, allowContactless = true,
resolve = { response -> resolve = { response ->
isPaying = false
showLoading = false
handler.post { handler.post {
Toast.makeText(context, "Pagamento realizado com sucesso!", Toast.LENGTH_LONG).show() Toast.makeText(context, "Pagamento realizado com sucesso!", Toast.LENGTH_LONG).show()
} }
}, },
reject = { errorCode, errorMessage -> reject = { errorCode, errorMessage ->
isPaying = false
showLoading = false
handler.post { handler.post {
Toast.makeText(context, "Erro no pagamento: $errorMessage", Toast.LENGTH_LONG).show() Toast.makeText(context, "Erro no pagamento: $errorMessage", Toast.LENGTH_LONG).show()
} }
} }
) )
},
enabled = sdkState == SdkState.IDLE,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = if (sdkState == SdkState.PAYING)
MaterialTheme.colorScheme.secondary
else
MaterialTheme.colorScheme.primary
)
) {
Text(
when (sdkState) {
SdkState.PAYING -> "Processando Pagamento..."
SdkState.IDLE -> "Realizar Pagamento"
else -> "Aguarde..."
}
)
}
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(
onClick = {
viewModel.abortOperation(
resolve = {
handler.post {
Toast.makeText(context, "Operação cancelada", Toast.LENGTH_SHORT).show()
} }
}, },
enabled = !isPaying, reject = { errorCode, errorMessage ->
modifier = Modifier.fillMaxWidth() handler.post {
Toast.makeText(context, "Erro ao cancelar: $errorMessage", Toast.LENGTH_SHORT).show()
}
}
)
},
enabled = sdkState in listOf(SdkState.PAYING, SdkState.PROCESSING, SdkState.CANCELING),
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)
) { ) {
Text(if (isPaying) "Processando Pagamento..." else "Realizar Pagamento") Text("Cancelar Operação")
} }
if (message.isNotEmpty() && !showLoading) { Button(
Text( onClick = {
text = "Mensagem do PIN pad: $message", viewModel.deactivate(
style = MaterialTheme.typography.bodyMedium resolve = { success ->
handler.post {
Toast.makeText(
context,
if (success) "SDK desativado com sucesso" else "Falha ao desativar SDK",
Toast.LENGTH_SHORT
).show()
}
},
reject = { errorCode, errorMessage ->
handler.post {
Toast.makeText(context, "Erro na desativação: $errorMessage", Toast.LENGTH_SHORT).show()
}
}
) )
} },
enabled = sdkState == SdkState.IDLE,
if (pinLength > 0) { colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary)
Text( ) {
text = "Comprimento do PIN: $pinLength", Text("Desativar SDK")
style = MaterialTheme.typography.bodyMedium
)
}
} }
} }
@Composable
fun LoadingScreen(message: String, onDismiss: () -> Unit) { if (sdkState in listOf(SdkState.INITIALIZING, SdkState.PAYING, SdkState.PROCESSING, SdkState.CANCELING)) {
Box( Card(
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.fillMaxSize() colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
.background(MaterialTheme.colorScheme.background.copy(alpha = 0.8f)),
contentAlignment = Alignment.Center
) { ) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp)
) { ) {
CircularProgressIndicator() CircularProgressIndicator()
Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = message.ifEmpty { "Processando..." }, text = when (sdkState) {
style = MaterialTheme.typography.bodyLarge SdkState.INITIALIZING -> "Inicializando SDK..."
SdkState.PAYING -> "Processando pagamento..."
SdkState.PROCESSING -> "Processando operação..."
SdkState.CANCELING -> "Cancelando operação..."
else -> "Aguarde..."
},
style = MaterialTheme.typography.bodyMedium
)
if (message.isNotEmpty()) {
Text(
text = message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
} }
} }
}
}
}
private fun getSdkStateText(state: SdkState): String {
return when (state) {
SdkState.NOT_INITIALIZED -> "Não Inicializado"
SdkState.INITIALIZING -> "Inicializando"
SdkState.IDLE -> "Pronto"
SdkState.PROCESSING -> "Processando"
SdkState.PAYING -> "Pagando"
SdkState.CANCELING -> "Cancelando"
}
}

View File

@ -10,12 +10,12 @@ import android.os.IBinder
import android.util.Log import android.util.Log
import br.com.aditum.IAditumSdkService import br.com.aditum.IAditumSdkService
import br.com.aditum.data.v2.model.MerchantData import br.com.aditum.data.v2.model.MerchantData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class PaymentApplication : Application() { class PaymentApplication : Application() {
companion object { companion object {
@ -55,81 +55,14 @@ class PaymentApplication : Application() {
} }
} }
private var mServiceConnectionListener: OnServiceConnectionListener? = null
var serviceConnectionListener: OnServiceConnectionListener?
get() = mServiceConnectionListener
set(listener) {
mServiceConnectionListener = listener
}
interface OnServiceConnectionListener { interface OnServiceConnectionListener {
fun onServiceConnection(serviceConnected: Boolean) fun onServiceConnection(serviceConnected: Boolean)
} }
override fun onCreate() { private var serviceConnectionListener: OnServiceConnectionListener? = null
super.onCreate()
Log.d(TAG, "onCreate")
startAditumSdkService()
}
override fun onTerminate() { fun setServiceConnectionListener(listener: OnServiceConnectionListener) {
super.onTerminate() this.serviceConnectionListener = listener
Log.d(TAG, "onTerminate")
}
fun startAditumSdkService() {
Log.d(TAG, "startAditumSdkService")
val intent = Intent(ACTION_COMMUNICATION_SERVICE).apply {
setPackage(PACKAGE_NAME)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Log.d(TAG, "Android Oreo or higher: ${Build.VERSION.SDK_INT}")
startForegroundService(intent)
} else {
Log.d(TAG, "Android Nougat or lower: ${Build.VERSION.SDK_INT}")
startService(intent)
}
bindService(intent, mServiceConnection, BIND_AUTO_CREATE or CONTEXT_IGNORE_SECURITY)
Log.d(TAG, "startAditumSdkService - bindService")
}
suspend fun ensureServiceConnected(): Boolean = withContext(Dispatchers.IO) {
if (isServiceConnected && mAditumSdkService?.asBinder()?.isBinderAlive == true) {
Log.d(TAG, "Service already connected")
return@withContext true
}
Log.d(TAG, "Service not connected, attempting to reconnect...")
val intent = Intent(ACTION_COMMUNICATION_SERVICE).apply {
setPackage(PACKAGE_NAME)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
val bound = bindService(
intent,
mServiceConnection,
BIND_AUTO_CREATE or CONTEXT_IGNORE_SECURITY
)
if (bound) {
repeat(30) {
delay(100)
if (isServiceConnected && mAditumSdkService?.asBinder()?.isBinderAlive == true) {
Log.d(TAG, "Service reconnected successfully")
return@withContext true
}
}
}
Log.e(TAG, "Failed to reconnect service")
return@withContext false
} }
private fun setServiceConnected(isConnected: Boolean) { private fun setServiceConnected(isConnected: Boolean) {
@ -137,7 +70,53 @@ class PaymentApplication : Application() {
synchronized(this) { synchronized(this) {
mIsServiceConnected = isConnected mIsServiceConnected = isConnected
_isServiceConnectedFlow.value = isConnected _isServiceConnectedFlow.value = isConnected
mServiceConnectionListener?.onServiceConnection(mIsServiceConnected) serviceConnectionListener?.onServiceConnection(isConnected)
} }
} }
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
}
override fun onTerminate() {
super.onTerminate()
Log.d(TAG, "onTerminate")
}
fun startAditumSdkService(context: Context): Boolean {
Log.d(TAG, "startAditumSdkService")
val intent = Intent(ACTION_COMMUNICATION_SERVICE).apply {
setPackage(PACKAGE_NAME)
}
return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
} catch (e: Exception) {
Log.e(TAG, "Failed to start or bind service: ${e.message}")
false
}
}
fun retryServiceConnection(context: Context, maxAttempts: Int = 3, delayMs: Long = 1000) {
var attempts = 0
while (attempts < maxAttempts && !isServiceConnected) {
if (startAditumSdkService(context)) {
return
}
Thread.sleep(delayMs)
attempts++
}
if (!isServiceConnected) {
Log.e(TAG, "Failed to connect to service after $maxAttempts attempts")
}
}
fun ensureServiceConnected(): Boolean {
return isServiceConnected
}
} }