7 - Motivo do Attestation

Entenda o processo de attestation do SDK Tap to Pay — verificação de segurança, configuração de credenciais e tratamento de erros.

O attestation é uma verificação de segurança que garante que o SDK está sendo executado em um dispositivo seguro e confiável.


Como funciona?

O attestation ocorre automaticamente antes de cada transação. Se a verificação falhar, o pagamento é interrompido.


pay()  →  Attestation  →  ✅ Aprovado  →  Transação inicia
                       →  ❌ Reprovado →  Pagamento interrompido (onError)

⚠️

Aviso

O erro de attestation pode ocorrer já no momento da criação do terminal. Nesse fluxo, não temos informações suficientes para identificar a causa exata do problema.

É possível reconhecer que se trata de um erro de attestation por meio do código retornado. No entanto, isso apenas indica a categoria do erro — não o motivo específico. Ou seja, saber que é um erro de attestation é diferente de saber o que causou esse erro, e neste ponto não temos visibilidade sobre essa causa.

Além disso, em alguns cenários, pode não haver detalhes adicionais disponíveis para consulta. Portanto, o integrador não deve assumir que todo erro de attestation virá acompanhado de informações completas para diagnóstico.

O que é verificado

VerificaçãoDescrição
🔒 Integridade do dispositivoDispositivo em modo seguro (sem root, sem debug).
📦 Validação do aplicativoAplicativo instalado através da Google Play Store.
🛡️ Verificação de segurançaAmbiente de execução confiável.
🏷️ Validação de versãoVersion code do aplicativo liberado e aprovado.

Validações manuais do dispositivo

Antes de iniciar transações, verifique se o dispositivo atende aos requisitos do Play Integrity. Use o checklist abaixo para identificar e corrigir problemas preventivamente.

Checklist de verificação

Execute estas verificações no dispositivo para garantir que ele passará no attestation:

#VerificaçãoComo validarResultado esperado
1Android 6+Settings > About phone > Android versionVersão 6.0 (API 23) ou superior.
2Google Play Store instalada e atualizadaSettings > Apps > Google Play Store > App detailsPlay Store presente e na versão mais recente.
3Google Play Services atualizadoSettings > Apps > Google Play servicesGoogle Play Services presente e atualizado.
4Conta Google configuradaSettings > AccountsPelo menos uma conta Google ativa no dispositivo.
5Bootloader bloqueadoSettings > About phone > Status (varia por fabricante)Bootloader no estado locked. Dispositivos com bootloader desbloqueado falham na verificação de integridade.
6Dispositivo sem rootVerificar com apps como Root CheckerDispositivo não rooteado. Root compromete a integridade do dispositivo.
7ROM oficial do fabricanteSettings > About phone > Build numberROM padrão do fabricante (não custom ROM como LineageOS, Pixel Experience, etc.).
8Modo de desenvolvedor desativadoSettings > Developer optionsOpções de desenvolvedor desativadas ou, no mínimo, USB debugging desligado.
9Conexão com a internetTestar acesso à internetConexão ativa. O Play Integrity precisa se comunicar com os servidores do Google para emitir o veredito.
10App instalado pela Play StoreSettings > Apps > [Seu App] > App details in storeO app deve ter sido instalado pela Google Play Store, não por sideloading (APK manual).
11Google Play Protect ativadoPlay Store > Menu > Play ProtectPlay Protect ativado e sem ameaças detectadas.
⚠️

Dispositivos que não passam nas verificações 5, 6 ou 7 não conseguirão realizar transações com o SDK Tap to Pay, pois falham na verificação de integridade do dispositivo (DeviceIntegrity_NotMet ou DeviceIntegrity_NotStrong).

Níveis de integridade do Play Integrity

O Google Play Integrity classifica dispositivos em três níveis. O SDK Tap to Pay exige no mínimo o nível MEETS_DEVICE_INTEGRITY:

