Tous les articles
Deep dive9 min de lecture

Un achat en attente ressemble à une vente, mais l'argent n'est pas arrivé, et l'accorder trop tôt revient à donner le produit

L'App Store comme Google Play ont un état d'achat en attente, une commande que la boutique a acceptée mais pas encore débitée. Débloquez-la avant que le paiement soit encaissé et chacun de ceux qui échouent est un coût sec. Voici comment fonctionnent les achats en attente sur chaque boutique, ce que coûte un achat accordé à tort, et comment les gérer sans fuite.

Un caissier tendant un petit paquet emballé par-dessus le comptoir d'un magasin sous une lumière chaude, illustrant un achat en attente où la marchandise part avant que le paiement soit encaissé

Points clés

  • Un achat en attente est une vraie commande que la boutique a acceptée mais pas encore débitée. Votre code voit un nouvel achat, mais l'argent n'est pas arrivé, et il pourrait ne jamais arriver.
  • N'accordez le droit d'accès que lorsque l'état est PURCHASED sur Google Play, ou lorsque la transaction est terminée sur Apple. Ne débloquez jamais sur un état PENDING ni sur le résultat en attente d'Apple.
  • Sur Google Play, les paiements en espèces en magasin, les virements bancaires et une partie de la facturation par l'opérateur sont réglés hors ligne, si bien que l'achat revient en PENDING, pas en PURCHASED, jusqu'à ce que le client paie vraiment.
  • Sur Apple, un achat en attente est le plus souvent Ask to Buy, où un organisateur familial doit l'approuver. L'approbation peut prendre des heures ou des jours, et la transaction terminée arrive plus tard via Transaction.updates.
  • Si vous débloquez sur une commande en attente qui s'annule ensuite, vous avez dépensé du calcul, des appels d'API, du stockage ou le versement d'un consommable pour un débit qui n'a jamais été encaissé. Contrairement à un remboursement, il n'y a pas d'argent à récupérer, car rien n'a jamais été perçu.
  • La boutique vous prévient quand une commande en attente échoue. Google envoie ONE_TIME_PRODUCT_CANCELED, type 2, ou SUBSCRIPTION_PENDING_PURCHASE_CANCELED, type 20. Apple, tout simplement, ne livre jamais de transaction terminée.
  • Une commande en attente peut devenir une vraie vente pendant que votre app est fermée, alors revérifiez au retour : appelez queryPurchasesAsync() dans onResume() sur Google Play, et continuez d'écouter Transaction.updates sur Apple.

Quelqu'un appuie sur acheter dans votre app. Vos journaux montrent une nouvelle commande, votre écouteur de facturation se déclenche, et vous remettez la marchandise. Sur la plupart des achats, c'est exactement ce qu'il faut. Sur un achat en attente, c'est une erreur, car la commande existe mais pas l'argent. Le client a choisi un moyen de paiement qui se règle plus tard, la boutique attend toujours d'être payée, et vous venez de livrer une fonctionnalité payante pour un débit qui ne sera peut-être jamais encaissé. C'est le cousin discret d'un remboursement. Ici, rien n'est annulé, car rien n'a jamais été perçu. Vous avez simplement donné le produit.

Un achat en attente est une vraie commande dans un état pas encore payé, et l'App Store comme Google Play en ont un. Les deux boutiques vous disent clairement d'attendre. Le piège, c'est qu'une commande en attente ressemble presque à s'y méprendre à une commande terminée dans votre code, si bien qu'une intégration qui traite chaque nouvel achat comme une vente livre de la marchandise sur des commandes que la boutique tente encore d'encaisser. Gérez-la correctement et vous ne perdez rien. Gérez-la mal et chaque paiement lent qui échoue est un coût sec, livré à vos frais.

Ce qu'est vraiment un achat en attente

Un achat en attente est une commande que la boutique a enregistrée mais pas encore débitée. L'acheteur a lancé le flux, la boutique l'a acceptée, et le règlement se produit quelque part que votre app ne peut pas voir. Google Play appelle cela l'état PENDING. Apple parle d'une transaction en attente, ou différée. Des noms différents, un même fait : la boutique garde une commande ouverte pendant qu'elle attend d'être payée, et elle vous a dit de ne pas traiter cette commande comme de l'argent.

