Durante os testes de IAP antes do lançamento, encontrámos um problema típico, mas muito confuso:

  • O utilizador toca em “Continuar compra”
  • O botão muda para “A processar”
  • Após alguns segundos volta a “Continuar compra”
  • A janela de confirmação de compra da App Store não aparece

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

1. Sintoma

A lista de produtos na página de compra carregava normalmente, mas ao tocar em comprar:

  • purchase() era chamado
  • Não surgia qualquer diálogo do sistema
  • A UI acabava por regressar ao estado de não comprado

À primeira vista, parecia que o fluxo de compra nem sequer arrancava, mas os logs mostraram outra realidade.

2. Primeira verificação: o fluxo de compra corre mesmo?

Adicionámos logs em todos os pontos críticos do fluxo:

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

Resultado:

  • purchase() devolvia .success
  • A verificação da transação era bem-sucedida, e transaction.finish() também era executado
  • Mas entitledIDs mantinha-se vazio

Isto indica que: o problema não era “a compra não começou”, mas 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 registo válido, mas já expirado
  3. Mais importante: antes da compra existiam transações antigas em Transaction.unfinished
  4. O purchase() atual devolvia uma transação de uma cadeia antiga, e não de um novo fluxo interativo

Isto explica porque “não aparece pop-up”:

  • O StoreKit devolve primeiro uma transação antiga processável (unfinished / cadeia histórica)
  • O teu código termina-a com finish()
  • O pedido parece “bem-sucedido”, mas não passa pelo novo caminho de confirmação esperado pelo utilizador

4. Resumo da causa raiz

Não foi uma falha isolada, mas a combinação de dois fatores:

  • Unfinished transactions a interferir com o fluxo de compra
  • Cadeia de subscrição sandbox em estado expirado (mesmo com latest transaction, pode não ser um entitlement ativo)

Comportamento final: sem pop-up, sucesso aparente e entitlements vazios.

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

5.1 Limpar unfinished transactions de forma proativa antes da compra

No arranque da app e antes de comprar, percorre Transaction.unfinished e executa 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 a pena manter estes logs ativos até ao 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 por dupla via (hardening opcional)

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

6. Checklist de IAP pré-lançamento

  1. Antes de cada teste, verifica o estado de subscrição da conta Sandbox (expirada ou não)
  2. Regista a contagem de unfinished nos caminhos críticos
  3. Testa três cenários: primeira compra / renovação após expirar / restaurar compras
  4. Verifica consistência entre currentEntitlements e latest
  5. Se ocorrer “sem pop-up”, confirma primeiro se foi atingido um caminho de transação antiga

7. Conclusão

“Tocar em comprar sem pop-up” 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 subscrição influenciam diretamente o comportamento final.

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