Entitlements

Den Zugriff nach einer Rückerstattung zu entziehen, erledigen weder Apple noch Google für Sie

Geldbewegung und Zugriffsende sind auf beiden Stores getrennte Vorgänge, und nur einer davon passiert automatisch. Google versteckt die Entfernung von Berechtigungen hinter einer standardmäßig deaktivierten Option; Apple kann ein bereits verbrauchtes Consumable nicht anrühren. Was Ihr Server übersieht, läuft weiter auf Kosten von Umsatz, den Sie längst nicht mehr haben.

5 min read
Den Zugriff nach einer Rückerstattung zu entziehen, erledigen weder Apple noch Google für Sie

Geldbewegung und Zugriffsende sind auf beiden Plattformen getrennte Vorgänge, und nur einer davon passiert automatisch. Google versteckt die Entfernung von Berechtigungen hinter einer standardmäßig deaktivierten Option. Apple verwaltet Lizenzen, kann aber ein Consumable, das jemand bereits verbraucht hat, nicht anrühren. Was Ihr Server nicht bereinigt, läuft weiter auf Kosten von Umsatz, den Sie nicht mehr haben.

Die wichtigsten Punkte

  • Das Erstatten einer Play-Bestellung lässt den Zugriff des Kunden unangetastet, sofern Sie nichts anderes festlegen. Entweder wird „Berechtigung entfernen" in der Play Console angehakt, oder Ihr API-Aufruf enthält revoke=true. Keines von beidem geschieht von selbst.

  • Rückerstattungen ohne diese Revoke-Option fehlen in der Voided Purchases API vollständig. Jeder Revocation-Job, der sich allein auf diesen Feed stützt, übersieht sie stillschweigend alle.

  • Lizenzen für Non-Consumables und Abonnements kann Apple für Sie entziehen. Consumables gelten in dem Moment, in dem sie verbraucht werden, als geliefert, sodass StoreKit nichts bleibt, was es entziehen könnte – das Abziehen des Guthabens obliegt also Ihrem eigenen Server.

  • Apple macht eigene Rückerstattungen manchmal wieder rückgängig. Eine REFUND_REVERSED-Benachrichtigung besagt, dass eine gewährte Rückerstattung widerrufen wurde, revocationDate wird wieder auf null gesetzt, und alles, was Sie entzogen haben, muss wiederhergestellt werden.

  • Neun verschiedene Werte füllen Googles voidedReason, von remorse und accidental_purchase bis hin zu fraud, friendly_fraud und unacknowledged_purchase – ein einziger Feed sagt Ihnen also sowohl, wann Sie den Zugriff kappen sollen, als auch, welche Fälle nach Missbrauch riechen.

  • Solange ein erstatteter Kauf weiterhin Zugriff hat, finanzieren Sie dessen Rechenleistung, Speicher und API-Aufrufe aus Umsatz, der bereits an den Käufer zurückgeflossen ist.

Warum der Zugriff eine Rückerstattung standardmäßig überlebt

Beide Unternehmen behandeln Zahlung und Berechtigung als getrennte Vorgänge. Die Zahlung richtet sich nach der Store-Richtlinie, und sobald eine Rückerstattung genehmigt ist, gilt sie als abgeschlossen und wird im Ledger erfasst. Die Berechtigung ist eine andere Sache – mal optional, mal liegt sie in Ihrer Verantwortung, oft beides zugleich, je nach Plattform und Produktart. Keiner der beiden Stores geht davon aus, dass eine Rückzahlung automatisch die Beendigung des Zugriffs bedeutet, denn in vielen legitimen Situationen – einer Teilrückerstattung, einer Kulanzgutschrift, einer Geste des Supports – soll der Kunde behalten, was er gekauft hat.

Google Play: das Häkchen, das übersehen wird

