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ção | O que significa | O que fazer |
|---|---|---|
approved | A operação foi aprovada | Concluir com o receipt recebido. Não cobrar de novo |
declined, cancelled ou error com outro código | Não houve aprovação | Pode cobrar de novo, com uma requisição nova |
error com operation_timeout | A autorização ainda pode voltar, por até 2 minutos depois do timeout | Aguardar 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_found | Nenhum desfecho registrado para esse id | A 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
PosIntegrationClientgera 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_usersnão fica guardada para recuperação: repetir a consulta custa o mesmo.
O que persistir no seu app
- Antes do
launch: oclientTransactionId, o valor, o pedido e o estado "aguardando terminal". - Ao receber a resposta: o status final e, se aprovada, o
receipt.transactionId. - Ao abrir o app: qualquer id ainda em "aguardando terminal" passa pela recuperação antes de liberar o caixa.