Lors des tests IAP avant mise en ligne, nous avons rencontré un problème classique mais très déroutant :
- L’utilisateur appuie sur « Continuer l’achat »
- Le bouton passe à « Traitement en cours »
- Quelques secondes plus tard, il revient à « Continuer l’achat »
- Aucune fenêtre de confirmation d’achat App Store ne s’affiche
Cet article retrace le chemin complet d’investigation, les logs clés, la cause racine et une stratégie de correction applicable avant le lancement.
1. Symptôme
La liste des produits se chargeait correctement sur l’écran d’achat, mais après le clic :
purchase()était bien appelé- Aucun dialogue système n’apparaissait
- L’UI revenait finalement à l’état non acheté
À première vue, cela ressemblait à un « achat non déclenché ». Les logs ont montré autre chose.
2. Première étape : vérifier que le flux d’achat s’exécute vraiment
Nous avons ajouté des logs sur tous les points critiques du flux :
- Paramètres au clic sur le bouton
- Entrée dans
purchaseSelectedProduct()et branchesguard - Avant/après
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
Résultat :
purchase()renvoyait.success- La transaction était vérifiée, et
transaction.finish()était bien exécuté - Mais
entitledIDsrestait vide
Conclusion : le problème n’était pas « l’achat ne démarre pas », mais « les droits ne sont pas reconnus après l’achat ».
3. Constats décisifs après approfondissement
Avec un logging plus poussé, plusieurs signaux clés sont apparus :
- Le nombre d’itérations de
Transaction.currentEntitlementsétait0 Transaction.latest(for: monthly)renvoyait un enregistrement valide, mais expiré- Plus important : des transactions anciennes existaient dans
Transaction.unfinishedavant l’achat - Le
purchase()actuel renvoyait une transaction de l’ancien flux, pas d’un nouvel achat interactif
Cela explique pourquoi « aucune popup » :
- StoreKit renvoie d’abord une ancienne transaction traitable (unfinished / chaîne historique)
- Le code la termine via
finish() - La requête semble « réussie », mais ne passe pas par la nouvelle confirmation d’achat attendue par l’utilisateur
4. Résumé de la cause racine
Ce n’était pas un problème isolé, mais la combinaison de deux facteurs :
- Des transactions inachevées (
unfinished transaction) qui perturbent le flux d’achat - Une chaîne d’abonnement sandbox en état expiré (même avec
latest transaction, ce n’est pas forcément un entitlement actif)
Comportement final : pas de popup, succès apparent, droits toujours vides.
5. Stratégie de correction (validée en pratique)
5.1 Nettoyer proactivement les transactions unfinished avant achat
Au démarrage de l’app et avant l’achat, parcourir Transaction.unfinished et exécuter finish() sur les transactions vérifiées, pour éviter qu’une ancienne transaction intercepte le nouveau flux d’achat.
5.2 Conserver et renforcer les logs de diagnostic (recommandé avant mise en ligne)
Conserver ces logs jusqu’au lancement :
- Snapshot de
unfinished transaction subscription status(state, renewal info)- Nombre d’itérations de
currentEntitlements - Détails
transaction/expirationdelatest(for:)
5.3 Vérifier les droits via un double canal (renforcement optionnel)
Si currentEntitlements est temporairement vide dans certains environnements, utiliser latest(for:) en fallback (en validant toujours le statut actif).
6. Checklist IAP avant lancement
- Avant chaque test, vérifier l’état de l’abonnement du compte Sandbox (expiré ou non)
- Logger le nombre de
unfinishedsur les chemins critiques - Tester trois scénarios : « premier achat / renouvellement après expiration / restauration d’achat »
- Vérifier la cohérence entre
currentEntitlementsetlatest - En cas de « pas de popup », vérifier d’abord si un ancien flux transactionnel a été déclenché
7. Conclusion
« Cliquer sur acheter sans popup » n’est pas forcément un problème d’UI ni de timing d’appel.
Avec StoreKit 2, l’état des transactions, la file unfinished et le cycle de vie de l’abonnement influencent directement le comportement final.
Ajouter ces logs et ce nettoyage avant lancement permet d’éviter de nombreux problèmes IAP difficiles à reproduire en production.