Die Play Console teilt das Erstatten in zwei Entscheidungen auf. Zuerst den Betrag, dann, ganz separat, ob „Berechtigung entfernen" angehakt wird. Die meisten Support-Mitarbeiter lassen es unangetastet, was nachvollziehbar ist, da die meisten Rückerstattungsanfragen tatsächlich Kulanz sind und der Kunde die Ware behalten soll. Was der Bestätigungsbildschirm nie erwähnt: Genau dieses unangetastete Häkchen ist der Grund, warum jemand nach einer Rückerstattung, die Sie als endgültig gedacht hatten, weiterhin ein Abonnement oder einen In-App-Artikel behält.

Dieselbe Falle gibt es auch in der API

Im Code zeigt sich dieselbe Weggabelung. Der orders.refund-Endpunkt verfügt über einen optionalen revoke-Query-Parameter, und setzt man ihn auf true, endet der Zugriff auf den Artikel sofort – bei einem wiederkehrenden Abonnement werden damit auch alle künftigen Zahlungen storniert. Lässt man den Parameter weg, erhält man standardmäßig false: Die Rückerstattung wird verbucht, der Kaufdatensatz ändert sich, und der Kunde fährt genau wie zuvor fort.

  • Erstattung in der Play Console, bei der „Berechtigung entfernen" nicht angehakt wird, und der Zugriff bleibt bestehen. Die Bestellung erreicht auch nie die Voided Purchases API.

  • Erstattung in der Play Console, bei der „Berechtigung entfernen" angehakt wird, und der Zugriff endet sofort. Die Bestellung taucht in der Voided Purchases API auf.

  • Aufruf von orders.refund ohne den revoke-Parameter, sodass er standardmäßig false ist, und der Zugriff bleibt bestehen. Auch hier erreicht nichts die Voided Purchases API.

  • Aufruf von orders.refund mit revoke=true, und der Zugriff endet sofort, wobei die Bestellung in der Voided Purchases API erscheint.

Ein Revocation-Job, der nur die Voided Purchases API ausliest, wird einer ohne das Revoke-Flag ausgestellten Rückerstattung nie begegnen. Der Eintrag kommt nicht etwa verspätet. Er kommt schlicht nie, und die Bestellung bleibt außerhalb dieses Feeds, als hätte die Rückerstattung nie stattgefunden.

Genau diese letzte Zeile bringt Integrationen zum Scheitern. Eine Revocation-Pipeline aufzubauen, ohne die Play Console manuell zu beobachten, ist genau der Zweck der Voided Purchases API – ihr Geltungsbereich umfasst jedoch nur Bestellungen, die tatsächlich mit Widerruf für ungültig erklärt wurden. Rückerstattungen ohne das Häkchen oder ohne revoke=true sind dort nicht etwa durch einen Fehler unsichtbar, sondern per Design.

voidedReason richtig einordnen

Für ungültig erklärte Käufe kommen mit einem voidedReason, der die Ursache über neun mögliche Werte erklärt: other, remorse, not_received, defective, accidental_purchase, fraud, friendly_fraud, chargeback und unacknowledged_purchase. Wer das nur als Buchführung behandelt, verschenkt Potenzial. Remorse und accidental_purchase beschreiben unauffällige Rückerstattungen. Fraud, friendly_fraud und chargeback verdienen eine Betrugsprüfungs-Warteschlange statt eines automatischen Revoke-and-Close, da Konten mit diesen Werten statistisch häufiger wiederkehren.

Wer direkt gegen diesen Feed entwickelt, braucht eine operative Kennzahl: purchases.voidedpurchases.list erlaubt 6,000 Abfragen täglich und 30 innerhalb eines beliebigen 30-Sekunden-Zeitraums. Enge Polling-Schleifen schöpfen dieses Kontingent schnell aus. Geplante Abrufe mit Pagination-Tokens sind das vorgesehene Design.

Apple: Lizenzen lassen sich entziehen, Consumables nicht