Google Play : le paiement se produit ailleurs

Certains moyens de paiement se règlent hors ligne. L'espèce dans un magasin physique, les virements bancaires et une partie de la facturation par l'opérateur exigent des étapes supplémentaires entre l'appui et le débit. Quand un client en choisit un, Google renvoie l'achat dans l'état PENDING au lieu de PURCHASED. Pour un paiement en espèces, le client reçoit un code par notification et par e-mail, l'apporte dans un magasin participant et paie à la caisse. Jusque-là, Google n'a rien encaissé, et vous non plus. La règle de Google tient en une ligne : utilisez getPurchaseState() et n'accordez le droit d'accès que lorsque l'état est PURCHASED. Elle vous dit aussi de ne pas confirmer un achat tant qu'il est en PENDING, car la confirmation appartient à une commande payée, pas à une commande promise.

Apple : l'achat attend l'appui de quelqu'un d'autre

L'état en attente d'Apple signifie que la transaction a besoin d'une action externe avant de pouvoir se terminer. La plus fréquente est Ask to Buy, où un enfant lance un achat et un organisateur familial doit l'approuver. Dans StoreKit 2, l'appel d'achat renvoie Product.PurchaseResult.pending. Dans l'ancien StoreKit, la transaction est signalée comme différée. Dans tous les cas, Apple n'a débité personne, et la transaction terminée, si elle vient, arrive de façon asynchrone via Transaction.updates. Montrez au client un état d'attente et ne débloquez rien tant que la transaction terminée n'a pas atterri.

Pourquoi accorder un achat en attente vous coûte de l'argent réel

La perte ici n'est pas du type remboursement, où l'argent que vous aviez comptabilisé est repris. C'est pire sur un point précis : il n'y a pas d'argent à reprendre, car rien n'a jamais été perçu.

Vous livrez, et la boutique n'encaisse jamais

Quand vous débloquez sur une commande en attente qui s'annule ensuite, vous avez déjà dépensé pour la servir. Le calcul qui a exécuté la fonctionnalité, les appels d'API tiers que vous avez payés, le stockage que vous avez alloué, et pour un consommable le versement réel de ce que vous avez vendu. Tout cela sort par la porte sur une commande qui n'a produit aucun revenu. Un remboursement part au moins d'un débit qui a eu lieu. Un achat en attente accordé à tort n'a jamais eu le moindre débit, si bien qu'il n'apparaît même pas comme de l'argent qui sort. Il apparaît comme rien, ce qui est justement pourquoi il est facile à manquer et facile à répéter.

Le signal d'annulation, et ce qu'il signifie

La boutique vous prévient bien quand une commande en attente meurt. Sur Google Play, un produit unique qui échoue envoie une notification ONE_TIME_PRODUCT_CANCELED, type 2, et un abonnement qui était en attente envoie SUBSCRIPTION_PENDING_PURCHASE_CANCELED, type 20. Quand ces mêmes commandes aboutissent au contraire, vous recevez ONE_TIME_PRODUCT_PURCHASED, type 1, ou SUBSCRIPTION_PURCHASED, type 4. Sur Apple, il n'y a pas d'événement d'annulation à capter, car une transaction différée qui est refusée ne devient tout simplement jamais une transaction terminée. Si vous avez débloqué trop tôt, ce silence est la facture.

QuestionGoogle PlayApple
Ce qui la déclencheEspèces, virement bancaire, une partie de la facturation par l'opérateurApprobation Ask to Buy, ou autre action requise
État que vous voyezPurchaseState PENDINGrésultat en attente, ou une transaction différée
Accordez l'accès quandL'état est PURCHASEDLa transaction est terminée
Elle a aboutiONE_TIME_PRODUCT_PURCHASED (1), SUBSCRIPTION_PURCHASED (4)Transaction terminée via Transaction.updates
Elle a échouéONE_TIME_PRODUCT_CANCELED (2), SUBSCRIPTION_PENDING_PURCHASE_CANCELED (20)Aucune transaction terminée n'arrive jamais
De l'argent a-t-il été perçuNon, pas avant PURCHASEDNon, pas avant que la transaction se termine

