Cofnięcie dostępu po zwrocie: krok, którego Apple i Google nie wykonają za ciebie
Apple i Google mogą przetworzyć zwrot pieniędzy, podczas gdy klient nadal zachowuje zakupiony produkt. Oto dokładnie, kiedy dostęp jest odbierany automatycznie, kiedy musi to zrobić twój serwer, i jedno powiadomienie, którego większość integracji nigdy nie obsługuje.

Najważniejsze wnioski
- W Google Play zwrot pieniędzy za zamówienie sam w sobie nie usuwa dostępu użytkownika. Dostęp jest odbierany tylko wtedy, gdy w Play Console zaznaczono opcję "Remove entitlement", albo gdy wywołanie API zawiera parametr revoke=true, co nie jest wartością domyślną.
- Zwrot wykonany bez opcji revoke nigdy nie pojawi się w Voided Purchases API. Zadanie odbierające dostęp, które czyta wyłącznie ten kanał danych, po cichu pominie każde takie zamówienie.
- Apple może automatycznie cofnąć licencję dla non-consumable i subskrypcji, ale consumable jest uznawany za dostarczony w chwili jego zużycia, więc StoreKit nie ma żadnego mechanizmu, by go cofnąć. Twój serwer musi sam odjąć ten stan konta.
- Apple może też cofnąć własny zwrot pieniędzy. Powiadomienie REFUND_REVERSED oznacza, że wcześniej przyznany zwrot został unieważniony, revocationDate wraca do null, a każdy dostęp lub stan konta, który usunąłeś, musi zostać przywrócony.
- Pole voidedReason od Google przyjmuje dziewięć wartości, od remorse i accidental_purchase po fraud, friendly_fraud i unacknowledged_purchase, więc ten sam kanał danych, który mówi ci, kiedy odebrać dostęp, mówi też, które cofnięcia to nadużycia, a które to błąd w twojej własnej integracji.
- Każdy dzień, w którym zwrócony zakup zachowuje dostęp, to dzień, w którym płacisz za moc obliczeniową, pamięć masową i wywołania API stojące za nim, z pieniędzy, które już oddałeś.
Zatwierdzenie zwrotu i odebranie dostępu to dwa oddzielne kroki, i tylko jeden z nich dzieje się automatycznie. Deweloperzy, którzy zakładają, że przetworzony zwrot oznacza cofnięty dostęp do zakupu, mylą się w przypadku obu wielkich sklepów, na różne sposoby i z różnych powodów. Dokładna wiedza o tym, gdzie kończy się automatyzacja, jest tym, co pozwala cofnąć dostęp po zwrocie, zamiast odkryć miesiące później, że nigdy do tego nie doszło.
Dlaczego zwrot pieniędzy domyślnie nie cofa dostępu
Apple i Google traktują pieniądze i dostęp jako dwie odrębne operacje. Strona pieniężna to polityka sklepu, gdy zwrot zostanie zatwierdzony, jest ostateczny i księgi zostają zaktualizowane. Strona dostępu jest opcjonalna, albo należy do dewelopera, albo do obu, w zależności od platformy i typu zakupu. Żaden sklep nie zakłada, że chcesz, aby dostęp zniknął w chwili sfinalizowania zwrotu, ponieważ wiele uzasadnionych przypadków (częściowy zwrot, gest dobrej woli, uprzejmość wobec klienta) w ogóle nie powinno odbierać produktu.
Google Play: pole wyboru, którego nikt nie zauważa
W Play Console zwrot pieniędzy za zamówienie to dwuczęściowy formularz, wybierasz kwotę zwrotu, a następnie osobno decydujesz, czy zaznaczyć "Remove entitlement". Pozostawienie tego pola niezaznaczonego to domyślna ścieżka wybierana przez większość konsultantów wsparcia, ponieważ większość próśb o zwrot to gesty dobrej woli, w których klient zachowuje to, co kupił. Problem w tym, że nic na ekranie potwierdzenia zwrotu nie mówi, że w ten sam sposób klient zachowuje subskrypcję lub przedmiot w aplikacji po zwrocie, który miał być ostateczny.
Ścieżka API ma tę samą pułapkę
Ten sam wybór istnieje w kodzie. Endpoint orders.refund przyjmuje opcjonalny parametr zapytania revoke; gdy ma wartość true, dostęp do przedmiotu zostaje natychmiast zakończony, a w przypadku subskrypcji odnawialnej anulowane zostają wraz z nim wszystkie przyszłe płatności. Pominięcie tego parametru sprawia, że domyślnie przyjmuje wartość false: zwrot zostaje zaksięgowany, rekord zakupu zostaje zaktualizowany, a użytkownik nadal korzysta z produktu dokładnie tak jak wcześniej.
| Działanie | Dostęp odebrany? | Widoczne w Voided Purchases API? |
|---|---|---|
| Zwrot w Play Console, "Remove entitlement" niezaznaczone | Nie | Nie |
| Zwrot w Play Console, "Remove entitlement" zaznaczone | Tak, natychmiast | Tak |
| API orders.refund, parametr revoke pominięty (domyślnie false) | Nie | Nie |
| API orders.refund, revoke=true | Tak, natychmiast | Tak |
To właśnie ten ostatni wiersz jest tym, co łapie integracje w pułapkę. Voided Purchases API istnieje po to, byś mógł zbudować pipeline odbierania dostępu bez ręcznego sprawdzania Play Console, ale wymienia on tylko zamówienia, które faktycznie zostały unieważnione wraz z cofnięciem. Zwrot przetworzony bez tego pola wyboru, albo bez revoke=true, jest niewidoczny w tym kanale danych z założenia, nie przez błąd.

