Android

Configuração, venda e exemplo completo em Kotlin.

Importante

Antes de publicar, registre os version codes no Play Integrity — veja Version Codes (Android).

Requisitos

ItemValor
minSdk29 (Android 10)
Java / KotlinJDK 17 · Kotlin 2.1.20 ou mais novo
ActivityComponentActivity ou AppCompatActivity
AparelhoNFC obrigatório; emulador não é suportado

Dependência

O repositório Maven e as credenciais de acesso ao SDK são enviados pela Stone no início da integração.

// app/build.gradle.kts
dependencies {
    implementation("com.stone.tapsdk:tap-core:<versão informada pela Stone>")
    // para coletar o Flow do pay()
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:<versão>")
}

android {
    packaging {
        resources {
            excludes += setOf(
                "META-INF/LICENSE.md", "META-INF/LICENSE.txt", "META-INF/NOTICE.md",
                "META-INF/NOTICE.txt", "META-INF/LICENSE-notice.md",
            )
        }
    }
}

O SDK tem a variante de testes (SDX) e a de produção. Se o build de release usa minificação (R8), valide a venda completa no build minificado antes de publicar.

Configuração

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.NFC" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-feature android:name="android.hardware.nfc" android:required="true" />

    <application android:name=".MainApplication" ...>
        <!-- suas activities -->
    </application>
</manifest>

A localização é permissão de runtime e é usada na validação do aparelho. Peça antes do primeiro setup. Sem ela, o setup falha com 331 (permissão ausente) ou 332 (localização desligada).

Confira se o NFC está ligado (NfcAdapter.isEnabled). Se não estiver, leve o usuário às configurações.

A classe Application cria o SDK, como em Setup do SDK (seção 3.1).

Imports usados nos exemplos:

import br.com.stone.sdk.android.error.StoneStatus
import com.stone.tap.core.TapFeeScreens
import com.stone.tap.core.TapSaleExit
import com.stone.tap.core.TapSdk
import com.stone.tap.core.TapStoneCo
import com.stone.tap.core.internal.di.Environment
import com.stone.tap.core.internal.transaction.model.PaymentMethod
import com.stone.tap.core.internal.transaction.model.TransactionData
import com.stone.tap.core.internal.transaction.model.TransactionEvent
import com.stone.tap.core.session.model.Credential
import com.stone.tap.core.session.model.SetupData

Processar Transação

O setup deve ser feito antes — veja Setup do SDK. O SDK exibe todas as telas da venda (Setup do SDK, seção 3.4); o app só trata o resultado final.

val data = TransactionData(
    amount = 1000,                        // R$ 10,00 (centavos)
    paymentMethod = PaymentMethod.CREDIT, // CREDIT, DEBIT ou VOUCHER
    installments = 1,                     // 1 = à vista
    softDescriptor = "",                  // texto na fatura do cliente
)

viewModelScope.launch {
    tapStoneCo.pay(data).collect { event ->
        if (event is TransactionEvent.OnResult) {
            if (event.result == StoneStatus.SUCCESS) {
                handleApproved(event.transactionResult)
            } else {
                handleError(event.result, event.transactionResult)
            }
        }
    }
}
  • O pay() não recebe o lojista: o SDK usa o da sessão ativa criada no setup().
  • A forma de pagamento e as parcelas finais são escolhidas pelo lojista na tela do SDK. Com repasse da taxa, o valor cobrado muda; ele chega em TapFeeScreens.onPaymentMethodConfirmed.
  • Operação: toda venda é autorização com captura. Parcelas: parcelado é sempre lojista.
  • softDescriptor: obrigatório; passe "" se não usar.
  • O OnResult é sempre o último evento. Os demais eventos do fluxo podem ser ignorados.

Cancelamento

O lojista cancela pelas próprias telas do SDK; nesse caso o OnResult vem com 7011 (OPERATION_CANCELLED_BY_USER). Não cancele a coleta do pay() antes do OnResult: o SDK fecha as telas e o resultado se perde.

Exemplo Completo — ViewModel

class PaymentViewModel(
    private val tapStoneCo: TapStoneCo,
) : ViewModel() {

    private val _uiState = MutableStateFlow<PaymentUiState>(PaymentUiState.Idle)
    val uiState: StateFlow<PaymentUiState> = _uiState

    init {
        // Valor final da venda (pode ter repasse da taxa)
        TapFeeScreens.onPaymentMethodConfirmed = { finalData ->
            saveFinalAmount(finalData)
        }
        // Saída da venda aprovada: nova venda ou fechar
        TapFeeScreens.onSaleFinished = { exit ->
            _uiState.value = when (exit) {
                TapSaleExit.NEW_SALE -> PaymentUiState.NewSale
                TapSaleExit.CLOSE -> PaymentUiState.Home
            }
        }
    }

    fun processPayment(amountCents: Int) {
        val data = TransactionData(
            amount = amountCents,
            paymentMethod = PaymentMethod.CREDIT,
            installments = 1,
            softDescriptor = "",
        )
        viewModelScope.launch {
            tapStoneCo.pay(data).collect { event ->
                if (event is TransactionEvent.OnResult) {
                    if (event.result == StoneStatus.SUCCESS) {
                        registerSale(event.transactionResult)
                    } else {
                        _uiState.value = PaymentUiState.Error(event.result.message)
                    }
                }
            }
        }
    }
}

Veja também


Integração: Tap to Pay para Terceiros
Etapa 4 de 7