OnPurchaseDeferred seems to be mandatory and callback is called from a secondary thread

Good day,

I have noticed two weird behaviors on both Unity IAP 2.2.7 and the latest 3.0.2 release:

  1. Despite what the comments and the documentation states,
    IGooglePlayStoreExtensions.SetDeferredPurchaseListener MUST be set for Android as well. It is NOT an iOS only thing. In fact, the callback gets called on Android when e.g. a Slow response test card is used. If not set, when a deferred purchase is triggered, a Null Reference Exception crashes the app.

  2. The deferred purchase callback is called (at least on Android) on a thread that is NOT the main one, thus causing lots of issues if the UI or anything Unity related is called or modified there. Failure and Success callbacks are instead called on the main thread. I have a workaround for this but seems to me that Deferred purchase callback should be called on the main tread as well.

I think those are bugs/issues but let me know otherwise, thanks!

Please provide a specific minimal example. I have not seen the exceptions that you refer to with or without the deferred listener, steps to reproduce would be helpful. I typically test with the Sample IAP Project v2 (it has a deferred listener)

Well, you need to trigger a a deferred purchase on an Android device. This means that you must setup a lot of stuff on the Google Play Console side of things (the app, the test users, the IAP items…), but provided that this is ready or that you already have an app setup this way you need to build the app for an Android device, and perform a IAP. Make sure your account is added to the test accounts and you will be given the option to choose between 4 possible credit cards: choose the Slow Card Always Approves and instead of ProcessPurchase or OnPurchaseFailed the Deferred Purchase Listener will be triggered.

  1. if it was not set, the app will crash

  2. it if was set, such callback is called on a secondary thread, so if I touch anything Unity related from there I will crash the app too

The sample is pretty simple, but as you can see all the effort is in setting up the google play environment to actually test it - unless you already have an app ready to be tested…

I don’t think there is another way to actually trigger a deferred purchase - even if it was possible to do it from the Editor I doubt that the thread issue will show up there…

Sorry, this is not correct. There is nothing more you need to do on the Google side. For all of IAP regardless of using a deferred listener, you always have to create your products and application on Google, there is nothing new. To trigger a deferred purchase, naturally you must initiate an actual test purchase. I’m not seeing any crashes from a “second thread”, we are not looking into this at this time because we haven’t received steps to reproduce.

Hi,

sorry I probably wasn’t clear in explaining how to reproduce.

You just need to run Sample IAP Project v2 on an Android device, initiate a test purchase and make sure the purchase is deferred. How you do that? The only way I know is to use the Slow test card, always approves/denies. If there is any other way please let me know.

(Of course, as you mentioned, you must create your products and application on Google, and this is what I meant in my previous message.)

Now, attach the device and open Android logcat provided with Unity - notice the TID of any Unity script.

I see that the example provided with Unity IAP has a Log in the deferred listener. At least until Unity IAP 3.0.2 that log in the deferred callback was printed by a thread with a different TID than any other unity related logs. If instead of just printing the log I call any method on any Unity related class, the app crashes - I assumed because of the different TID.

Also, if the deferred listener callback was not set, when it gets called, the app crashes and in the logcat I see a null reference exception happening down in the native stack.

If this explanation is clear but you still don’t see it happening, I don’t really know what to do - this was pretty consistent on both my low end android devices (I am running still Android 8.0 on both).

Just wanted to make sure that my explanation was correct, thanks a lot for your time and help!

I will test a Slow Credit Card Succeeds without a deferred listener, and look for the exception. Is this a crashing bug, or are you just seeing the exception in the logs. Can you share an example of the code you are using on a that crashes? You mention “call any method on any Unity related class”, is this code in the deferred listener itself?

Not registering the callback seems to be a crashing bug, in the sense that the game freezes and I have to kill it.

The OnDeferred callback bug instead is not a crashing bug but since we block the UI and show a waiting screen when the user initiates a purchase, and we remove it when either the purchase succeeds, fails or gets deferred, this error prevents us from hiding the waiting screen and re-enabling the user input, thus basically forcing the user to restart the app.

Here’s a small sample:

