Search Unity

UnityPurchasing.Initialize ArgumentException

Discussion in 'Unity IAP' started by SparklingYuri, Sep 24, 2018.

  1. SparklingYuri

    SparklingYuri

    Joined:
    Sep 24, 2018
    Posts:
    5
    Hi Unity forums,

    I'm experiencing an issue with the UnityPurchasing.Initialize call. I'm using the following code to construct products with the ConfigurationBuilder:
    Code (CSharp):
    1.  
    2. ConfigurationBuilder builder = ConfigurationBuilder.Instance(module);
    3.  
    4. T[] availableProducts = _iapCatalog.AvailableProducts;
    5. int c = availableProducts.Length;
    6. for(int i = 0; i < c; i++)
    7. {
    8.     string productID = availableProducts[i].Identifier;
    9.     IDs ids = new IDs();
    10.     ids.Add(availableProducts[i].SKU, new string[] { GooglePlay.Name, AppleAppStore.Name, MacAppStore.Name, AmazonApps.Name, WindowsStore.Name, XiaomiMiPay.Name });
    11.     builder.AddProduct(productID, ProductType.Consumable, ids);
    12. }
    13.  
    14. UnityPurchasing.Initialize(this, builder);
    15.  
    In this code availableProducts.Identifier is unique, and availableProducts.SKU is the same over multiple products. This is because we have multiple IAPs that link back to the same consumable store purchase. There is no documentation indicating this is not allowed. However when running our game on device (Android and iOS) I receive the following error and the game crashes:


    ArgumentException: An element with the same key already exists in the dictionary.
    at System.Collections.Generic.Dictionary`2[System.String,UnityEngine.Purchasing.Product].Add (System.String key, UnityEngine.Purchasing.Product value) [0x0007e] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:404
    at System.Linq.Enumerable.ToDictionary[Product,String,Product] (IEnumerable`1 source, System.Func`2 keySelector, System.Func`2 elementSelector, IEqualityComparer`1 comparer) [0x0002f] in /Users/builduser/buildslave/mono/build/mcs/class/System.Core/System.Linq/Enumerable.cs:2425
    at System.Linq.Enumerable.ToDictionary[Product,String] (IEnumerable`1 source, System.Func`2 keySelector, IEqualityComparer`1 comparer) [0x00000] in /Users/builduser/buildslave/mono/build/mcs/class/System.Core/System.Linq/Enumerable.cs:2438
    at System.Linq.Enumerable.ToDictionary[Product,String] (IEnumerable`1 source, System.Func`2 keySelector) [0x00000] in /Users/builduser/buildslave/


    This 2015 question already seems to indicate that adding the same storeIDs to multiple products crashes the API, however it does not indicate why this is the case. It seems like a valid use case to me to have your system work this way.

    Could anyone offer me more insight?
     
    Last edited: Sep 24, 2018
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    That is likely the case but would need to test. Are you somehow tracking the separate purchases when using identical product identifiers? In other words, cannot you just re-use the same one?
     
  3. SparklingYuri

    SparklingYuri

    Joined:
    Sep 24, 2018
    Posts:
    5
    Internally we use the different IDs because we need to be able to know which specific product someone bought if for example the game crashes or the user quits during a purchase, however we would like to be able to use the same storeID. Mainly because there are a lot of products which are pretty much the same and else it would become a huge list of IDs for each store.
    I know it is a fairly specific case, but the docs and method signatures do seem to suggest it should be possible. And why shouldn't it?
     
  4. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Likely because we are not looking for Identifier + SKU as the dictionary key (as the error suggests). I will check with the IAP team and see if this feature is planned for a future release.
     
  5. SparklingYuri

    SparklingYuri

    Joined:
    Sep 24, 2018
    Posts:
    5
    I think it would make sense to use the Identifier as the key and only use storeID (SKU) when communicating with the stores. Don't see why storeID would ever need to be used as a dictionary key as it can differ per store (as per the AddProducts signature I used).
    Would like to know if this gets treated or if we need to find a different solution!
     
  6. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    That is certainly up to you! We couldn't change the current functionality because it would break every installation in the field that uses this feature. If we decide to create another/non-breaking option, it would likely be in a release after the next one, so I might expect 1-2 months or so. We have not received this suggestion before, so it's not often requested.
     
  7. SparklingYuri

    SparklingYuri

    Joined:
    Sep 24, 2018
    Posts:
    5
    Good to know, thanks for the info!
     
  8. unityjingyao

    unityjingyao

    Unity Technologies

    Joined:
    Feb 20, 2017
    Posts:
    220
    Hi @SparklingYuri ,
    Unity IAP uses unique product IDs to retrieve products from stores. So you'll get that 'ArgumentException' message if there are some repeated store specific IDs.
    Here is my workaround:
    Let's say that you have one store specific product ID '100.gold.coins'. And its product IDs in your game are '100Coins0' and '100Coins1'.
    Code (CSharp):
    1. availableProducts = new AvailableProduct[2];
    2. availableProducts[0] = new AvailableProduct("100Coins0", "100.gold.coins");
    3. availableProducts[1] = new AvailableProduct("100Coins1", "100.gold.coins");
    Since I don't know the struct of your '_iapCatalog.AvailableProducts', I made my own 'AvailableProduct' class based on the fields you used.
    Code (CSharp):
    1. class AvailableProduct
    2. {
    3.     public string Identifier;
    4.     public string SKU;
    5.  
    6.     public AvailableProduct(string Identifier, string SKU)
    7.     {
    8.         this.Identifier = Identifier;
    9.         this.SKU = SKU;
    10.     }
    11. }
    When you add product via 'builder.AddProduct', please use just one of these repeated store specific IDs. You can use the first one as the code below.
    Code (CSharp):
    1. HashSet<string> SKUs = new HashSet<string>();
    2. for (int i = 0; i < availableProducts.Length; i++)
    3. {
    4.     if (SKUs.Contains(availableProducts[i].SKU))
    5.         continue;
    6.     SKUs.Add(availableProducts[i].SKU);
    7.  
    8.     string productID = availableProducts[i].Identifier;
    9.     IDs ids = new IDs();
    10.     ids.Add(availableProducts[i].SKU, new string[] { GooglePlay.Name, AppleAppStore.Name, MacAppStore.Name, AmazonApps.Name, WindowsStore.Name, XiaomiMiPay.Name });
    11.     builder.AddProduct(productID, ProductType.Consumable, ids);
    12. }
    When you buy a product, you can find the store specific product via the ID in your game. Then call 'm_Controller.InitiatePurchase' with this store specific product.
    Code (CSharp):
    1. public void BuyProduct(string productIdInGame)
    2. {
    3.     Debug.Log("BuyProduct: " + productIdInGame);
    4.     for (int i = 0; i < availableProducts.Length; i++)
    5.     {
    6.         if (availableProducts[i].Identifier == productIdInGame)
    7.         {
    8.             Product product = m_Controller.products.WithStoreSpecificID(availableProducts[i].SKU);
    9.             m_Controller.InitiatePurchase(product);
    10.             break;
    11.         }
    12.     }
    13. }
    Hope it's useful for you.
     
  9. SparklingYuri

    SparklingYuri

    Joined:
    Sep 24, 2018
    Posts:
    5
    Thanks for the reply. This solution does make sure the exception is not thrown, but it does not solve our problem. The reason is that we WANT to have the combination of ID and SKU because we want to know which ID was used in case of failure after purchase. The IAP system would in this case give us a callback on the next startup with the product in it, however when using your workaround the ID will be the same as the SKU (store ID) and thus will give us no info on which internal product was bought.
    Could you update this topic if a fix is pushed, so we can update our game?
     
  10. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    We have no plans to change this behavior at this time. We can submit a feature request for possible inclusion in a future release. In the meantime, could you send a Analytics Custom Event with the required information on PurchaseFailed? Granted this solution would require a Pro license and Raw Data Export to see the data.