Révoquer l'accès après un remboursement : l'étape qu'Apple et Google ne font pas pour vous
Apple et Google peuvent tous deux traiter un remboursement pendant que le client garde son achat. Voici exactement quand l'accès est retiré automatiquement, quand votre serveur doit s'en charger, et la notification que presque aucune intégration ne gère.

Points clés
- Sur Google Play, rembourser une commande ne retire pas l'accès de l'utilisateur en soi. L'accès n'est retiré que si "Remove entitlement" est coché dans Play Console, ou si l'appel API inclut revoke=true, ce qui n'est pas le comportement par défaut.
- Un remboursement émis sans l'option de révocation n'apparaît jamais dans la Voided Purchases API. Un traitement de révocation qui ne lit que ce flux va silencieusement manquer chacune de ces commandes.
- Apple peut révoquer automatiquement une licence pour les achats non consommables et les abonnements, mais un consommable est déjà considéré comme livré dès qu'il est dépensé, donc StoreKit n'a aucun moyen de le révoquer. Votre serveur doit déduire le solde lui-même.
- Apple peut aussi annuler son propre remboursement. Une notification REFUND_REVERSED signifie qu'un remboursement précédemment accordé a été annulé, revocationDate repasse à null, et tout accès ou solde que vous aviez retiré doit être restauré.
- Le champ voidedReason de Google comporte neuf valeurs, de remorse et accidental_purchase jusqu'à fraud, friendly_fraud et unacknowledged_purchase, donc le même flux qui vous indique quand révoquer l'accès vous indique aussi quelles révocations relèvent de la fraude et lesquelles relèvent d'un bug dans votre propre intégration.
- Chaque jour où un achat remboursé conserve son accès est un jour où vous payez le calcul, le stockage et les appels API qui vont avec, sur de l'argent que vous avez déjà rendu.
Approuver un remboursement et retirer l'accès sont deux étapes distinctes, et une seule des deux se produit automatiquement. Les développeurs qui supposent qu'un remboursement traité équivaut à un achat révoqué se trompent sur les deux principales boutiques, de façons différentes et pour des raisons différentes. Savoir précisément où s'arrête l'automatisation, c'est ce qu'il faut pour révoquer l'accès après un remboursement, plutôt que de découvrir des mois plus tard que cela n'a jamais eu lieu.
Pourquoi un remboursement ne révoque pas l'accès par défaut
Apple et Google traitent tous deux l'argent et l'accès comme deux opérations distinctes. Le volet financier relève de la politique du magasin : une fois le remboursement approuvé, il est définitif et le registre est mis à jour. Le volet accès est optionnel, ou relève du développeur, ou les deux, selon la plateforme et le type d'achat. Aucune des deux boutiques ne suppose que vous voulez que l'accès disparaisse à l'instant où le remboursement est finalisé, car de nombreux cas légitimes (un remboursement partiel, un crédit de courtoisie, un geste commercial) ne sont pas censés retirer le produit du tout.
Google Play : la case à cocher que personne ne remarque
Dans Play Console, rembourser une commande est un formulaire en deux parties : choisir le montant du remboursement, puis décider séparément de cocher ou non "Remove entitlement". Laisser cette case décochée est le chemin par défaut que suivent la plupart des agents du support, car la plupart des demandes de remboursement sont des gestes commerciaux où le client garde ce qu'il a acheté. Le problème, c'est que rien sur l'écran de confirmation du remboursement ne vous indique que c'est aussi ainsi qu'un client conserve un abonnement ou un article intégré après un remboursement que vous vouliez définitif.
Le chemin de l'API comporte le même piège
Le même choix existe dans le code. Le point de terminaison orders.refund accepte un paramètre de requête optionnel revoke ; quand il vaut true, l'accès à l'article est immédiatement supprimé, et pour un abonnement récurrent, chaque paiement futur est annulé avec lui. Omettre le paramètre le fait passer par défaut à false : le remboursement est enregistré, la fiche d'achat est mise à jour, et l'utilisateur continue d'utiliser le produit exactement comme avant.
| Action | Accès retiré ? | Apparaît dans la Voided Purchases API ? |
|---|---|---|
| Remboursement Play Console, "Remove entitlement" décoché | Non | Non |
| Remboursement Play Console, "Remove entitlement" coché | Oui, immédiatement | Oui |
| API orders.refund, paramètre revoke omis (false par défaut) | Non | Non |
| API orders.refund, revoke=true | Oui, immédiatement | Oui |
Cette dernière ligne est celle qui piège les intégrations. La Voided Purchases API existe pour vous permettre de construire un pipeline de révocation sans consulter Play Console à la main, mais elle ne liste que les commandes réellement annulées avec révocation. Un remboursement traité sans la case cochée, ou sans revoke=true, est invisible pour ce flux par conception, pas par bug.

