Alle artikelen
Deep dive8 min leestijd

Toegang intrekken na een terugbetaling: de stap die Apple en Google niet voor je nemen

Apple en Google kunnen allebei een terugbetaling verwerken terwijl de klant de aankoop gewoon blijft houden. Hier lees je precies wanneer toegang automatisch wordt ingetrokken, wanneer je server dit zelf moet doen, en de ene melding die de meeste integraties nooit afhandelen.

Een smartphone die 's nachts op een bureau oplicht, ter illustratie van het moment waarop toegang tot een terugbetaalde aankoop nog niet is ingetrokken

Belangrijkste inzichten

  • Bij Google Play zorgt het terugbetalen van een bestelling op zichzelf niet voor het intrekken van de toegang van de gebruiker. Toegang wordt alleen ingetrokken als "Remove entitlement" is aangevinkt in Play Console, of als de API-aanroep revoke=true meegeeft, wat niet de standaardinstelling is.
  • Een terugbetaling die zonder de revoke-optie is uitgevoerd, verschijnt nooit in de Voided Purchases API. Een intrekkingsproces dat alleen die feed uitleest, mist stilzwijgend elk van die bestellingen.
  • Apple kan een licentie voor non-consumables en abonnementen automatisch intrekken, maar een consumable wordt al als geleverd beschouwd zodra deze is verbruikt, waardoor StoreKit geen enkel mechanisme heeft om die in te trekken. Je server moet het saldo dan zelf verlagen.
  • Apple kan ook zijn eigen terugbetaling ongedaan maken. Een REFUND_REVERSED melding betekent dat een eerder toegekende terugbetaling is teruggedraaid, revocationDate gaat weer terug naar null, en elke toegang of elk saldo dat je had verwijderd, moet worden hersteld.
  • Het veld voidedReason van Google kent negen waarden, van remorse en accidental_purchase tot fraud, friendly_fraud en unacknowledged_purchase, waardoor dezelfde feed die je vertelt wanneer je toegang moet intrekken, je ook vertelt welke intrekkingen misbruik zijn en welke een bug in je eigen integratie.
  • Elke dag dat een terugbetaalde aankoop nog toegang heeft, is een dag waarop je betaalt voor de compute, opslag en API-aanroepen erachter, met geld dat je al hebt teruggegeven.

Een terugbetaling goedkeuren en toegang intrekken zijn twee aparte stappen, en slechts een van de twee gebeurt automatisch. Ontwikkelaars die ervan uitgaan dat een verwerkte terugbetaling gelijkstaat aan ingetrokken toegang, hebben het bij beide grote stores mis, op verschillende manieren en om verschillende redenen. Precies weten waar de automatisering ophoudt, is wat nodig is om toegang na een terugbetaling in te trekken, in plaats van maanden later te ontdekken dat dit nooit is gebeurd.

Waarom een terugbetaling niet standaard toegang intrekt

Apple en Google behandelen geld en toegang allebei als twee aparte processen. De geldkant is winkelbeleid, zodra een terugbetaling is goedgekeurd, is die definitief en wordt het grootboek bijgewerkt. De toegangskant is optioneel, of de taak van de ontwikkelaar, of allebei, afhankelijk van het platform en het type aankoop. Geen van beide stores gaat ervan uit dat je wilt dat toegang meteen verdwijnt zodra de terugbetaling is afgerond, want in veel legitieme gevallen (een gedeeltelijke terugbetaling, een coulance-krediet, een gebaar vanuit support) is het helemaal niet de bedoeling dat het product wordt weggehaald.

Google Play: het vinkje dat niemand opmerkt

In Play Console is het terugbetalen van een bestelling een formulier in twee delen: je kiest het terugbetalingsbedrag en beslist vervolgens apart of je "Remove entitlement" aanvinkt. Dit vinkje uitgeschakeld laten is de standaardweg die de meeste supportmedewerkers volgen, omdat de meeste terugbetalingsverzoeken coulancegebaren zijn waarbij de klant houdt wat hij heeft gekocht. Het probleem is dat niets op het bevestigingsscherm van de terugbetaling je vertelt dat dit ook de manier is waarop een klant een abonnement of in-app item behoudt na een terugbetaling die voor jou definitief had moeten zijn.

Het API-pad heeft dezelfde valkuil

Dezelfde keuze bestaat ook in de code. Het orders.refund endpoint accepteert een optionele revoke query-parameter; als deze true is, wordt de toegang tot het item onmiddellijk beëindigd, en bij een terugkerend abonnement wordt elke toekomstige betaling er meteen mee geannuleerd. Laat je de parameter weg, dan is de standaardwaarde false: de terugbetaling wordt geboekt, de aankooprecord wordt bijgewerkt, en de gebruiker blijft het product precies zo gebruiken als daarvoor.

