Callbacks (PosPinpadCallback)
Referência da interface PosPinpadCallback (com.tupifintech.apossdk.sdk.api) — eventos e notificações do SDK.
Visão Geral
O integrador implementa PosPinpadCallback e a passa em PosPinpad.create(context, callback). Cada grupo de callbacks representa uma etapa do fluxo: cartão, senha, entradas do operador, resultado, estorno, pré-autorização, PIX, impressão, notificações e erros.
- Os callbacks do fluxo de pagamento (cartão, senha, entradas do operador, resultado, estorno, pré-autorização, PIX, notificações e
onAppError) são entregues na main thread: é seguro atualizar a UI diretamente. - Os callbacks de impressão (
onPrintCompleted,onNoHasPaper,onNotifyPrinterError), de tela secundária (onSecondaryDisplayReady,onSecondaryDisplayError) e de erro HTTP (onNetworkConnectionError,onClientError,onUnauthorizedError,onSessionExpired,onNotFoundError,onServerError,onGenericError) chegam na thread que os originou. UserunOnUiThreadouDispatchers.Mainantes de mexer na UI. - Alguns callbacks entregam uma função (
setPinCoordinates,onInputReceived,confirmRefund...). O fluxo fica parado até o app chamá-la, ou até o tempo da etapa esgotar. - Callbacks com implementação default (
{}) são opcionais; os demais devem ser implementados.
Interface (resumo)
enum class NotificationType { INFO, ERROR, WAITING_CARD, PROCESSING }
interface PosPinpadCallback {
// Cartão
fun onStartGetCard()
fun onCardRemovalRequested() {}
fun onCardRemoved() {}
// PIN
fun onStartGetPinCoordinates(setPinCoordinates: (List<String>) -> Unit)
fun onPinEntry(pinMask: String, message: String, amount: Long)
fun onPinError(remainingAttempts: Int, message: String)
// Transação
fun onTransactionCompleted(result: PaymentResult)
// Inputs do usuário
fun onRequestUserInput(
inputCode: InputCode, minLength: Int, maxLength: Int,
onTimeout: (() -> Unit) -> Unit, onInputReceived: (String?) -> Unit
)
fun onRequestMenuSelection(
title: String, options: List<String>,
onTimeout: (() -> Unit) -> Unit, onOptionSelected: (selectedIndex: Int?) -> Unit
)
// PIX
fun onPixQrCodeGenerated(qrCode: String, transactionId: String, amount: Long, expiresInSeconds: Int? = null)
// Estorno
fun onRefundTransactionsAvailable(
transactions: List<TransactionSummary>, cardLastDigits: String, cardBrand: String, cardBin: String,
confirmRefund: (transactionIndex: Int?, refundAmount: Long?) -> Unit
)
fun onRefundCompleted(result: PaymentResult)
fun onRefundError(errorCode: String, message: String, cardLastDigits: String = "", cardBrand: String = "", cardBin: String = "")
// Pré-autorização
fun onPreAuthTransactionsAvailable(
transactions: List<TransactionSummary>, cardLastDigits: String, cardBrand: String, cardBin: String,
confirmPreAuth: (selectedIndex: Int?, newAmount: Long?) -> Unit
)
// Impressão
fun onPrintCompleted()
fun onNoHasPaper()
fun onNotifyPrinterError(code: Int, msg: String)
// Tela secundária (OCTA 400)
fun onSecondaryDisplayReady() {}
fun onSecondaryDisplayError(errorCode: String, message: String) {}
// Notificações gerais
fun onUiNotify(text: String, type: NotificationType)
fun onTablesLoaded(ok: Boolean)
// Erros
fun onAppError(error: SdkErrorCode, message: String)
fun onNetworkConnectionError() // sem rede
fun onClientError() // HTTP 400
fun onUnauthorizedError() // HTTP 401/403
fun onSessionExpired() // sessão expirada e não renovada
fun onNotFoundError() // HTTP 404
fun onServerError() // HTTP 5xx
fun onGenericError() // outra resposta não 2xx
}
Eventos de Cartão
| Evento | Descrição | Ação recomendada |
|---|---|---|
onStartGetCard() | SDK aguardando cartão | "Aproxime, insira ou passe o cartão" |
onCardRemovalRequested() | SDK solicita remoção do cartão de chip (por exemplo, após um erro) | "Retire o cartão" |
onCardRemoved() | Cartão removido | Prosseguir |
O tipo de leitura e as instruções ao portador ("Aproxime, insira ou passe o cartão", "Processando") chegam por onUiNotify. Sempre que o cartão foi lido pelo chip, result.requiresCardRemoval é true, qualquer que seja o status (aprovada, negada ou erro): chame posPinpad.removeCard() e peça a retirada.
override fun onStartGetCard() {
showMessage("Aproxime, insira ou passe o cartão")
}
override fun onCardRemovalRequested() {
showMessage("Retire o cartão")
}
Eventos de PIN
onStartGetPinCoordinates()
fun onStartGetPinCoordinates(setPinCoordinates: (List<String>) -> Unit)
Na primeira senha do processo, o SDK solicita as coordenadas do teclado PIN. O app exibe a tela de senha, mede a posição de cada botão e devolve a lista pela função setPinCoordinates, em até 5 s. Formato e ordem em Inicialização do Terminal.
override fun onStartGetPinCoordinates(setPinCoordinates: (List<String>) -> Unit) {
showPinScreen(onCoordinatesReady = { setPinCoordinates(it) })
}
onPinEntry() / onPinError()
| Evento | Descrição |
|---|---|
onPinEntry(pinMask, message, amount) | Tecla pressionada — atualizar máscara ("****") |
onPinError(remainingAttempts, message) | PIN offline incorreto; remainingAttempts é 1 ou 2 |
Senha offline bloqueada por excesso de tentativas chega como onAppError(SdkErrorCode.PIN_BLOCKED, ...).
override fun onPinEntry(pinMask: String, message: String, amount: Long) {
updatePinMask(pinMask)
}
override fun onPinError(remainingAttempts: Int, message: String) {
showWarning("$message — tentativas restantes: $remainingAttempts")
}
Resultado da Transação
fun onTransactionCompleted(result: PaymentResult)
Fim de uma venda com cartão, pré-autorização ou captura, e PIX aprovado.
override fun onTransactionCompleted(result: PaymentResult) {
when (result.status) {
PaymentResult.PaymentStatus.APPROVED -> result.receipt?.let { showReceipt(it) }
PaymentResult.PaymentStatus.DECLINED -> showError(result.displayMessage ?: "Pagamento negado")
PaymentResult.PaymentStatus.ERROR -> showError(result.displayMessage ?: "Erro no processamento")
}
// Chip: retirar o cartão também em negadas e erros
if (result.requiresCardRemoval) lifecycleScope.launch { posPinpad.removeCard() }
}
Veja PaymentResult.
Inputs do Usuário
Em alguns cartões o terminal precisa de dados ou escolhas do operador. O app mostra a tela e devolve a resposta pela função recebida. CVV, últimos dígitos, seleção de produto e seleção de transação têm 30 s; a seleção de aplicação EMV usa o tempo informado pelo pinpad.
onRequestUserInput()
O SDK pede um dado adicional (InputCode.CVV ou InputCode.LAST_4_DIGITS). O app coleta e responde por onInputReceived(valor); onInputReceived(null) cancela a operação. onTimeout { ... } registra o que fazer se o tempo esgotar (por exemplo, fechar a tela).
override fun onRequestUserInput(
inputCode: InputCode, minLength: Int, maxLength: Int,
onTimeout: (() -> Unit) -> Unit, onInputReceived: (String?) -> Unit
) {
onTimeout { dismissInputDialog() }
val title = when (inputCode) {
InputCode.CVV -> "Digite o CVV"
InputCode.LAST_4_DIGITS -> "Últimos 4 dígitos"
}
showInputDialog(title, minLength, maxLength,
onConfirm = { value -> onInputReceived(value) },
onCancel = { onInputReceived(null) })
}
onRequestMenuSelection()
Pedido de escolha numa lista, como a seleção de aplicação EMV em cartões com mais de uma aplicação. Devolva o índice com onOptionSelected(indice); onOptionSelected(null) cancela.
override fun onRequestMenuSelection(
title: String, options: List<String>,
onTimeout: (() -> Unit) -> Unit, onOptionSelected: (selectedIndex: Int?) -> Unit
) {
onTimeout { dismissMenu() }
showMenu(title, options) { index -> onOptionSelected(index) }
}
PIX
override fun onPixQrCodeGenerated(qrCode: String, transactionId: String, amount: Long, expiresInSeconds: Int?) {
showPixQrCode(qrCode, expiresInSeconds)
}
Só o pagamento aprovado chega em onTransactionCompleted. QR Code expirado, inválido, não autorizado ou sem pagamento dentro da validade chega como onAppError(SdkErrorCode.PP_CANCEL, mensagem), com o motivo em mensagem.
Estorno
| Evento | Descrição |
|---|---|
onRefundTransactionsAvailable(...) | Transações estornáveis encontradas. Chame confirmRefund(indice, valor): valor = null estorna o saldo total; indice = null cancela |
onRefundCompleted(result) | Estorno aprovado |
onRefundError(errorCode, message, ...) | Falha no fluxo de estorno. errorCode é texto (veja a tabela abaixo) |
Valores de errorCode em onRefundError:
errorCode | Quando ocorre |
|---|---|
TIMEOUT | Tempo esgotado aguardando o cartão. Também chega onAppError(PP_TIMEOUT) |
CANCELLED | Leitura do cartão cancelada. Também chega onAppError(PP_CANCEL) |
CARD_READ_ERROR | Falha na leitura do cartão |
NO_DATA | O cartão foi lido, mas sem dados utilizáveis |
NO_TRANSACTIONS | Nenhuma transação estornável para o cartão (ou nenhum PIX na lista do dia) |
NO_TRANSACTIONS_FOR_METHOD | O cartão tem vendas no dia, mas de outro tipo (cartão × voucher). Basta voltar e trocar o tipo |
INVALID_SELECTION | Índice inválido em confirmRefund |
DETAIL_FETCH_FAILED | Falha ao buscar o detalhe da transação escolhida |
UNSUPPORTED_METHOD | startPixRefundFlow chamado com uma transação que não é PIX |
REFUND_EXCEEDS_BALANCE | Valor pedido acima do saldo estornável |
MAPPING_ERROR | Falha ao processar o resultado do estorno |
| Outro valor | Código de resposta do gateway, com a mensagem em message |
Tempo esgotado e cancelamento na seleção da transação (confirmRefund não chamado a tempo, ou confirmRefund(null, ...)) chegam só como onAppError(PP_TIMEOUT) / onAppError(PP_CANCEL).
override fun onRefundTransactionsAvailable(
transactions: List<TransactionSummary>, cardLastDigits: String, cardBrand: String, cardBin: String,
confirmRefund: (transactionIndex: Int?, refundAmount: Long?) -> Unit
) {
showSelection("Selecione a transação (**** $cardLastDigits)", transactions) { index, partialAmount ->
confirmRefund(index, partialAmount) // partialAmount null = saldo total
}
}
override fun onRefundCompleted(result: PaymentResult) {
showSuccess("Estorno concluído: ${result.receipt?.transactionId}")
}
override fun onRefundError(errorCode: String, message: String, cardLastDigits: String, cardBrand: String, cardBin: String) {
showError("Erro no estorno [$errorCode]: $message")
}
Pré-Autorização
onPreAuthTransactionsAvailable entrega as pré-autorizações do cartão lido. Chame confirmPreAuth(indice, novoValor): novoValor = null captura o valor original; indice = null cancela. O desfecho da captura chega em onTransactionCompleted.
override fun onPreAuthTransactionsAvailable(
transactions: List<TransactionSummary>, cardLastDigits: String, cardBrand: String, cardBin: String,
confirmPreAuth: (selectedIndex: Int?, newAmount: Long?) -> Unit
) {
showSelection("Pré-autorizações (**** $cardLastDigits)", transactions) { index ->
confirmPreAuth(index, null)
}
}
Diferente do estorno, as falhas da pré-autorização chegam por onAppError:
| Situação | Callback |
|---|---|
| Nenhuma pré-autorização para o cartão lido | onAppError(SdkErrorCode.NO_TRANSACTIONS_FOUND, ...) |
O preAuthId informado em startPreAuthFlow não pertence ao cartão lido | onAppError(SdkErrorCode.PRE_AUTH_NOT_FOUND_FOR_CARD, ...) |
| Falha ao buscar as pré-autorizações | onAppError(SdkErrorCode.GATEWAY_NETWORK_ERROR, ...) |
Cancelamento e Tempo Esgotado
Cancelamentos e tempos esgotados chegam como erro:
| Situação | Callback |
|---|---|
Operador cancelou (no pinpad, numa seleção ou com cancelPayment()) | onAppError(SdkErrorCode.PP_CANCEL, ...) |
| Tempo esgotado aguardando o portador ou o operador | onAppError(SdkErrorCode.PP_TIMEOUT, ...) |
| PIX expirado, inválido ou não pago dentro da validade | onAppError(SdkErrorCode.PP_CANCEL, mensagem) |
No estorno com cartão, tempo esgotado e cancelamento durante a leitura do cartão chegam também em onRefundError (TIMEOUT / CANCELLED). Veja Estorno.
override fun onAppError(error: SdkErrorCode, message: String) {
when (error) {
SdkErrorCode.PP_CANCEL -> voltarAoCarrinho()
SdkErrorCode.PP_TIMEOUT -> showMessage("Tempo esgotado")
else -> showError(message)
}
}
Impressão
| Evento | Descrição |
|---|---|
onPrintCompleted() | Impressão concluída (somente quando notifyComplete = true) |
onNoHasPaper() | Sem papel |
onNotifyPrinterError(code, msg) | Erro na impressora (veja Erros da impressora) |
Estes callbacks não chegam na main thread.
override fun onPrintCompleted() {
runOnUiThread { showMessage("Comprovante impresso") }
}
override fun onNoHasPaper() {
runOnUiThread { showError("Sem papel. Recarregue a bobina.") }
}
override fun onNotifyPrinterError(code: Int, msg: String) {
runOnUiThread { showError("Erro na impressora: $msg") }
}
Tela Secundária (OCTA 400)
| Evento | Descrição |
|---|---|
onSecondaryDisplayReady() | Tela secundária pronta |
onSecondaryDisplayError(code, msg) | Erro (NOT_AVAILABLE, INIT_FAILED, SHOW_ERROR, ...) |
Estes callbacks chegam na thread de origem, não necessariamente na main thread.
Notificações Gerais
onUiNotify(text, type) traz as mensagens do terminal, já formatadas para exibição.
NotificationType | Uso |
|---|---|
WAITING_CARD | Aguardando o cartão ("Aproxime, insira ou passe o cartão") |
PROCESSING | Processando ("Processando", "Aguarde") |
INFO | Informação geral (ex.: "Carregando tabelas", aplicação selecionada) |
ERROR | Reservado. O SDK não emite hoje: erros chegam por onAppError |
override fun onUiNotify(text: String, type: NotificationType) {
when (type) {
NotificationType.PROCESSING -> showLoading(text)
else -> showMessage(text)
}
}
override fun onTablesLoaded(ok: Boolean) {
// Chamado uma vez por adquirente na inicialização, e também nas recargas automáticas
if (!ok) showError("Erro ao carregar tabelas EMV")
}
Eventos de Erro
| Evento | Descrição |
|---|---|
onAppError(error: SdkErrorCode, message) | Erro do SDK ou do pinpad. message já vem pronta para exibir (veja SdkErrorCode) |
onNetworkConnectionError() | Sem conexão de rede |
onClientError() | Requisição inválida (HTTP 400) |
onUnauthorizedError() | Não autorizado (HTTP 401/403) |
onSessionExpired() | A renovação do token de sessão falhou. Encerre a sessão e leve o operador de volta ao cadastro do terminal |
onNotFoundError() | Recurso não encontrado (HTTP 404) |
onServerError() | Erro do servidor (HTTP 5xx) |
onGenericError() | Outra resposta não 2xx |
Códigos de SdkErrorCode frequentes:
| Código | Significado |
|---|---|
TERMINAL_NOT_INITIALIZED | Operação chamada antes de terminalInitialization() |
TERMINAL_INITIALIZATION_FAILED | Falha na inicialização do terminal |
PP_CANCEL / PP_TIMEOUT | Cancelado / tempo esgotado |
PIN_BLOCKED | Senha offline bloqueada |
PP_ERRKEY | Chave do adquirente ausente no terminal |
GATEWAY_NETWORK_ERROR / GATEWAY_TIMEOUT / GATEWAY_SERVER_ERROR | Falha de comunicação com o gateway |
AMOUNT_BELOW_MINIMUM / AMOUNT_ABOVE_MAXIMUM / INSTALLMENTS_EXCEEDED | Valor ou parcelas fora dos limites da carga |
INVALID_TRACK_DATA | Leitura de tarja incompleta: passe o cartão de novo |
NO_TRANSACTIONS_FOUND / PRE_AUTH_NOT_FOUND_FOR_CARD | Pré-autorização: nenhuma para o cartão / a informada não é deste cartão |
A lista completa está em Modelos. Erros do estorno chegam em onRefundError, com código em texto: veja Estorno.
ErrorMessageProvider (pacote com.tupifintech.apossdk.sdk.model) traduz o código em texto:
| Método | Uso |
|---|---|
getTitle(context, error) | Título da tela de erro |
getFriendlyMessage(context, error) | Mensagem amigável para a tela de erro (a usada em onAppError quando não há mensagem específica do servidor) |
getDisplayMessage(context, error) | Mensagem curta para a tela do POS durante a operação com cartão; sem mensagem curta, devolve a amigável |
getCategory(error) | ErrorCategory do erro (CARD_READ, CARD_BLOCKED, CARD_NOT_SUPPORTED, TIMEOUT, CANCELLED, SECURITY, CONNECTION, PIN, SYSTEM, TRANSACTION, VALIDATION, GATEWAY, NETWORK, UNKNOWN), útil para escolher ícone ou ação |
Erros da impressora
onNotifyPrinterError(code, msg) traz o código nativo da impressora. Para interpretá-lo, use PrinterErrorCode.fromNativeCode(code) (pacote com.tupifintech.apossdk.domain.enums):
PrinterErrorCode | code | Significado |
|---|---|---|
INIT_FAILED | 161 | Impressora não inicializada (serviço de impressão não está pronto) |
REMOTE_ERROR | 162 | Falha de comunicação com o serviço de impressão |
UNKNOWN | outro | Código não mapeado |
Falta de papel não passa por aqui: chega em onNoHasPaper().
Exemplo
override fun onAppError(error: SdkErrorCode, message: String) {
Log.e("YBY", "${error.name} (${error.code}): $message")
showError(message)
}
// Os callbacks de erro HTTP não chegam na main thread
override fun onNetworkConnectionError() {
runOnUiThread { showError("Sem conexão com a internet") }
}
override fun onUnauthorizedError() {
runOnUiThread { navigateToLogin() }
}
override fun onSessionExpired() {
runOnUiThread { navigateToLogin() }
}
Boas Práticas
- Sempre responda às funções recebidas:
setPinCoordinates,onInputReceived,onOptionSelected,confirmRefundeconfirmPreAuth. Para cancelar, passenull— caso contrário o fluxo trava até o timeout. - Não bloqueie a main thread nos callbacks do fluxo de pagamento: eles são entregues nela. Nos de impressão, tela secundária e erro HTTP, troque para a main thread antes de mexer na UI.
- Trate todos os erros e mostre
messageao operador. - Retire o cartão de chip sempre que
result.requiresCardRemovalfortrue, inclusive em negadas e erros. - Não decida pelo texto das mensagens: use
SdkErrorCodeePaymentResult.status. - Nunca registre em log dados sensíveis (PAN, PIN, track).