La fenêtre, et qui attend qui

Un achat en attente n'est pas un chrono contre lequel vous courez. C'est un chrono qui, de votre côté, n'a pas démarré.

Google Play donne au client des jours, pas des minutes

Un paiement en espèces ou par virement bancaire se règle selon le calendrier du client, pas le vôtre. La commande reste en PENDING jusqu'à ce que le client paie ou que la fenêtre expire et que Google l'annule. Votre propre fenêtre de confirmation de trois jours, celle qui rembourse automatiquement un achat que vous ne confirmez pas, ne commence même pas tant que l'achat n'est pas passé de PENDING à PURCHASED. Il n'y a donc aucune urgence à servir une commande en attente. Il y a seulement la discipline d'attendre que l'état change.

L'approbation d'Apple est sur le téléphone de l'organisateur familial

Une demande Ask to Buy arrive sur l'appareil de l'organisateur sous forme d'une invite qu'il approuve ou refuse quand il y arrive. Cela peut être des minutes, des heures ou un jour plus tard, et votre app ne peut pas presser les choses. Le seul comportement correct est de refléter l'état d'attente et de laisser StoreKit vous remettre la transaction terminée si et quand l'approbation arrive.

Un carton scellé à côté d'un sablier sur un bureau, illustrant un achat en attente où la marchandise est prête mais le paiement n'a pas encore été encaissé

Comment gérer les achats en attente sans fuite

Tout le travail se résume à quatre habitudes. Aucune n'est difficile, et en sauter une seule est par où l'argent s'en va.

Accordez sur l'état payé, jamais sur l'état en attente

Sur Google Play, vérifiez getPurchaseState() et n'accordez que sur PURCHASED, et ne confirmez pas un achat tant qu'il est en PENDING. Sur Apple, ne débloquez que sur une transaction terminée et jamais sur le résultat en attente. Cette seule règle referme toute la fuite. Tout le reste consiste à vous assurer que vous remarquez bien l'arrivée de l'état payé.

Revérifiez quand l'app revient

Le passage de en attente à payé se produit souvent pendant que votre app ne tourne pas. Sur Google Play, appelez queryPurchasesAsync() dans votre gestionnaire onResume() pour récupérer les commandes passées en PURCHASED en arrière-plan, et gardez votre écouteur de Real-time Developer Notifications comme source de vérité côté serveur. Sur Apple, écoutez Transaction.updates pendant toute la vie de l'app, car une transaction approuvée peut arriver bien après le retour de l'appel d'achat d'origine.

Activez la prise en charge des achats en attente et testez les deux issues

Google exige que vous appeliez enablePendingPurchases() quand vous construisez le BillingClient, et prendre en charge les transactions en attente pour les produits uniques est obligatoire, pas facultatif. Testez-le avant de publier. Les testeurs sous licence obtiennent deux instruments de test supplémentaires pour les formes de paiement différé, où le paiement se termine automatiquement ou s'annule automatiquement après quelques minutes, pour que vous puissiez observer de bout en bout aussi bien le chemin du paiement que celui de l'échec.

Dites au client que la commande n'est pas terminée

Un acheteur en attente est un vrai client au milieu d'un achat, pas un échec. Montrez-lui que la commande attend son paiement ou une approbation, et donnez-lui un chemin clair pour revenir la terminer. Un état en attente silencieux perd des ventes qu'un état bien étiqueté récupère, car la plupart de ces acheteurs veulent encore la chose et n'ont plus qu'une étape à faire.

Les mêmes flux de Real-time Developer Notifications et d'App Store Server Notifications que RefundHalt lit déjà pour les remboursements et les rétrofacturations portent aussi ces signaux. La notification d'achat qui dit qu'une commande en attente a enfin été encaissée, et la notification d'annulation qui dit que non, atterrissent dans votre tableau de bord à côté du reste de vos événements de revenu, si bien qu'une commande en attente qui a échoué est quelque chose que vous pouvez voir au lieu de quelque chose que vous avez payé par accident.