Lire le champ voidedReason Quand un achat apparaît comme annulé, le champ voidedReason en indique la raison, sur une échelle de neuf valeurs : other, remorse, not_received, defective, accidental_purchase, fraud, friendly_fraud, chargeback et unacknowledged_purchase. Ce n'est pas qu'une question de comptabilité. Remorse et accidental_purchase sont des remboursements ordinaires. Fraud, friendly_fraud et chargeback méritent d'être orientés vers une file de revue anti-fraude plutôt que vers un simple chemin de révocation automatique, car le même compte a statistiquement plus de chances de récidiver.
Un détail opérationnel compte si vous développez directement contre ce flux : purchases.voidedpurchases.list est plafonné à 6 000 requêtes par jour et 30 requêtes sur n'importe quelle fenêtre de 30 secondes. Une conception qui interroge en boucle serrée atteint vite ce plafond ; le modèle prévu est une récupération planifiée avec des jetons de pagination, pas un polling continu.
Apple : l'App Store peut révoquer une licence, mais pas un consommable
Les App Store Server Notifications V2 d'Apple envoient une notification REFUND dès qu'un remboursement est finalisé, aussi bien pour les consommables, les non-consommables, les abonnements à renouvellement automatique que les abonnements sans renouvellement. Ce qui se passe après cette notification dépend entièrement du type d'achat, car le mécanisme de révocation propre à StoreKit ne couvre que deux des quatre types.
Pourquoi les consommables font exception
Un non-consommable ou un abonnement possède un droit permanent que les systèmes d'Apple peuvent désactiver. Un consommable n'en a pas : une fois utilisé, il est considéré comme livré, et il ne reste rien dans StoreKit à révoquer. Si un client a acheté 500 pièces, les a dépensées, puis a obtenu un remboursement, les pièces ont déjà disparu du point de vue d'Apple. Le seul registre qui les conserve encore est le vôtre. La notification REFUND est votre unique signal pour retrouver l'identifiant de transaction original correspondant et déduire ce solde de vos propres registres ; rien du côté d'Apple ne le fera pour vous.
| Notification | Ce que cela signifie | Ce que votre serveur doit faire |
|---|---|---|
| REFUND | Apple a remboursé un achat consommable, non-consommable, à renouvellement automatique ou sans renouvellement | Révoquer immédiatement l'accès aux non-consommables et abonnements ; déduire vous-même le solde du consommable |
| REFUND_DECLINED | Apple a refusé la demande de remboursement d'un client | Rien. L'accès n'a jamais été en danger |
| REFUND_REVERSED | Apple a annulé un remboursement déjà accordé | Restaurer l'accès ou le solde que la notification REFUND vous avait fait retirer |
| CONSUMPTION_REQUEST | Apple veut vos preuves avant de statuer sur un remboursement | Répondre sous 12 heures ; ceci se déclenche avant un REFUND, pas après |
La notification que presque personne ne gère
REFUND_REVERSED est celle que la plupart des intégrations ignorent complètement, car elle décrit Apple annulant sa propre décision, un cas que peu de développeurs anticipent du premier coup. Quand elle arrive, revocationDate repasse à null sur la transaction : pour Apple, l'achat est de nouveau valide. Si votre serveur a supprimé un droit d'accès, annulé l'enregistrement d'un abonnement ou remis à zéro un solde de consommable lors du REFUND initial, REFUND_REVERSED est le signal pour tout restaurer.
Comment combler l'écart, étape par étape
- Sur Google Play, faites de revoke=true le comportement par défaut de chaque chemin de remboursement, que ce soit via Play Console ou l'API, sauf si un cas de support précis exige que le client garde l'accès.
- Interrogez purchases.voidedpurchases.list selon un calendrier avec des jetons de pagination, pas en boucle serrée, et restez sous 6 000 requêtes par jour et 30 par tranche de 30 secondes.
- Orientez les valeurs de voidedReason fraud, friendly_fraud et chargeback vers une file de revue plutôt que vers une révocation automatique suivie d'une clôture.
- Chez Apple, abonnez-vous aux App Store Server Notifications V2 et révoquez l'accès aux non-consommables et abonnements dès qu'une notification REFUND les mentionne.
- Conservez l'identifiant de transaction original pour chaque achat consommable, afin qu'une notification REFUND puisse retrouver le solde exact à déduire sans recherche manuelle.
- Traitez REFUND_REVERSED explicitement et enregistrez chaque révocation en la reliant à son identifiant de transaction ou de commande, pour qu'une annulation puisse restaurer précisément ce qui a été retiré.
Ce que coûte réellement cet écart
Rien de tout cela ne concerne l'équité envers le client ; il s'agit de ce qui continue de tourner une fois l'argent parti. Un abonnement remboursé qui continue d'appeler votre API, de stocker des données et de consommer du calcul n'est pas un problème moral, c'est une ligne de coût : vous payez la facture d'infrastructure d'un achat sur lequel vous avez déjà rendu le revenu. Les boutiques ne suivent pas ce coût à votre place, car de leur côté, le remboursement est terminé dès que l'argent bouge. Le volet accès est entièrement à votre charge.
C'est la même raison pour laquelle RefundHalt traite la révocation comme faisant partie du pipeline de remboursement, et non comme une réflexion après coup : un événement REFUND et un changement d'accès sont enregistrés comme une seule action, et un événement REFUND_REVERSED restaure l'état automatiquement, au lieu d'attendre que quelqu'un remarque que le client est encore bloqué hors de quelque chose qu'Apple lui a pourtant rendu.
Questions fréquentes
- Le remboursement d'un achat sur Google Play révoque-t-il automatiquement l'accès ?
- Non. L'accès n'est retiré que si "Remove entitlement" est coché dans Play Console ou si l'appel à l'API orders.refund inclut revoke=true. Aucun des deux n'est le comportement par défaut, donc un remboursement peut être enregistré pendant que le client continue d'utiliser le produit.
- Que se passe-t-il quand un achat intégré consommable est remboursé sur l'App Store ?
- Apple envoie une notification REFUND, mais StoreKit n'a aucun moyen de révoquer un consommable car il est considéré comme livré dès qu'il est dépensé. Le serveur du développeur doit retrouver la transaction d'origine et déduire le solde manuellement.
- Qu'est-ce que la notification REFUND_REVERSED et nécessite-t-elle un traitement particulier ?
- REFUND_REVERSED signifie qu'Apple a annulé un remboursement déjà accordé ; revocationDate repasse à null et l'achat redevient valide. Tout accès ou solde retiré par le REFUND initial doit être restauré, et la plupart des intégrations n'écoutent jamais cette notification.
- Pourquoi une commande Google Play remboursée pourrait-elle ne pas apparaître dans la Voided Purchases API ?
- L'API ne liste que les commandes annulées avec révocation. Si un remboursement a été traité sans l'option de révocation, la commande n'est tout simplement pas renvoyée par ce point de terminaison, donc un pipeline qui s'appuie uniquement dessus manquera complètement ces remboursements.
- À quelle fréquence puis-je interroger la Voided Purchases API de Google ?
- Le point de terminaison est limité à 6 000 requêtes par jour et 30 requêtes sur n'importe quelle fenêtre de 30 secondes, il est donc conçu pour une récupération planifiée avec des jetons de pagination plutôt que pour un polling continu.
Sources et lectures complémentaires
- 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
Le pilote automatique des remboursements pour l'App Store et Google Play
Poursuivre la lecture
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.