Search Unity

[Solved] [Unity Purchasing] Getting back multiple callbacks with a single purchse call?

Discussion in 'Unity IAP' started by vexe, Apr 26, 2016.

Thread Status:
Not open for further replies.
  1. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Greetings,

    We just noticed a serious bug in our Android build where users would tap on a store item once, pay for it, but then everytime they tap on it they get it for free!

    When players tap the item button I make a call to 'BuyProduct' with the product id, which calls in to 'InitiatePurchase' - And then in my 'ProcessPurchase' I notify the game the purchase has succeeded to award the player the item and return 'PurchaseProcessingResult.Complete'. I assume there's something I need to do inside 'ProcessPurchase' before letting the game know the purchase was successful.

    After the first time the user purchases an item, if they tap on it, it would just reward it to them without prompting them to pay for it. It's as if the item is not getting consumed or something. Even though I explicitly mark all consumable items we have with 'ProductType.Consumable' in 'builder.AddProduct' AND the products are marked with 'Managed' on the Google side.

    NOTE: This is not happening on our iOS build, same exact code runs on both.

    Here's the initialization code:

    Code (CSharp):
    1.     public void Initialize(
    2.         Action<bool> handleAuthentication,
    3.         PurchaseSucceededEvent purchaseSucceeded,
    4.         PurchaseFailedEvent purchaseFailed,
    5.         InitializationFailedEvent initializationFailed)
    6.     {
    7.         PurchaseSucceededCallback = purchaseSucceeded;
    8.         PurchaseFailedCallback = purchaseFailed;
    9.         InitializationFailedCallback = initializationFailed;
    10.  
    11.         if (Application.platform == RuntimePlatform.Android)
    12.             TinyGPS.Initialize(); // Initialize google play games instance
    13.  
    14.         Social.localUser.Authenticate(handleAuthentication);
    15.  
    16.         // Initialize Purchasing/IAP
    17.         {
    18.             Assert.IsFalse(IsInitialized());
    19.  
    20.             // Create a builder, first passing in a suite of Unity provided stores.
    21.             var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    22.  
    23.             var consumables = new string[]
    24.             {
    25.                 "consumable item ids..."
    26.             };
    27.  
    28.             var nonConsumables = new string[]
    29.             {
    30.                 "non-consumable item ids..."
    31.             };
    32.  
    33.             foreach (var x in consumables)
    34.                 builder.AddProduct(x, ProductType.Consumable);
    35.  
    36.             foreach (var x in nonConsumables)
    37.                 builder.AddProduct(x, ProductType.NonConsumable);
    38.  
    39.             UnityPurchasing.Initialize(this, builder);
    40.         }
    41.     }
    Here's the BuyProduct:

    Code (CSharp):
    1. public static void BuyProduct(string productId)
    2.     {
    3.         // If the stores throw an unexpected exception, use try..catch to protect my logic here.
    4.         try
    5.         {
    6.             if (IsInitialized())
    7.             {
    8.                 Product product = StoreController.products.WithID(productId);
    9.  
    10.                 if (product != null && product.availableToPurchase)
    11.                 {
    12.                     Debug.Log(string.Format("[Services] Purchasing product (asychronously): '{0}'", product.definition.id));
    13.                     StoreController.InitiatePurchase(product);
    14.                 }
    15.                 else
    16.                 {
    17.                     Debug.Log("[Services] BuyProduct: Failed purchasing. Product is either not found or not available for purchase");
    18.                 }
    19.             }
    20.             else
    21.             {
    22.                 // Purchasing has not succeeded initializing yet. Consider waiting longer or retrying initiailization.
    23.                 Debug.Log("[Services] BuyProduct Failed. Purchasing not initialized.");
    24.             }
    25.         }
    26.         catch (Exception e)
    27.         {
    28.             Debug.Log("[Services] Exception during purchase. " + e);
    29.         }
    30.     }
    Finally, the ProcessPurchase function:

    Code (CSharp):
    1.     public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    2.     {
    3.         try {
    4.             PurchaseSucceededCallback(args);
    5.         }
    6.         catch(Exception e) {
    7.             Debug.Log("[Services] Error processing purchase: " + args.purchasedProduct + " Message: " + e.Message);
    8.         }
    9.  
    10.         return PurchaseProcessingResult.Complete;
    11.     }

    The 'PurchaseSucceededCallback(args)' is what calls into the game so that it awards the item to the player. This what's getting called everytime they tap on the item after the first time they buy it...

    The other 'PurchaseProcessingResult' besides 'Complete' is 'Pending', which I'm not sure how useful or relevant that is.

    Any ideas, thoughts or solutions are appreciated!

    Thanks.
     
  2. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    We caught the bug.

    The issue was that in our game's purchase succeed handler, we log to a database that the user made a purchase. We get the player's id from 'Social.localUser.id' - which on Android is implemented via PlayGamesPlatform. And that is nulling out throwing an exception which causes the ProcessPurchase function to _never_ return 'Complete' (There was no try/catch in 'ProcessPurchase')

    I was never able to get a valid user id from PlayGamesPlatform, I have to contact the author of Unity's GooglePlayGames and see what's up...
     
    mpinol and erika_d like this.
Thread Status:
Not open for further replies.