ActieToegang ingetrokken?Verschijnt in Voided Purchases API?
Terugbetaling via Play Console, "Remove entitlement" niet aangevinktNeeNee
Terugbetaling via Play Console, "Remove entitlement" aangevinktJa, directJa
orders.refund API, revoke-parameter weggelaten (standaard false)NeeNee
orders.refund API, revoke=trueJa, directJa

Die laatste rij is precies waar integraties in de val lopen. De Voided Purchases API bestaat zodat je een intrekkingspipeline kunt bouwen zonder Play Console handmatig te controleren, maar hij vermeldt alleen bestellingen die daadwerkelijk zijn geannuleerd met intrekking. Een terugbetaling die is verwerkt zonder dat vinkje, of zonder revoke=true, is met opzet onzichtbaar voor die feed, niet door een bug.

Een serverrek met één rood statuslampje tussen rijen groene lampjes, als beeld voor een stille intrekkingsbeslissing

Het veld voidedReason lezen Als een aankoop wel als ingetrokken verschijnt, vertelt het veld voidedReason je waarom, op een schaal van negen waarden: other, remorse, not_received, defective, accidental_purchase, fraud, friendly_fraud, chargeback en unacknowledged_purchase. Dat is niet zomaar boekhouding. Remorse en accidental_purchase zijn gewone terugbetalingen. Fraud, friendly_fraud en chargeback zijn de waarden die het waard zijn om naar een fraudebeoordelingswachtrij te sturen in plaats van gewoon in te trekken en te vergeten, omdat datzelfde account statistisch gezien vaker herhaalt.

Er is een operationeel detail dat ertoe doet als je hier rechtstreeks op bouwt: purchases.voidedpurchases.list is beperkt tot 6.000 queries per dag en 30 queries binnen een venster van 30 seconden. Een ontwerp dat in een strakke lus blijft pollen, botst snel tegen dat plafond; het bedoelde patroon is een geplande ophaalronde met pagination tokens, niet continu pollen.

Apple: de App Store kan een licentie intrekken, maar geen consumable

De App Store Server Notifications V2 van Apple sturen een REFUND melding zodra een terugbetaling wordt afgerond, zowel voor consumables, non-consumables, auto-renewable abonnementen als non-renewing abonnementen. Wat er na die melding gebeurt, hangt volledig af van het type aankoop, omdat het eigen intrekkingsmechanisme van StoreKit slechts twee van de vier types dekt.

Waarom consumables de uitzondering zijn

Een non-consumable of een abonnement heeft een blijvend recht dat de systemen van Apple kunnen uitschakelen. Een consumable heeft dat niet: zodra deze is gebruikt, wordt hij als geleverd beschouwd, en er blijft niets meer over in StoreKit om in te trekken. Als een klant 500 coins kocht, ze uitgaf, en vervolgens een terugbetaling kreeg, zijn die coins vanuit Apple's oogpunt al verdwenen. Het enige grootboek dat ze nog bevat, is dat van jou. De REFUND melding is je enige signaal om de bijbehorende original transaction ID te vinden en dat saldo zelf van je eigen administratie af te trekken; niets aan de kant van Apple doet dit voor je.

MeldingWat het betekentWat je server moet doen
REFUNDApple heeft een consumable, non-consumable, auto-renewable of non-renewing aankoop terugbetaaldTrek non-consumable en abonnementstoegang direct in; trek het consumable-saldo zelf af
REFUND_DECLINEDApple heeft het terugbetalingsverzoek van een klant geweigerdNiets. Toegang liep nooit risico
REFUND_REVERSEDApple heeft een terugbetaling die het al had toegekend teruggedraaidHerstel elke toegang of elk saldo dat je door de REFUND melding had verwijderd
CONSUMPTION_REQUESTApple wil jouw bewijs voordat het over een terugbetaling beslistReageer binnen 12 uur; dit wordt verstuurd vóór een REFUND, niet erna

De melding die bijna niemand afhandelt

REFUND_REVERSED is de melding die de meeste integraties helemaal overslaan, omdat hij beschrijft dat Apple zijn eigen beslissing terugdraait, een geval waar weinig ontwikkelaars vanaf het begin rekening mee houden. Als hij binnenkomt, gaat revocationDate op de transactie weer terug naar null: wat Apple betreft is de aankoop weer geldig. Als je server een recht verwijderde, een abonnementsrecord annuleerde, of een consumable-saldo op nul zette toen de oorspronkelijke REFUND binnenkwam, is REFUND_REVERSED het signaal om dat allemaal terug te draaien.

