Tous les articles
Deep dive7 min de lecture

Il existe un endpoint qui renvoie tout l'historique des remboursements App Store d'un client, et voici ce qu'il retourne

L'endpoint Get Refund History d'Apple renvoie l'historique complet des remboursements App Store d'un client sous forme de transactions signées. Voici chaque champ, comment le jeton revision pagine, pourquoi il fonctionne par client et non par app, et ce que vous coûte un remboursement que vous manquez.

Une scène de bureau vue de dessus avec un smartphone, un registre papier de transactions et une loupe, illustrant la récupération de l'historique des remboursements App Store d'un client depuis l'endpoint Get Refund History d'Apple

Points clés

  • Get Refund History est un endpoint de l'App Store Server API qui renvoie les achats intégrés remboursés d'un client pour votre app sous forme de liste de transactions signées, pour que vous puissiez rapprocher les remboursements et révoquer l'accès même quand une notification ne vous est jamais parvenue.
  • Vous appelez GET /inApps/v2/refund/lookup/{transactionId} avec n'importe quel id de transaction de ce client, et Apple renvoie ses remboursements pour tous les types d'achat de votre app, pas seulement celui que vous avez demandé.
  • La réponse comporte trois champs : signedTransactions, jusqu'à 20 transactions JWS par page triées du remboursement le plus ancien en premier, plus un jeton revision et un booléen hasMore pour la pagination.
  • Conservez le jeton revision final. Renvoyez-le la prochaine fois et Apple ne renvoie que les remboursements plus récents que ce point, ce qui transforme un vidage complet de l'historique en une courte liste de nouvelles lignes à chaque exécution.
  • Chaque transaction décodée porte revocationDate et revocationReason. Un revocationReason de 1 signifie que le client a été remboursé pour un problème réel ou perçu dans votre app, et 0 signifie une autre raison, comme un achat accidentel.
  • L'endpoint fonctionne par client, pas par app. Il n'existe aucun appel unique qui liste tous les remboursements de toute votre app, vous rapprochez donc par compte à partir d'un id de transaction, ou vous lisez votre flux de notifications REFUND pour la vue à l'échelle de l'app.
  • La raison de le mettre en place, c'est l'argent. Un remboursement que vous ne détectez jamais garde un compte actif, et vous continuez à payer le calcul, les appels à l'API du modèle, le stockage et les reversements pour un client qu'App Store a déjà remboursé.

Apple conserve un enregistrement interrogeable de chaque remboursement qu'elle a accordé sur le compte d'un client pour votre app, et un seul appel le renvoie. L'endpoint s'appelle Get Refund History, il fait partie de l'App Store Server API, et il vous remet l'historique complet des remboursements App Store de ce client sous forme de liste de transactions signées. Vous passez un id de transaction, vous récupérez ce qu'Apple a remboursé, et vous le rapprochez de ce que vous avez encore activé.

Voici pourquoi cela en vaut la peine. Un remboursement que vous ne voyez jamais est un remboursement que vous continuez à payer. L'argent est déjà parti, mais le compte reste actif, et chaque heure où il l'est, vous continuez à dépenser en calcul, appels à l'API du modèle, stockage et tout reversement lié à ce client. Vos notifications de remboursement sont censées attraper cela dès que ça se produit. Get Refund History est le filet de sécurité pour quand elles échouent, après une panne, un déploiement qui a perdu un webhook, ou un cas de support où vous avez besoin de la vue d'ensemble en un seul appel.

Ce que renvoie l'endpoint d'historique des remboursements App Store

Vous appelez GET /inApps/v2/refund/lookup/{transactionId} sur l'App Store Server API, signé avec le même JWT que vous utilisez pour tous les autres appels. L'id de transaction dans le chemin peut être n'importe quelle transaction du client. Apple le lit comme une identité, pas comme un filtre, et renvoie les achats remboursés de ce client dans toute votre app : consommables, non consommables, abonnements à renouvellement automatique et non renouvelables sans distinction. L'ancienne V1 de cet endpoint renvoyait jusqu'à 50 remboursements en une seule réponse et est obsolète. La version actuelle pagine, vous gérez donc les clients aux longs historiques sans une charge géante.

La réponse comporte trois champs

ChampCe qu'il contient
signedTransactionsJusqu'à 20 transactions remboursées pour ce client, chacune un JWS signé que vous vérifiez et décodez. Triées du remboursement le plus ancien en premier, par revocationDate. Un tableau vide signifie que le client n'a aucun remboursement dans votre app
revisionUn jeton de pagination. Renvoyez-le pour obtenir la page suivante, et conservez le dernier pour ne récupérer que les nouveaux remboursements la prochaine fois
hasMoreVrai quand Apple détient plus de transactions remboursées que cette page n'en a renvoyé, vous rappelez donc avec le revision

