ça fait un bail que je n'ai pas posté de petits tutos ici et voilà que la lutte quotidienne m'a fait traverser l'utilisation du module IAP(In-App Purchase).
Kézako? C'est tout simplement le service Unity qui permet d'avoir des éléments payants (pour de vrai) dans votre application.
Ca peut servir à trois choses différentes :
- les éléments Consummable, soit des éléments qu'on peut acheter autant de fois que l'on souhaite (ex : des balles pour une arme)
- les éléments Non Consummable, soit des éléments qu'on achètera qu'une seule fois (ex : un super gun de ouf)
- les éléments Subscription, soit un abonnement renouvelable tous les X temps.
En ce qui me concerne je devais gérer un élément non consommable pour que mon application est un statut freemium et premium.
Juste que là tout va bien et il y a de la doc sur Unity pour vous aider, mais j'avoue que j'ai été un peu pommée au départ. Parce qu'on manipule du vrai argent de la vraie vie, on a pas envie d'avoir un truc qui est complètement foireux et qui marche pas.
Il existe des outils pour faire de l'achat in App sans coder la moindre chose. J'ai essayé et j'ai pas aimé. On est plus limité, on peut pas par exemple montrer des popup dans l'appli ou autre bref, c'est sûrement bien quand on code pas mais en tant que dev, ça me gratait plus l'arrière fond du crâne qu'autre chose.
Les indispensables avant de commencer :
https://unity3d.com/fr/learn/tutorials/ ... -your-game => le tuto de base pour un peu piger de quoi ça s'agit ainsi que le package de début : mettre les services IAP et Analytics en route notamment et le fonctionnement de base.
C'est bien MAIS s'tun peu l'bazar! Vous vous prenez un code dans la tronche avec des fonctions dans tous les sens et zetes pommés de la vie. Du coup voici un petit décortiquage du code proposé, qui est une très bonne base hein
![Dan.San ;)](./images/smilies/6.gif)
Code : Tout sélectionner
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
//faites ce que vous voulez, si vous n'avez pas de namescpae bazarder la ligne et les accolades correspondantes.
namespace CompleteProject
{
//voici LA classe qui va permettre de gérer nos petits achats pépère
public class Purchaser : MonoBehaviour, IStoreListener
{
private static IStoreController m_StoreController; // The Unity Purchasing system.
private static IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.
//en anglais on a tout une explication longue et relou
//et d'où c'est static ça HEIN!!! Hérétique!
//toussa pour dire que en gros il faut faire une diff entre les ID de ses produits (sans dec?)
//et et que eux s'amuse à rajouter quel type de produit on ajoute.
//en gros on explique c'est bien de faire en sorte que son produit soit identifier suivant le store
//en ce qui me concerne je n'ai pour l'instant testé qu'avec Android et en suivant un autre tuto j'ai l'impression qu'on s'en cogne
//c'est tout bêtement par particité pour suivre l'analyse des données je pense.
public static string kProductIDConsumable = "consumable";
public static string kProductIDNonConsumable = "nonconsumable";
public static string kProductIDSubscription = "subscription";
private static string kProductNameAppleSubscription = "com.unity3d.subscription.new";
private static string kProductNameGooglePlaySubscription = "com.unity3d.subscription.original";
void Start()
{
//pour faire de l'achat de produit il faut en gros créer une sorte de store virtuel qui contiendra ce que le vrai store contient
//On initialise donc cette "copie" de store.
if (m_StoreController == null)
{
InitializePurchasing();
}
}
public void InitializePurchasing()
{
if (IsInitialized())
{
return;
}
//On créé notre store virtuel et on va le remplir en prenant soin d'un truc giga important, le nom de l'identifiant
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
//On ajout un produit consommable, non consommable et un abonnement
//l'id de chaque produit est UNIQUE et doit correspondre à ce que vous ajouterai dans le store
builder.AddProduct(kProductIDConsumable, ProductType.Consumable);
builder.AddProduct(kProductIDNonConsumable, ProductType.NonConsumable);
//une autre façon d'écrire le AddProduct. Ici on spécifie des noms différents pour les stores
//au dessus on le fait pas et j'avoue avoir opter pour la solution du dessus.
builder.AddProduct(kProductIDSubscription, ProductType.Subscription, new IDs(){
{ kProductNameAppleSubscription, AppleAppStore.Name },
{ kProductNameGooglePlaySubscription, GooglePlay.Name },
});
//une fois le store rempli on va faire définitivement créer notre store virtuel, copie du réel
UnityPurchasing.Initialize(this, builder);
}
//la fonction qu'on appelle tout le temps avant de faire quoi que ce soit comme achat ou autre
private bool IsInitialized()
{
return m_StoreController != null && m_StoreExtensionProvider != null;
}
//achat d'un élément consomable
//en gros c'est la fonction que vous appelerez en cliquand sur votre joli bouton d'UI.
//que ce soit du consommable, non-consommable ou un abo on s'en fiche en vrai
//l'important c'est BuyProductID(l'id unique de votre produit)
public void BuyConsumable()
{
BuyProductID(kProductIDConsumable);
}
public void BuyNonConsumable()
{
BuyProductID(kProductIDNonConsumable);
}
//petite subtilité ici : on donne l'id générique, pas celui specifique attaché Google ou Apple ;)
public void BuySubscription()
{
BuyProductID(kProductIDSubscription);
}
void BuyProductID(string productId)
{
if (IsInitialized())
{
Product product = m_StoreController.products.WithID(productId);
// cherchons le produit correspondant à l'identifiant et achetons le
if (product != null && product.availableToPurchase)
{
Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));
//on lance l'achat qui va ensuite répondre Oui j'ai réussi ou Non j'ai pas réussi.
m_StoreController.InitiatePurchase(product);
}
// Si on c'est planté on se prend un débug dans la tête. On pourrait mettre une belle popup avec
// une insulte en rouge pour dire au dev que c'est un gros c*n
else
{
Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
}
}
//Si le store n'est pas initialisé, on se mange encore une info.
else
{
Debug.Log("BuyProductID FAIL. Not initialized.");
}
}
// Si vous êtes masochiste et que votre application doit se trouver sur Apple
//cette petite fonction est nécessaire. Alors que les autres stores vont cherché tout seul comme des grands à regarder
// si oui ou zut vous avez acheté un abonnement ou un non consommable (ben oui pour le consommable on s'en fiche un peu
// les conséquences d'achat d'un consommable sont visibles dans le jeu, vos paramètres toussa, donc c'est sauvé autrement)
// Apple ne sait pas faire. Ca signifie que si vous désinstaller l'application, Apple ne va pas savoir ce que vous avez acheter si vous
//n'en faites pas la demande.... Il faut donc appeler cette fonction pour récupérer son dû!
public void RestorePurchases()
{
if (!IsInitialized())
{
Debug.Log("RestorePurchases FAIL. Not initialized.");
return;
}
// On vérifie la plateforme parce que sur autre chose que iOS on s'en fiche
if (Application.platform == RuntimePlatform.IPhonePlayer ||
Application.platform == RuntimePlatform.OSXPlayer)
{
Debug.Log("RestorePurchases started ...");
//on récup nos achats
var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
apple.RestoreTransactions((result) => {
// On va recevoir les infos sur ce qu'il faut récupérer ou non. La fonction ProcessPurchase sera appelé si on a acheté des trucs. Je n'ai cependant pas encore testé les effets de cette fonction de récup.
Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
});
}
else
{
Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
}
}
//
// --- IStoreListener
// Ci dessous la mécanique d'achat réel avec l'achat réussi/rejeté et les messages qui vont avec.
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
// Purchasing has succeeded initializing. Collect our Purchasing references.
Debug.Log("OnInitialized: PASS");
// Overall Purchasing system, configured with products for this application.
m_StoreController = controller;
// Store specific subsystem, for accessing device-specific store features.
m_StoreExtensionProvider = extensions;
}
public void OnInitializeFailed(InitializationFailureReason error)
{
// Purchasing set-up has not succeeded. Check error for reason. Consider sharing this reason with the user.
Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
}
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
//en fonction de ce que l'on vient d'acheter on fait les différentes actions nécessaires sur l'UI ou autre.
//si on est ici, c'est que l'achat à réussi.
if (String.Equals(args.purchasedProduct.definition.id, kProductIDConsumable, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
//Ajouter des sous
ScoreManager.score += 100;
}
else if (String.Equals(args.purchasedProduct.definition.id, kProductIDNonConsumable, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
//ajouter une super arme de fifou malade à sa collection
}
else if (String.Equals(args.purchasedProduct.definition.id, kProductIDSubscription, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
//avoir un mois d'abonnement à Kikou magazine
}
// Si ce n'est ni consommage, ni non-consommable, ni un abo, ce produit ne devrait même pas exister et on se fait jeter :)
else
{
Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
}
//On renvoit ensuite une info importante ici qui dit si on a bien traité notre achat ou si on doit s'en souvenir au prochain
//lancement. Si jamais on arrive pas à sauver dans le Cloud l'info qu'on a acheté le produit, on peut renvoyer
Use PurchaseProcessingResult.Pending et il faudra le traiter au prochain lancement. Je n'ai aps encore eu l'occas de tester
//mais pour quelqu'un qui n'est pas connecté à internet ça doit servir :)
return PurchaseProcessingResult.Complete;
}
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
}
}
}
Cool et maintenant??
Maintenant il faut configurer votre store pour tester.
Google : https://docs.unity3d.com/Manual/UnityIA ... ation.html
Apple : https://docs.unity3d.com/Manual/UnityIA ... ation.html
![Dan.San :!:](./images/smilies/40.gif)
![Dan.San :!:](./images/smilies/40.gif)
Une fois ceci fait, vous allez pouvoir tester sur Google par exemple (le plus rapide) vos achats. Suivez bien la doc proposée en lien, ça donne TOUT. Petit hic, l'interface Google à changer depuis mais on s'en sort.
Infos importantes tout de même qui m'ont vallu une perte de temps énorme :
Vous avez publié votre application et à présent vous ajouter de l'IAP, vous souhaitez donc tester votre script. Que faire? Faire une version de test Interne et créé un liste de testeurs.
![Dan.San :!:](./images/smilies/40.gif)
![Dan.San :!:](./images/smilies/40.gif)
EDIT :
Et sur iOS ça se passe comment?
Alors j'ai pu porter mon code sur iOS et ça se passe plutôt très bien en fait (
![Dan.San :|](./images/smilies/20.gif)
![Dan.San :|](./images/smilies/20.gif)
![Dan.San :/](./images/smilies/23.gif)
Quand on a un serveur avec des données associés à un compte ça va, on peut stocker plein de choses, dont ce qu'on a acheté, du coup pas de problème de Restore en ce cas. Mais dans le cas contraire c'est pénible.
Après pour tester il y a la procédure dans les liens suivants (un testeur Sandbox et le tour est joué, c'est même pour une fois plus rapide et plus simple que Google
![Dan.San :o](./images/smilies/16.gif)
Du coup j'ai mon bouzin qui fonctionne en tests de partout et c'est cool. Reste à voir si je peux pas forcer le Restore à chaque fois que l'initialisation du store soit faite.
Bref encore un peu d'effort mais le gros de l'explication est entre vos mains. Au plaisir si ça peut servir.
Les liens pour pas crever et qui m'ont personnellement sauver la mise
![Dan.San :)](./images/smilies/1.gif)
https://answers.unity.com/questions/151 ... se-no.html
https://docs.unity3d.com/Manual/UnityIA ... ation.html
https://docs.unity3d.com/Manual/UnityIA ... ation.html