Podczas testów IAP przed wydaniem trafiliśmy na typowy, ale bardzo mylący problem:

  • Użytkownik klika „Kontynuuj zakup”
  • Przycisk przechodzi w stan „Przetwarzanie”
  • Po kilku sekundach wraca do „Kontynuuj zakup”
  • Nie pojawia się okno potwierdzenia zakupu z App Store

W tym artykule opisujemy pełną ścieżkę diagnostyczną, kluczowe logi, root cause i praktyczną strategię naprawy przed publikacją.

1. Objaw

Lista produktów na ekranie zakupu ładowała się poprawnie, ale po kliknięciu kupna:

  • purchase() było wywoływane
  • Nie pojawiało się żadne okno systemowe
  • UI ostatecznie wracało do stanu „niekupione”

Na pierwszy rzut oka wyglądało to tak, jakby flow zakupu w ogóle nie startował, ale logi pokazały coś innego.

2. Pierwszy krok: potwierdzić, czy flow zakupu faktycznie działa

Dodaliśmy logi do wszystkich krytycznych punktów ścieżki zakupu:

  • Parametry kliknięcia przycisku
  • Wejście do purchaseSelectedProduct() i gałęzie guard
  • Przed/po product.purchase()
  • Transaction.updates
  • refreshEntitlements()
  • Transaction.currentEntitlements
  • Transaction.latest(for:)

Wynik:

  • purchase() zwracało .success
  • Weryfikacja transakcji przechodziła poprawnie i wykonywało się transaction.finish()
  • Ale entitledIDs pozostawało puste

To oznacza: problemem nie było „zakup nie wystartował”, tylko „po zakupie uprawnienia nie zostały rozpoznane”.

3. Kluczowe ustalenia po głębszej analizie

Po rozszerzeniu logowania pojawiły się decydujące sygnały:

  1. Liczba iteracji Transaction.currentEntitlements wynosiła 0
  2. Transaction.latest(for: monthly) zwracało poprawny rekord, ale już wygasły
  3. Najważniejsze: przed zakupem istniały stare transakcje w Transaction.unfinished
  4. Bieżące purchase() zwracało transakcję ze starego łańcucha, a nie z nowego interaktywnego zakupu

To wyjaśnia, dlaczego „nie ma popupu”:

  • StoreKit najpierw zwraca starą transakcję możliwą do przetworzenia (unfinished / historyczny łańcuch)
  • Kod kończy ją przez finish()
  • Żądanie wygląda na „udane”, ale nie przechodzi przez nową ścieżkę potwierdzenia oczekiwaną przez użytkownika

4. Podsumowanie przyczyny źródłowej

To nie był pojedynczy błąd, tylko nałożenie się dwóch czynników:

  • Niedokończone transakcje (unfinished transaction) zakłócające flow zakupu
  • Łańcuch subskrypcji sandbox w stanie wygasłym (nawet przy latest transaction nie musi to oznaczać aktywnego entitlement)

Efekt końcowy: brak popupu, pozorny sukces, puste uprawnienia.

5. Strategia naprawy (sprawdzona w praktyce)

5.1 Proaktywne czyszczenie unfinished transactions przed zakupem

Przy starcie aplikacji i przed zakupem przejdź przez Transaction.unfinished i wykonaj finish() dla zweryfikowanych transakcji, aby stare transakcje nie przechwytywały nowego flow zakupu.

5.2 Utrzymanie i wzmocnienie logów diagnostycznych (zalecane przed wydaniem)

Warto utrzymać te logi aż do publikacji:

  • Snapshot unfinished transaction
  • subscription status (state, renewal info)
  • Liczbę iteracji currentEntitlements
  • Szczegóły transaction/expiration z latest(for:)

5.3 Dwutorowa weryfikacja uprawnień (opcjonalne utwardzenie)

Jeśli w niektórych środowiskach currentEntitlements bywa chwilowo puste, użyj latest(for:) jako fallbacku (nadal wymagane jest sprawdzenie aktywnego statusu).

6. Checklista IAP przed publikacją

  1. Przed każdym testem sprawdź stan subskrypcji konta Sandbox (czy wygasła)
  2. Loguj liczbę unfinished na ścieżkach krytycznych
  3. Przetestuj trzy scenariusze: „pierwszy zakup / odnowienie po wygaśnięciu / przywracanie zakupów”
  4. Sprawdź spójność między currentEntitlements i latest
  5. Gdy wystąpi „brak popupu”, najpierw sprawdź, czy trafiłeś w starą ścieżkę transakcji

7. Wnioski

„Klikam kup i nie ma popupu” nie musi oznaczać problemu UI ani timingu wywołania.
W StoreKit 2 stan transakcji, kolejka unfinished i cykl życia subskrypcji bezpośrednio wpływają na końcowe zachowanie.

Dodanie tych logów i kroków czyszczących przed wydaniem znacząco ogranicza problemy IAP trudne do odtworzenia na produkcji.