Search Unity

Restore Transactions is not calling ProcessPurchase

Discussion in 'Unity IAP' started by alexandros356, Jun 3, 2019.

  1. alexandros356

    alexandros356

    Joined:
    Dec 3, 2012
    Posts:
    105
    Hi, I have an issue restoring my transactions. In the log the operation is done but the
    ProcessPurchase method isn't being called.

    The product it has to restore is a renewable susbcription. And it is not cancelled.

    Platform: Android
    IAP Version: 1.22.0
    Unity: 5.5.6f1


    UnityIAP: invoking callback
    06-03 10:45:10.650 14517 14687 I UnityIAP: Querying owned items, item type: inapp
    06-03 10:45:10.651 14517 14687 I UnityIAP: Package name: com.game.mysupergame
    06-03 10:45:10.651 14517 14687 I UnityIAP: Calling getPurchases with continuation token: null
    06-03 10:45:10.655 14517 14687 I UnityIAP: Owned items response: 0
    06-03 10:45:10.655 14517 14687 I UnityIAP: Continuation token: null
    06-03 10:45:10.655 14517 14687 I UnityIAP: Querying SKU details.
    06-03 10:45:10.655 14517 14687 I UnityIAP: queryPrices: nothing to do because there are no SKUs.
    06-03 10:45:10.655 14517 14687 I UnityIAP: Querying owned items, item type: subs
    06-03 10:45:10.655 14517 14687 I UnityIAP: Package name: com.game.mysupergame
    06-03 10:45:10.655 14517 14687 I UnityIAP: Calling getPurchases with continuation token: null
    06-03 10:45:10.666 14517 14687 I UnityIAP: Owned items response: 0
    06-03 10:45:10.666 14517 14687 I UnityIAP: Sku is owned: com.game.mysupergame.sevendays

    06-03 10:45:10.666 14517 14687 I UnityIAP: Continuation token: null
    06-03 10:45:10.667 14517 14687 I UnityIAP: Querying SKU details.
    06-03 10:45:10.679 14517 14687 I UnityIAP: Querying owned items' purchase history, item type: subs
    06-03 10:45:10.679 14517 14687 I UnityIAP: Package name: com.game.mysupergame
    06-03 10:45:10.679 14517 14687 I UnityIAP: Calling getPurchaseHistory with continuation token: null
    06-03 10:45:11.104 14517 14687 I UnityIAP: Purchase history response: 0
    06-03 10:45:11.104 14517 14687 I UnityIAP: Continuation token: null
    06-03 10:45:11.104 14517 14687 I UnityIAP: Querying owned items' purchase history, item type: inapp
    06-03 10:45:11.104 14517 14687 I UnityIAP: Package name: com.game.mysupergame
    06-03 10:45:11.104 14517 14687 I UnityIAP: Calling getPurchaseHistory with continuation token: null
    06-03 10:45:11.318 14517 14687 I UnityIAP: Purchase history response: 0
    06-03 10:45:11.319 14517 14687 I UnityIAP: Continuation token: null
    06-03 10:45:11.320 14517 14687 I UnityIAP: RestoreInventoryFinished: true
    06-03 10:45:11.320 14517 14687 I UnityIAP: Inventory refresh successful. (response: 0:OK)
     
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    How are you restoring? On Android, restore by default only happens during a reinstall.
     
  3. alexandros356

    alexandros356

    Joined:
    Dec 3, 2012
    Posts:
    105
    Hi, I'm calling this function

    m_StoreExtensionProvider.GetExtension<IGooglePlayStoreExtensions>().RestoreTransactions(result =>
    {
    if (result)
    {

    }
    else
    {
    }
    });
     
  4. alexandros356

    alexandros356

    Joined:
    Dec 3, 2012
    Posts:
    105
    My issue is when the user load a saved game from google cloud and lost the subscription
     
  5. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Can you please test the Restore with a new install of your app, to compare?
     
  6. alexandros356

    alexandros356

    Joined:
    Dec 3, 2012
    Posts:
    105
    If I reinstall the app the restore is automatic. When I open the app for the first time the method ProcessPurchase is called
     
  7. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Got it, so Restore works during reinstall, just not when calling the extension method. I will check here too.
     
  8. alexandros356

    alexandros356

    Joined:
    Dec 3, 2012
    Posts:
    105
    Thank you
     
  9. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    Has there been other reports of this issue? I am seeing the issue on iOS. And, from my understanding, iOS requires this functionality. I am using the Apple sandbox. Could that be the issue? Or, should IAP work, regardless.
    Unity3D: 2018.3.14f1
    IAP Package: 2.0.6
    IAP Asset: 1.22.0
     
    Last edited: Oct 21, 2019
  10. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    What issue are you describing? Apple requires a separate button and method for Restore. https://docs.unity3d.com/Manual/UnityIAPRestoringTransactions.html
     
  11. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    Yes, that is what I have. I have a button that calls mAppleStoreExtensions.RestoreTransactions( this.OnTransactionsRestored );
    As a note, mAppleStoreExtensions is IAppleExtensions.

    This is the output from Xcode and my implementation of ProcessPurchase is never called.
    =============
    InAppPurchaseManager.RestorePurchases started ...
    InAppPurchaseManager:RestorePurchases()
    UnityEngine.Events.UnityEvent:Invoke()
    UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)
    UnityEngine.EventSystems.StandaloneInputModule:processTouchPress(PointerEventData, Boolean, Boolean)
    UnityEngine.EventSystems.StandaloneInputModule:processTouchEvents()
    UnityEngine.EventSystems.StandaloneInputModule:process()

    (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)

    2019-10-22 14:45:51.326997-0500 myapp[3392:557241] UnityIAP: Restore transactions
    2019-10-22 14:45:51.327107-0500 myapp[3392:557241] UnityIAP: RestorePurchase
    2019-10-22 14:45:53.006952-0500 myapp[3392:557241] UnityIAP: UpdatedTransactions
    2019-10-22 14:45:53.060937-0500 myapp[3392:557241] UnityIAP: Finishing transaction 1000000582611804
    2019-10-22 14:45:53.061299-0500 myapp[3392:557241] UnityIAP: Finishing transaction 1000000582611805
    2019-10-22 14:45:53.061504-0500 myapp[3392:557241] UnityIAP: Finishing transaction 1000000582611806
    <snip ~ more of the same with different transaction numbers>
    2019-10-22 14:45:53.068076-0500 myapp[3392:557241] UnityIAP: Finishing transaction 1000000582611842
    2019-10-22 14:45:53.068264-0500 myapp[3392:557241] UnityIAP: Finishing transaction 1000000582611843
    2019-10-22 14:45:53.068445-0500 myapp[3392:557241] UnityIAP: Finishing transaction 1000000582611844
    2019-10-22 14:45:53.070903-0500 myapp[3392:557241] UnityIAP: PaymentQueueRestoreCompletedTransactionsFinished
    InAppPurchaseManager.OnTransactionsRestored Transactions restored.True
    InAppPurchaseManager:OnTransactionsRestored(Boolean)
    UnityEngine.Purchasing.Extension.UnityUtil:Update()
    =============
     
  12. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Got it. Yes, I would expect ProcessPurchase to be called in this scenario. You might try with IAP 1.23, we made some changes with respect to Restore.
     
  13. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    I upgraded and it acted the same.
    Curiously, when I try to purchase an item again, I receive all the events. Obviously this is not a workable solution for the end-user.
    Additionally, after I purchase the items, I also see "Unable to confirm purchase; Product has missing or empty transactionID" coming from Unity. After doing some digging, I found that when I call

    Product product = mStoreController.products.WithID( onlineStoreID );
    mStoreController.ConfirmPendingPurchase( product );

    during the "Restore", the transaction ID is not always part of the "product" instance. I am using the "Pending" workflow where I communicate to my own servers about transactions before I tell IAP that it is "Complete". How can I make a copy of the original transaction's "product" instance so that I can give IAP the proper data? The class Product cannot be created or copied by me (internal and private 'set's). Additionally, it is not serializable.
     
    Last edited: Oct 24, 2019
  14. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
  15. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    Thanks. The example app works as I expected. And, my code mirrors the implementation. And, therein lies the problem. The example app has one product to one transaction ratio between the Pending and Complete status. My code, however, reaches out to contact our server to register the sale and do some data work. During that time, I cannot block the end-user and the end-user may choose purchase another, so I loose one of the transactions to the ether. As does the sample app (if you press Consumable twice in Pending mode and then hit Complete, only one is completed). If I could retain a copy of the instance of the Product at time of ProcessPurchase, then I could work around this issue.
     
  16. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    You can get the productID info from the parameters into ProcessPurchase.
     
  17. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    Should I expect ProcessPurchase to be called after RestorePurchases is called while running in the editor? If so, it is not being called. If not, I will roll an iOS app and test it there.
     
  18. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    I can see all the data that I need, the problem is that IStoreController.ConfirmPendingPurchase requires an instance of UnityEngine.Purchasing.Product, which I cannot create, copy or serialize.
     
  19. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
  20. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    In ProcessPurchase, Product product = args.purchasedProduct;
     
  21. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    Thanks! I saw that. After I read it, I realized what was happening. I verified my theory by stepping through my code and watching IStoreController.ConfirmPendingPurchase. It was mentioned in the article to serialize the UnityEngine.Purchasing.Product, but unfortunately, UnityEngine.Purchasing.Product is not serializable. If it were, I could fix this quickly. The summary of what is happening can be seen in the test program you sent me.
    1) Start the app
    2) Toggle Complete to be off
    3) Press Consumable twice
    4) Press complete
    The upshot is that the first consumable transaction ID is lost. Yes, I could record the transaction ID and handle it later, but as I mentioned, I cannot create, copy or serialize the UnityEngine.Purchasing.Product in order to complete the purchase. Is there a call that I could make to retrieve a list of all the Products (fully fleshed out) that are Pending? I cannot find anything like that in the documentation.
     
  22. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Perhaps you missed my previous replies, I provided information that give you the product in ProcessPurchase from the passed argument.
     
  23. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    Yes, I saw your replies. Thanks so much! Yes, I can get the current information in ProcessPurchase. However, my issue is more subtle. At the end of ProcessPurchase, I return Pending and notify my server. However, before I hear back from my server, I may have another purchase come through for another of the same item. So, when I finally hear back from my server, I may have more than 1 purchase of the exact same item that needs to be completed. So, I need to get an instance of a Product from 2+ previous purchases that are full Product instances that have the appropriate transactionId.

    Here is the scenario:
    1) The user purchases com.myapp.50gold
    2) iOS approve, ProcessPurchase is called and I return Pending to IAP. Transaction ID: 10011
    3) I send com.myapp.50gold(10011) to my server and wait for verification
    4) Before I receive confirmation from my server, the user purchases another com.myapp.50gold
    5) iOS approves, ProcessPurchase is called and I return Pending to IAP. Transaction ID: 10022
    6) I send com.myapp.50gold(10022) to my server and wait for verification
    7) I receive confirmation from my server for com.myapp.50gold(10011).
    ===> At this point, the instance of the Product that I need to call IStoreController.ConfirmPendingPurchase is not available to me. Only transaction 10022 is available. How can I get 10011?
    8) I receive confirmation from my server for com.myapp.50gold(10022).
     
  24. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    You will receive ONE ProcessPurchase for EACH product that is restoring. You will always have the product information during Restore. If you need something else, you'll need to persist the Product similarly to the Sample project, perhaps an array of Product objects. But it is not something we would be able to specifically support.
     
  25. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    I would love to do that! However, the sample project does not persist any Products. It simply sets a reference to the same object that is controlled internally by IAP.

    // This sets a reference to the Product instance that is internal to IAP. Nothing is persisted.
    test_product = args.purchasedProduct

    // This uses the reference to the Product that is internal to IAP. When the data is updated in
    // IAP, so is the data pointed to by test_product (since it's the same data)
    m_StoreController.ConfirmPendingPurchase(test_product);

    Product instances cannot be copied, created or serialized by my code (therefore I cannot keep an array of them). If that were possible, then this would not be an issue.

    There are three ways to solve this and, quite unfortunately, none of these can be done by me.
    1. Allow a deep copy of an instance of a Product (so that I can call the existing ConfirmPendingPurchase) through:
      1. Serialization
      2. Copy constructor
      3. Duplicator
    2. Expose a method that gives me a list of Product instances of unconfirmed purchases so that I can call the existing ConfirmPendingPurchase. The instances would have to be fully realized (transactionsIDs, etc...) like they are in the happy-path through the code.
    3. Expand ConfirmPendingPurchase to allow the passing of a productID and transactionID to complete the transaction.
    I'm open to any other ideas. I really appreciate your time on this. I would like to submit this as an enhancement request. What is the best way for me to do that?
     
  26. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Yes, I actually do persist products. Well, just one, but I could easily add more. I use a static Product object. You would want to modify your code similarly.

    private static Product test_product = null;

    You can use the Unity Bug Reporter to submit a feature request. In the report, please elaborate as to why the above won't work for you. You are stating "cannot be copied, created or serialized by my code", please explain why. I copy the product in my code.
     
  27. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Why don't you just disable the Buy button until your server call returns?
     
  28. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    That is what I'll have to do. The issue is that if the user quits the app before the server responds, I have no way to complete the transaction.

    Actually, this is not creating a new instance. I proved this to myself by making the following changes to your code before my last post to verify that it was simply a reference pointer.

    * Add line 13:
    12: private static Product test_product = null;
    13: private static Product test_product2 = null;

    * Change line 146 (test_product1 = args.purchasedProduct) to:
    if ( test_product == null )
    {
    test_product = args.purchasedProduct;
    }
    else
    {
    test_product2 = args.purchasedProduct;
    }

    * Change line 81 and add line line 82 (MyDebug("Completed purchase ...") to
    81: MyDebug("Test transaction ID" + test_product1.transactionID.ToString());
    82: MyDebug("Test2 transaction ID" + test_product2.transactionID.ToString());

    * Run in the editor
    1. Toggle off "Complete" (so that Pending is sent)
    2. Hit "Subscription" twice (just twice, neither more nor less). You will see in the Game view that there are subscriptions with different transactionId's
    3. Hit "Complete"
    4. Look at the output. Both test_product and test_product2 will have the same output and it will match the second (most recent) transaction. This is because test_product and test_product2 are references pointing to the same data inside of IAP. You may need to switch to Scene view to see the bottom lines.
     
  29. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Please review my previous posts, just disable the button. Problem resolved, two concurrent purchases are not possible. Or if you quit the app with a product still in Pending, ProcessPurchase will be fired on next app launch and IAP intialization.
     
  30. Arctous

    Arctous

    Joined:
    Aug 25, 2014
    Posts:
    26
    This is new to our conversation. The documentation should be updated to include this information. The lock-out is what I'll have to do.

    I am not seeing ProcessPurchase appear at launch/initialization in your test app. Should your test app do this? Should I be seeing this behavior on my laptop? Or, do I need to move it to iOS and check to see if it works?
     
  31. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    There are no real transactions or receipts on the laptop when running in the Editor. Transactions only happen on a mobile device through either the App Store (iOS) or Google Play (Android). Disabling the button is common practice in UI design when you are waiting on a process. You don't want a user to continuously click on a button as they are often tempted to do. Only consumables can be purchased more than once. If the user has previously purchased a subscription or a non-consumable, the button should be permanently disabled, or hidden.
     
  32. game_unity849

    game_unity849

    Joined:
    Sep 27, 2021
    Posts:
    14
    Is there any other way to restore purchases in the Google Store other than reinstalling? We plan to initialize user information after the beta test, but we don't want to give users the hassle of reinstalling the app.