App Store Server Notifications V2 liefern in dem Moment, in dem eine Rückerstattung abgeschlossen ist, eine REFUND-Benachrichtigung – und zwar unterschiedslos für Consumables, Non-Consumables, automatisch verlängerbare Abonnements und nicht verlängerbare Abonnements. Was als Nächstes geschehen sollte, unterscheidet sich je nach Produktart vollständig, denn nur zwei dieser vier Typen fallen unter StoreKits eigene Revocation-Mechanik.

Warum Consumables davon ausgenommen sind

Dauerhafte Berechtigungen gehören zu Non-Consumables und Abonnements, und Apples Systeme können diese abschalten. Bei Consumables gibt es so etwas nicht. Wird eines verbraucht, gilt es als geliefert, sodass StoreKit nichts mehr bleibt, was es zurückfordern könnte. Ein Kunde, der 500 Münzen gekauft, verbraucht und anschließend eine Rückerstattung erhalten hat, hat aus Apples Sicht bereits alles erhalten. Nur in Ihrem Ledger existieren diese Münzen noch. Diese REFUND-Benachrichtigung ist Ihr einziger Hinweis darauf, die passende ursprüngliche transaction ID zu finden und das Guthaben selbst abzuziehen, denn von Apples Seite wird das nicht geschehen.

  • REFUND bedeutet, dass ein Consumable-, Non-Consumable-, automatisch verlängerbarer oder nicht verlängerbarer Kauf erstattet wurde. Entziehen Sie den Zugriff für Non-Consumables und Abonnements sofort, und ziehen Sie ein etwaiges Consumable-Guthaben selbst ab.

  • REFUND_DECLINED bedeutet, dass Apple den Rückerstattungsantrag des Kunden abgelehnt hat. Es gibt nichts zu tun, da der Zugriff nie in Gefahr war.

  • REFUND_REVERSED bedeutet, dass eine bereits von Apple gewährte Rückerstattung rückgängig gemacht wurde. Stellen Sie alles wieder her, was Sie aufgrund der ursprünglichen REFUND-Benachrichtigung entfernt hatten.

  • CONSUMPTION_REQUEST bedeutet, dass Apple vor seiner Entscheidung Ihre Nachweise haben möchte. Antworten Sie innerhalb von 12 Stunden, und beachten Sie, dass diese Benachrichtigung vor einer REFUND eintrifft, nicht danach.

Die Benachrichtigung, die fast jeder ignoriert

REFUND_REVERSED ist diejenige, die Integrationen routinemäßig auslassen, da sie darstellt, dass Apple eine eigene Entscheidung rückgängig macht – etwas, wofür anfangs kaum jemand plant. Mit ihrem Eintreffen wird revocationDate wieder auf null gesetzt, das heißt, Apple betrachtet den Kauf nun wieder als vollkommen gültig. Alles, was Ihr Server bei der ursprünglichen REFUND getan hat – eine Berechtigung gelöscht, einen Abonnement-Datensatz storniert, ein Consumable-Guthaben auf null gesetzt – muss rückgängig gemacht werden.

Erwarten Sie nicht, dass eine rückgängig gemachte Rückerstattung wie ein neuer Kauf aussieht, denn das tut sie nicht. Verhängte Sperren, entzogener Zugriff, als verbraucht markierte Guthaben: REFUND_REVERSED ist die Anweisung, genau diese Dinge rückgängig zu machen, und es wird später keine weitere Benachrichtigung eintreffen, die Sie daran erinnert.

Die Lücke Schritt für Schritt schließen

  • Machen Sie revoke=true zum Standard für jeden Play-Rückerstattungsweg, sowohl in der Console als auch in der API, und reservieren Sie die Alternative für Support-Fälle, in denen der Kunde den Zugriff ausdrücklich behalten soll.

  • Rufen Sie purchases.voidedpurchases.list nach einem Zeitplan mit Pagination-Tokens ab, statt eng zu loopen, und bleiben Sie dabei unter 6,000 Anfragen täglich und 30 pro 30 Sekunden.

  • Senden Sie voidedReason-Werte wie fraud, friendly_fraud und chargeback in eine Prüf-Warteschlange, statt sie automatisch per Revoke-and-Close zu behandeln.

  • Abonnieren Sie bei Apple die App Store Server Notifications V2, und entziehen Sie den Zugriff für Non-Consumables und Abonnements, sobald eine REFUND-Benachrichtigung diese identifiziert.

  • Speichern Sie zu jedem Consumable-Kauf die ursprüngliche transaction ID, damit eine REFUND-Benachrichtigung genau das abzuziehende Guthaben finden kann, ohne dass es jemand manuell nachschlagen muss.

  • Implementieren Sie REFUND_REVERSED bewusst, und erfassen Sie jeden Widerruf anhand seiner transaction ID oder Order-ID, damit eine Umkehrung genau das wiederherstellt, was entzogen wurde.

