Google Play Refund Management

Un remboursement Google Play ne vous préviendra pas, mais l'API Voided Purchases le fera

Un remboursement sur Google Play arrive en silence : la transaction est annulée, le client continue d'utiliser l'app, et rien ne change de votre côté à moins de vérifier. C'est l'API Voided Purchases qui permet cette vérification. Voici l'API champ par champ, les limites qui l'encadrent, et où l'argent s'évapore quand elle n'est pas connectée.

5 min read
Un remboursement Google Play ne vous préviendra pas, mais l'API Voided Purchases le fera

Un remboursement sur Google Play arrive en silence. La transaction est annulée, le client continue d'utiliser l'app, et rien ne change de votre côté à moins de vérifier délibérément. Vérifier, c'est exactement à cela que sert l'API Voided Purchases. Elle renvoie les commandes qui ont été annulées, remboursées ou ayant fait l'objet d'un chargeback, afin que vous puissiez retirer l'accès à ce que le client a cessé de payer. Connectez-y une tâche planifiée, lisez ce qui en revient, et révoquez le droit d'accès. C'est tout le mécanisme.

Un piège surprend presque toutes les équipes, et il relève de la politique plutôt que du code. Cette API ne liste que les commandes réellement révoquées. Remboursez un achat dans la Play Console sans cocher l'option de révocation, et cette commande n'apparaîtra jamais ici : votre tâche s'exécute parfaitement proprement pendant qu'un client remboursé repart avec tout ce que vous lui avez vendu. La suite couvre l'API champ par champ, les limites qui l'encadrent, et où l'argent s'évapore discrètement quand elle n'est pas mise en place.

Points clés

  • L'API Voided Purchases, accessible via la méthode purchases.voidedpurchases.list, liste les commandes que Google Play a annulées, remboursées ou qui ont fait l'objet d'un chargeback, ce qui vous permet de construire un système retirant l'accès aux achats que le client ne détient plus.

  • Elle n'affiche que les commandes révoquées. Un remboursement émis par le développeur sans révocation reste invisible ici : pour retirer l'accès, il faut donc rembourser avec la révocation activée.

  • Sa portée est une fenêtre glissante de 30 jours. Comme startTime ne peut pas remonter au-delà de 30 jours, tout serveur hors ligne plus d'un mois perd définitivement ces annulations, d'où l'obligation d'interroger l'API selon un planning régulier.

  • voidedSource identifie qui a déclenché l'annulation : 0 pour l'utilisateur, 1 pour le développeur, 2 pour Google. voidedReason en explique la raison, allant de 0 pour Other jusqu'à 7 pour Chargeback et 8 pour Unacknowledged_purchase.

  • Les notifications développeur en temps réel déclenchent une VoidedPurchaseNotification dès qu'un achat est annulé, mais traitez-la comme une simple alerte. Confirmez via l'API Voided Purchases avant de révoquer quoi que ce soit.

  • Distinguez les renouvellements d'abonnement par orderId, jamais par purchaseToken. Un même purchaseToken couvre tous les renouvellements d'un abonnement, si bien que le token seul ne peut pas séparer une période de la suivante.

  • Les plafonds sont de 6,000 requêtes par jour et de 30 sur toute période de 30 secondes, limitez donc chaque requête à une fenêtre temporelle et parcourez-la à l'aide du token de continuation, jamais une requête par commande.

Ce que renvoie l'API

L'endpoint répond à une seule question : quelles commandes de cette app ont été annulées récemment. Une annulation regroupe trois cas de figure qui rendent tous l'argent au client, à savoir une annulation, un remboursement ou un chargeback. Elle couvre à la fois les produits intégrés ponctuels et les abonnements, et un paramètre en définit la portée. Laissez type à 0, la valeur par défaut, et seuls les achats intégrés annulés reviennent. Réglez type sur 1 et vous recevez à la fois les achats intégrés annulés et les abonnements annulés.