private void InitializeStuff()
{

...

//
// Setup platform specific extensions - NOTE: if i don't set this, the game freezes in case of deferred purchase. 
//
m_GooglePlayStoreExtensions.SetDeferredPurchaseListener(OnPurchaseDeferred);

...

}

private void InitiatePurchase(string sProductId)
{
  m_kWaitingOverlay.gameObject.setActive(true);

 Debug.Log("Check the TID of this log!");

  m_kStoreController.InitiatePurchase(m_kStoreController.products.WithID(sProductId));
}

private void OnPurchaseDeferred(Product kProduct)
{
// This gets printed in logcat, on Unity IAP 3.0.2 this callback is called on a secondary thread (different TID). TID is different than the one printed in the InitiatePurchase()
Debug.LogFormat("[OnPurchaseDeferred] Purchase deferred : {0}", kProduct.definition.id);

// The following call is what causes the exception, I think
m_kWaitingOverlay.gameObject.setActive(false);

// Here is where I assume I see the exception log in logcat

// This DOES NOT get printed in logcat
Debug.Log("Test log");
}

I hope this is enough to help you figure out what we do in our game, let me know otherwise and thanks!

That is correct, you don’t want to block the UI. You’ll never receive a ProcessPurchase nor OnPurchasedFailed for deferred purchases. It is required by Google, there is no way to disable deferred purchases.

Hi!

So is there no way to disable the deferred purchases?
I saw in Google documentation that this deferred purchases are disabled as default and you have to enable it if you want to use it:
https://developer.android.com/google/play/billing/unity#deferred-purchases

Please see my previous statement. There is no way to turn it off, Google will not allow IAP to initialize. We already tested this.

Hi!

Please let me know what we should do inside the callback:

void OnPurchaseDeferred(Product product)
{
// Unlock the paid content or not? How we can know is it fail or success?
}

You take no action, you would let your user know that you’re received the purchase request if you want. Also, some studios put up a wait dialog waiting on ProcessPurchase or OnPurchaseFailed, this callback would let you know to dismiss such a dialog. When you relaunch the app, you might see ProcessPurchase triggered with purchaseState = 4 which means that you are still waiting for the user to pay. Once the purchaseState = 1, you return Complete and award the product. We plan to make this flag available as a property. In the meantime, this user has provided some good code https://forum.unity.com/threads/google-play-iap-problem.1140367/#post-7351220

1 Like

Thank you!

Hi @JeffDUnity3D ,

We are also seeing our Android deferred purchase handler not being called on the main thread.

googleConfigure.SetDeferredPurchaseListener(OnDeferredPurchase);

This causes a crash on device when doing anything Unity related in the callback.

This can of course be worked around, but it is messy. It is also not in keeping with the other part of Unity IAP which is all executed on the main thread.

Can this be addressed in a future release, please?

Thank you.

Can you elaborate how you know it’s not being called on the main thread, can you share your code that crashes so I can reproduce here? You might be right, but would speed things up. I’m also checking with the team here.

You can see an exception in LogCat:

“Unity UnityException: XXX can only be called from the main thread.”

In my case XXX was “get_frameCount” caused by usage of “Time.frameCount” in a debug log.

You should be able to repro this simply by accessing Time.frameCount inside the deferred purchase callback (but pretty much any Unity API call would do, GameObejct.SetActive() etc).

1 Like

What version of IAP are you using? Does this still happen with IAP 4.1.3?

We’re using 4.1.2.

I don’t see anything in the changelog of 4.1.3 that would change this behaviour: Unity IAP package 4.12.2 is now available page-2#post-7879486

1 Like

@nindim I did not see this behavior, it’s working correctly for me. I did a “Slow Credit Card, Always Approves” and got the deferred listener callback. I did not see any exception. I’m using the Sample IAP Project v2

builder.Configure<IGooglePlayConfiguration>().SetDeferredPurchaseListener(OnDeferredPurchase);

and

void OnDeferredPurchase(Product product)
    {
        Debug.Log($"Purchase of {product.definition.id} is deferred");
        btnGold.enabled = false;

    }

Hi @JeffDUnity3D ,

Did you try using Time.frameCount in the OnDeferredPurchase function?

The problem will only surface if you try and use Unity API that can only be used on the main thread.

Thanks.