Durante os testes de IAP antes do lançamento, encontramos um problema clássico, porém muito confuso:

  • O usuário toca em “Continuar compra”
  • O botão muda para “Processando”
  • Alguns segundos depois volta para “Continuar compra”
  • A janela de confirmação de compra da App Store não aparece

Este artigo registra o caminho completo da investigação, os logs-chave, a causa raiz e uma estratégia de correção aplicável antes de publicar.

1. Sintoma

A lista de produtos na tela de compra carregava normalmente, mas após tocar em comprar:

  • purchase() era chamado
  • Nenhum diálogo do sistema aparecia
  • A UI acabava voltando para o estado de não comprado

À primeira vista, parecia que o fluxo de compra nem tinha começado, mas os logs mostraram outra realidade.

2. Primeira rodada: confirmar se o fluxo de compra realmente executa

Adicionamos logs em todos os pontos críticos do fluxo:

  • Parâmetros do clique no botão
  • Entrada de purchaseSelectedProduct() e ramificações de guard
  • Antes/depois de product.purchase()
  • Transaction.updates
  • refreshEntitlements()
  • Transaction.currentEntitlements
  • Transaction.latest(for:)

Resultado:

  • purchase() retornava .success
  • A verificação da transação passava, e transaction.finish() também era executado
  • Mas entitledIDs continuava vazio

Isso indica que: não era “a compra não iniciou”, e sim “os entitlements não foram reconhecidos após a compra”.

3. Descobertas decisivas após aprofundar

Com logs mais detalhados, surgiram sinais importantes:

  1. A contagem de iterações de Transaction.currentEntitlements era 0
  2. Transaction.latest(for: monthly) tinha um registro válido, mas já expirado
  3. Mais importante: antes da compra havia transações antigas em Transaction.unfinished
  4. O purchase() atual retornava uma transação de uma cadeia antiga, e não de um novo fluxo interativo

Isso explica por que “não aparece popup”:

  • O StoreKit primeiro retorna uma transação antiga processável (unfinished / cadeia histórica)
  • Seu código finaliza essa transação com finish()
  • A requisição parece “bem-sucedida”, mas não passa pelo novo caminho de confirmação esperado pelo usuário

4. Resumo da causa raiz

Não foi uma falha de ponto único, e sim a combinação de dois fatores:

  • Unfinished transactions interferindo no fluxo de compra
  • Cadeia de assinatura sandbox em estado expirado (mesmo com latest transaction, pode não ser um entitlement ativo)

Comportamento final: sem popup, sucesso aparente e entitlements vazios.

5. Estratégia de correção (validada na prática)

5.1 Limpar unfinished transactions proativamente antes da compra

Na inicialização do app e antes de comprar, varra Transaction.unfinished e execute finish() nas transações verificadas, para evitar que transações antigas “sequestrem” o novo fluxo.

5.2 Manter e reforçar logs de diagnóstico (recomendado antes do lançamento)

Vale manter estes logs ativos até o release:

  • Snapshot de unfinished transaction
  • subscription status (state, renewal info)
  • Contagem de iterações de currentEntitlements
  • Detalhes de transaction/expiration em latest(for:)

5.3 Usar validação de entitlement em dupla via (hardening opcional)

Se em alguns ambientes currentEntitlements ficar temporariamente vazio, use latest(for:) como fallback (sempre validando status ativo).

6. Checklist de IAP pré-lançamento

  1. Antes de cada teste, verifique o estado de assinatura da conta Sandbox (expirada ou não)
  2. Faça log da contagem de unfinished nos caminhos críticos
  3. Teste três cenários: primeira compra / renovação após expirar / restaurar compras
  4. Verifique consistência entre currentEntitlements e latest
  5. Se ocorrer “sem popup”, cheque primeiro se caiu em um caminho de transação antiga

7. Conclusão

“Tocar em comprar sem popup” não significa necessariamente problema de UI ou de timing da chamada.
No StoreKit 2, estado das transações, fila de unfinished e ciclo de vida da assinatura impactam diretamente o comportamento final.

Adicionar esses logs e a rotina de limpeza antes do lançamento reduz muitos problemas de IAP difíceis de reproduzir em produção.