Antes de publicar, registre os version codes no Play Integrity — veja Version Codes (Android).
Requisitos
| Item | Valor |
|---|---|
| minSdk | 29 (Android 10) |
| Java / Kotlin | JDK 17 · Kotlin 2.1.20 ou mais novo |
| Activity | ComponentActivity ou AppCompatActivity |
| Aparelho | NFC 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 nosetup(). - 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