Chaque élément de la réponse est un objet voided purchase avec un ensemble court et riche de champs :

  • orderId identifie de façon unique un achat ponctuel, un abonnement, ou un renouvellement individuel au sein de celui-ci. Traitez-le comme votre clé de jointure.

  • purchaseToken identifie un achat ponctuel ou un abonnement, mais ne distingue pas les renouvellements : appuyez-vous sur orderId pour cela.

  • purchaseTimeMillis enregistre le moment de l'achat, en millisecondes depuis l'epoch.

  • voidedTimeMillis enregistre le moment de l'annulation, du remboursement ou du chargeback, également en millisecondes depuis l'epoch.

  • voidedSource indique qui a initié l'annulation, où 0 correspond à l'utilisateur, 1 au développeur et 2 à Google.

  • voidedReason donne la cause sous forme d'entier de 0 à 8.

  • voidedQuantity porte le nombre annulé issu d'un remboursement partiel basé sur la quantité, et n'apparaît que lorsque includeQuantityBasedPartialRefund vaut true.

Comme un seul purchaseToken couvre tout un abonnement alors que chaque renouvellement reçoit un orderId flambant neuf, indexer vos droits d'accès sur le token vous fera révoquer la mauvaise période. Indexez plutôt sur orderId.

Lisez voidedReason avant de toucher à quoi que ce soit

voidedReason est ce qui transforme une simple liste en véritable décision, car un remboursement pour changement d'avis et un chargeback bancaire partagent le même flux tout en n'ayant rien en commun. La liste complète est la suivante :

1. Other ne porte aucune catégorie assignée. Révoquez et passez à la suite.

2. Remorse correspond à un client qui a changé d'avis, un remboursement ordinaire.

3. Not_received correspond à une réclamation selon laquelle le produit n'est jamais arrivé, ce qui mérite un coup d'œil à votre livraison.

4. Defective signifie que cela n'a pas fonctionné, un signal de qualité à consigner.

5. Accidental_purchase correspond à un achat involontaire, fréquemment sur un appareil partagé.

6. Fraud correspond à une transaction signalée comme frauduleuse par Google.

7. Friendly_fraud correspond à un chargeback où le véritable titulaire de la carte conteste un paiement qu'il a réellement effectué.

8. Chargeback correspond à l'annulation du paiement par la banque du client, définitive auprès de la banque et désormais facturée à vous.

9. Unacknowledged_purchase correspond au remboursement automatique par Google d'un achat que votre app n'a jamais accusé réception.

Le motif 9 est un remboursement que vous vous êtes infligé vous-même. Tout achat laissé sans accusé de réception par votre app voit son argent restitué par Google, de sorte qu'un unacknowledged_purchase dans ce flux représente un revenu perdu à cause d'un simple appel manqué, et non d'un choix de l'acheteur.

La fenêtre de 30 jours qui vide discrètement la liste

L'API ne remonte pas au-delà des 30 derniers jours. Par défaut, startTime vaut l'heure actuelle moins 30 jours et refuse d'être fixé plus tôt, tandis qu'endTime vaut par défaut l'heure présente : vous obtenez donc une fenêtre glissante d'un mois plutôt qu'une archive permanente.

L'implication est brutale. Laissez une tâche d'interrogation tomber en panne et passer inaperçue pendant cinq semaines, et les annulations de la première semaine sont déjà sorties de l'API, sans qu'aucun appel ne puisse les récupérer. Ces commandes restent non révoquées, et vous ne sauriez même pas qu'elles ont existé si vous ne les aviez pas capturées ailleurs. Le filet a un trou exactement aussi large que votre plus longue panne. Interrogez l'API au moins une fois par jour et recoupez avec les notifications en temps réel, en traitant les 30 jours comme l'horloge de suppression impitoyable qu'elle est réellement.

Qu'une commande apparaisse ou non dépend de la révocation

C'est la raison numéro un pour laquelle les équipes jugent l'API cassée. Elle ne renvoie que des commandes révoquées, rien d'autre. Les remboursements lancés par les utilisateurs, les annulations, les chargebacks et les remboursements initiés par Google se révoquent tous automatiquement, et atterrissent donc toujours dans le flux. Un remboursement initié par le développeur fait exception. Émettre vous-même le remboursement, que ce soit depuis la Play Console ou via l'Orders API, laisse le choix de la révocation comme une décision distincte à prendre. Si vous la refusez, la commande se solde avec le client mais n'apparaît jamais dans ce flux.

