Pular para o conteúdo principal

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. Use runOnUiThread ou Dispatchers.Main antes 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​

EventoDescriçãoAçã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 removidoProsseguir

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()​

EventoDescriçã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​

EventoDescriçã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:

errorCodeQuando ocorre
TIMEOUTTempo esgotado aguardando o cartão. Também chega onAppError(PP_TIMEOUT)
CANCELLEDLeitura do cartão cancelada. Também chega onAppError(PP_CANCEL)
CARD_READ_ERRORFalha na leitura do cartão
NO_DATAO cartão foi lido, mas sem dados utilizáveis
NO_TRANSACTIONSNenhuma transação estornável para o cartão (ou nenhum PIX na lista do dia)
NO_TRANSACTIONS_FOR_METHODO 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_FAILEDFalha ao buscar o detalhe da transação escolhida
UNSUPPORTED_METHODstartPixRefundFlow chamado com uma transação que não é PIX
REFUND_EXCEEDS_BALANCEValor pedido acima do saldo estornável
MAPPING_ERRORFalha ao processar o resultado do estorno
Outro valorCó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çãoCallback
Nenhuma pré-autorização para o cartão lidoonAppError(SdkErrorCode.NO_TRANSACTIONS_FOUND, ...)
O preAuthId informado em startPreAuthFlow não pertence ao cartão lidoonAppError(SdkErrorCode.PRE_AUTH_NOT_FOUND_FOR_CARD, ...)
Falha ao buscar as pré-autorizaçõesonAppError(SdkErrorCode.GATEWAY_NETWORK_ERROR, ...)

Cancelamento e Tempo Esgotado​

Cancelamentos e tempos esgotados chegam como erro:

SituaçãoCallback
Operador cancelou (no pinpad, numa seleção ou com cancelPayment())onAppError(SdkErrorCode.PP_CANCEL, ...)
Tempo esgotado aguardando o portador ou o operadoronAppError(SdkErrorCode.PP_TIMEOUT, ...)
PIX expirado, inválido ou não pago dentro da validadeonAppError(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​

EventoDescriçã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)​

EventoDescriçã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.

NotificationTypeUso
WAITING_CARDAguardando o cartão ("Aproxime, insira ou passe o cartão")
PROCESSINGProcessando ("Processando", "Aguarde")
INFOInformação geral (ex.: "Carregando tabelas", aplicação selecionada)
ERRORReservado. 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​

EventoDescriçã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ódigoSignificado
TERMINAL_NOT_INITIALIZEDOperação chamada antes de terminalInitialization()
TERMINAL_INITIALIZATION_FAILEDFalha na inicialização do terminal
PP_CANCEL / PP_TIMEOUTCancelado / tempo esgotado
PIN_BLOCKEDSenha offline bloqueada
PP_ERRKEYChave do adquirente ausente no terminal
GATEWAY_NETWORK_ERROR / GATEWAY_TIMEOUT / GATEWAY_SERVER_ERRORFalha de comunicação com o gateway
AMOUNT_BELOW_MINIMUM / AMOUNT_ABOVE_MAXIMUM / INSTALLMENTS_EXCEEDEDValor ou parcelas fora dos limites da carga
INVALID_TRACK_DATALeitura de tarja incompleta: passe o cartão de novo
NO_TRANSACTIONS_FOUND / PRE_AUTH_NOT_FOUND_FOR_CARDPré-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étodoUso
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):

PrinterErrorCodecodeSignificado
INIT_FAILED161Impressora não inicializada (serviço de impressão não está pronto)
REMOTE_ERROR162Falha de comunicação com o serviço de impressão
UNKNOWNoutroCó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​

  1. Sempre responda às funções recebidas: setPinCoordinates, onInputReceived, onOptionSelected, confirmRefund e confirmPreAuth. Para cancelar, passe null — caso contrário o fluxo trava até o timeout.
  2. 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.
  3. Trate todos os erros e mostre message ao operador.
  4. Retire o cartão de chip sempre que result.requiresCardRemoval for true, inclusive em negadas e erros.
  5. Não decida pelo texto das mensagens: use SdkErrorCode e PaymentResult.status.
  6. Nunca registre em log dados sensíveis (PAN, PIN, track).