Durante i test IAP prima della pubblicazione, ci siamo trovati davanti a un problema tipico ma molto confuso:
- L’utente tocca “Continua acquisto”
- Il pulsante passa a “Elaborazione”
- Dopo pochi secondi torna a “Continua acquisto”
- La finestra di conferma acquisto di App Store non compare
In questo articolo documentiamo l’intero percorso di analisi, i log chiave, la causa radice e una strategia di correzione applicabile prima del rilascio.
1. Sintomo
L’elenco prodotti nella schermata di acquisto si caricava correttamente, ma dopo il tap su acquista:
purchase()veniva chiamato- Non appariva alcuna finestra di sistema
- La UI tornava infine allo stato non acquistato
A prima vista sembrava che il flusso d’acquisto non partisse affatto, ma i log raccontavano altro.
2. Primo controllo: il flusso d’acquisto viene davvero eseguito?
Abbiamo aggiunto log in tutti i punti critici del flusso:
- Parametri del tap sul pulsante
- Entry di
purchaseSelectedProduct()e ramiguard - Prima/dopo
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
Risultato:
purchase()restituiva.success- La verifica della transazione andava a buon fine e veniva eseguito anche
transaction.finish() - Ma
entitledIDsrimaneva vuoto
Questo indica che: non era “l’acquisto non è partito”, ma “gli entitlement non venivano riconosciuti dopo l’acquisto”.
3. Evidenze decisive emerse con analisi più profonda
Estendendo i log, sono emersi segnali chiave:
- Il conteggio iterazioni di
Transaction.currentEntitlementsera0 Transaction.latest(for: monthly)riportava un record valido, ma scaduto- Più importante: prima dell’acquisto c’erano vecchie transazioni in
Transaction.unfinished - L’attuale
purchase()restituiva una transazione da una catena vecchia, non da un nuovo acquisto interattivo
Questo spiega perché “non compare il popup”:
- StoreKit restituisce prima una vecchia transazione processabile (unfinished / catena storica)
- Il codice la conclude con
finish() - La richiesta sembra “riuscita”, ma non passa per il nuovo percorso di conferma acquisto atteso dall’utente
4. Riepilogo della causa radice
Non si trattava di un singolo punto di guasto, ma della combinazione di due fattori:
- Transazioni unfinished che interferiscono con il flusso d’acquisto
- Catena di abbonamento sandbox in stato scaduto (anche con
latest transaction, non è detto che sia un entitlement attivo)
Comportamento finale: nessun popup, successo apparente, entitlement vuoti.
5. Strategia di correzione (validata in pratica)
5.1 Pulizia proattiva delle transazioni unfinished prima dell’acquisto
All’avvio app e prima dell’acquisto, scansiona Transaction.unfinished ed esegui finish() sulle transazioni verificate, così le vecchie transazioni non intercettano il nuovo flusso.
5.2 Mantenere e rafforzare i log diagnostici (consigliato prima del rilascio)
Conviene mantenere attivi fino al rilascio:
- Snapshot di
unfinished transaction subscription status(state, renewal info)- Conteggio iterazioni di
currentEntitlements - Dettagli transaction/expiration in
latest(for:)
5.3 Verifica entitlement a doppio canale (hardening opzionale)
Se in alcuni ambienti currentEntitlements risulta temporaneamente vuoto, usa latest(for:) come fallback (continuando comunque a validare lo stato attivo).
6. Checklist IAP pre-rilascio
- Prima di ogni test, verifica lo stato abbonamento dell’account Sandbox (scaduto o no)
- Logga il conteggio
unfinishednei percorsi critici - Testa tre scenari: primo acquisto / rinnovo dopo scadenza / ripristino acquisti
- Controlla coerenza tra
currentEntitlementselatest - Se appare “nessun popup”, verifica prima se è stato colpito un vecchio percorso transazionale
7. Conclusione
“Toccare acquista senza popup” non implica per forza un problema UI o di timing della chiamata.
In StoreKit 2, stato delle transazioni, coda unfinished e ciclo di vita dell’abbonamento influenzano direttamente il comportamento finale.
Aggiungere questi log e la pulizia preventiva prima del rilascio riduce molti problemi IAP difficili da riprodurre in produzione.