Ce qu'une transaction remboursée vous apprend

Chaque entrée de signedTransactions est un JWS. Vérifiez-le contre la chaîne de certificats d'Apple, décodez-le, et vous avez une charge utile de transaction ordinaire avec les champs de remboursement renseignés. Ce sont ceux qui comptent ici.

ChampCe qu'il vous apprend
transactionIdL'id de la transaction remboursée, votre clé de jointure vers l'achat que vous avez enregistré
originalTransactionIdL'id du premier achat de la chaîne, comment vous reliez les renouvellements d'un abonnement
productIdLe produit qui a été remboursé, pour que vous révoquiez le bon droit et rien d'autre
revocationDateL'heure UNIX, en millisecondes, à laquelle Apple a remboursé la transaction
revocationReasonPourquoi Apple l'a remboursé. 1 signifie un problème réel ou perçu avec votre app, 0 signifie une autre raison, comme un achat accidentel
price, currencyLe montant, en milliunits, et son code de devise ISO 4217, pour que vous puissiez totaliser l'argent rendu
appAccountTokenL'UUID que vous avez attaché à l'achat, le moyen le plus propre de rattacher un remboursement à votre propre utilisateur

Le jeton revision est ce qui vous évite de relire toute la liste

La façon naïve d'utiliser cet endpoint est de rechercher un client et de parcourir chaque page à chaque fois. Ça marche, et sur un client avec cinquante remboursements, ce sont cinquante lignes que vous connaissiez déjà plus la seule nouvelle. Le jeton revision existe pour éliminer ce gaspillage. Chaque réponse porte un revision. Quand hasMore est vrai, vous le renvoyez pour obtenir la page suivante. Quand vous atteignez la fin, vous conservez le dernier revision que vous avez vu.

Ce que cet endpoint ne fera pas

Il y a une attente à abandonner avant de construire dessus. Get Refund History fonctionne par client, pas par app. Vous ne pouvez pas lui demander tous les remboursements que votre app a subis la semaine dernière. Il répond à une question, quels remboursements ce compte a-t-il, et vous devez arriver avec un id de transaction de ce compte pour la poser. Les développeurs se heurtent à ce mur en permanence et partent chercher un endpoint de remboursements à l'échelle de l'app qui n'existe pas.

La vue à l'échelle de l'app se trouve ailleurs. Votre flux App Store Server Notifications envoie une notification REFUND au moment où Apple accorde chacune, et Get Notification History vous permet de rejouer ce flux filtré sur les types de remboursement sur une plage de dates. La division est donc nette. Les notifications et leur historique vous donnent le flux à l'échelle de l'app. Get Refund History vous donne la liste faisant autorité d'un compte, à la demande, ce qui est ce que vous voulez à un guichet de support ou après une panne.

Une loupe survolant une ligne surlignée d'un registre papier de transactions à côté d'un smartphone, illustrant la recherche des remboursements d'un seul client dans l'historique des remboursements App Store

Ce qu'un remboursement manqué vous coûte en argent

L'endpoint, c'est la plomberie. La facture est la raison pour laquelle vous posez le tuyau. Chaque remboursement de cette liste est de l'argent déjà rendu, et la seule variable qui reste sous votre contrôle est combien de temps vous continuez à dépenser sur un compte qui ne paie plus.

Vous continuez à payer pour servir un compte remboursé

Le prix d'achat est parti à l'instant où Apple accorde le remboursement. Ce qui continue de tourner, c'est le coût de la livraison. Pour une app qui fait un vrai travail par utilisateur, c'est le calcul, les appels à l'API du modèle, le stockage et tout reversement à un créateur ou un partenaire lié à son usage. Un client remboursé dont vous ne coupez jamais l'accès est un abonnement que vous financez de votre poche. Rapprocher avec Get Refund History et révoquer selon ce que vous trouvez, c'est ainsi que vous coupez ce compteur quand une notification est passée à travers.

Une raison de remboursement de 1 est un rapport de défaut déguisé

revocationReason vous coûte deux fois si vous l'ignorez. Le premier coût est le remboursement lui-même. Le second est chaque remboursement futur dû à la même cause. Quand un produit revient sans cesse avec revocationReason 1, un problème réel ou perçu dans votre app, Apple vous remet un échantillon étiqueté de ce qui pousse les clients à demander leur argent. Suivez la tendance par produit et vous pourrez colmater la fuite au lieu de la payer un remboursement à la fois.

Le détecter tard vaut quand même mieux que de ne pas le détecter