NívelLabelRequisitos
🟢 BásicoMEETS_BASIC_INTEGRITYO dispositivo passa em verificações básicas de integridade do sistema. Pode incluir dispositivos com bootloader desbloqueado ou ROMs não certificadas. Não é suficiente para o SDK.
🟡 DispositivoMEETS_DEVICE_INTEGRITYO dispositivo é certificado pelo Google, possui Google Play Services e atende aos requisitos de compatibilidade do Android. Nível mínimo exigido pelo SDK.
🔵 ForteMEETS_STRONG_INTEGRITYO dispositivo possui garantias de integridade baseadas em hardware (hardware-backed key attestation) e secure boot. Requer atualizações de segurança recentes (último ano) em Android 13+. Nível recomendado para transações financeiras.

Validação programática

Adicione uma verificação antes de chamar pay() para orientar o usuário caso o dispositivo não atenda aos requisitos básicos:

import android.os.Build
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.common.ConnectionResult

fun checkDeviceReadiness(context: Context): List<String> {
    val issues = mutableListOf<String>()

    // Verificar versão do Android (mínimo API 23)
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        issues.add("Android 6.0 (API 23) ou superior é necessário. Versão atual: ${Build.VERSION.RELEASE}")
    }

    // Verificar Google Play Services
    val googleApiAvailability = GoogleApiAvailability.getInstance()
    val resultCode = googleApiAvailability.isGooglePlayServicesAvailable(context)
    if (resultCode != ConnectionResult.SUCCESS) {
        if (googleApiAvailability.isUserResolvableError(resultCode)) {
            issues.add("Google Play Services precisa ser atualizado.")
        } else {
            issues.add("Google Play Services não está disponível neste dispositivo.")
        }
    }

    // Verificar conectividade
    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as android.net.ConnectivityManager
    val activeNetwork = connectivityManager.activeNetwork
    if (activeNetwork == null) {
        issues.add("Sem conexão com a internet. O attestation requer acesso à rede.")
    }

    return issues
}

// Uso antes de chamar pay()
val issues = checkDeviceReadiness(context)
if (issues.isNotEmpty()) {
    issues.forEach { issue ->
        Log.w("PreAttestation", "⚠️ $issue")
    }
    // Exibir mensagem ao usuário com os problemas encontrados
} else {
    Log.d("PreAttestation", "✅ Dispositivo pronto para attestation")
    // Prosseguir com TapOnPhone.pay(...)
}
💡

Essa verificação programática cobre apenas os pré-requisitos básicos. Verificações mais profundas — como bootloader, root e integridade da ROM — são realizadas diretamente pela Google Play Integrity API durante o attestation automático do SDK.


Configuração das credenciais de Attestation

Para receber informações detalhadas sobre falhas de segurança, configure as credenciais de attestation no parâmetro attestation da classe Credentials durante o setConfig:

val credentials = Credentials(
    clientId = "seu_client_id",
    clientSecret = "seu_client_secret",
    marketplace = "seu_marketplace",
    seller = "seu_seller",
    accessKey = "seu_access_key",

    // Credenciais de attestation (opcionais)
    attestation = Attestation(
        clientId = "seu_client_id_attestation",
        clientSecret = "seu_client_secret_attestation"
    )
)

TapOnPhone.setConfig(
    ConfigParameters(
        context = context,
        credentials = credentials,
        sdkConfig = sdkConfig
    )
)
💡

O campo attestation é completamente opcional. Sem ele, o SDK funciona normalmente, mas não fornece detalhes específicos sobre erros de attestation. Preencha apenas se quiser receber informações detalhadas sobre falhas de segurança do dispositivo.

🔑

As credenciais de attestation são diferentes das credenciais principais do SDK. Elas são recebidas durante o onboarding e são específicas para a Cliente Role. Consulte a seção Onboarding para mais detalhes sobre os tipos de credenciais.


API de Status de Attestation

A API de Status de Attestation por Instance ID fornece informações sobre as verificações realizadas contra um identificador de instância específico, permitindo identificar e gerenciar rapidamente problemas de attestation.

⏱️

Os dados de attestation ficam disponíveis por apenas 1 hora após a consulta.


Quando o Attestation falha

Quando o attestation falha, o SDK executa automaticamente os seguintes passos:

① Identifica o erro  →  ② Consulta a API  →  ③ Retorna detalhes no onError
   DEVICE_STATE_FAILURE     de attestation        attestationStatus + integrityErrorCode

