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 deguard - Antes/depois de
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
Resultado:
purchase()devolvia.success- A verificação da transação era bem-sucedida, e
transaction.finish()também era executado - Mas
entitledIDsmantinha-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:
- A contagem de iterações de
Transaction.currentEntitlementsera0 Transaction.latest(for: monthly)tinha um registo válido, mas já expirado- Mais importante: antes da compra existiam transações antigas em
Transaction.unfinished - 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/expirationemlatest(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
- Antes de cada teste, verifica o estado de subscrição da conta Sandbox (expirada ou não)
- Regista a contagem de
unfinishednos caminhos críticos - Testa três cenários: primeira compra / renovação após expirar / restaurar compras
- Verifica consistência entre
currentEntitlementselatest - 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.