Une rétrofacturation est définitive avec la banque et, sur l'autre store, elle entraîne désormais des frais que le développeur absorbe. Un remboursement App Store, ce n'est pas cela. Il est réglé, mais le droit vous appartient, à révoquer dès que vous le savez. Donc même un remboursement que vous trouvez avec des jours de retard via cet endpoint vaut la peine d'être trouvé. Vous ne pouvez pas récupérer l'argent, mais vous pouvez arrêter la dépense qui tournait encore derrière.

Comment cela s'articule avec les notifications, et avec Google

Voyez les pièces comme un seul système. La notification REFUND est le signal en direct, poussé vers votre serveur au fur et à mesure qu'Apple décide. Get Refund History est la source de vérité en mode pull pour un seul client, l'appel que vous faites quand le push a échoué ou quand une personne a besoin du compte complet sous les yeux. Du côté de Google Play, la forme est la même idée avec des noms différents : une VoidedPurchaseNotification est poussée en temps réel, et la Voided Purchases API est la liste que vous tirez. Les deux stores vous donnent un flux et un registre. L'erreur est de ne se fier qu'au flux, car les flux tombent.

La mise en place à la manière de RefundHalt

La boucle est courte une fois chaque pièce en place. Prenez une notification REFUND comme déclencheur. Rapprochez avec Get Refund History pour qu'un webhook perdu ne laisse jamais un compte remboursé actif. Décodez chaque transaction, rattachez-la par appAccountToken ou transactionId à votre utilisateur, lisez revocationReason pour qu'un remboursement dû à un défaut soit signalé et pas seulement classé, et révoquez le droit exact plutôt que tout le compte. Paginez avec le jeton revision pour lire les nouveaux remboursements, pas les anciens.

C'est la partie que RefundHalt exécute pour vous. Il écoute les notifications de remboursement, se rabat sur Get Refund History quand il lui faut la liste faisant autorité, vérifie chaque transaction signée, révoque l'achat précis, et conserve le revision pour que chaque passage ne lise que ce qui a changé. Vous obtenez l'accès coupé en quelques secondes et un enregistrement propre de qui a été remboursé, pour quoi et pourquoi, sans avoir à monter vous-même le polling et la vérification JWS.

Questions fréquentes

Comment voir tous les remboursements de toute mon app, et pas seulement d'un client ?
Vous ne pouvez pas avec Get Refund History, car il fonctionne par client et a besoin d'un id de transaction du compte concerné. Pour la vue à l'échelle de l'app, utilisez votre flux App Store Server Notifications, qui envoie une notification REFUND pour chaque remboursement au moment où Apple l'accorde, et Get Notification History pour rejouer ce flux filtré sur les types de remboursement sur une plage de dates.
Combien de remboursements l'endpoint Get Refund History renvoie-t-il ?
La version actuelle renvoie jusqu'à 20 transactions remboursées par page, triées du remboursement le plus ancien en premier, et pagine le reste avec un jeton revision quand hasMore est vrai. L'endpoint V1 obsolète renvoyait jusqu'à 50 en une seule réponse. Il n'y a pas de plafond sur le total, un client avec un long historique s'étale simplement sur plus de pages.
À quoi sert le jeton revision ?
C'est ainsi que vous paginez et que vous évitez de relire tout l'historique d'un client à chaque fois. Chaque réponse inclut un revision. Vous le renvoyez pour récupérer la page suivante, et vous conservez le dernier afin que votre prochaine recherche ne renvoie que les remboursements plus récents que ce point. Cela réduit un rapprochement planifié à une courte liste de nouvelles lignes.
Que signifie revocationReason dans une transaction remboursée ?
C'est la raison pour laquelle Apple a remboursé la transaction. Une valeur de 1 signifie que le client a été remboursé à cause d'un problème réel ou perçu dans votre app, et 0 signifie une autre raison, comme un achat accidentel. revocationDate vous dit quand le remboursement a eu lieu, en millisecondes UNIX. Lire revocationReason vous permet de distinguer un défaut de produit d'un remboursement ponctuel de regret.
En ai-je encore besoin si je gère déjà les notifications REFUND ?
Oui, comme filet de sécurité. Les notifications sont le signal en direct, mais un push peut échouer à arriver pendant une panne, un mauvais déploiement ou un changement de webhook, et un remboursement manqué laisse un compte remboursé actif et vous coûtant de l'argent. Get Refund History est la source de vérité en mode pull avec laquelle vous rapprochez pour que rien ne reste activé qu'Apple a déjà remboursé.

Sources et lectures complémentaires

RefundHalt

Le pilote automatique des remboursements pour l'App Store et Google Play

Poursuivre la lecture

La prochaine demande de remboursement est déjà en route.

Configurez RefundHalt dans le temps qu'il vous faudrait pour lire un nouvel e-mail d'assistance au sujet d'un remboursement que vous n'avez pas pu contester.