Tratamento no código

O callback onError recebe os detalhes da falha de attestation:

TapOnPhone.pay(
    request = PaymentRequest(
        amount = 10000,  // R$ 100,00
        paymentType = PaymentType.CREDIT,
        installments = 2,
        referenceId = UUID.randomUUID().toString(),
        metadata = """
            {
                "clientId": "1234",
                "name": "John Doe"
            }
        """
    ),
    onSuccess = { result ->
        println("Pagamento Aprovado! Id: ${result.transactionId}")
    },
    onError = { error ->
        when (error.type) {
            ErrorResponse.ErrorType.Payment -> {
                val paymentError = error.error as? PaymentErrorResponse
                paymentError?.let {
                    println("Pagamento negado - id: ${it.transactionId}")
                    println("Mensagem: ${it.message}")
                    println("Código: ${it.code}")
                    println("Descrição: ${it.description}")

                    // Informações de attestation (se disponíveis)
                    it.attestationStatus?.forEach { attestation ->
                        Log.d("status", attestation.status)
                        Log.d("timestamp", attestation.timestampUtc)
                        attestation.outcomes.forEach { outcome ->
                            Log.d(
                                "outcome",
                                "moniker[${outcome.moniker}] " +
                                "code[${outcome.statusCode}] " +
                                "reason[${outcome.reason}]"
                            )
                        }
                    }

                    // Código de erro da Google Play Integrity API
                    it.integrityErrorCode?.let { integrityError ->
                        Log.e("Payment", "Code: ${integrityError.code}")
                        Log.e("Payment", "Description: ${integrityError.description}")
                    }
                }
            }
            else -> {
                // Tratar outros tipos de erro
            }
        }
    },
    onEvent = { event ->
        Log.d("Payment", "Evento: ${event.name}")
    }
)

Tabela de erros de Attestation

Códigos retornados no campo attestationStatus.outcomes:

CódigoReasonDescrição
100ERROR_UnableToDecodeNão foi possível decodificar a resposta.
101RequestDetail_NameNotMatchO nome na requisição não confere.
102RequestDetail_NonceNotMatchO nonce na requisição não confere.
103RequestDetail_InvalidTimeStampTimestamp da requisição inválido.
104AppIntegrity_UnevaluatedA integridade do app não foi avaliada.
105AppIntegrity_UnrecognizedVersionVersão do app não reconhecida pela Play Store.
106AppIntegrity_PackageNameNotMatchO packageName não confere com o esperado.
107AppIntegrity_CertNotMatchO certificado de assinatura não confere.
108AppIntegrity_VersionCodeNotMatchO versionCode não confere com o liberado.
109AppIntegrity_UnsuccessfulResultA verificação de integridade do app falhou.
110DeviceIntegrity_NotMetO dispositivo não atende aos requisitos de integridade.
111DeviceIntegrity_NotStrongO nível de integridade do dispositivo não é suficiente.
112AccountDetails_UnlicensedA conta Google não possui licença para o app.
113AccountDetails_UnevaluatedOs detalhes da conta não foram avaliados.
114AccountDetails_UnsuccessfulResultA verificação da conta falhou.

Integrity Error Codes

A Google Play Integrity API retorna códigos de erro quando não é possível verificar a integridade do dispositivo ou da aplicação. O SDK captura esses erros e os disponibiliza através do campo integrityErrorCode nos objetos SessionErrorResponse e PaymentErrorResponse.

Códigos de erro