La leçon est courte. Lorsque votre objectif est de couper l'accès, remboursez avec la révocation activée. Si vous l'omettez, vous avez rendu l'argent tout en laissant la porte ouverte, et votre tâche de révocation, aussi soigneusement conçue soit-elle, n'a rien à se mettre sous la dent.

L'interroger sans dépasser le quota

L'endpoint est soumis à une limitation de débit, et les plafonds sont assez bas pour qu'une boucle mal conçue les déclenche. Vous disposez de 6,000 requêtes par jour, comptées en heure du Pacifique, et jamais plus de 30 sur une période de 30 secondes. Ce budget convient à une interrogation par fenêtres et pénalise toute conception d'une requête par commande.

Fenêtres temporelles et token de continuation

maxResults vaut 1,000 par défaut, et c'est aussi le maximum possible. Si une fenêtre contient plus d'annulations qu'une page ne peut en contenir, la réponse inclut un objet tokenPagination portant un nextPageToken. Renvoyez ce token lors de votre prochain appel pour avancer dans les pages. Fixez les bornes de la fenêtre avec startTime et endTime, continuez de paginer jusqu'à épuisement du token, et seulement alors faites avancer la fenêtre. Cette cadence reste à l'écart à la fois du plafond de rafale de 30 secondes et du quota quotidien.

Les notifications en temps réel comblent l'écart quotidien

Même une interrogation quotidienne vous laisse aveugle jusqu'à 24 heures, et ce sont précisément les longs écarts que pénalise la fenêtre de 30 jours. Les notifications développeur en temps réel comblent ce décalage. Dès qu'un achat est annulé, une VoidedPurchaseNotification est publiée sur un topic Cloud Pub/Sub que vous contrôlez, et votre backend la consomme en quelques secondes. Sa charge utile reste compacte :

  • purchaseToken est le token de l'achat d'origine.

  • orderId est l'identifiant de la transaction annulée, généré à neuf à chaque renouvellement d'abonnement.

  • productType vaut 1 pour un abonnement et 2 pour un achat ponctuel.

  • refundType distingue un remboursement complet, marqué 1, d'un remboursement partiel basé sur la quantité, marqué 2.

Considérez la notification comme une alerte et non comme une vérité absolue. Dès qu'une VoidedPurchaseNotification arrive, interrogez l'API Voided Purchases pour vérifier où en est réellement la commande, et ne révoquez qu'à ce moment-là. L'alerte vous dit de vérifier ; l'API vous dit la vérité.

Ce que cela vous coûte en argent

L'API n'est qu'une tuyauterie, mais la raison de la poser est une facture, et deux de ses chiffres grimpent.

À partir du 3 août 2026, la facture du chargeback est pour vous

À compter du 3 août 2026, Google transfère le coût d'un chargeback vers le développeur. Vous perdez le prix d'achat et prenez en charge en plus les frais bancaires de chargeback. Un voidedReason de 7 cesse d'être une simple vente perdue et devient une ligne budgétaire assortie de frais. Le chargeback lui-même ne peut pas être annulé, puisqu'il est définitif auprès de la banque, mais détecter rapidement l'annulation vous permet de révoquer le droit d'accès et, pour tout ce qui est encore en cours de livraison, d'arrêter de dépenser pour un client qui a été remboursé puis dont le paiement a été contesté.

Vous continuez à financer un client déjà remboursé

Le prix de vente disparaît dès qu'une annulation survient. Ce que vous maîtrisez encore, c'est le coût de la poursuite de la livraison. Pour chaque heure où un droit d'accès remboursé reste actif, les factures que le client a cessé de couvrir continuent d'arriver de votre côté : le calcul, les appels au fournisseur de modèle, le stockage, et tout versement à un créateur ou partenaire que son activité déclenche. Construire le pipeline de révocation sur cette API, c'est ainsi que cette dépense est coupée. Faites l'impasse dessus et vous financez le produit pour des gens que le store a déjà remboursés.