La version courte

Un achat en attente est une commande sans paiement, et les deux boutiques sont explicites sur le fait que vous devez attendre. Google Play renvoie les commandes en espèces, par virement bancaire et une partie de la facturation par l'opérateur dans un état PENDING et vous dit de n'accorder l'accès que sur PURCHASED. Apple renvoie une transaction en attente ou différée pour Ask to Buy et d'autres actions requises et livre la transaction terminée plus tard via Transaction.updates. Débloquez sur l'état payé, revérifiez quand votre app reprend, activez et testez la prise en charge des achats en attente, et étiquetez la commande en attente pour le client. Faites cela et un achat en attente ne vous coûte rien. Sautez-le et vous livrez un produit payant pour un débit qui n'est jamais venu, ce qui est la seule perte sans reçu à pointer du doigt.

Questions fréquentes

Qu'est-ce qu'un achat en attente ?
Un achat en attente est une commande que la boutique a acceptée mais pas encore débitée. Sur Google Play, c'est l'état d'achat PENDING, utilisé pour les moyens de paiement qui se règlent plus tard comme les espèces, le virement bancaire et une partie de la facturation par l'opérateur. Sur Apple, c'est une transaction en attente ou différée, le plus souvent un achat Ask to Buy attendant l'approbation d'un organisateur familial. Dans les deux cas, aucun argent n'a encore été perçu, vous ne devez donc pas accorder l'accès.
Dois-je accorder l'accès pendant qu'un achat est en attente ?
Non. N'accordez le droit d'accès que lorsque l'état est PURCHASED sur Google Play, ou lorsque la transaction est terminée sur Apple. Si vous débloquez une fonctionnalité pendant que la commande est encore en attente et que le paiement n'est jamais encaissé, vous avez livré le produit gratuitement, et il n'y a aucun débit à annuler puisqu'aucun n'a jamais été fait.
Quels moyens de paiement provoquent un achat en attente sur Google Play ?
Les moyens de paiement qui se règlent hors ligne. Les paiements en espèces dans un magasin physique, les virements bancaires et certaines options de facturation par l'opérateur exigent des étapes supplémentaires entre l'appui et le débit, si bien que Google renvoie l'achat dans l'état PENDING au lieu de PURCHASED. Pour un paiement en espèces, le client reçoit un code par notification et e-mail, puis paie dans un magasin participant.
Qu'est-ce qu'Ask to Buy, et quel est son rapport avec les achats en attente ?
Ask to Buy est la fonctionnalité Family Sharing d'Apple qui permet à un enfant de demander un achat qu'un organisateur familial doit approuver. Tant que la demande attend, l'achat est dans l'état en attente d'Apple, renvoyé comme Product.PurchaseResult.pending dans StoreKit 2 ou comme une transaction différée dans l'ancien StoreKit. La transaction terminée n'arrive, via Transaction.updates, que si et quand l'organisateur l'approuve.
Que se passe-t-il si un achat en attente n'est jamais payé ?
La commande est annulée et aucun argent ne change de mains. Sur Google Play, vous recevez une notification ONE_TIME_PRODUCT_CANCELED, type 2, pour un produit unique, ou SUBSCRIPTION_PENDING_PURCHASE_CANCELED, type 20, pour un abonnement. Sur Apple, la transaction différée ne devient tout simplement jamais une transaction terminée. Si vous aviez déjà accordé l'accès, c'est le moment où la perte devient réelle.
Un achat en attente est-il la même chose qu'un remboursement ?
Non. Un remboursement annule un paiement qui a réellement été perçu. Un achat en attente qui échoue n'a jamais été débité au départ, il n'y a donc rien à annuler et rien n'apparaît dans un rapport de remboursements. Si vous l'avez débloqué trop tôt, le coût est le calcul, les appels d'API, le stockage ou le consommable que vous avez dépensés à servir une commande qui n'a produit aucun revenu.

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.