În timpul testării IAP înainte de lansare, am întâlnit o problemă clasică, dar foarte derutantă:
- Utilizatorul apasă „Continuă cumpărarea”
- Butonul trece în starea „Se procesează”
- După câteva secunde revine la „Continuă cumpărarea”
- Nu apare fereastra de confirmare a cumpărării din App Store
Acest articol documentează traseul complet de investigație, logurile-cheie, cauza rădăcină și o strategie de remediere aplicabilă înainte de lansare.
1. Simptom
Lista de produse din ecranul de cumpărare se încărca normal, dar după apăsarea butonului de cumpărare:
purchase()era apelată- Nu apărea niciun dialog de sistem
- UI-ul revenea în final la starea necumpărat
La prima vedere părea că fluxul de cumpărare nici măcar nu pornește, însă logurile au arătat altceva.
2. Prima verificare: fluxul de cumpărare chiar rulează?
Am adăugat loguri în toate punctele critice ale fluxului:
- Parametrii clickului pe buton
- Intrarea în
purchaseSelectedProduct()și ramurileguard - Înainte/după
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
Rezultat:
purchase()returna.success- Verificarea tranzacției reușea, iar
transaction.finish()era și ea executată - Dar
entitledIDsrămânea gol
Asta înseamnă că: problema nu era „cumpărarea nu a pornit”, ci „entitlement-urile nu au fost recunoscute după cumpărare”.
3. Constatări decisive după analiză aprofundată
După extinderea logării, au apărut semnale clare:
- Numărul de iterații pentru
Transaction.currentEntitlementsera0 Transaction.latest(for: monthly)avea un înregistrare validă, dar expirată- Mai important: înainte de cumpărare existau tranzacții vechi în
Transaction.unfinished purchase()curentă returna o tranzacție dintr-un lanț vechi, nu dintr-un flux interactiv nou
Asta explică de ce „nu apare pop-up-ul”:
- StoreKit returnează mai întâi o tranzacție veche procesabilă (unfinished / lanț istoric)
- Codul o finalizează cu
finish() - Cererea pare „reușită”, dar nu trece prin noul flux de confirmare a cumpărării așteptat de utilizator
4. Rezumatul cauzei rădăcină
Nu a fost un singur punct de eșec, ci combinația a doi factori:
- Unfinished transactions care interferează cu fluxul de cumpărare
- Lanț de abonament sandbox în stare expirată (chiar dacă există
latest transaction, nu înseamnă neapărat entitlement activ)
Comportamentul final: fără pop-up, succes aparent, entitlement-uri goale.
5. Strategia de remediere (validată în practică)
5.1 Curățare proactivă a unfinished transactions înainte de cumpărare
La pornirea aplicației și înainte de cumpărare, scanează Transaction.unfinished și rulează finish() pe tranzacțiile verificate, astfel încât tranzacțiile vechi să nu „captureze” fluxul nou.
5.2 Păstrează și întărește logurile de diagnostic (recomandat înainte de lansare)
Merită să păstrezi active aceste loguri până la release:
- Snapshot pentru
unfinished transaction subscription status(state, renewal info)- Numărul de iterații pentru
currentEntitlements - Detalii
transaction/expirationdinlatest(for:)
5.3 Verificare entitlement pe două căi (întărire opțională)
Dacă în anumite medii currentEntitlements rămâne temporar gol, folosește latest(for:) ca fallback (dar verifică în continuare statusul activ).
6. Checklist IAP înainte de lansare
- Înainte de fiecare test, verifică starea abonamentului contului Sandbox (expirat sau nu)
- Loghează numărul de
unfinishedpe traseele critice - Testează trei scenarii: prima cumpărare / reînnoire după expirare / restaurare cumpărări
- Verifică consistența dintre
currentEntitlementsșilatest - Dacă apare „fără pop-up”, verifică mai întâi dacă a fost lovit un traseu vechi de tranzacție
7. Concluzie
„Apeși cumpără fără pop-up” nu înseamnă neapărat o problemă de UI sau de timing al apelului.
În StoreKit 2, starea tranzacțiilor, coada unfinished și ciclul de viață al abonamentului influențează direct comportamentul final.
Adăugarea acestor loguri și pași de curățare înainte de lansare reduce multe probleme IAP greu de reprodus în producție.