Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

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

Discussion in 'Unity IAP' started by badseed-gtino, Apr 20, 2021.

  1. badseed-gtino

    badseed-gtino

    Joined:
    Jan 18, 2019
    Posts:
    6
    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!
     
  2. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    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)
     
  3. badseed-gtino

    badseed-gtino

    Joined:
    Jan 18, 2019
    Posts:
    6
    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...
     
  4. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    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.
     
  5. badseed-gtino

    badseed-gtino

    Joined:
    Jan 18, 2019
    Posts:
    6
    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!
     
  6. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    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?
     
  7. badseed-gtino

    badseed-gtino

    Joined:
    Jan 18, 2019
    Posts:
    6
    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:

    Code (CSharp):
    1. private void InitializeStuff()
    2. {
    3.  
    4. ...
    5.  
    6. //
    7. // Setup platform specific extensions - NOTE: if i don't set this, the game freezes in case of deferred purchase.
    8. //
    9. m_GooglePlayStoreExtensions.SetDeferredPurchaseListener(OnPurchaseDeferred);
    10.  
    11. ...
    12.  
    13. }
    14.  
    15. private void InitiatePurchase(string sProductId)
    16. {
    17.   m_kWaitingOverlay.gameObject.setActive(true);
    18.  
    19.  Debug.Log("Check the TID of this log!");
    20.  
    21.   m_kStoreController.InitiatePurchase(m_kStoreController.products.WithID(sProductId));
    22. }
    23.  
    24. private void OnPurchaseDeferred(Product kProduct)
    25. {
    26. // 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()
    27. Debug.LogFormat("[OnPurchaseDeferred] Purchase deferred : {0}", kProduct.definition.id);
    28.  
    29. // The following call is what causes the exception, I think
    30. m_kWaitingOverlay.gameObject.setActive(false);
    31.  
    32. // Here is where I assume I see the exception log in logcat
    33.  
    34. // This DOES NOT get printed in logcat
    35. Debug.Log("Test log");
    36. }
    I hope this is enough to help you figure out what we do in our game, let me know otherwise and thanks!
     
  8. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    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.
     
  9. alexm_scp

    alexm_scp

    Joined:
    Nov 5, 2019
    Posts:
    24
    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
     
  10. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    Please see my previous statement. There is no way to turn it off, Google will not allow IAP to initialize. We already tested this.
     
  11. Riddik

    Riddik

    Joined:
    Jun 16, 2017
    Posts:
    20
    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?
    }
     
  12. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    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
     
    Riddik likes this.
  13. Riddik

    Riddik

    Joined:
    Jun 16, 2017
    Posts:
    20
    Thank you!
     
  14. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    Hi @JeffDUnity3D,

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

    Code (CSharp):
    1. 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.
     
  15. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    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.
     
  16. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    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).
     
    JeffDUnity3D likes this.
  17. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    What version of IAP are you using? Does this still happen with IAP 4.1.3?
     
  18. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    JeffDUnity3D likes this.
  19. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    @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

    Code (CSharp):
    1. builder.Configure<IGooglePlayConfiguration>().SetDeferredPurchaseListener(OnDeferredPurchase);
    and
    Code (CSharp):
    1. void OnDeferredPurchase(Product product)
    2.     {
    3.         Debug.Log($"Purchase of {product.definition.id} is deferred");
    4.         btnGold.enabled = false;
    5.  
    6.     }
     
  20. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    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.
     
    Last edited: Feb 11, 2022
  21. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    Please show your code where you are hooking up the callback. The syntax provided earlier in this thread is incorrect, please see my syntax above. I did as you mentioned, I set an object enabled to false. Does this work for you?
     
  22. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    The syntax is correct, I cached the Google extensions to a variable.

    I don't have the problem code anymore as I have refactored and added complexity to ensure it runs on the main thread.

    Please try accessing Time.frameCount in your callback as I advised in my initial report of the issue.
     
    Last edited: Feb 11, 2022
  23. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    I just hit it again:

    upload_2022-2-11_18-56-19.png

    upload_2022-2-11_18-58-29.png

    Line 317 of IAPManager is:

    upload_2022-2-11_18-57-0.png


    The Dbg class uses Time.frameCount:

    upload_2022-2-11_18-57-47.png
     
  24. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    @nindim I will check! Unfortunately I don't have my test device with me now, are you able to test with the code I posted? I will check Time.framecount later today.
     
  25. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    @nindim We believe we have identified the issue, and should be included in a future release. I wasn't able to reproduce myself however, logcat doesn't capture any exceptions. My output shows:

    Purchase of gold50 is deferred: framecount = 558

    with the code:
    Code (CSharp):
    1. void OnDeferredPurchase(Product product)
    2.     {
    3.         MyDebug($"Purchase of {product.definition.id} is deferred: framecount = " + Time.frameCount.ToString());
    4.         btnGold.enabled = false;
    5.  
    6.     }
     
  26. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    It's weird that you weren't able to reproduce... Maybe a Project Setting or device specific issue? I was testing on a OnePlus 6T with the mono backend in Unity 2019.4.31.
     
  27. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    @nindim So you know, iOS 11 and above does not support Mono. So if you're only publishing to Android, you should be OK. I'm using IL2CPP and Unity 2020.3 and is likely why we are seeing a difference.
     
  28. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    Hey @JeffDUnity3D ,

    We use IL2CPP for all our Production builds, but nothing comes close to the speed of a local Mono Android build for on device iteration and testing :)

    Due to Unity Ads we have been forced to use IL2CPP for a while now for all our iOS builds so nothing will need to change there with iOS 11, thanks for the heads up though!