CódigoErroDescrição
-1API_NOT_AVAILABLEA Integrity API não está disponível. A Play Store pode estar desatualizada ou não instalada.
-2PLAY_STORE_NOT_FOUNDO app da Play Store não foi encontrado no dispositivo.
-3NETWORK_ERRORErro de conexão com a internet. Verifique a conexão do dispositivo.
-4PLAY_STORE_ACCOUNT_NOT_FOUNDNenhuma conta Google encontrada no dispositivo.
-5APP_UID_MISMATCHO UID da aplicação chamadora não corresponde ao UID do pacote.
-6TOO_MANY_REQUESTSMuitas requisições feitas em curto período. Tente novamente mais tarde.
-7CANNOT_BIND_TO_SERVICENão foi possível conectar ao serviço da Play Store. Pode haver outra versão do serviço em execução.
-8GOOGLE_SERVER_UNAVAILABLEServidores do Google indisponíveis temporariamente.
-9PLAY_STORE_VERSION_OUTDATEDA versão da Play Store está desatualizada. Atualize a Play Store.
-10APP_NOT_INSTALLEDA aplicação não está instalada (pode ocorrer em emuladores ou ambientes de teste mal configurados).
-12CLIENT_TRANSIENT_ERRORErro transiente no cliente. Tente novamente com backoff exponencial.
-100INTERNAL_ERRORErro interno desconhecido.

Como tratar

Trate esses erros exibindo mensagens apropriadas ao usuário. Os cenários mais comuns:

CódigoCenárioOrientação ao usuário
-3Sem conexãoSolicite que o usuário verifique a internet.
-4Sem conta GoogleSolicite que o usuário faça login com uma conta Google no dispositivo.
-9Play Store desatualizadaSolicite que o usuário atualize a Play Store.
-6Muitas requisiçõesAguarde alguns minutos e tente novamente.
-8Servidores indisponíveisTente novamente mais tarde.
📘

Para erros internos ou desconhecidos (como -100), oriente o usuário a tentar novamente mais tarde. Se o problema persistir, colete os logs do SDK e entre em contato com o suporte Zoop.


Onde o campo attestationStatus aparece

O campo attestationStatus existe apenas nos objetos públicos abaixo:

  • SessionErrorResponse — usado em ErrorResponse.Session quando a falha ocorre no contexto de sessão (OperationType.session).
  • PaymentErrorResponse — usado em ErrorResponse.Payment quando a falha ocorre no pagamento (OperationType.pay).
ErrorResponseTipo de errosCampo attestationStatus
InitializeTapOnPhoneErrorNão
TerminalTapOnPhoneErrorNão
SessionSessionErrorResponseSim
PaymentPaymentErrorResponseSim

Outras variantes de ErrorResponse (ex.: Pix, SMS, Default) usam outros tipos em erros. O que esta página chama de status remoto de attestation restringe-se às linhas Sim da tabela. Nas linhas Não, TapOnPhoneError em Initialize e Terminal mantém mensagem, códigos, kernel e demais metadados; apenas não há enriquecimento com o array retornado pela API {instanceId}/attestation-status.

Falha antes de existir instância no backend

Se a criação do terminal falhar antes de haver instanceId (instância ainda não criada ou ID indisponível), o SDK não consegue montar a chamada de status de attestation. O erro exposto nesses casos segue como TapOnPhoneError (Initialize ou Terminal), sem attestationStatus. Isso se alinha à regra de que o instanceId só fica disponível após inicialização bem-sucedida do terminal ver Inicialização .

SessionErrorResponse

data class SessionErrorResponse(
    override val message: String? = "session error",
    val code: Int?,
    val attestationStatus: Array<AttestationStatus>?,
    val kernel: KernelError?,
    val integrityErrorCode: IntegrityErrorCode?,
) : ErrorBase(message) {
    data class KernelError(
        val code: Int,
        val name: String,
        val description: String,
    )
}

PaymentErrorResponse

Trecho ilustrativo — a classe pública inclui também identificação da transação, fonte do erro, integridade e outros campos; o campo relevante para attestation remota é attestationStatus.

data class PaymentErrorResponse(
    val transactionId: String?,
    val referenceId: String?,
    override val message: String? = "payment error",
    val code: Int?,
    val source: String?,
    val description: String?,
    val errorSource: String?,
    val operationType: String?,
    val kernel: KernelError?,
    val attestationStatus: Array<AttestationStatus>,
) : ErrorBase(message) {
    data class KernelError(
        val code: Int,
        val name: String,
        val description: String,
    )
}

Array vazio

O SDK pode preencher attestationStatus com um array vazio quando o enriquecimento não se aplica ou quando a consulta à API de attestation não é concluída com sucesso internamente. O integrador deve tratar a presença do campo como informativa, não como garantia de que uma análise remota foi obtida.