Was die Lücke tatsächlich kostet

Fairness gegenüber dem Kunden ist hier nicht das Thema. Das Thema ist, was weiterläuft, nachdem der Umsatz verschwunden ist. Ein erstattetes Abonnement, das weiterhin Ihre API anspricht, weiterhin Daten vorhält, weiterhin Rechenleistung verbraucht, stellt kein moralisches Problem dar – es stellt eine Rechnung. Infrastruktur wurde für einen Kauf bezahlt, dessen Umsatz Sie bereits zurückerstattet haben. Keiner der beiden Stores erfasst diese Kosten für Sie, denn aus ihrer Sicht war die Rückerstattung in dem Moment abgeschlossen, in dem das Geld floss. Die Kontrolle über den Zugriff zu behalten, ist ganz allein Ihre Aufgabe.

Refund Sensor behandelt den Widerruf daher als Teil der Rückerstattungs-Pipeline und nicht als nachträglich angeflanschten Zusatz. Ein REFUND-Ereignis und die entsprechende Zugriffsänderung werden als eine einzige Aktion geschrieben, und ein REFUND_REVERSED-Ereignis stellt den vorherigen Zustand eigenständig wieder her, statt darauf zu warten, dass jemand bemerkt, dass ein Kunde von etwas ausgesperrt ist, das Apple ihm vor Tagen zurückgegeben hat.

Wo diese Regeln dokumentiert sind

Häufig gestellte Fragen

Nein. Der Zugriff endet nur, wenn in der Play Console „Berechtigung entfernen" angehakt wurde oder der Aufruf von orders.refund mit revoke=true erfolgte. Da keines von beidem der Standard ist, werden Rückerstattungen regelmäßig verbucht, während Kunden das Produkt weiter nutzen.

Es trifft eine REFUND-Benachrichtigung ein, aber StoreKit kann ein Consumable nicht entziehen, da dessen Verbrauch als Lieferung zählt. Die ursprüngliche Transaktion zu finden und das Guthaben abzuziehen, muss Ihr eigener Server übernehmen.

Es bedeutet, dass eine bereits von Apple gewährte Rückerstattung rückgängig gemacht wurde. revocationDate wird wieder auf null gesetzt, und der Kauf gilt erneut als gültig. Jeglicher Zugriff oder jedes Guthaben, das die ursprüngliche REFUND-Benachrichtigung Sie zu entfernen veranlasst hat, muss wiederhergestellt werden – und sehr viele Integrationen abonnieren diese Benachrichtigung überhaupt nicht.

Weil dieser Endpunkt nur Bestellungen zurückgibt, die mit Widerruf (Revocation) für ungültig erklärt wurden. Rückerstattungen ohne die Revoke-Option sind darin nicht enthalten, sodass jede Pipeline, die sich ausschließlich auf diesen Feed verlässt, sie vollständig übersieht.

Sechstausend Abfragen pro Tag und nicht mehr als 30 innerhalb eines beliebigen 30-Sekunden-Fensters. Der Endpunkt ist für geplante Abrufe mit Pagination-Tokens ausgelegt, nicht für kontinuierliches Polling.

#refunds#entitlements#voided purchases#refund_reversed#revoke access#play billing
Refund SensorRefund Sensor TeamRefund defense for App Store and Google Play developers