How to know if a user canceled or refunded an IAP item?

I currently keep all purchases validation local.
Say, a user purchased IAP on android/iOS (I’m talking about non-consumable items only), I save a player-pref for that purchase so I know whether the user bought this item or not.

But I see a bug that when users ask for refunds from google, they get the money back but the process was not made through the game loop and I keep showing them the item even though they canceled it outside of the game environment.

I thought that there would be a way to use android and iOS API to know whether the user has this item active currently or not. But I couldn’t find those endpoints.

Even if I use a backend server (such as firebase) as a more appropriate way of validating purchases, how do I know if the purchase was canceled/refunded if it doesn’t go through the game loop? How do I then disable the item to that user?

Since I make games for android and iOS I would be happy to address both platforms.

Cancelled and refunded (voided) purchases are removed from Unity IAP’s product.hasReceipt property - it then returns false. It might get cached for a while (up to 3 days) though. If you have a limited amount of IAP items, you can unlock your game content by checking that property directly, instead of writing to PlayerPref.

Otherwise, Google has a Voided Purchases API, but that is more complex to setup and in addition to a backend server, requires user authentication too.

1 Like

Thanks for your fast response! The Unity IAP’s product.hasReceipt property sounds perfect for me.

But both on Android and iOS it throughs a null exception.
Here’s the code I used:

            Product cproduct = m_StoreController.products.WithID(productID);
            if (cproduct != null && cproduct.hasReceipt)
            {
                Debug.Log("Bought: " + productID);
            }
            else
            {
                Debug.Log("Didn't buy: " + productID);
            }

In the Unity editor, it doesn’t through any errors, but on mobile devices, it does through a null exception error.
Could it be that m_StoreController or m_StoreController.products aren’t initialized on time?

Yes, you need to initialize IAP first. Where is the null reference exception? Are you able to purchase that productID?

Yes, the IAP works great on both Android and iOS for that product id.
m_StoreController is null, but now I see that if I wait long enough it works!

Now I have 2 questions:

  1. When would be the ideal time (function wise) to check about the purchased products so m_StoreController won’t be null?

  2. If that property can replace the player prefs, that means that there must be internet access when using the app. Does it cache locally the “hasPurchased” property for 3 days every time there’s an Internet connection?

You would check after OnInitialized is called. Or in that method. IAP won’t initialize without the internet.

1 Like

I have an issue about IAP in my game. whenever user click on IAP product and then cancel it not purchasing successful then an exception throws on this following method :
UnityPurchaser.OnPurchaseFailed() and its a
NullReferenceException · Object reference not set to an instance of an object.

Hello,

Would you be able to provide us with a call stack for your Null Reference Exception so we can help you better?

8602470--1154175--upload_2022-11-21_18-5-13.png8602470--1154175--upload_2022-11-21_18-5-13.png

This seems to be the product being null. You will want to null check this on your script handling the OnPurchaseFailed.

The only case where we explicitly return a null product is when Purchasing was not initialized correctly since in that case, we are unable to match it to an existing product.

Product is not null and i have checked it. when user cancel inapp purchasing process then this happened.

Does your IAP Listener have a script attached to the On Purchase Failed?

Also, is UnityPurchaser part of your own code? If so, would you be able to provide that code and the GVIAPListener.purchaseFailed so I can help you better?

yes i have attached both files

8608011–1155174–UnityPurchaser.cs (7.49 KB)
8608011–1155177–GVIAPListener.cs (1.52 KB)

Based on the callstack and your files, I believe it’s the purchaseFail in GVIAPListener that’s null.

Could you double check the purchaseFail event?

yes i have checked there is no nothing to be null but i have attached file that have purchaseFail callback

8610390–1155657–GVIAPButton.cs (3.31 KB)

From what I can see, it might be that purchaseFail is called twice, the first call will deregister the callback, and the second call will attempt to call null.

You could add a trace in purchaseFailed to see how often it’s being called to confirm this.

Could it be that you have both Coded and Codeless IAP at the same time?
In the IAP Catalog, make sure “Automatically initialize UnityPurchasing (recommended)” is turned off to disable Codeless.

If this doesn’t work, could you try to only do the registerCallbacks once and never deregisterCallbacks to see if that works?

1 Like

product.hasReceipt can be used for polling the receipts at various MonoBehaviour lifecycle points, right? But how can the app be notified that someone has cancelled or refunded a transaction (in my case, nonconsumable), in the event that the refund completes while the app is already running? we can register a callback for?