La friendly fraud est une tendance, pas un incident isolé

Un voidedReason de 5 ou 6 est rarement isolé. La fraude et la friendly fraud se concentrent autour de certains comptes, appareils, et parfois de promotions particulières. Comme l'API associe voidedSource et voidedReason à chaque annulation, vous disposez de quoi suivre l'abus par compte plutôt que d'absorber chaque contestation comme une perte isolée. Un compte qui subit un deuxième chargeback vous dit ce que le premier remboursement n'a pas dit.

Assembler le tout

Le modèle complet est réduit une fois les pièces en main. Écoutez les VoidedPurchaseNotification en temps réel pour que rien n'attende une journée entière. Traitez l'API Voided Purchases comme la source de vérité, indexée sur orderId pour que les renouvellements ne se confondent jamais. Lisez voidedSource et voidedReason pour qu'un chargeback soit traité différemment d'un remboursement pour changement d'avis. Interrogez l'API à une cadence assez serrée pour que la fenêtre de 30 jours ne morde jamais, et remboursez avec la révocation activée chaque fois que votre intention est de couper l'accès.

C'est cette couche que Refund Sensor fait fonctionner pour vous. Il consomme les notifications en temps réel, recoupe chaque annulation avec l'API, révoque la commande précise plutôt que le produit entier, et distingue un chargeback bancaire d'un remboursement ordinaire pour que les cas coûteux apparaissent au lieu de se cacher. Vous obtenez un retrait de l'accès en quelques secondes et un historique complet de qui a annulé quoi et pourquoi, sans avoir à mettre en place le moindre pipeline Pub/Sub ou la moindre tâche d'interrogation.

Où ces règles sont documentées

Questions fréquentes

Parce que ce flux ne contient que les commandes révoquées. Les remboursements lancés par l'utilisateur, ainsi que les annulations, les chargebacks et les remboursements initiés par Google, se révoquent automatiquement et apparaissent toujours. Un remboursement que vous émettez vous-même ne s'affiche que si vous avez aussi choisi de le révoquer. Remboursez sans révoquer et la commande est soldée mais invisible ici : activez donc la révocation dès que votre objectif est de retirer l'accès.

Trente jours, pas un de plus. Par défaut, startTime correspond à l'heure actuelle moins 30 jours et refuse toute valeur antérieure : l'endpoint se comporte donc comme une fenêtre glissante d'un mois plutôt que comme une archive. Une fois qu'une annulation dépasse 30 jours, elle disparaît sans aucun moyen de la récupérer, ce qui explique pourquoi vous devez interroger l'API selon un planning régulier et vous appuyer en complément sur les notifications en temps réel.

Les deux ont un rôle à jouer. Une VoidedPurchaseNotification vous parvient en quelques secondes et vous indique qu'il faut vérifier, mais Google recommande de la traiter comme un signal et non comme une vérité absolue. Confirmez l'état réel via l'API Voided Purchases, puis révoquez. La notification supprime le délai ; l'API fournit les valeurs faisant foi de voidedSource et voidedReason sur lesquelles vous agissez réellement.

Regardez voidedReason. Un 7 correspond à un chargeback, c'est-à-dire que la banque du client a annulé le paiement, et un 6 correspond à de la friendly fraud. Un 1 est un remboursement classique pour simple changement d'avis. Cette distinction compte, car à partir du 3 août 2026, Google répercute sur le développeur le prix du chargeback ainsi que les frais bancaires, de sorte qu'un 7 vous coûte plus cher qu'un simple remboursement.

Oui. Réglez le paramètre type sur 1 pour recevoir à la fois les achats intégrés annulés et les abonnements annulés ; la valeur par défaut, 0, ne renvoie que les produits intégrés seuls. Pour les abonnements, identifiez précisément la période annulée via orderId, car un même purchaseToken couvre tous les renouvellements alors que chaque transaction de renouvellement reçoit son propre orderId.

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