Hoe je dit gat stap voor stap dicht

  • Zet bij Google Play revoke=true standaard voor elk terugbetalingspad, of het nu via Play Console of via de API gaat, tenzij een specifiek supportgeval vereist dat de klant toegang behoudt.
  • Vraag purchases.voidedpurchases.list volgens een schema op met pagination tokens, niet in een strakke lus, en blijf onder de 6.000 aanvragen per dag en 30 per 30 seconden.
  • Stuur voidedReason waarden zoals fraud, friendly_fraud en chargeback naar een beoordelingswachtrij in plaats van automatisch in te trekken en af te sluiten.
  • Abonneer je bij Apple op App Store Server Notifications V2 en trek non-consumable en abonnementstoegang in zodra een REFUND melding ze noemt.
  • Sla de original transaction ID op bij elke consumable aankoop, zodat een REFUND melding het exacte saldo kan vinden om af te trekken zonder handmatig opzoekwerk.
  • Handel REFUND_REVERSED expliciet af en log elke intrekking bij zijn transaction of order ID, zodat een terugdraaiing precies kan herstellen wat werd verwijderd.

Wat het gat werkelijk kost

Niets hiervan gaat over eerlijkheid tegenover de klant; het gaat over wat blijft draaien nadat het geld weg is. Een terugbetaald abonnement dat je API blijft aanroepen, data blijft opslaan, en compute blijft verbruiken, is geen moreel probleem, het is een kostenpost: je betaalt de infrastructuurrekening voor een aankoop waarvan je de omzet al hebt teruggegeven. De stores houden die kosten niet voor je bij, want vanuit hun kant is de terugbetaling voltooid zodra het geld is verplaatst. De toegangskant is helemaal jouw rekening om te beheersen.

Dit is dezelfde reden waarom RefundHalt intrekking als onderdeel van de terugbetalingspipeline behandelt, niet als een bijgedachte: een REFUND event en een toegangswijziging worden als één actie vastgelegd, en een REFUND_REVERSED event herstelt de status automatisch in plaats van te wachten tot iemand merkt dat de klant nog steeds geen toegang heeft tot iets dat Apple al had teruggegeven.

Veelgestelde vragen

Trekt het terugbetalen van een aankoop op Google Play automatisch de toegang in?
Nee. Toegang wordt alleen ingetrokken als "Remove entitlement" is aangevinkt in Play Console of als de API-aanroep orders.refund revoke=true bevat. Geen van beide is de standaard, dus een terugbetaling kan worden geboekt terwijl de klant het product blijft gebruiken.
Wat gebeurt er als een consumable in-app aankoop wordt terugbetaald in de App Store?
Apple stuurt een REFUND melding, maar StoreKit heeft geen manier om een consumable in te trekken omdat deze als geleverd wordt beschouwd zodra hij is verbruikt. De server van de ontwikkelaar moet de oorspronkelijke transactie vinden en het saldo handmatig aftrekken.
Wat is de REFUND_REVERSED melding en vereist die speciale afhandeling?
REFUND_REVERSED betekent dat Apple een eerder toegekende terugbetaling heeft teruggedraaid; revocationDate gaat terug naar null en de aankoop is weer geldig. Elke toegang of elk saldo dat door de oorspronkelijke REFUND is verwijderd, moet worden hersteld, en de meeste integraties luisteren helemaal nooit naar deze melding.
Waarom zou een terugbetaalde Google Play bestelling niet verschijnen in de Voided Purchases API?
De API vermeldt alleen bestellingen die zijn geannuleerd met intrekking. Als een terugbetaling is verwerkt zonder de revoke-optie, wordt de bestelling helemaal niet door dat endpoint teruggegeven, waardoor een pipeline die er uitsluitend op vertrouwt, die terugbetalingen volledig mist.
Hoe vaak mag ik de Voided Purchases API van Google opvragen?
Het endpoint is beperkt tot 6.000 queries per dag en 30 queries binnen een willekeurig venster van 30 seconden, dus het is bedoeld voor een geplande ophaalronde met pagination tokens in plaats van continu pollen.

Bronnen en verder lezen

RefundHalt

De terugbetalingsautopiloot voor de App Store en Google Play

Lees verder

Het volgende terugbetalingsverzoek is al onderweg.

Stel RefundHalt in binnen de tijd die het kost om nog een supportmail te lezen over een terugbetaling die je niet kon betwisten.