Pular para o conteúdo principal

Cancelamento e recuperação

Nunca cobre de novo depois de no_response ou operation_timeout sem antes recuperar o desfecho. A transação pode ter sido aprovada no instante em que o host ou o seu app foi encerrado.

Cancelamento​

O botão Voltar não cancela a operação. Enquanto há uma venda em curso, a tecla é ignorada de propósito: destruir a tela não cancelaria nada no kernel EMV, e o seu app receberia RESULT_CANCELED para uma operação ainda viva.

Quem cancela é o botão Cancelar da tela do terminal. Ele aparece enquanto o terminal espera alguém:

  • aguardando o cartão;
  • na digitação do CVV ou dos últimos dígitos;
  • na seleção de aplicação EMV;
  • na escolha da transação a estornar;
  • na digitação da senha, pela tecla CANCELAR do teclado.

A tela do QR Code do Pix não tem Cancelar: o Pix termina quando é pago ou quando o QR Code expira.

O cancelamento devolve status = cancelled. Uma transação desfeita pelo terminal também volta como cancelled.

O botão some durante a autorização. Depois que a transação foi enviada ao adquirente, ela pode já estar aprovada, e não há o que cancelar do lado do terminal. Nessa fase, espere o desfecho ou recupere-o pelo id.

O protocolo não tem uma operação de cancelar disparada pelo seu app.

Timeout​

O host tem um teto por operação: 5 minutos para venda, estorno e Pix, e 60 segundos para impressão. Estourado o teto, operation_timeout devolve o controle ao seu app, mas não cancela nada no terminal: a autorização ainda pode voltar aprovada. Por até 2 minutos depois disso, qualquer operação nova recebe terminal_busy, porque aceitá-la faria o desfecho da primeira ser entregue como se fosse o da segunda.

Se a autorização voltar dentro desses 2 minutos, ela substitui o timeout registrado. Por isso o caminho é recuperar, e não cobrar de novo. Passada a janela, o timeout fica como desfecho definitivo pelo terminal.

operation_timeout também é o desfecho quando o cartão não é apresentado no prazo. Nesse caso não há transação em curso, e o desfecho não muda.

Se a tela do host for fechada no meio da operação, o desfecho ainda é registrado e pode ser recuperado. Se o processo do host for encerrado, o desfecho pode não ser registrado: a recuperação responderá transaction_not_found, e a confirmação deve ser feita por list_transactions ou pelo portal.

Recuperação: recover_result​

// Na volta de no_response ou operation_timeout, ou ao reabrir o app com um id pendente
val recuperacao = PosIntegrationClient.recoverResult(idPersistido)
pos.launch(recuperacao.intent)

A consulta não disputa o terminal: funciona mesmo com outra operação em curso.

recoverResult(idPersistido)
│
▼
Desfecho registrado?
│
┌───────────────┬───────────┴───────────┬───────────────────────┐
▼ ▼ ▼ ▼
approved declined, cancelled operation_timeout transaction_not_found
ou outro erro
│ │ │ │
Concluir. Não houve aprovação. Ainda pode voltar Nenhum desfecho para
NÃO cobrar Pode cobrar de novo aprovada por até 2 min. o id. Confirmar no
de novo. com requisição nova. Depois disso, confirmar portal antes de cobrar.
no portal antes de
cobrar.
Resposta da recuperaçãoO que significaO que fazer
approvedA operação foi aprovadaConcluir com o receipt recebido. Não cobrar de novo
declined, cancelled ou error com outro códigoNão houve aprovaçãoPode cobrar de novo, com uma requisição nova
error com operation_timeoutA autorização ainda pode voltar, por até 2 minutos depois do timeoutAguardar alguns segundos e consultar de novo. Se ainda vier operation_timeout depois de 2 minutos, o desfecho não virá mais pelo terminal: confirme por list_transactions ou pelo portal antes de cobrar de novo
error com transaction_not_foundNenhum desfecho registrado para esse idA Intent não chegou ao host, foi recusada na entrada (busy, malformed_request, unauthorized_caller), o processo do host foi encerrado durante a operação, ou o registro passou de 24 h. Confirme no portal antes de cobrar de novo

Idempotência e guarda dos desfechos​

  • O host guarda o desfecho de cada operação por 24 horas, pelo clientTransactionId.
  • O mesmo id é a chave de idempotência: reenviar uma requisição com um id já concluído devolve o resultado original, sem criar outra venda. Enquanto a primeira ainda está em execução, o reenvio recebe busy.
  • Recusas de entrada (busy, malformed_request, unauthorized_caller) não ficam guardadas: reenviar o mesmo id depois executa a operação normalmente.
  • Cada chamada do PosIntegrationClient gera um id novo. Para retentar de propósito, monte uma requisição nova.
  • Um duplo toque no botão de pagar não gera cobrança dupla: como o terminal executa uma operação por vez, a segunda Intent recebe busy.
  • Os desfechos são isolados por app: a recuperação, a idempotência e a reimpressão só enxergam o que o seu pacote executou.
  • list_users não fica guardada para recuperação: repetir a consulta custa o mesmo.

O que persistir no seu app​

  1. Antes do launch: o clientTransactionId, o valor, o pedido e o estado "aguardando terminal".
  2. Ao receber a resposta: o status final e, se aprovada, o receipt.transactionId.
  3. Ao abrir o app: qualquer id ainda em "aguardando terminal" passa pela recuperação antes de liberar o caixa.