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

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:

    public void Initialize(
        Action<bool> handleAuthentication,
        PurchaseSucceededEvent purchaseSucceeded,
        PurchaseFailedEvent purchaseFailed,
        InitializationFailedEvent initializationFailed)
    {
        PurchaseSucceededCallback = purchaseSucceeded;
        PurchaseFailedCallback = purchaseFailed;
        InitializationFailedCallback = initializationFailed;

        if (Application.platform == RuntimePlatform.Android)
            TinyGPS.Initialize(); // Initialize google play games instance

        Social.localUser.Authenticate(handleAuthentication);

        // Initialize Purchasing/IAP
        {
            Assert.IsFalse(IsInitialized());

            // Create a builder, first passing in a suite of Unity provided stores.
            var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

            var consumables = new string[]
            {
                "consumable item ids..."
            };

            var nonConsumables = new string[]
            {
                "non-consumable item ids..."
            };

            foreach (var x in consumables)
                builder.AddProduct(x, ProductType.Consumable);

            foreach (var x in nonConsumables)
                builder.AddProduct(x, ProductType.NonConsumable);

            UnityPurchasing.Initialize(this, builder);
        }
    }

Here’s the BuyProduct:

public static void BuyProduct(string productId)
    {
        // If the stores throw an unexpected exception, use try..catch to protect my logic here.
        try
        {
            if (IsInitialized())
            {
                Product product = StoreController.products.WithID(productId);

                if (product != null && product.availableToPurchase)
                {
                    Debug.Log(string.Format("[Services] Purchasing product (asychronously): '{0}'", product.definition.id));
                    StoreController.InitiatePurchase(product);
                }
                else
                {
                    Debug.Log("[Services] BuyProduct: Failed purchasing. Product is either not found or not available for purchase");
                }
            }
            else
            {
                // Purchasing has not succeeded initializing yet. Consider waiting longer or retrying initiailization.
                Debug.Log("[Services] BuyProduct Failed. Purchasing not initialized.");
            }
        }
        catch (Exception e)
        {
            Debug.Log("[Services] Exception during purchase. " + e);
        }
    }

Finally, the ProcessPurchase function:

    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    {
        try {
            PurchaseSucceededCallback(args);
        }
        catch(Exception e) {
            Debug.Log("[Services] Error processing purchase: " + args.purchasedProduct + " Message: " + e.Message);
        }

        return PurchaseProcessingResult.Complete;
    }

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.

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…

2 Likes