Odczytywanie pola voidedReason Gdy zakup rzeczywiście pojawia się jako unieważniony, pole voidedReason mówi dlaczego, w skali dziewięciu wartości: other, remorse, not_received, defective, accidental_purchase, fraud, friendly_fraud, chargeback i unacknowledged_purchase. To nie jest tylko księgowość. Remorse i accidental_purchase to zwykłe zwroty. Fraud, friendly_fraud i chargeback to wartości warte skierowania do kolejki weryfikacji nadużyć zamiast zwykłej ścieżki cofnij-i-zapomnij, ponieważ to samo konto statystycznie częściej powtarza takie zachowanie.
Jeśli budujesz coś bezpośrednio na tym kanale danych, liczy się jeden szczegół operacyjny: purchases.voidedpurchases.list ma limit 6000 zapytań dziennie i 30 zapytań w dowolnym oknie 30 sekund. Rozwiązanie, które odpytuje w ciasnej pętli, szybko osiąga ten limit; zamierzonym wzorcem jest zaplanowane pobieranie z tokenami paginacji, a nie ciągłe odpytywanie.
Apple: App Store może cofnąć licencję, ale nie consumable
App Store Server Notifications V2 od Apple wysyła powiadomienie REFUND w chwili sfinalizowania zwrotu, zarówno dla consumable, non-consumable, subskrypcji auto-renewable, jak i non-renewing. To, co dzieje się po tym powiadomieniu, zależy całkowicie od typu zakupu, ponieważ własny mechanizm cofania StoreKit obejmuje tylko dwa z czterech typów.
Dlaczego consumable są wyjątkiem
Non-consumable lub subskrypcja mają stały stan uprawnienia, który systemy Apple mogą wyłączyć. Consumable go nie ma: gdy tylko zostanie zużyty, jest traktowany jako dostarczony, i w StoreKit nie zostaje już nic do cofnięcia. Jeśli klient kupił 500 coins, wydał je, a następnie otrzymał zwrot, te coins z punktu widzenia Apple już nie istnieją. Jedyną księgą, która nadal je posiada, jest twoja. Powiadomienie REFUND to twój jedyny sygnał, by odnaleźć pasujący original transaction ID i odjąć ten stan konta z własnych rekordów; nic po stronie Apple nie zrobi tego za ciebie.
| Powiadomienie | Co oznacza | Co musi zrobić twój serwer |
|---|---|---|
| REFUND | Apple zwróciło pieniądze za zakup typu consumable, non-consumable, auto-renewable lub non-renewing | Natychmiast cofnij dostęp do non-consumable i subskrypcji; sam odejmij stan konta consumable |
| REFUND_DECLINED | Apple odrzuciło prośbę klienta o zwrot | Nic. Dostęp nigdy nie był zagrożony |
| REFUND_REVERSED | Apple cofnęło zwrot, który wcześniej przyznało | Przywróć dostęp lub stan konta, który usunąłeś na skutek powiadomienia REFUND |
| CONSUMPTION_REQUEST | Apple chce twoich dowodów, zanim zdecyduje o zwrocie | Odpowiedz w ciągu 12 godzin; to powiadomienie pojawia się przed REFUND, nie po nim |
Powiadomienie, którego prawie nikt nie obsługuje
REFUND_REVERSED to powiadomienie, które większość integracji całkowicie pomija, ponieważ opisuje sytuację, w której Apple cofa własną decyzję, przypadek, na który mało który deweloper projektuje rozwiązanie za pierwszym razem. Gdy nadchodzi, revocationDate transakcji wraca do null: z punktu widzenia Apple zakup znów jest ważny. Jeśli twój serwer usunął uprawnienie, anulował rekord subskrypcji albo wyzerował stan konta consumable, gdy nadeszło pierwotne REFUND, REFUND_REVERSED to sygnał, by to wszystko przywrócić.
Jak zamknąć tę lukę, krok po kroku
- W Google Play ustaw revoke=true jako domyślne dla każdej ścieżki zwrotu, niezależnie czy przechodzi ona przez Play Console czy przez API, chyba że konkretny przypadek wsparcia wymaga, aby klient zachował dostęp.
- Odpytuj purchases.voidedpurchases.list zgodnie z harmonogramem, z tokenami paginacji, a nie w ciasnej pętli, i utrzymuj się poniżej 6000 żądań dziennie oraz 30 na 30 sekund.
- Kieruj wartości voidedReason takie jak fraud, friendly_fraud i chargeback do kolejki weryfikacji, zamiast automatycznie cofać dostęp i zamykać sprawę.
- U Apple zasubskrybuj App Store Server Notifications V2 i cofaj dostęp do non-consumable oraz subskrypcji, gdy tylko powiadomienie REFUND je wymieni.
- Zapisuj original transaction ID przy każdym zakupie typu consumable, aby powiadomienie REFUND mogło znaleźć dokładny stan konta do odjęcia bez ręcznego wyszukiwania.
- Obsługuj REFUND_REVERSED w sposób jawny i loguj każde cofnięcie dostępu wraz z jego transaction lub order ID, aby cofnięcie zwrotu mogło precyzyjnie przywrócić to, co zostało usunięte.
Ile tak naprawdę kosztuje ta luka
Nic z tego nie dotyczy uczciwości wobec klienta; chodzi o to, co nadal działa po tym, jak pieniądze zniknęły. Zwrócona subskrypcja, która nadal wywołuje twoje API, przechowuje dane i zużywa moc obliczeniową, to nie problem moralny, to pozycja kosztowa: płacisz rachunek za infrastrukturę za zakup, za który już oddałeś przychód. Sklepy nie śledzą tego kosztu za ciebie, ponieważ z ich strony zwrot jest zakończony w chwili, gdy pieniądze się przemieszczają. Strona dostępu w całości należy do rachunku, który musisz kontrolować sam.
To ten sam powód, dla którego RefundHalt traktuje cofanie dostępu jako część pipeline'u zwrotów, a nie jako coś dodanego na końcu: zdarzenie REFUND i zmiana dostępu są rejestrowane jako jedna akcja, a zdarzenie REFUND_REVERSED automatycznie przywraca stan, zamiast czekać, aż ktoś zauważy, że klient wciąż nie ma dostępu do czegoś, co Apple mu oddało.
Często zadawane pytania
- Czy zwrot pieniędzy za zakup w Google Play automatycznie cofa dostęp?
- Nie. Dostęp jest odbierany tylko wtedy, gdy w Play Console zaznaczono "Remove entitlement" albo wywołanie API orders.refund zawiera revoke=true. Żadne z nich nie jest wartością domyślną, więc zwrot może zostać zaksięgowany, podczas gdy klient nadal korzysta z produktu.
- Co się dzieje, gdy zakup typu consumable w aplikacji zostaje zwrócony w App Store?
- Apple wysyła powiadomienie REFUND, ale StoreKit nie ma sposobu, by cofnąć consumable, ponieważ jest on uznawany za dostarczony w chwili zużycia. Serwer dewelopera musi odnaleźć oryginalną transakcję i ręcznie odjąć stan konta.
- Czym jest powiadomienie REFUND_REVERSED i czy wymaga specjalnej obsługi?
- REFUND_REVERSED oznacza, że Apple cofnęło zwrot, który wcześniej przyznało; revocationDate wraca do null, a zakup znów jest ważny. Każdy dostęp lub stan konta usunięty przez pierwotne REFUND musi zostać przywrócony, a większość integracji w ogóle nie nasłuchuje tego powiadomienia.
- Dlaczego zwrócone zamówienie z Google Play może nie pojawić się w Voided Purchases API?
- API wymienia tylko zamówienia, które zostały unieważnione wraz z cofnięciem dostępu. Jeśli zwrot został przetworzony bez opcji revoke, zamówienie w ogóle nie zostanie zwrócone przez ten endpoint, więc pipeline, który opiera się wyłącznie na nim, całkowicie pominie takie zwroty.
- Jak często mogę odpytywać Voided Purchases API od Google?
- Endpoint ma limit 6000 zapytań dziennie i 30 zapytań w dowolnym oknie 30 sekund, więc jest przeznaczony do zaplanowanego pobierania z tokenami paginacji, a nie do ciągłego odpytywania.
Źródła i materiały dodatkowe
- Apple: Handling refund notifications
- Apple: notificationType (App Store Server Notifications)
- Apple: revocationReason (App Store Server Notifications)
- Google: Voided Purchases API overview
- Google: purchases.voidedpurchases REST resource
- Google: orders.refund API reference (revoke parameter)
- Google Play Console Help: manage your app's orders and issue refunds
RefundHalt
Autopilot zwrotów dla App Store i Google Play
Czytaj dalej
The refunds you cannot contest: what Apple and Google decide without asking you
Only two refund flows ever ask for your evidence: Apple's consumption request and Google Play's chargeback review. Everything else, the 48-hour Play refund, support refunds, voided purchases, unacknowledged trials, is decided without you. Here is the full map, what you can prevent, and what you can only record.
Apple's CONSUMPTION_REQUEST, explained: the 12 hours that decide your refund
When a customer asks Apple for a refund, Apple asks you first. Here's exactly what the CONSUMPTION_REQUEST notification contains, what Apple expects back, how to answer one end to end, and why most developers silently forfeit.