![Quelles sont les nouveautés de l'API StoreKit 2 et comment Apple a simplifié l'intégration des achats intégrés aux applications. 7 62fdf0da8b35ff3ba074dbce jp android tutorial 1 configuration 5](https://adapty.io/wp-content/uploads/2023/01/62fdf0da8b35ff3ba074dbce_jp-android-tutorial-1-configuration-5.png)
Tendances et perspectives
septembre 5, 2022
Updated: mars 20, 2023
La validation par le serveur (validation du reçu (receipt validation) côté serveur) est un moyen de vérifier l’authenticité de l’achat. Contrairement à la validation basée sur le dispositif, la validation du serveur se produit – attendez – du côté du serveur. La validation signifie que l’appareil ou le serveur fait une requête aux serveurs d’Apple pour savoir si l’achat a réellement eu lieu et s’il était valide.
Il convient de noter que la validation du serveur n’est pas obligatoire – les achats intégrés fonctionneront toujours sans elle. Il présente cependant quelques avantages :
D’après notre expérience, le premier avantage suffit à lui seul à mettre en place le traitement des achats par serveur.
En général, le processus de validation des reçus sous iOS ressemble à ceci :
Pour envoyer une demande de validation de paiement, vous devez inclure le secret partagé pour autoriser la demande. Vous pouvez en générer un dans App Store Connect.
Le secret partagé peut être créé pour une application spécifique (secret spécifique à l’application) ou pour toutes les applications du compte (secret principal).
Pour générer un secret spécifique à l’application, ouvrez la page de l’application dans App Store Connect, allez dans Achats intégrés → Gérer et cliquez sur Secret partagé spécifique à l’application. Dans la fenêtre qui s’ouvre, vous pourrez générer un nouveau jeton ou copier le jeton existant.
Pour recevoir le secret pour toutes les applications de votre compte, ouvrez la page Users and Access et sélectionnez l’onglet Secret partagé.
Une fois que vous avez reçu le secret partagé, vous pouvez envoyer des reçus pour les faire valider sur les serveurs d’Apple. Cela se fait par le biais de la requête verifyReceipt. Vous devez envoyer une requête POST à https://buy.itunes.apple.com/verifyReceipt. Dans le corps de requête JSON, transmettez le secret partagé dans le champ password et le reçu dans le champ receipt-data. Il y a aussi le paramètre optionnel exclude-old-transactions. S’il s’agit de la valeur réelle, alors pour chaque abonnement auto-renouvelable, vous recevrez uniquement la dernière transaction en lieu et place de l’historique complet des renouvellements.
Voici la charge utile de la demande de validation d’achat :
{
"password": "f4d35830e3...52aae",
"receipt-data": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"exclude-old-transactions": false
}
Si vous travaillez dans un environnement Sandbox – c’est-à-dire que vous testez des achats -, envoyez des demandes de validation à https://sandbox.itunes.apple.com/verifyReceipt. Le secret partagé, ainsi que les formats de la charge utile et de la réponse restent les mêmes.
C’est pourquoi, dans les systèmes réels, la meilleure pratique consiste à diriger la première requête vers le serveur de Production et à la rediriger vers le serveur Sandbox au cas où la clé d’état renvoie le code d’erreur 21007. C’est pourquoi, dans les systèmes réels, la meilleure pratique consiste à diriger la première requête vers le serveur de Production et à la rediriger vers le serveur Sandbox au cas où la clé d’état renvoie le code d’erreur 21007. Ce comportement est indispensable lors de la révision de l’application, car il permet aux employés d’Apple de tester les achats tout en permettant aux utilisateurs réels de votre application de les effectuer.
Parmi les autres erreurs à mentionner, il y a le code d’erreur 21004 qui signifie que l’on utilise le mauvais secret. Il est important d’en assurer le suivi, car il a un impact à la fois sur l’expérience des utilisateurs et sur la précision des analyses. Dans le pire des cas, l’application peut être retirée de l’App Store si l’utilisateur n’a jamais accès aux fonctionnalités premium après avoir payé pour celles-ci.
Si la validation a réussi (statut=0), la réponse contiendra le détail des transactions de l’utilisateur.
Voici la réponse à la demande de validation du paiement :
{
"environment": "Production",
"receipt": {
"receipt_type": "Production",
"adam_id": 123,
"app_item_id": 123,
"bundle_id": "com.adapty.sample_app",
"application_version": "1",
"download_id": 123,
"version_external_identifier": 123,
"receipt_creation_date": "2021-04-28 19:42:01 Etc/GMT",
"receipt_creation_date_ms": "1619638921000",
"receipt_creation_date_pst": "2021-04-28 12:42:01 America/Los_Angeles",
"request_date": "2021-08-09 18:26:02 Etc/GMT",
"request_date_ms": "1628533562696",
"request_date_pst": "2021-08-09 11:26:02 America/Los_Angeles",
"original_purchase_date": "2017-04-09 21:18:41 Etc/GMT",
"original_purchase_date_ms": "1491772721000",
"original_purchase_date_pst": "2017-04-09 14:18:41 America/Los_Angeles",
"original_application_version": "1",
"in_app": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "1000000831360853",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1619638918000",
"purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-05-05 19:41:58 Etc/GMT",
"expires_date_ms": "1620243718000",
"expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000397200750",
"is_trial_period": "true",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED"
}
]
},
"latest_receipt_info": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001020690335",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-08-04 19:41:58 Etc/GMT",
"purchase_date_ms": "1628106118000",
"purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-11 19:41:58 Etc/GMT",
"expires_date_ms": "1628710918000",
"expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000438372383",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
},
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001017218955",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-07-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1627501318000",
"purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-04 19:41:58 Etc/GMT",
"expires_date_ms": "1628106118000",
"expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000849023623",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
],
"latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"pending_renewal_info": [
{
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853",
"auto_renew_status": "1"
}
],
"status": 0
}
La réponse est assez lourde et a été simplifiée dans la nouvelle version de App Store Server API mais l’implémentation actuelle n’est pas si difficile à maîtriser.
Pour savoir si l’utilisateur a accès aux fonctionnalités premium de l’application, vous devez pouvoir déterminer le statut de son abonnement. La version actuelle de l’API ne comporte pas de requête dédiée à la récupération de l’état de l’abonnement. Vous devrez donc travailler avec l’historique des transactions dans tous les cas.
Le tableau latest_receipt_info contient, par défaut, toutes les transactions d’achat intégrées à l’application d’un utilisateur particulier, à l’exception des produits consommables qui sont effectués du côté de l’application. De cette façon, vous pouvez retrouver l’historique complet des achats de l’utilisateur. C’est très utile à la fois pour l’analyse et pour déterminer l’état actuel de l’abonnement.
Il semble que les transactions arrivent toujours déjà triées en premier. Pour être sûr, cependant, je recommande toujours d’implémenter votre propre tri par date pour les transactions.
La charge utile de la transaction :
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "1000000831360853",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1619638918000",
"purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-05-05 19:41:58 Etc/GMT",
"expires_date_ms": "1620243718000",
"expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000397200750",
"is_trial_period": "true",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
Pour vérifier l’état actuel de l’abonnement, il suffit de récupérer la dernière transaction de la chaîne et de regarder la propriété expires_date. L’exception à cette règle est le délai de grâce, dont nous parlerons un peu plus tard.
À des fins d’analyse, je recommande de sauvegarder les propriétés suivantes :
Apple a annoncé lors de la WWDC 2021 qu’elle prévoyait d’ajouter un champ appAccountToken aux informations sur les transactions. Il contiendra l’identifiant de l’utilisateur dans votre système. Cet identifiant doit être au format UUID et est défini du côté de l’application lorsque l’achat est initialisé. S’il est défini, il sera renvoyé dans toutes les transactions de cette chaîne (renouvellements d’abonnement (subscription renewal), problèmes de facturation, etc.), ce qui signifie qu’il vous sera facile de comprendre quel utilisateur a effectué l’achat.
Vous devez également tenir compte du paramètre subscription_group_identifier. Si l’utilisateur a déjà effectué des transactions avec des essais actifs ou des offres de lancement (intro offers), il ne devrait pas y avoir accès pour le même groupe d’abonnements. Cela devrait être suivi du côté du serveur.
Le tableau pending_renewal_info stocke les données de renouvellement de l’abonnement. Il permet de comprendre ce qu’il adviendra de l’abonnement lors de la prochaine période de facturation. Par exemple, si vous avez découvert que l’utilisateur a refusé le renouvellement automatique, vous pouvez lui suggérer de passer à un autre plan ou lui présenter une offre promotionnelle. Ces événements (events) peuvent être facilement suivis grâce aux notifications du serveur (server notifications), dont je parlerai bientôt.
Données relatives au renouvellement de l’abonnement :
{
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853",
"auto_renew_status": "1"
}
Si l’utilisateur n’a pas de produits auto-renouvelables (auto-renewable products), les clés latest_receipt_info et pending_renewal_info ne seront pas retournées. Dans ce cas, les transactions peuvent être trouvées dans receipt → in_app. Le format de la transaction est similaire à celui des transactions auto-renouvelables mais ne comporte pas de champs pour l’expiration, les renouvellements, les offres et autres propriétés exclusives aux transactions auto-renouvelables.
Il convient de noter que receipt → in_app arrivera également pour les transactions auto-renouvelables, mais la meilleure pratique consiste à utiliser latest_receipt_info, car les données d’abonnement qu’il contient seront les plus à jour.
2024 subscription benchmarks and insights
Get your free copy of our latest subscription report to stay ahead in 2024.
Il y a quelque temps, vous deviez concevoir un système complexe pour suivre les changements d’état des abonnements. Par exemple, pour savoir si l’abonnement a été renouvelé ou non, il faudrait envoyer une demande d’état d’abonnement aux serveurs Apple toutes les heures à partir de 24 heures avant l’expiration de l’abonnement. Au fil du temps, Apple a ajouté de plus en plus de notifications de serveur, et celles ci couvrent désormais pratiquement tous les événements importants qui ont trait aux abonnements. C’est très utile : dès que des changements seront apportés du côté d’Apple, vous en serez informé sur votre propre serveur. En d’autres termes, vous serez informé des nouveaux achats, des renouvellements d’abonnement, des problèmes de facturation, etc. Cela vous permet de recueillir des données analytiques beaucoup plus précises et facilite la gestion de l’état de l’abonnement.
Vous pouvez activer les notifications du serveur dans App Store Connect. Ouvrez la page de l’application et allez à General -> App information. Ensuite, placez le lien dans le champ URL pour les notifications du serveur App Store et enregistrez vos modifications.
Notification du serveur :
{
"notification_type": "DID_RENEW",
"password": "f4d35830e3...52aae",
"environment": "PROD",
"auto_renew_product_id": "basic_subscription_1_month",
"auto_renew_status": "true",
"unified_receipt": {
"status": 0,
"environment": "Production",
"latest_receipt_info": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001020690335",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-08-04 19:41:58 Etc/GMT",
"purchase_date_ms": "1628106118000",
"purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-11 19:41:58 Etc/GMT",
"expires_date_ms": "1628710918000",
"expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000438372383",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
},
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001017218955",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-07-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1627501318000",
"purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-04 19:41:58 Etc/GMT",
"expires_date_ms": "1628106118000",
"expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000849023623",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
],
"latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"pending_renewal_info": [
{
"auto_renew_status": "1",
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853"
}
]
},
"bid": "com.adapty.sample_app",
"bvrs": "0"
}
Le format de notification du serveur est similaire à celui de la réponse de validation du paiement. Les détails de la transaction sont stockés dans unified_receipt → latest_receipt_info. La clé du mot de passe stocke le secret partagé pour votre application, ce qui vous permet de vérifier l’authenticité de la demande. La clé notification_type stocke le type d’événement. À mon avis, les plus utiles d’entre eux sont :
La validation du serveur peut donner un coup de fouet à l’analyse des données que vous recueillez dans votre application. Il est également plus difficile pour les fraudeurs d’accéder à votre contenu premium, ainsi que pour vous permettre de rendre vos abonnements multiplateformes. En même temps, la mise en œuvre de la validation du serveur peut prendre beaucoup de temps, surtout si vous avez besoin de données très précises. Il faudrait pour cela tenir compte de nombreux cas particuliers : mise à niveau d’un abonnement, déclassement d’un abonnement, périodes d’essai, offres promotionnelles/de lancement, délais de grâce, remboursements, abonnements familiaux, etc. Il faut également connaître et prendre en compte diverses nuances, par exemple la politique d’Apple consistant à réduire la commission de 30 % à 15 % pour les abonnements qui sont renouvelés régulièrement pendant plus d’un an.
Further reading
Tendances et perspectives
septembre 5, 2022