Search Unity

IAP restore issues (Android)

Discussion in 'Unity IAP' started by DanWeston, Mar 31, 2019.

  1. Livealot

    Livealot

    Joined:
    Sep 2, 2013
    Posts:
    109
    Aha! I did not know there was an update post 1.23. Thanks for sharing that tidbit. We'll take the update and report back, although it sounds like others who have the update are still seeing the same issue.

    Confirming other details:
    1. Test accounts are opted in on each of the test devices
    2. Test devices are downloading via Google Play with a build from the Internal Test Track
    Sorry if this forks the thread, but the release notes for 1.23.1 are not very clear. It sounds like a fix was added, a previous fix was removed, and there's some sort of recommendation to devs. Is there something we're supposed to do to handle the restore scenarios now?
     
  2. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    No, it should work as expected with 1.23.1 per your steps.
     
  3. haiduongbk

    haiduongbk

    Joined:
    Oct 5, 2015
    Posts:
    4
    newest version can not fix this issue. i tried with check more from code but it can not pass Receipts. any other way to check items owned?
     
  4. bhushan_rathod

    bhushan_rathod

    Joined:
    Jun 17, 2019
    Posts:
    4
    hi,
    I am using latest version of unity IAP 1.23.1
    I tested following cases using test 2 registered test id and 1 non-registered id on 3 devices, i have 3 non-consumable product

    1. buy 2 product on device 1 and 2 with id A, and 1 on device 3 with same id
    2. buy 2 diff product on device 1 and 2 with id B, and 3 on device 3 with same id
    3. you cant buy with C id because it is not included in testing list
    4. A on 1,2 - show receipt for both product and on 3rd device show only one receipt only
    5. B also show result similar to point 4 and on device 3 show receipt for all 3 product
    6. while id C is not included in testing it dont show receipt
    7. after clearing play store cache data(clear data) and game cache data on device 1, all 3 id dont show receipt
    8. while on device 2 and 3 result are as it is.

    is there a way to fetch receipt from play store, are receipt saved/stored to play store data locally & which is stable version of IAP to use in android

    test cases checked on basis of this post
    https://forum.unity.com/threads/product-hasreceipt-and-product-transactionid-are-per-device.590107/
     
    Last edited: Dec 30, 2019
  5. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    The receipts will be retrieved during Initialization. I'm not sure what you mean by non-registered id on a phone, do you mean an unlocked Android device with no user associated with the phone profile? How are you changing users? Keep in mind we are talking about the phone profile user, not an email account on the phone. Ensure that the user is logged into Google Play before testing, and they have browsed to the Google Play test URL using a browser on the device to enable the device for testing. And then install via Google Play (not direct via USB). Please confirm that ProcessPurchase is being properly called during Restore.
     
  6. bhushan_rathod

    bhushan_rathod

    Joined:
    Jun 17, 2019
    Posts:
    4

    Non-registered id means mail id not included in testing list, I am using 2019.1.6f1 of unity I also tried 2018.4.14f1 LTS.
    I am using Google Play test URL and after installing base file I side load new app on device, I followed step mention in documentation
    https://docs.unity3d.com/Manual/UnityIAPGoogleConfiguration.html
    but if you clean google play store cache,
    uninstall game app,
    then refollow steps u will notice that there is no receipt attached to product

    https://drive.google.com/open?id=1jhCcMD3tG47qkirp7T3ELs0J6MfNNtgO
    https://drive.google.com/open?id=1axqQfa395OyXXv1leLuuHL44-GUgJZxg

    clear play store cache

    https://drive.google.com/open?id=1RXhyCej3LsXPmlrt906GmpSpWLsk5-3X
    https://drive.google.com/open?id=1llEzCJup6XJipdw8Cip931hSDhvTkeR-

    issue is not only that if you login in fresh device and follow step it will return null receipt.
    and I did not find exact how google in-app purchase works.
    according to me it is not fetching receipt from server
     
  7. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Please don't use external links. Please show the code that you are using to add the product, I'm specifically looking for the product type. Is this a Google subscription? Please share a screenshot of the product on your Google dashboard https://play.google.com/apps/publish
     
  8. haiduongbk

    haiduongbk

    Joined:
    Oct 5, 2015
    Posts:
    4
    Hey @JeffDUnity3D. i hope next version it will be fixed.
    Some information i hope it is good to help unityIAP team fix it in next version.

    This is my code:
    Code (CSharp):
    1. var m_GooglePlayExtensions = m_StoreExtensionProvider.GetExtension<IGooglePlayStoreExtensions>();
    2.             Dictionary<string, string> Dict = m_GooglePlayExtensions.GetProductJSONDictionary();
    3.             bool canRemoveAds = false;
    4.             int i = m_StoreController.products.all.Length;
    5.             Debug.Log("HDBK|-"+i);
    6.             foreach (Product item in m_StoreController.products.all)
    7.             {
    8.                 i--;
    9.                 Debug.Log(item.hasReceipt + "|1|HDBK");
    10.                 if (item.receipt!=null)
    11.                 {
    12.                     Debug.Log(item.definition.storeSpecificId + "|2|HDBK");
    13.                     if (item.definition.type == ProductType.Subscription)
    14.                     {
    15.                         //print("Subscription");
    16.                         Debug.Log(item.definition.storeSpecificId + "|3|HDBK");
    17.                         string json = (Dict == null || !Dict.ContainsKey(item.definition.storeSpecificId)) ? null : Dict[item.definition.storeSpecificId];
    18.                         SubscriptionManager s = new SubscriptionManager(item, json);
    19.                         SubscriptionInfo info = s.getSubscriptionInfo();
    20.                        // if (PlayerPrefs.GetInt("removeads", 0) == 1)
    21.                         //{
    22.                             //if (String.Equals(info.getProductId(), RemoveAds, StringComparison.Ordinal))
    23.                             //{
    24.                                 if (info.isSubscribed() == Result.True)
    25.                                 {
    26.                                 canRemoveAds = true;
    27.                                 }
    28.                             //}
    29.                         //}
    30.                     }
    31.                     else if (item.definition.type == ProductType.NonConsumable)
    32.                     {
    33.                             Debug.Log(item.definition.storeSpecificId + "|4|HDBK");
    34.                         canRemoveAds = true;
    35.                     }
    36.  
    37.                 }
    38.                 Debug.Log(item.definition.storeSpecificId + "|HDBK|" + i);
    39.                 if (i==0)
    40.                 {
    41.                     if (canRemoveAds)
    42.                         ResultRemoveAds();
    43.                     else
    44.                         EnableAds();
    45.                 }
    i only use non comsumable item for remove ads. At device A, i buy it and it work. all logs i added work
    Debug.Log("HDBK|-"+i);
    Debug.Log(item.hasReceipt + "|1|HDBK");
    Debug.Log(item.definition.storeSpecificId + "|2|HDBK");
    Debug.Log(item.definition.storeSpecificId + "|4|HDBK");
    Debug.Log(item.definition.storeSpecificId + "|HDBK|" + i);
    it still work on Device A even uninstall and reinstall, restore purchase work well and all log still print.

    And then i install it for another device (Device B) with same google account. But my app can not restore purchase remove ads on device B. i checked logs and only get logs work
    Debug.Log("HDBK|-"+i);
    Debug.Log(item.hasReceipt + "|1|HDBK");
    Debug.Log(item.definition.storeSpecificId + "|HDBK|" + i);

    yea. it can not pass if (item.receipt!=null). This issue can happen with wipe data or remove data google play on device A, but i didnt checked that i can sure with it.

    That is all. Hope it is useful information. Please fix it in next version soon. thank you and Happy new year 2020!
     
  9. bhushan_rathod

    bhushan_rathod

    Joined:
    Jun 17, 2019
    Posts:
    4
    This is my code only one product is added for now

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Purchasing;
    6.  
    7. public class IAP : MonoBehaviour, IStoreListener
    8. {
    9.     private static IStoreController m_StoreController;
    10.     private static IExtensionProvider m_StoreExtensionProvider;
    11.     public static string kRemoveAds = "remove_ads";
    12.  
    13.     // temp var to display few debug on GUI
    14.     private string test;
    15.  
    16.     private void Start()
    17.     {
    18.         InitializePurchasing();
    19.     }
    20.  
    21.     public void InitializePurchasing()
    22.     {
    23.         if (IsInitialized())
    24.         {
    25.             test = "Allready initialized.";
    26.             return;
    27.         }
    28.         test = "Started initialized.";
    29.  
    30.         var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    31.  
    32.         builder.AddProduct(kRemoveAds, ProductType.NonConsumable, new IDs { { kRemoveAds, GooglePlay.Name } });
    33.      
    34.         UnityPurchasing.Initialize(this, builder);
    35.     }
    36.  
    37.  
    38.     private bool IsInitialized()
    39.     {
    40.         return m_StoreController != null;
    41.     }
    42.  
    43.     public void BuyProduct(string productId)
    44.     {
    45.         BuyProductID(productId);
    46.     }
    47.  
    48.     void BuyProductID(string productId)
    49.     {
    50.         if (IsInitialized())
    51.         {
    52.             Product product = m_StoreController.products.WithID(productId);
    53.  
    54.             if (product != null && product.availableToPurchase)
    55.             {
    56.                 m_StoreController.InitiatePurchase(product);
    57.                 test = string.Format("Can Buy - {0}", product.metadata.localizedTitle);
    58.             }
    59.             else
    60.             {
    61.                 Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
    62.                 test = "BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase";
    63.             }
    64.         }
    65.         else
    66.         {
    67.             Debug.Log("BuyProductID FAIL. Not initialized.");
    68.             test = "BuyProductID FAIL. Not initialized.";
    69.             if (m_StoreController == null)
    70.             {
    71.                 InitializePurchasing();
    72.             }
    73.         }
    74.     }
    75.  
    76.     public void RestorePurchases()
    77.     {
    78.         if (!IsInitialized())
    79.         {
    80.             Debug.Log("RestorePurchases FAIL. Not initialized.");
    81.             return;
    82.         }
    83.  
    84.         if (Application.platform == RuntimePlatform.IPhonePlayer ||
    85.             Application.platform == RuntimePlatform.OSXPlayer)
    86.         {
    87.             Debug.Log("RestorePurchases started ...");
    88.  
    89.             var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
    90.             apple.RestoreTransactions((result) => {
    91.                 Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
    92.             });
    93.         }
    94.         else
    95.         {
    96.             Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
    97.         }
    98.     }
    99.  
    100.  
    101.     //
    102.     // --- IStoreListener
    103.     //
    104.  
    105.     public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    106.     {
    107.         m_StoreController = controller;
    108.         m_StoreExtensionProvider = extensions;
    109.     }
    110.  
    111.  
    112.     public void OnInitializeFailed(InitializationFailureReason error)
    113.     {
    114.         Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    115.         switch (error)
    116.         {
    117.             case InitializationFailureReason.AppNotKnown:
    118.                 test = "Is your App correctly uploaded on the relevant publisher console?";
    119.                 break;
    120.             case InitializationFailureReason.PurchasingUnavailable:
    121.                 test = "Billing disabled!";
    122.                 break;
    123.             case InitializationFailureReason.NoProductsAvailable:
    124.                 test = "No products available for purchase!";
    125.                 break;
    126.         }
    127.     }
    128.  
    129.  
    130.     public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    131.     {
    132.         if (string.Equals(args.purchasedProduct.definition.id, kRemoveAds, StringComparison.Ordinal))
    133.         {
    134.             BaseManager.instance.noAds = true;
    135.         }
    136.         else
    137.         {
    138.             Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
    139.         }
    140.         return PurchaseProcessingResult.Complete;
    141.     }
    142.  
    143.     public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    144.     {
    145.         MenuManager.instance.popRewardHeading.text = "purchase failed";
    146.     }
    147.  
    148.     private void OnGUI()
    149.     {
    150.         int width = Screen.width;
    151.         int height = Screen.height;
    152.         int pos = 50;
    153.         GUIStyle guistyle = new GUIStyle();
    154.         guistyle.alignment = TextAnchor.UpperLeft;
    155.         guistyle.fontSize = height * 2 / 100;
    156.         guistyle.normal.textColor = new Color(1f, 1f, 1f, 1f);
    157.         Rect position = new Rect(0f, pos, width, height * 2 / 100);
    158.         GUI.Label(position, test, guistyle);
    159.  
    160.         foreach (var item in m_StoreController.products.all)
    161.         {
    162.             if (item.availableToPurchase)
    163.             {
    164.                 pos += 25;
    165.                 position = new Rect(0f, pos, width, height * 2 / 100);
    166.                 string text = string.Format("{0:0}", string.Join(" - ",
    167.                     new[]
    168.                     {
    169.                         item.metadata.localizedTitle,
    170.                         item.metadata.localizedPriceString,
    171.                         item.transactionID,
    172.                         item.hasReceipt.ToString(),
    173.                         item.receipt
    174.                     }));
    175.                 GUI.Label(position, text, guistyle);
    176.             }
    177.         }
    178.     }
    179.  
    180.     private void OnApplicationQuit()
    181.     {
    182.         Destroy(gameObject);
    183.     }
    184.  
    185. // reason for adding this is because on application quit you exit your app but when you reopen after few sec it will not initialize in-app purchase(m_StoreController = Null)
    186.     public void QuitGame()
    187.     {
    188.         #if UNITY_EDITOR
    189.         UnityEditor.EditorApplication.isPlaying = false;
    190.         #else
    191.         // commented line unable to exit application on android
    192.         // System.Diagnostics.Process.GetCurrentProcess().Kill();
    193.         Application.Quit();
    194.         #endif
    195.     }
    196. }
    197.  
    please look into it as well as second issue mentioned at last of code
     
  10. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Can you buy the same subscription or product again on the second device? If so, then it's not the same user or it's not a non-consumable. Can you confirm that the same user is logged into Google Play on both devices?
     
  11. pertz

    pertz

    Joined:
    Jan 8, 2015
    Posts:
    93
    I've been a bit away from this thread because we've been investigating for the past couple months alternatives to Unity to decrease our dependency on it, as we're tired of waiting for fixes that seems to never come.

    I have reported this same problem on this thread MULTIPLE TIMES for SEVERAL MONTHS, but without all this extensive research/explanation as you did, which was awesome. Others have also reported this problem on this thread.

    But if you go back and read the whole thread you will see that Unity team simply ignores it when it's mentioned, and pretend there's no bug. You wont see a single reply to those bug reports, it's like if they're trying to hide it. Even for your message you can see that they didnt reply it directly, they will reply other parts of your message but will ignore the parts that mention Unity is consuming purchases automatically, which is a really weird way to treat paying customers (and Unity is far from cheap!).
     
  12. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Myself and other members of the team tested this, and IAP restore works without issue. Are you referring to a BugID? Clearly "pretending there is no bug" is not in our best interest, nor yours. Without specific steps to reproduce, there is nothing we can do. If your user can purchase the same product on a second device, then either 1) a different user 2) a consumable product or 3) a bug. We are continuing to test. The bug would be that in "some cases", we are consuming the purchase for non-consumables, but so far, have not been able to reproduce.
     
  13. haiduongbk

    haiduongbk

    Joined:
    Oct 5, 2015
    Posts:
    4
    Yes, i can buy it again on 2nd device and it is same google account logged into Google Play
     
  14. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    We are looking into this.
     
  15. Livealot

    Livealot

    Joined:
    Sep 2, 2013
    Posts:
    109
    Happy New Year! And back to work on restore bugs.

    Scenario recap:
    • Unity 2019.2.0f1
    • Unity IAP 1.23.1
    • Android
    • Test accounts are opted in on each of the test devices
    • Test devices are downloading via Google Play with a build from the Internal Test Track
    • Testing a non-consummable SKU ("Animals")
    Good news is that Restore is working on the ORIGINAL device. Bad news is that Restore is NOT working on the 2nd device.

    @JeffDUnity3D I have full logs if you want them. But I am providing a pruned log of from each device with what looks to be different to me, including:
    1. The original device sends extra data when initializing SKUs
    2. Timing issue where I/API returns earlier on the 2nd device, before manual receipt check that I added
    Hope this helps identify what's going wrong. Thanks!
     

    Attached Files:

  16. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    I've just reproduced, sorry for the delay. Previously I was testing only with a subscription.
     
  17. Jmonroe

    Jmonroe

    Joined:
    Jul 7, 2012
    Posts:
    90
    Reviewing my notes of Google Billing, from Google servers perspective, the only difference between a non-consumable and consumable for a "managed" product is calling consumeProduct method, right?
    Could the issue just be to simply uncheck the "consume purchase" box on the IAP button component?
     
  18. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    That might help for codeless, but I tested with scripted IAP and see the issue.
     
  19. TheFellhuhn

    TheFellhuhn

    Joined:
    Feb 3, 2017
    Posts:
    8
    IIRC the codeless button is labelled wrong as it just changes the return of the Purchase-method to Completed from Pending state. Which should not consume the purchase.

    Restoration still not working with codeless, Google and Apple. Apple users don't even get the password query. But I have to say for such a critical feature the documentation is utter crap. It states the callback function defined in the restore button (codeless, in the inspector) will be called. But you can't even define one if it is a restore button. Instead it seems as if the all purchase buttons get triggered as they register upon initialization. Such a bogus documentation doesn't help with all the confusion. Will analyze further once I get back to work on it and report my findings here.

    But I still wonder how Unity manages to F*** up such a critical feature that is just a few lines of code when using Google's SDK in plain Java... Would it be against Unity's TOS to just circumvent their IAP sdk and use native code and Google's sdk directly?
     
    DarekRusin likes this.
  20. dioniguerra

    dioniguerra

    Joined:
    Sep 7, 2017
    Posts:
    4
    Hi @JeffDUnity3D

    Is there an ETA on the next Unity IAPLib release, fixing this Google Restore issue? We are getting a large number of complains from live users which is affecting our game integrity and release.
     
  21. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    This is our highest priority at the moment, and hopefully the next release will be in a few weeks.
     
  22. dioniguerra

    dioniguerra

    Joined:
    Sep 7, 2017
    Posts:
    4
    Hi @JeffDUnity3D

    Thanks for the update. On a side node, we have gotten reports that some users are experiencing similar issue on iOS (failing to restore Non-Consumable IAPs on a different device with same iTunes account), though we have not been able to reproduce the problem in-house through testing. Not sure if your propose fix is Android specific but worth having a look on iOS just in case.
     
    JeffDUnity3D likes this.
  23. millar5001

    millar5001

    Joined:
    Feb 13, 2017
    Posts:
    15
    Received a nice 1 star review today...

     
  24. millar5001

    millar5001

    Joined:
    Feb 13, 2017
    Posts:
    15
    Oh it gets better in an email...
     
  25. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Is this iOS? It must be, since Android would not require a restore button. Are these upset users installing on a separate device? Just want to make sure it's the same issue as discussed in this thread.
     
  26. millar5001

    millar5001

    Joined:
    Feb 13, 2017
    Posts:
    15
    That review was from an Android user. I have not had any reports about this from iOS users. I guess when purchases did not auto restore he went looking for a restore button which like you say is not required for Android.

    EDIT: As for being the same device I am not sure but I presume so as he says "... reinstall".

    In my experience this tends to only happen to users that factory reset their device or try to install on another device with the same credentials.
     
  27. pertz

    pertz

    Joined:
    Jan 8, 2015
    Posts:
    93


    Only 1 review and email like that? You're lucky. I must have had hundreds of those over the past few months.​
     
  28. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Yes, restore on the same device should work. The product would need to be either a non-consumable or subscription, the issue is restore on a second device is not working
     
  29. PanicEnsues

    PanicEnsues

    Joined:
    Jul 17, 2014
    Posts:
    88
    I'm having issues with restoring purchases on the same device, so I'm not sure if this is the same problem or not:
    1. I have 6 In-app products, one of which is non-consumable.
    2. If I build and run on an Android device, the app launches and successfully restores the non-consumable item.
    3. If I then exit and re-launch the app on the device, UnityIAP does not restore the non-consumable item.
    4. In any subsequent launches, calls to CodelessIAPStoreListener.Instance.GetProduct fail with "attempted to get unknown product".
    5. All subsequent launches on the device will continue to fail.
    6. Any Build and Run will always work once.
    This is with Unity 2018.4, IAP 1.23.1. (EDIT: Also happens with IAP 1.22)
    Here are the UnityIAP logs from an initial, successful app startup:

    EDIT #2: Removing logs because this is apparently a different issue; on the subsequent starts I have the log error "Scripting proxy object was destroyed, because Unity player was unloaded."

    Force-quitting the app causes the next launch to successfully restore IAP.

    So this is still a Unity IAP bug, but just not THIS Unity IAP bug. :)

    -Scott
     
    Last edited: Jan 18, 2020
  30. Berno

    Berno

    Joined:
    Oct 29, 2014
    Posts:
    18
    I think you should report this guys threats to Google obviously.
    The sooner you start with that the better as it seems you have to keep prodding for a while before you actually talk to a human in regards to any problems on Google Play. My app was pulled for no good reason once and I had to get through a wall of robots before I finally got a person to fix it.

    I think we also need to be aware that some of these users problems are non genuine.
    Once it started to get around on reviews that if you contacted me with this problem I would give you a product key I started to get many more emails.

    One user with a distinctive avatar and name said he had the restore issue so I gave him a key.
    Then he got back saying that key didn't work so I gave him another, oh that didn't work either.
    By then I smelt a rat and I issued him a refund saying I couldn't help him anymore.
    The next day he leaves a 5 star review and offers up a list of features he would like added to the app!?!?
    He has even updated his wish list a number of times since then.
    Why would you do this if you are stuck in demo mode and have been refunded?
    Obviously this guy was after as many free keys as he could scam out of me.

    After that I have been much more suspicious of users with this issue.
    I received many emails saying their products didn't restore and when asked for a GPA key to prove their original purchase they go silent.

    In fact I now doubt I had any genuine issues since downgrading my IAP.
    Have been too scared to release with a new version of IAP to be honest.
     
  31. millar5001

    millar5001

    Joined:
    Feb 13, 2017
    Posts:
    15
    I have thought about this but it's not really a problem for me right now as I only have 2 Unity apps and they have only 1 purchase option that is not very expensive so I just refund people straight away and every time up until now they have repurchased. Even the guy that sent the messages above.

    I do feel for other Unity devs right now though. Specifically those with uncapped spending limits in their apps. I can imagine this issue might leave some of them in sticky situations.
     
  32. pertz

    pertz

    Joined:
    Jan 8, 2015
    Posts:
    93
    Yes, this is a known/old issue that affects IAP. It seems the only way to fix it is to not close the app via code (ie, when the user pressed BACK). Either ignore the BACK, or run a command to send the app to background (as if the player pressed HOME). Dont have the code right now but you can search on Google to find the thread of this bug/solution. This affects up to 2019.1 if I recall correctly, fixed on 2019.2 (but 2019.2 introduces several new problems so I cant upgrade).
     
  33. bhushan_rathod

    bhushan_rathod

    Joined:
    Jun 17, 2019
    Posts:
    4

    https://forum.unity.com/threads/sol...r-application-quit.665497/page-2#post-4805402

    instead of using Application.Quit();
    use new AndroidJavaClass("java.lang.System").CallStatic("exit", 0);

    it will probably solve your problem.
     
  34. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Restore on Android only happens on initial install or a reinstall, not each app launch
     
  35. pertz

    pertz

    Joined:
    Jan 8, 2015
    Posts:
    93
    Im probably misinterpreting your post. Or else how would a purchase made on DeviceA show up on DeviceB if app is already installed on both devices?
     
  36. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Restore (with the corresponding ProcessPurchase call for each restored product) would only show up upon app install on the second device. Not on each app launch.
     
  37. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    119
    I think what @pertz is saying is that what you're describing is not the desired behavior. Even if a user has the product installed on both devices and then buys a non-consumable on either of the two devices, both apps should recognize the purchase without having to ask our users to reinstall the app and potentially lose their app data (i.e. game progress).
     
  38. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Restore is handled by the respective stores. You should be able to parse the receipts however by iterating through the products in the store_controller during IAP initialization on each app launch, but ProcessPurchase for Restore will ONLY be called during app install. All purchased products should be available in the controller. Look for .hasReceipt when going through the products in the controller.

    Code (CSharp):
    1. foreach (UnityEngine.Purchasing.Product item in controller.products.all)
    2.         {
    3.  
    4.             if (item.receipt != null)
    5.            {
    6.               // check product here
    7.            }
    8.      }
     
    Last edited: Jan 22, 2020 at 11:29 PM
  39. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    119
    Understood, that makes sense to add that. However, we already have a screen where we display to users the app version and we enumerate/list the validated receipts. What you're saying is that we should see the non-consumable products listed there on both devices. In these cases, the second device does not list any purchased products, even though the customer claims that they are using the same Google Play account. I've also had them go through all the troubleshooting steps listed here with no luck. As other developers had said, you end up having to send customers a promo code to redeem for their secondary devices.
     
  40. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Are you seeing the same behavior in your testing? We are aware that Restore doesn't happen on the second device, but not missing receipts also. We will test.
     
  41. pertz

    pertz

    Joined:
    Jan 8, 2015
    Posts:
    93
    I dont think that problem exists, I was just trying to understand what you meant (and now understand). If a purchase made on DeviceA didnt automatically show on DeviceB we would have seen that bug ages ago. The bug of this thread is something more specific and that only started happening around 10 months ago after a certain IAP update. Before that I had no complains about IAPs.
     
  42. RRvfx

    RRvfx

    Joined:
    Jun 14, 2018
    Posts:
    2
    Hi Jeff,
    is there a more precise date for the next release? by the end of the month?

    thanks
     
  43. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    No precise date, I might estimate a couple of weeks
     
  44. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    119
    Yes, for example we perform a test purchase on a Pixel XL running Android 10 and Google Play (Store v18.4.86, Services v19.8.31). Then on an older HTC m8 running Android 4.2.2 and Google Play (Store v18.5.59, Services v20.03.13), we run the game and list the valid purchases and nothing displays or is recognized by the game. On that HTC device we run Google Play and check the account Purchase History and the test purchase is listed there, but doesn't restore or show up in the game on that HTC device.

    Here's the code we use to check if a receipt is valid or not:

    Code (CSharp):
    1.     // If the receipt is invalid, refunded or cancelled then this method returns NULL
    2.     IPurchaseReceipt ValidatePurchase(Product product)
    3.     {
    4.         if (product.hasReceipt)
    5.         {
    6.             try {
    7.                 var result = validator.Validate(product.receipt);
    8.                 foreach(IPurchaseReceipt receipt in result)
    9.                 {
    10.                     // On Google Play, result has one product.
    11.                     var gpr = receipt as GooglePlayReceipt;
    12.                     if (gpr != null && gpr.productID == product.definition.storeSpecificId && gpr.purchaseState == GooglePurchaseState.Purchased)
    13.                         return receipt;
    14.                     // On Apple stores, receipts contain multiple products.
    15.                     var asr = receipt as AppleReceipt;
    16.                     if (asr != null)
    17.                     {
    18.                         foreach(var pr in asr.inAppPurchaseReceipts)
    19.                             if (pr != null && pr.productID == product.definition.storeSpecificId)
    20.                                 return receipt;
    21.                     }
    22.                 }
    23.             } catch(System.Exception) { }
    24.         }
    25.         return null;
    26.     }
     
    Last edited: Jan 24, 2020 at 4:16 AM
  45. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    Can you show the code where you iterate through the receipts in the store controller during IAP Initialization to list the valid purchases? Is this Alpha/Beta testing? If so, you need to browse to the testing opt-in link on the device (important) and then download via Google Play.
     
  46. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    119
    I don't think so, but I'm not sure exactly. When I go to Google Play "My apps & games" and click on the Beta tag on both devices, the app is not listed. We are installing the app on the device using adb install. Google correctly identifies the account as a developer account because we can make test purchases on both devices. Let me know what you advise.

    Sure, for example here's the code that the UI runs to display a list of purchases. This returns an empty list on that HTC device. The code we run on IAP init is very similar.
    Code (CSharp):
    1.     public List<string> GetPurchasesLog()
    2.     {
    3.         List<string> log = new List<string>();
    4.         if (controller != null && controller.products != null)
    5.         {
    6.             foreach(var p in controller.products.all)
    7.             {
    8.                 if (p.hasReceipt && p.definition.type != ProductType.Consumable)
    9.                 {
    10.                     var receipt = ValidatePurchase(p);
    11.                     if (receipt != null)
    12.                         log.Add( string.Format("{0} ({1}) {2:yyyy-MM-dd}", receipt.transactionID, receipt.productID, receipt.purchaseDate) );
    13.                 }
    14.             }
    15.         }
    16.         return log;
    17.     }
    18.  
     
  47. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    That could be your issue then. Make sure you set up your testers, and download from Google Play instead of side loading via adb. Typically the Google Play download is only needed the first time for the tester, subsequently you can then side load. You would first enter the opt-in link on a browser on the device (not through Google Play). And I don't believe you need to validate your receipts each time, generally only at initial purchase. (not sure what ValidatePurchase is doing) but that is a separate topic. There is more information here regarding testing https://docs.unity3d.com/Manual/UnityIAPGoogleConfiguration.html
     
  48. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    119
    OK, we'll uninstall and try this and then we'll let you know if that fixes it on our test devices.

    ValidatePurchase code was what I sent in the post yesterday. If validation is necessary when it's first purchased then I would expect it would be helpful to do every time, since at IAP init, a controller product receipt could activate any non-consumable that a hacker wanted, without receipt validation. For example, a full-game unlock upgrade.

    Unless you think the code performance is a problem or that the receipts might not validate in subsequent checks, I would be inclined to leave the ValidatePurchase check there (for IAP init). Keep in mind that once a non-consumable purchase has been made in our game, we never "reverse" that purchase, even after refunds, unless the user uninstalls and reinstalls the app.

    Thanks for the help.
     
  49. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    6,534
    You bet. And agreed, although you are receiving the existing receipts directly from Google, I suppose a hacker could intercept the receipt over the wire and modify it, but not sure how they would associate a fake receipt with a known product and change the association, although they can be quite sneaky! Keep in mind that Restore on a second device is currently not working, so you don't need to test that scenario. I haven't had a chance yet either, but perhaps the receipts are also missing. Would be interesting to try to purchase a subscription a second time on the second device, it should result in a Duplicate Transaction or Product already purchased error. Make sure you use the testing opt-in link in a browser on all devices the first time you test. And make sure the same user is logged into Google Play on both devices.
     
  50. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    119
    OK, we've uninstalled on the second device and re-joined the beta program. Then we installed as a Beta tester from Google Play. Same result, there was no valid receipts listed.

    I'm not sure what you mean by "Restore"? A) When UnityIAP calls ProcessPurchase after a reinstall? or B) where the user presses a button which calls IGooglePlayStoreExtensions.RestoreTransactions() or IAppleExtensions.RestoreTransactions()?

    Regarding Restore type A, I just tried reinstalling on the second device and UnityIAP didn't call ProcessPurchase like it would if I had reinstalled it on the first device (the one that purchased the product). Regarding type B, we don't use this on Android Google Play.

    We are seeing missing receipts on the second device, under the scenario I have been describing. Google Play also let us purchase the non-consumable item again on the second device, even though we had already purchased it on the same account from the first device! ProcessPurchase was called upon completed purchase. When I open Google Play and go to Purchase History, it shows two consecutive purchases (same on both devices). In the game, when I display the receipts, I see just that one purchase listed, a different one on each device.

    We get the same result regardless of whether we're running the app installed from Google Play or the newer one that we side-loaded afterwards.

    Yup, that's what we did on the second device. However, when I made the test purchase on the first device, that user was not a member of the beta program (he was a few months ago, but then left a few weeks ago). We've uninstalled and side-loaded that app many times. Google seems to recognize that an account is a developer account regardless of beta membership and so when you make an IAP purchase, it's always a test purchase. If you think we should uninstall and then install under the beta program on both devices, and then redo the test purchase, we can certainly try that.
     
unityunity