diff --git a/android/src/googleWallet/java/com/builders/wallet/googletapandpay/GoogleWalletImplementation.kt b/android/src/googleWallet/java/com/builders/wallet/googletapandpay/GoogleWalletImplementation.kt index 964ad57..7f32169 100644 --- a/android/src/googleWallet/java/com/builders/wallet/googletapandpay/GoogleWalletImplementation.kt +++ b/android/src/googleWallet/java/com/builders/wallet/googletapandpay/GoogleWalletImplementation.kt @@ -43,7 +43,7 @@ class GoogleWalletImplementation( try { // Inicializar TapAndPayClient diretamente (sem reflexão!) tapAndPayClient = TapAndPay.getClient(reactContext) - + // Inicializar WalletOpener walletOpener = WalletOpener(reactContext) @@ -74,7 +74,7 @@ class GoogleWalletImplementation( } else { WalletLogger.d(TAG, "🔍 [GOOGLE] Intent data é null") } - + WalletLogger.i(TAG, "Push tokenize OK - Retornando resolve vazio") resolve(null) } @@ -89,7 +89,7 @@ class GoogleWalletImplementation( val errorCodeName = ErrorCode.getErrorCodeName(resultCode) val message = ErrorCode.getErrorMessage(resultCode) val errorMessage = "$message ($errorCodeName) - result_code:$resultCode" - + WalletLogger.w(TAG, "PUSH_TOKENIZE_ERROR: $errorMessage") reject("PUSH_TOKENIZE_ERROR", errorMessage) } @@ -109,7 +109,7 @@ class GoogleWalletImplementation( val errorCodeName = ErrorCode.getErrorCodeName(resultCode) val message = ErrorCode.getErrorMessage(resultCode) val errorMessage = "$message ($errorCodeName) - result_code:$resultCode" - + WalletLogger.w(TAG, "CREATE_WALLET_ERROR: $errorMessage") reject("CREATE_WALLET_ERROR", errorMessage) } @@ -135,13 +135,13 @@ class GoogleWalletImplementation( WalletLogger.w(TAG, "❌ [GOOGLE] Android ${Build.VERSION.SDK_INT} não suportado. Versão mínima requerida: Android 9.0 (API ${MIN_ANDROID_VERSION})") return false } - + // Verificar se o cliente TapAndPay está inicializado if (tapAndPayClient == null) { WalletLogger.w(TAG, "❌ [GOOGLE] Cliente TapAndPay não foi inicializado") return false } - + WalletLogger.d(TAG, "✅ [GOOGLE] Android ${Build.VERSION.SDK_INT} suportado e SDK disponível") return true } @@ -547,10 +547,10 @@ class GoogleWalletImplementation( try { intentListenerActive = true checkPendingDataFromMainActivity() - + // Processar eventos de nenhuma intent pendentes GoogleWalletModule.processNoIntentReceivedEvent(reactContext) - + promise.resolve(true) } catch (e: Exception) { WalletLogger.e(TAG, "SET_INTENT_LISTENER_ERROR: ${e.message}", e) @@ -634,7 +634,7 @@ class GoogleWalletImplementation( val webUrl = GOOGLE_WALLET_PLAY_STORE_URL val success = walletOpener!!.openWallet(packageName, appName, playStoreUrl, webUrl) - + if (success) { WalletLogger.d(TAG, "✅ [GOOGLE] Wallet aberto com sucesso") promise.resolve(true) @@ -725,33 +725,33 @@ class GoogleWalletImplementation( try { // Verificar se há dados pendentes val hasData = hasPendingData() - + if (hasData) { WalletLogger.d(TAG, "✅ [GOOGLE] Dados pendentes encontrados") - + // Obter os dados pendentes sem limpar val data = getPendingIntentDataWithoutClearing() val action = getPendingIntentAction() val callingPackage = getPendingCallingPackage() - + if (data != null && data.isNotEmpty()) { WalletLogger.d(TAG, "📋 [GOOGLE] Processando dados pendentes: ${data.length} caracteres") WalletLogger.d(TAG, "📋 [GOOGLE] Action: $action, CallingPackage: $callingPackage") - + // Verificar se action e callingPackage estão disponíveis if (action == null) { WalletLogger.e(TAG, "❌ [GOOGLE] Action é null - não é possível processar intent") return } - + if (callingPackage == null) { WalletLogger.e(TAG, "❌ [GOOGLE] CallingPackage é null - não é possível processar intent") return } - + // Processar os dados como um intent usando os valores reais processWalletIntentData(data, action, callingPackage) - + // Limpar dados após processamento bem-sucedido clearPendingData() } else { @@ -772,18 +772,18 @@ class GoogleWalletImplementation( WalletLogger.d(TAG, "🔍 [GOOGLE] processWalletIntentData chamado") try { WalletLogger.d(TAG, "✅ [GOOGLE] Intent processado: $action") - + // Determinar o tipo de intent baseado na action val intentType = if (action.endsWith(".action.ACTIVATE_TOKEN")) { "ACTIVATE_TOKEN" } else { "WALLET_INTENT" } - + // Decodificar dados de base64 para string normal var decodedData = data var dataFormat = "raw" - + try { // Tentar decodificar como base64 val decodedBytes = android.util.Base64.decode(data, android.util.Base64.DEFAULT) @@ -795,22 +795,22 @@ class GoogleWalletImplementation( WalletLogger.w(TAG, "⚠️ [GOOGLE] Não foi possível decodificar como base64, usando dados originais: ${e.message}") dataFormat = "raw" } - + val eventData = Arguments.createMap() eventData.putString("action", action) eventData.putString("type", intentType) eventData.putString("data", decodedData) eventData.putString("dataFormat", dataFormat) eventData.putString("callingPackage", callingPackage) - + // Adicionar dados originais em base64 para referência eventData.putString("originalData", data) - + WalletLogger.d(TAG, "🔍 [GOOGLE] Evento preparado - Action: $action, Type: $intentType, Format: $dataFormat") - + // Enviar evento para React Native sendEventToReactNative("GoogleWalletIntentReceived", eventData) - + } catch (e: Exception) { WalletLogger.e(TAG, "❌ [GOOGLE] Erro ao processar dados da intent: ${e.message}", e) } @@ -864,10 +864,10 @@ class GoogleWalletImplementation( private const val GOOGLE_WALLET_PACKAGE = "com.google.android.gms" private const val GOOGLE_WALLET_APP_PACKAGE = "com.google.android.apps.walletnfcrel" private val GOOGLE_WALLET_PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=$GOOGLE_WALLET_APP_PACKAGE&hl=pt_BR" - + // Versão mínima do Android suportada pelo Google Wallet: Android 9.0 (Pie) - API level 28 private const val MIN_ANDROID_VERSION = Build.VERSION_CODES.P - + // Variáveis estáticas para armazenar dados da intent @Volatile private var pendingIntentData: String? = null @@ -875,11 +875,11 @@ class GoogleWalletImplementation( private var pendingIntentAction: String? = null @Volatile private var pendingCallingPackage: String? = null - + // Flag para indicar se há dados pendentes @Volatile private var hasPendingIntentData: Boolean = false - + @JvmStatic fun getPendingIntentData(): String? { val data = pendingIntentData @@ -892,16 +892,16 @@ class GoogleWalletImplementation( } return data } - + @JvmStatic fun getPendingIntentAction(): String? = pendingIntentAction - + @JvmStatic fun getPendingCallingPackage(): String? = pendingCallingPackage - + @JvmStatic fun getPendingIntentDataWithoutClearing(): String? = pendingIntentData - + @JvmStatic fun clearPendingData() { pendingIntentData = null @@ -909,33 +909,33 @@ class GoogleWalletImplementation( pendingCallingPackage = null hasPendingIntentData = false } - + @JvmStatic fun hasPendingData(): Boolean = hasPendingIntentData @JvmStatic fun processIntent(activity: Activity, intent: Intent) { WalletLogger.d(TAG, "🔍 [GOOGLE] processIntent chamado") - + WalletLogger.d(TAG, "🔍 [GOOGLE] Intent encontrada: ${intent.action}") - + // Verificar se é um intent do Google Pay/Wallet if (isGooglePayIntent(intent)) { WalletLogger.d(TAG, "✅ [GOOGLE] Intent do Google Pay detectada") - + // Extrair dados da intent val extraText = intent.getStringExtra(Intent.EXTRA_TEXT) if (!extraText.isNullOrEmpty()) { WalletLogger.d(TAG, "🔍 [GOOGLE] Dados EXTRA_TEXT encontrados: ${extraText.length} caracteres") - + // Armazenar dados para processamento posterior pendingIntentData = extraText pendingIntentAction = intent.action pendingCallingPackage = activity.callingPackage hasPendingIntentData = true - + WalletLogger.d(TAG, "✅ [GOOGLE] Dados armazenados para processamento - Action: ${intent.action}, CallingPackage: ${activity.callingPackage}") - + // Limpar intent para evitar reprocessamento activity.intent = Intent() } else { @@ -945,7 +945,7 @@ class GoogleWalletImplementation( WalletLogger.d(TAG, "🔍 [GOOGLE] Intent não relacionada ao Google Pay") } } - + /** * Verifica se uma intent é relacionada ao Google Pay/Wallet */ @@ -953,10 +953,10 @@ class GoogleWalletImplementation( val action = intent.action WalletLogger.d(TAG, "🔍 [GOOGLE] Verificando intent - Action: $action") - + // Verificar action val isValidAction = action != null && action.endsWith(".action.ACTIVATE_TOKEN") - + return isValidAction } @@ -967,7 +967,7 @@ class GoogleWalletImplementation( fun isValidCallingPackage(activity: Activity): Boolean { val callingPackage = activity.callingPackage WalletLogger.d(TAG, "🔍 [GOOGLE] Chamador: $callingPackage") - + return callingPackage != null && (callingPackage == GOOGLE_WALLET_PACKAGE || callingPackage == GOOGLE_WALLET_APP_PACKAGE) } } diff --git a/android/src/googleWallet/java/com/builders/wallet/googletapandpay/util/ErrorCode.kt b/android/src/googleWallet/java/com/builders/wallet/googletapandpay/util/ErrorCode.kt index 84b7573..885f95b 100644 --- a/android/src/googleWallet/java/com/builders/wallet/googletapandpay/util/ErrorCode.kt +++ b/android/src/googleWallet/java/com/builders/wallet/googletapandpay/util/ErrorCode.kt @@ -26,7 +26,7 @@ object ErrorCode { CommonStatusCodes.CONNECTION_SUSPENDED_DURING_CALL -> "CONNECTION_SUSPENDED_DURING_CALL" CommonStatusCodes.RECONNECTION_TIMED_OUT_DURING_UPDATE -> "RECONNECTION_TIMED_OUT_DURING_UPDATE" CommonStatusCodes.RECONNECTION_TIMED_OUT -> "RECONNECTION_TIMED_OUT" - + // TapAndPay Status Codes TapAndPayStatusCodes.TAP_AND_PAY_NO_ACTIVE_WALLET -> "TAP_AND_PAY_NO_ACTIVE_WALLET" TapAndPayStatusCodes.TAP_AND_PAY_TOKEN_NOT_FOUND -> "TAP_AND_PAY_TOKEN_NOT_FOUND" @@ -42,11 +42,11 @@ object ErrorCode { TapAndPayStatusCodes.TAP_AND_PAY_PAYMENT_CREDENTIALS_DELIVERY_TIMEOUT -> "TAP_AND_PAY_PAYMENT_CREDENTIALS_DELIVERY_TIMEOUT" TapAndPayStatusCodes.TAP_AND_PAY_USER_CANCELED_FLOW -> "TAP_AND_PAY_USER_CANCELED_FLOW" TapAndPayStatusCodes.TAP_AND_PAY_ENROLL_FOR_VIRTUAL_CARDS_FAILED -> "TAP_AND_PAY_ENROLL_FOR_VIRTUAL_CARDS_FAILED" - + else -> "UNKNOWN_ERROR_$errorCode" } } - + fun getErrorMessage(errorCode: Int): String { return when (errorCode) { // Common Status Codes - Mensagens amigáveis @@ -69,7 +69,7 @@ object ErrorCode { CommonStatusCodes.CONNECTION_SUSPENDED_DURING_CALL -> "Conexão suspensa durante chamada" CommonStatusCodes.RECONNECTION_TIMED_OUT_DURING_UPDATE -> "Tempo de reconexão esgotado durante atualização" CommonStatusCodes.RECONNECTION_TIMED_OUT -> "Tempo de reconexão esgotado" - + // TapAndPay Status Codes - Mensagens amigáveis TapAndPayStatusCodes.TAP_AND_PAY_NO_ACTIVE_WALLET -> "Nenhuma carteira ativa" TapAndPayStatusCodes.TAP_AND_PAY_TOKEN_NOT_FOUND -> "Token não encontrado" @@ -85,7 +85,7 @@ object ErrorCode { TapAndPayStatusCodes.TAP_AND_PAY_PAYMENT_CREDENTIALS_DELIVERY_TIMEOUT -> "Tempo limite de entrega de credenciais" TapAndPayStatusCodes.TAP_AND_PAY_USER_CANCELED_FLOW -> "Usuário cancelou a operação" TapAndPayStatusCodes.TAP_AND_PAY_ENROLL_FOR_VIRTUAL_CARDS_FAILED -> "Falha ao registrar cartões virtuais" - + else -> "Erro desconhecido" } } diff --git a/android/src/main/java/com/builders/wallet/googletapandpay/GoogleWalletMock.kt b/android/src/main/java/com/builders/wallet/googletapandpay/GoogleWalletMock.kt index 97ff5a9..4cbef87 100644 --- a/android/src/main/java/com/builders/wallet/googletapandpay/GoogleWalletMock.kt +++ b/android/src/main/java/com/builders/wallet/googletapandpay/GoogleWalletMock.kt @@ -39,7 +39,7 @@ class GoogleWalletMock(private val reactContext: ReactApplicationContext) : Goog private const val GOOGLE_WALLET_PACKAGE = "com.google.android.gms" private const val GOOGLE_WALLET_APP_PACKAGE = "com.google.android.apps.walletnfcrel" private val GOOGLE_WALLET_PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=$GOOGLE_WALLET_APP_PACKAGE&hl=pt_BR" - + // Versão mínima do Android suportada pelo Google Wallet: Android 9.0 (Pie) - API level 28 private const val MIN_ANDROID_VERSION = android.os.Build.VERSION_CODES.P @@ -443,7 +443,7 @@ class GoogleWalletMock(private val reactContext: ReactApplicationContext) : Goog WalletLogger.w(TAG, "❌ [MOCK] Android ${android.os.Build.VERSION.SDK_INT} não suportado. Versão mínima requerida: Android 9.0 (API ${MIN_ANDROID_VERSION})") return false } - + WalletLogger.d(TAG, "✅ [MOCK] Android ${android.os.Build.VERSION.SDK_INT} suportado") return true } @@ -466,14 +466,14 @@ class GoogleWalletMock(private val reactContext: ReactApplicationContext) : Goog override fun checkWalletAvailability(promise: Promise) { WalletLogger.d(TAG, "🔍 [MOCK] checkWalletAvailability chamado") - + // Verificar versão mínima do Android (Android 9.0 - API level 28) if (android.os.Build.VERSION.SDK_INT < MIN_ANDROID_VERSION) { WalletLogger.w(TAG, "❌ [MOCK] Android ${android.os.Build.VERSION.SDK_INT} não suportado. Versão mínima requerida: Android 9.0 (API ${MIN_ANDROID_VERSION})") promise.resolve(false) return } - + fetchFromLocalAPI( endpoint = "/wallet/availability", defaultResponse = { true }, @@ -675,11 +675,11 @@ class GoogleWalletMock(private val reactContext: ReactApplicationContext) : Goog promise: Promise ) { WalletLogger.d(TAG, "🔍 [MOCK] isTokenized chamado - LastFour: $fpanLastFour, Network: $cardNetwork, Provider: $tokenServiceProvider") - + if (!ensureWalletAvailable(promise, "isTokenized")) { return } - + val endpoint = "/wallet/is-tokenized?lastFour=$fpanLastFour&network=$cardNetwork&provider=$tokenServiceProvider" fetchFromLocalAPI( endpoint = endpoint, @@ -707,11 +707,11 @@ class GoogleWalletMock(private val reactContext: ReactApplicationContext) : Goog promise: Promise ) { WalletLogger.d(TAG, "🔍 [MOCK] viewToken chamado - Provider: $tokenServiceProvider, TokenId: $issuerTokenId") - + if (!ensureWalletAvailable(promise, "viewToken")) { return } - + val endpoint = "/wallet/view-token?provider=$tokenServiceProvider&tokenId=$issuerTokenId" fetchFromLocalAPI( endpoint = endpoint, @@ -781,7 +781,7 @@ class GoogleWalletMock(private val reactContext: ReactApplicationContext) : Goog if (!ensureWalletAvailable(promise, "addCardToWallet")) { return } - + // Validar dados do cartão (mesmo que na implementação real) val validationError = validateCardData(cardData) if (validationError != null) { @@ -913,11 +913,11 @@ class GoogleWalletMock(private val reactContext: ReactApplicationContext) : Goog override fun createWalletIfNeeded(promise: Promise) { WalletLogger.d(TAG, "🔍 [MOCK] createWalletIfNeeded chamado") - + if (!ensureWalletAvailable(promise, "createWalletIfNeeded")) { return } - + fetchFromLocalAPI( endpoint = "/wallet/create", defaultResponse = { true }, @@ -1076,7 +1076,7 @@ class GoogleWalletMock(private val reactContext: ReactApplicationContext) : Goog // Verificar dados pendentes da MainActivity automaticamente checkPendingDataFromMainActivity() - + // Processar eventos de nenhuma intent pendentes GoogleWalletModule.processNoIntentReceivedEvent(reactContext) @@ -1248,7 +1248,7 @@ class GoogleWalletMock(private val reactContext: ReactApplicationContext) : Goog val webUrl = GOOGLE_WALLET_PLAY_STORE_URL val success = walletOpener!!.openWallet(packageName, appName, playStoreUrl, webUrl) - + if (success) { WalletLogger.d(TAG, "✅ [MOCK] Wallet aberto com sucesso") promise.resolve(true) diff --git a/example/yarn.lock b/example/yarn.lock index a4aa3d7..65e2769 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -4465,14 +4465,14 @@ __metadata: linkType: hard "js-yaml@npm:^3.13.1": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" dependencies: argparse: ^1.0.7 esprima: ^4.0.0 bin: js-yaml: bin/js-yaml.js - checksum: bef146085f472d44dee30ec34e5cf36bf89164f5d585435a3d3da89e52622dff0b188a580e4ad091c3341889e14cb88cac6e4deb16dc5b1e9623bb0601fc255c + checksum: 626fc207734a3452d6ba84e1c8c226240e6d431426ed94d0ab043c50926d97c509629c08b1d636f5d27815833b7cfd225865631da9fb33cb957374490bf3e90b languageName: node linkType: hard @@ -5146,6 +5146,15 @@ __metadata: languageName: node linkType: hard +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: ^7.1.2 + checksum: a15e6f0128f514b7d41a1c68ce531155447f4669e32d279bba1c1c071ef6c2abd7e4d4579bb59ccc2ed1531346749665968fdd7be8d83eb6b6ae2fe1f3d370a7 + languageName: node + linkType: hard + "mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -5155,15 +5164,6 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^3.0.1": - version: 3.0.1 - resolution: "mkdirp@npm:3.0.1" - bin: - mkdirp: dist/cjs/src/bin.js - checksum: 972deb188e8fb55547f1e58d66bd6b4a3623bf0c7137802582602d73e6480c1c2268dcbafbfb1be466e00cc7e56ac514d7fd9334b7cf33e3e2ab547c16f83a8d - languageName: node - linkType: hard - "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -6366,16 +6366,15 @@ __metadata: linkType: hard "tar@npm:^7.4.3": - version: 7.4.3 - resolution: "tar@npm:7.4.3" + version: 7.5.9 + resolution: "tar@npm:7.5.9" dependencies: "@isaacs/fs-minipass": ^4.0.0 chownr: ^3.0.0 minipass: ^7.1.2 - minizlib: ^3.0.1 - mkdirp: ^3.0.1 + minizlib: ^3.1.0 yallist: ^5.0.0 - checksum: 8485350c0688331c94493031f417df069b778aadb25598abdad51862e007c39d1dd5310702c7be4a6784731a174799d8885d2fde0484269aea205b724d7b2ffa + checksum: 26fbbdf536895814167d03e4883f80febb6520729169c54d0f29ee8a163557283862752493f0e5b60800a6f3608aac3250c41fac8e20a4f056ba4fa63f3dbad7 languageName: node linkType: hard diff --git a/google-wallet-app-mock/app/src/main/java/com/google/android/gms_mock/MainActivity.kt b/google-wallet-app-mock/app/src/main/java/com/google/android/gms_mock/MainActivity.kt index b5aee61..69021f0 100644 --- a/google-wallet-app-mock/app/src/main/java/com/google/android/gms_mock/MainActivity.kt +++ b/google-wallet-app-mock/app/src/main/java/com/google/android/gms_mock/MainActivity.kt @@ -13,12 +13,17 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -32,6 +37,7 @@ import com.google.android.gms_mock.ui.theme.GoogleWalletMockTheme class MainActivity : ComponentActivity() { companion object { private const val TAG = "GoogleWalletMock" + internal const val DEFAULT_SIMULATED_DATA = "9e4eeb4e-71af-4024-b3ff-05c7a2d4460d" } // Variável para controlar o estado do alerta @@ -40,6 +46,9 @@ class MainActivity : ComponentActivity() { // Variável para controlar o estado do resultado na tela private var resultState by mutableStateOf(ResultState()) + // Estado do input de dados simulados + private var simulatedDataInput by mutableStateOf(DEFAULT_SIMULATED_DATA) + data class AlertState( val show: Boolean = false, val title: String = "", @@ -165,7 +174,9 @@ class MainActivity : ComponentActivity() { GoogleWalletMockTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> App2AppSimulator( - onSimulateClick = { simulateApp2App() }, + simulatedDataInput = simulatedDataInput, + onSimulatedDataChange = { simulatedDataInput = it }, + onSimulateClick = { simulateApp2App(simulatedDataInput) }, onClearClick = { clearResults() }, resultState = resultState, modifier = Modifier.padding(innerPadding) @@ -199,11 +210,8 @@ class MainActivity : ComponentActivity() { } } - private fun simulateApp2App() { + private fun simulateApp2App(simulatedData: String) { try { - // Gerar dados simulados com tokenReferenceId - val simulatedData = "9e4eeb4e-71af-4024-b3ff-05c7a2d4460d" - Log.d(TAG, "📋 [GOOGLE] Simulated Data: $simulatedData") val intent = Intent(BuildConfig.TARGET_APP_ACTION).apply { @@ -238,6 +246,8 @@ class MainActivity : ComponentActivity() { @Composable fun App2AppSimulator( + simulatedDataInput: String, + onSimulatedDataChange: (String) -> Unit, onSimulateClick: () -> Unit, onClearClick: () -> Unit, resultState: MainActivity.ResultState, @@ -256,6 +266,27 @@ fun App2AppSimulator( modifier = Modifier.padding(bottom = 32.dp) ) + TextField( + value = simulatedDataInput, + onValueChange = onSimulatedDataChange, + label = { Text("Token Reference ID") }, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + singleLine = true, + textStyle = MaterialTheme.typography.bodyMedium, + trailingIcon = { + if (simulatedDataInput.isNotEmpty()) { + IconButton(onClick = { onSimulatedDataChange("") }) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Limpar" + ) + } + } + } + ) + Button( onClick = onSimulateClick ) { @@ -393,6 +424,8 @@ fun ResultDisplay( fun App2AppSimulatorPreview() { GoogleWalletMockTheme { App2AppSimulator( + simulatedDataInput = MainActivity.DEFAULT_SIMULATED_DATA, + onSimulatedDataChange = { }, onSimulateClick = { }, onClearClick = { }, resultState = MainActivity.ResultState() diff --git a/package.json b/package.json index bebc15d..8c731c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@platformbuilders/wallet-bridge-react-native", - "version": "1.1.1", + "version": "1.1.2", "description": "Este repositório contém um pacote React Native para integração com carteiras digitais. Ele atua como uma ponte (bridge) que se conecta aos SDKs nativos de cada carteira, abstraindo a complexidade dos fluxos de provisionamento (Push e App2App). Inicialmente compatível com Google Pay Wallet, com planos de expansão para Samsung Pay e Apple Wallet.", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/samsung-wallet-app-mock/app/src/main/java/com/samsung/android/spay_mock/MainActivity.kt b/samsung-wallet-app-mock/app/src/main/java/com/samsung/android/spay_mock/MainActivity.kt index 1017b0a..b5254ab 100644 --- a/samsung-wallet-app-mock/app/src/main/java/com/samsung/android/spay_mock/MainActivity.kt +++ b/samsung-wallet-app-mock/app/src/main/java/com/samsung/android/spay_mock/MainActivity.kt @@ -13,12 +13,17 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -32,6 +37,7 @@ import com.samsung.android.spay_mock.ui.theme.SamsungwalletappmockTheme class MainActivity : ComponentActivity() { companion object { private const val TAG = "SamsungWalletMock" + internal const val DEFAULT_SIMULATED_DATA = "9e4eeb4e-71af-4024-b3ff-05c7a2d4460d" } // Estado de alerta @@ -40,6 +46,9 @@ class MainActivity : ComponentActivity() { // Estado de resultado Samsung private var samsungResultState by mutableStateOf(SamsungResultState()) + // Estado do input de dados simulados + private var simulatedDataInput by mutableStateOf(DEFAULT_SIMULATED_DATA) + data class AlertState( val show: Boolean = false, val title: String = "", @@ -154,17 +163,9 @@ class MainActivity : ComponentActivity() { return message.toString() } - private fun simulateSamsungApp2App() { + private fun simulateSamsungApp2App(simulatedData: String) { try { - // Gerar dados simulados (Mastercard/Visa) - val simulatedJson = generateSamsungSimulatedData() - val simulatedData = android.util.Base64.encodeToString( - simulatedJson.toByteArray(Charsets.UTF_8), - android.util.Base64.NO_WRAP - ) - - Log.d(TAG, "📋 [SAMSUNG] JSON gerado: $simulatedJson") - Log.d(TAG, "📋 [SAMSUNG] Base64 gerado: $simulatedData") + Log.d(TAG, "📋 [SAMSUNG] Simulated Data: $simulatedData") val intent = Intent(BuildConfig.SAMSUNG_TARGET_APP_ACTION).apply { setPackage(BuildConfig.SAMSUNG_TARGET_APP_PACKAGE) @@ -193,21 +194,6 @@ class MainActivity : ComponentActivity() { } } - private fun generateSamsungSimulatedData(): String { - - val mastercardData = """ - { - "paymentAppProviderId": "MASTERCARD_PROVIDER_123465", - "paymentAppInstanceId": "INSTANCE_1234", - "tokenUniqueReference": "TOKEN_1758556574675_98397", - "accountPanSuffix": "1234", - "accountExpiry": "12/25" - } - """.trimIndent() - - return mastercardData - } - private fun clearResults() { samsungResultState = SamsungResultState() Log.d(TAG, "🧹 [SAMSUNG] Resultados limpos") @@ -220,7 +206,9 @@ class MainActivity : ComponentActivity() { SamsungwalletappmockTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> SamsungApp2AppSimulator( - onSamsungSimulateClick = { simulateSamsungApp2App() }, + simulatedDataInput = simulatedDataInput, + onSimulatedDataChange = { simulatedDataInput = it }, + onSamsungSimulateClick = { simulateSamsungApp2App(simulatedDataInput) }, onClearClick = { clearResults() }, samsungResultState = samsungResultState, modifier = Modifier.padding(innerPadding) @@ -246,6 +234,8 @@ class MainActivity : ComponentActivity() { @Composable fun SamsungApp2AppSimulator( + simulatedDataInput: String, + onSimulatedDataChange: (String) -> Unit, onSamsungSimulateClick: () -> Unit, onClearClick: () -> Unit, samsungResultState: MainActivity.SamsungResultState, @@ -264,6 +254,27 @@ fun SamsungApp2AppSimulator( modifier = Modifier.padding(bottom = 24.dp) ) + TextField( + value = simulatedDataInput, + onValueChange = onSimulatedDataChange, + label = { Text("Token Reference ID") }, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + singleLine = true, + textStyle = MaterialTheme.typography.bodyMedium, + trailingIcon = { + if (simulatedDataInput.isNotEmpty()) { + IconButton(onClick = { onSimulatedDataChange("") }) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Limpar" + ) + } + } + } + ) + Button( onClick = onSamsungSimulateClick, colors = androidx.compose.material3.ButtonDefaults.buttonColors( @@ -412,6 +423,8 @@ fun SamsungResultDisplay( fun SamsungApp2AppSimulatorPreview() { SamsungwalletappmockTheme { SamsungApp2AppSimulator( + simulatedDataInput = MainActivity.DEFAULT_SIMULATED_DATA, + onSimulatedDataChange = { }, onSamsungSimulateClick = { }, onClearClick = { }, samsungResultState = MainActivity.SamsungResultState() diff --git a/src/__tests__/__mocks__/index.ts b/src/__tests__/__mocks__/index.ts index 7141636..d31da87 100644 --- a/src/__tests__/__mocks__/index.ts +++ b/src/__tests__/__mocks__/index.ts @@ -5,15 +5,15 @@ // que utilizam a biblioteca BuildersWallet import type { - GoogleTokenInfo, - GoogleTokenStatus, - GoogleWalletConstants, - GoogleWalletData, + GoogleTokenInfo, + GoogleTokenStatus, + GoogleWalletConstants, + GoogleWalletData, } from '../../types/google-wallet.types'; import type { - SamsungCard, - SamsungWalletConstants, - SamsungWalletInfo, + SamsungCard, + SamsungWalletConstants, + SamsungWalletInfo, } from '../../types/samsung-wallet.types'; // ============================================================================ @@ -50,6 +50,15 @@ export const mockGoogleWalletConstants: GoogleWalletConstants = { TAP_AND_PAY_INVALID_TOKEN_STATE: 3, TAP_AND_PAY_ATTESTATION_ERROR: 4, TAP_AND_PAY_UNAVAILABLE: 5, + TAP_AND_PAY_SAVE_CARD_ERROR: 6, + TAP_AND_PAY_INELIGIBLE_FOR_TOKENIZATION: 7, + TAP_AND_PAY_TOKENIZATION_DECLINED: 8, + TAP_AND_PAY_CHECK_ELIGIBILITY_ERROR: 9, + TAP_AND_PAY_TOKENIZE_ERROR: 10, + TAP_AND_PAY_TOKEN_ACTIVATION_REQUIRED: 11, + TAP_AND_PAY_PAYMENT_CREDENTIALS_DELIVERY_TIMEOUT: 12, + TAP_AND_PAY_USER_CANCELED_FLOW: 13, + TAP_AND_PAY_ENROLL_FOR_VIRTUAL_CARDS_FAILED: 14, // Token States TOKEN_STATE_ACTIVE: 1, @@ -119,6 +128,8 @@ export const mockGoogleWalletModule = { setActivationResult: jest.fn().mockResolvedValue(true), finishActivity: jest.fn().mockResolvedValue(true), openWallet: jest.fn().mockResolvedValue(true), + setLogListener: jest.fn().mockResolvedValue(true), + removeLogListener: jest.fn().mockResolvedValue(true), }; // ============================================================================ @@ -257,6 +268,8 @@ export const mockSamsungWalletModule = { setActivationResult: jest.fn().mockResolvedValue(true), finishActivity: jest.fn().mockResolvedValue(true), openWallet: jest.fn().mockResolvedValue(true), + setLogListener: jest.fn().mockResolvedValue(true), + removeLogListener: jest.fn().mockResolvedValue(true), }; // ============================================================================ diff --git a/src/google-wallet.ios.ts b/src/google-wallet.ios.ts index 7661447..e3dbca7 100644 --- a/src/google-wallet.ios.ts +++ b/src/google-wallet.ios.ts @@ -7,12 +7,12 @@ import type { GoogleActivationStatus } from './enums'; import type { - GooglePushTokenizeRequestForCard, - GoogleTokenInfo, - GoogleTokenStatus, - GoogleWalletConstants, - GoogleWalletData, - GoogleWalletSpec, + GooglePushTokenizeRequestForCard, + GoogleTokenInfo, + GoogleTokenStatus, + GoogleWalletConstants, + GoogleWalletData, + GoogleWalletSpec, } from './types/google-wallet.types'; // ============================================================================ @@ -49,6 +49,15 @@ const iOS_STUB_CONSTANTS: GoogleWalletConstants = { TAP_AND_PAY_INVALID_TOKEN_STATE: 3, TAP_AND_PAY_ATTESTATION_ERROR: 4, TAP_AND_PAY_UNAVAILABLE: 5, + TAP_AND_PAY_SAVE_CARD_ERROR: 6, + TAP_AND_PAY_INELIGIBLE_FOR_TOKENIZATION: 7, + TAP_AND_PAY_TOKENIZATION_DECLINED: 8, + TAP_AND_PAY_CHECK_ELIGIBILITY_ERROR: 9, + TAP_AND_PAY_TOKENIZE_ERROR: 10, + TAP_AND_PAY_TOKEN_ACTIVATION_REQUIRED: 11, + TAP_AND_PAY_PAYMENT_CREDENTIALS_DELIVERY_TIMEOUT: 12, + TAP_AND_PAY_USER_CANCELED_FLOW: 13, + TAP_AND_PAY_ENROLL_FOR_VIRTUAL_CARDS_FAILED: 14, // Google Token State TOKEN_STATE_ACTIVE: 1, diff --git a/src/types/google-wallet.types.ts b/src/types/google-wallet.types.ts index 64069d0..a6c3ee3 100644 --- a/src/types/google-wallet.types.ts +++ b/src/types/google-wallet.types.ts @@ -120,6 +120,26 @@ export interface GoogleWalletConstants { TAP_AND_PAY_ATTESTATION_ERROR: number; /** A API TapAndPay não pode ser chamada pelo aplicativo atual. Se você receber este erro, certifique-se de que está chamando a API usando um nome de pacote e impressão digital que adicionamos à nossa lista de permissões. */ TAP_AND_PAY_UNAVAILABLE: number; + /** Falha ao salvar o FPAN como um cartão nos registros. */ + TAP_AND_PAY_SAVE_CARD_ERROR: number; + /** O cartão não é elegível para tokenização. */ + TAP_AND_PAY_INELIGIBLE_FOR_TOKENIZATION: number; + /** A tokenização foi recusada pelo TSP (caminho vermelho). */ + TAP_AND_PAY_TOKENIZATION_DECLINED: number; + /** Ocorreu um erro ao verificar a elegibilidade para Tap and Pay. */ + TAP_AND_PAY_CHECK_ELIGIBILITY_ERROR: number; + /** Não é possível chamar a API TapAndPay pelo aplicativo atual. */ + TAP_AND_PAY_TOKENIZE_ERROR: number; + /** A tentativa de provisionamento foi bem-sucedida, mas precisa concluir a verificação extra (caminho amarelo). + * O Google Pay recomenda enfaticamente que os tokens de caminho amarelo não sejam provisionados por push. */ + TAP_AND_PAY_TOKEN_ACTIVATION_REQUIRED: number; + /** O tempo limite para a entrega das credenciais de pagamento foi atingido. */ + TAP_AND_PAY_PAYMENT_CREDENTIALS_DELIVERY_TIMEOUT: number; + /** A tentativa de provisionamento falhou porque o usuário cancelou intencionalmente o fluxo. + * É possível (embora raro) ainda conseguir um token se o usuário cancelar o fluxo mais tarde na tentativa. */ + TAP_AND_PAY_USER_CANCELED_FLOW: number; + /** Falha tentativa de inscrição nos cartões virtuais. */ + TAP_AND_PAY_ENROLL_FOR_VIRTUAL_CARDS_FAILED: number; //GOGLE TOKEN STATE /** O token está ativo e disponível para pagamentos. */ diff --git a/yarn.lock b/yarn.lock index 3fed510..cde7c33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1707,11 +1707,11 @@ __metadata: linkType: hard "@isaacs/brace-expansion@npm:^5.0.0": - version: 5.0.0 - resolution: "@isaacs/brace-expansion@npm:5.0.0" + version: 5.0.1 + resolution: "@isaacs/brace-expansion@npm:5.0.1" dependencies: "@isaacs/balanced-match": ^4.0.1 - checksum: d7a3b8b0ddbf0ccd8eeb1300e29dd0a0c02147e823d8138f248375a365682360620895c66d113e05ee02389318c654379b0e538b996345b83c914941786705b1 + checksum: 21f8192f022c320f7acf899730feb419b1a5f4ccc741481ef8f4b3111e97a41c06e5783871bb240da2e87de909c7fc5b0d07f73818db521fee06541c086ea351 languageName: node linkType: hard @@ -10501,15 +10501,15 @@ __metadata: linkType: hard "tar@npm:^7.5.2": - version: 7.5.2 - resolution: "tar@npm:7.5.2" + version: 7.5.9 + resolution: "tar@npm:7.5.9" dependencies: "@isaacs/fs-minipass": ^4.0.0 chownr: ^3.0.0 minipass: ^7.1.2 minizlib: ^3.1.0 yallist: ^5.0.0 - checksum: 192559b0e7af17d57c7747592ef22c14d5eba2d9c35996320ccd20c3e2038160fe8d928fc5c08b2aa1b170c4d0a18c119441e81eae8f227ca2028d5bcaa6bf23 + checksum: 26fbbdf536895814167d03e4883f80febb6520729169c54d0f29ee8a163557283862752493f0e5b60800a6f3608aac3250c41fac8e20a4f056ba4fa63f3dbad7 languageName: node linkType: hard