Během předprodukčního testování IAP jsme narazili na typický, ale velmi matoucí problém:

  • Uživatel klikne na „Pokračovat v nákupu“
  • Tlačítko přejde do stavu „Zpracovává se“
  • Po několika sekundách se vrátí na „Pokračovat v nákupu“
  • Nezobrazí se potvrzovací okno nákupu z App Storu

V tomto článku shrnujeme celý postup diagnostiky, klíčové logy, root cause i praktický způsob opravy před vydáním.

1. Jak se problém projevoval

Seznam produktů na nákupní stránce se načítal správně, ale po kliknutí na nákup:

  • Zavolalo se purchase()
  • Neobjevilo se žádné systémové okno
  • UI se nakonec vrátilo do stavu „nenakoupeno“

Na první pohled to vypadalo, že se nákup vůbec nespustil. Logy ale ukázaly jiný obraz.

2. První krok: ověřit, zda nákupní tok skutečně probíhá

Nejprve jsme doplnili logy do všech kritických bodů nákupního toku:

  • Parametry kliknutí na tlačítko
  • Vstup do purchaseSelectedProduct() a jednotlivé guard větve
  • Stav před/po product.purchase()
  • Transaction.updates
  • refreshEntitlements()
  • Transaction.currentEntitlements
  • Transaction.latest(for:)

Výsledek:

  • purchase() vrátilo .success
  • Ověření transakce proběhlo a transaction.finish() se také vykonalo
  • entitledIDs ale zůstalo prázdné

To znamená: nejde o „nákup se nespustil“, ale o „po nákupu se nerozpoznala oprávnění“.

3. Klíčová zjištění při hlubší analýze

Po rozšíření logování se objevily rozhodující signály:

  1. Transaction.currentEntitlements měl počet iterací 0
  2. Transaction.latest(for: monthly) obsahoval platný záznam, ale už expirovaný
  3. Zásadní bylo, že před nákupem existovaly staré transakce v Transaction.unfinished
  4. Aktuální purchase() vracelo transakci ze starého řetězce, nikoli z nového interaktivního nákupu

Tím se vysvětluje, proč se „nic neukázalo“:

  • StoreKit nejdřív vrátil zpracovatelnou starou transakci (unfinished / historický tok)
  • Kód ji označil jako finish()
  • Nákup vypadal jako „úspěšný“, ale neprošel novým potvrzovacím dialogem, který uživatel očekává

4. Shrnutí příčiny

Nešlo o jedinou chybu, ale o kombinaci dvou faktorů:

  • Nedokončené transakce (unfinished transaction) narušily nákupní tok
  • Sandboxový subscription řetězec byl v expirovaném stavu (i když existuje latest transaction, nemusí jít o aktivní entitlement)

Výsledné chování: bez pop-upu, zdánlivý úspěch, prázdná oprávnění.

5. Opravná strategie (ověřená v praxi)

5.1 Aktivní vyčištění unfinished transakcí před nákupem

Při startu aplikace i před kliknutím na nákup projděte Transaction.unfinished a u ověřených transakcí zavolejte finish(), aby staré transakce „nepřebily“ nový nákupní tok.

5.2 Zachovat a posílit diagnostické logy (doporučeno před release)

Doporučené logy ponechat zapnuté až do vydání:

  • Snapshot unfinished transaction
  • subscription status (state, renewal info)
  • Počet iterací currentEntitlements
  • transaction/expiration u latest(for:)

5.3 Dvoukanálové vyhodnocení oprávnění (volitelné vylepšení)

Pokud je v některých prostředích currentEntitlements dočasně prázdné, lze použít latest(for:) jako fallback (stále je nutné ověřit, že entitlement je aktivní).

6. IAP checklist před vydáním

  1. Před každým testem ověřte stav subscription na Sandbox účtu (zda není expirovaný)
  2. V klíčové cestě logujte počet unfinished transakcí
  3. Otestujte tři scénáře: „první nákup / obnova po expiraci / obnova nákupu“
  4. Zkontrolujte konzistenci mezi currentEntitlements a latest
  5. Pokud se objeví „žádné potvrzovací okno“, nejdřív ověřte zásah starého transakčního toku

7. Závěr

„Klik na koupit bez zobrazení okna“ nemusí být problém UI ani načasování volání.
Ve StoreKit 2 stav transakcí, fronta unfinished a životní cyklus předplatného přímo ovlivňují finální chování.

Doplnění těchto logů a předběžného čištění transakcí před release výrazně snižuje množství IAP problémů, které se v produkci špatně reprodukují.