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()undguard-Zweige - Vor und nach
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
Ergebnis:
purchase()lieferte.success- Transaktion wurde erfolgreich verifiziert,
transaction.finish()wurde ebenfalls ausgeführt entitledIDsblieb 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:
Transaction.currentEntitlementshatte eine Iterationsanzahl von0Transaction.latest(for: monthly)lieferte einen gültigen Eintrag, dieser war aber abgelaufen- Noch wichtiger: Vor dem Kauf existierten alte Transaktionen in
Transaction.unfinished - 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 transactionist 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/expirationauslatest(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
- Vor jedem Test den Abo-Status des Sandbox-Accounts prüfen (abgelaufen oder nicht)
- In der kritischen Strecke die Anzahl von
unfinishedloggen - Drei Szenarien testen: „Erstkauf / Verlängerung nach Ablauf / Käufe wiederherstellen“
- Prüfen, ob
currentEntitlementsundlatestkonsistent sind - 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.