Beim IAP-Testing vor dem Release sind wir auf ein typisches, aber sehr verwirrendes Problem gestoßen:

  • Nutzer tippt auf „Kauf fortsetzen“
  • Button wechselt in „Wird verarbeitet“
  • Nach ein paar Sekunden wieder „Kauf fortsetzen“
  • Kein App-Store-Kaufbestätigungsdialog erscheint

Dieser Artikel dokumentiert den kompletten Analyseweg, entscheidende Logs, die Root Cause und eine praxistaugliche Lösung für die Zeit vor dem Launch.

1. Problembild

Die Produktliste auf der Kaufseite wurde korrekt geladen. Nach dem Klick auf Kaufen passierte jedoch:

  • purchase() wurde aufgerufen
  • Kein Systemdialog erschien
  • Die UI fiel am Ende in den Zustand „nicht gekauft“ zurück

Auf den ersten Blick sah es so aus, als würde der Kaufprozess gar nicht starten. Die Logs zeigten aber etwas anderes.

2. Erste Analyse: Läuft die Kaufkette überhaupt durch?

Wir haben zunächst alle kritischen Punkte in der Kaufkette mit Logs versehen:

  • Parameter beim Button-Klick
  • Einstieg in purchaseSelectedProduct() und guard-Zweige
  • Vor und nach product.purchase()
  • Transaction.updates
  • refreshEntitlements()
  • Transaction.currentEntitlements
  • Transaction.latest(for:)

Ergebnis:

  • purchase() lieferte .success
  • Transaktion wurde erfolgreich verifiziert, transaction.finish() wurde ebenfalls ausgeführt
  • entitledIDs blieb jedoch leer

Das heißt: Es war nicht „Kauf wurde nicht gestartet“, sondern „Berechtigungen wurden nach dem Kauf nicht erkannt“.

3. Entscheidende Erkenntnisse bei tieferer Analyse

Nach zusätzlichem Logging zeigten sich mehrere klare Signale:

  1. Transaction.currentEntitlements hatte eine Iterationsanzahl von 0
  2. Transaction.latest(for: monthly) lieferte einen gültigen Eintrag, dieser war aber abgelaufen
  3. Noch wichtiger: Vor dem Kauf existierten alte Transaktionen in Transaction.unfinished
  4. Das aktuelle purchase() gab eine Transaktion aus der alten Kette zurück, nicht aus einem neuen interaktiven Kauf

Damit wird klar, warum „kein Dialog“ erscheint:

  • StoreKit liefert zuerst eine verarbeitbare alte Transaktion (unfinished / historischer Pfad)
  • Der Code ruft dafür finish() auf
  • Der Kauf wirkt „erfolgreich“, läuft aber nicht über den erwarteten neuen Bestätigungsdialog

4. Root Cause zusammengefasst

Das Problem war kein Einzelpunkt, sondern eine Überlagerung aus zwei Faktoren:

  • Unfertige Transaktionen (unfinished transaction) stören den Kaufablauf
  • Die Sandbox-Abo-Kette befindet sich im abgelaufenen Zustand (selbst mit latest transaction ist das nicht zwingend ein aktives Entitlement)

Das äußert sich dann als: kein Dialog, scheinbarer Erfolg, leere Berechtigungen.

5. Lösungsstrategie (in der Praxis wirksam)

5.1 Unfinished-Transaktionen vor dem Kauf aktiv bereinigen

Beim App-Start und vor dem Kauf Transaction.unfinished durchlaufen und für verifizierte Transaktionen finish() ausführen, damit alte Transaktionen den neuen Kaufpfad nicht abfangen.

5.2 Diagnose-Logs beibehalten und ausbauen (vor Launch empfohlen)

Folgende Logs sollten bis zum Launch aktiv bleiben:

  • Snapshot von unfinished transaction
  • subscription status (state, renewal info)
  • Anzahl der Iterationen von currentEntitlements
  • transaction/expiration aus latest(for:)

5.3 Entitlement-Prüfung über „zwei Kanäle“ (optionale Härtung)

Falls currentEntitlements in manchen Umgebungen kurzzeitig leer ist, kann latest(for:) als Fallback dienen (mit zusätzlicher Prüfung auf aktiven Status).

6. IAP-Checkliste vor dem Release

  1. Vor jedem Test den Abo-Status des Sandbox-Accounts prüfen (abgelaufen oder nicht)
  2. In der kritischen Strecke die Anzahl von unfinished loggen
  3. Drei Szenarien testen: „Erstkauf / Verlängerung nach Ablauf / Käufe wiederherstellen“
  4. Prüfen, ob currentEntitlements und latest konsistent sind
  5. Bei „kein Dialog“ zuerst prüfen, ob ein alter Transaktionspfad getroffen wurde

7. Fazit

„Kaufen klicken ohne Dialog“ ist nicht zwingend ein UI- oder Timing-Problem.
Unter StoreKit 2 beeinflussen Transaktionszustand, unfinished-Queue und Abo-Lebenszyklus das Endverhalten direkt.

Wenn diese Logs und Bereinigungsschritte vor dem Launch ergänzt werden, lassen sich viele schwer reproduzierbare IAP-Probleme in Produktion vermeiden.