Search Unity

SubscriptionManager and Android KeyNotFoundException

Discussion in 'Unity IAP' started by tokyo-ramen, Feb 13, 2019.

  1. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
    Hi,

    I'm trying to use SubscriptionManager and SubscriptionInfo with Unity but getting KeyNotFoundException.

    All the samples I've seen were using apple extension like the following

    Code (CSharp):
    1. Dictionary<string, string> dict = m_AppleExtensions.GetIntroductoryPriceDictionary();
    2.  
    3. string intro_json = (dict == null || !dict.ContainsKey(item.definition.storeSpecificId)) ? null :  dict[item.definition.storeSpecificId];
    4. SubscriptionManager p = new SubscriptionManager(item, intro_json);
    5.  
    I'm on android so I tried

    Code (CSharp):
    1.  
    2. string intro_json = null;
    3. Debug.Log("json:" +intro_json);
    4. SubscriptionManager p = new SubscriptionManager(item, intro_json);
    5. SubscriptionInfo info = p.getSubscriptionInfo();
    6.  
    and also

    Code (CSharp):
    1.  
    2. Dictionary<string, string> Dict = m_GooglePlayStoreExtensions.GetProductJSONDictionary();
    3. string intro_json = (Dict == null || !Dict.ContainsKey(item.definition.storeSpecificId)) ? null : Dict[item.definition.storeSpecificId];
    4.  
    But in both cases I get

    Any help is greatly appreciated.

    Thanks for advance.
     
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    What version of IAP are you using? Are you using a valid receipt, and checking the product type? Please show your full code. Can you try using SubscriptionManager p = new SubscriptionManager(item, null);
     
  3. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
    Thank you for your prompt reply.
    I'm using Playstore's Internal test track and the e-mail address I use for purchase is

    The e-mail I use for purchase is with testing access.
    License Test Response is set to Normal.
    upload_2019-2-14_12-28-40.png

    I tried that, but got the same problem.
     
  4. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
    I have created a minimal application with only this script to reproduce and the problem was reproduced.
    It happens after buying the subscription then rebooting the application.

    I can report a bug if required.

    Code (CSharp):
    1. #if UNITY_PURCHASING
    2.  
    3. #if UNITY_ANDROID || UNITY_IPHONE || UNITY_STANDALONE_OSX || UNITY_TVOS
    4. // You must obfuscate your secrets using Window > Unity IAP > Receipt Validation Obfuscator
    5. // before receipt validation will compile in this sample.
    6. //#define RECEIPT_VALIDATION
    7. #endif
    8.  
    9. //#define DELAY_CONFIRMATION // Returns PurchaseProcessingResult.Pending from ProcessPurchase, then calls ConfirmPendingPurchase after a delay
    10. //#define USE_PAYOUTS // Enables use of PayoutDefinitions to specify what the player should receive when a product is purchased
    11. //#define INTERCEPT_PROMOTIONAL_PURCHASES // Enables intercepting promotional purchases that come directly from the Apple App Store
    12. //#define SUBSCRIPTION_MANAGER //Enables subscription product manager for AppleStore and GooglePlay store
    13.  
    14. using System;
    15. using System.Collections;
    16. using System.Collections.Generic;
    17. using UnityEngine;
    18. using UnityEngine.UI;
    19. using UnityEngine.Purchasing;
    20. using UnityEngine.Store; // UnityChannel
    21.  
    22. #if RECEIPT_VALIDATION
    23. using UnityEngine.Purchasing.Security;
    24. #endif
    25.  
    26. /// <summary>
    27. /// An example of Unity IAP functionality.
    28. /// To use with your account, configure the product ids (AddProduct).
    29. /// </summary>
    30. public class Purchaser : MonoBehaviour, IStoreListener
    31. {
    32.     // Unity IAP objects
    33.     private IStoreController m_Controller;
    34.  
    35.     private IAppleExtensions m_AppleExtensions;
    36.     private IMoolahExtension m_MoolahExtensions;
    37.     private ISamsungAppsExtensions m_SamsungExtensions;
    38.     private IMicrosoftExtensions m_MicrosoftExtensions;
    39.     private IUnityChannelExtensions m_UnityChannelExtensions;
    40.     private ITransactionHistoryExtensions m_TransactionHistoryExtensions;
    41. #if SUBSCRIPTION_MANAGER
    42.     private IGooglePlayStoreExtensions m_GooglePlayStoreExtensions;
    43. #endif
    44.  
    45. #pragma warning disable 0414
    46.     private bool m_IsGooglePlayStoreSelected;
    47. #pragma warning restore 0414
    48.     private bool m_IsSamsungAppsStoreSelected;
    49.     private bool m_IsCloudMoolahStoreSelected;
    50.     private bool m_IsUnityChannelSelected;
    51.  
    52.     private string m_LastTransactionID;
    53.     private bool m_IsLoggedIn;
    54.     private UnityChannelLoginHandler unityChannelLoginHandler; // Helper for interfacing with UnityChannel API
    55.     private bool m_FetchReceiptPayloadOnPurchase = false;
    56.  
    57.     private bool m_PurchaseInProgress;
    58.  
    59.     private Dictionary<string, IAPDemoProductUI> m_ProductUIs = new Dictionary<string, IAPDemoProductUI>();
    60.  
    61.     public GameObject productUITemplate;
    62.     public RectTransform contentRect;
    63.  
    64.     public Button restoreButton;
    65.     public Button loginButton;
    66.     public Button validateButton;
    67.  
    68.     public Text versionText;
    69.  
    70. #if RECEIPT_VALIDATION
    71.     private CrossPlatformValidator validator;
    72. #endif
    73.  
    74.     /// <summary>
    75.     /// This will be called when Unity IAP has finished initialising.
    76.     /// </summary>
    77.     public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    78.     {
    79.         m_Controller = controller;
    80.         m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();
    81.         m_SamsungExtensions = extensions.GetExtension<ISamsungAppsExtensions>();
    82.         m_MoolahExtensions = extensions.GetExtension<IMoolahExtension>();
    83.         m_MicrosoftExtensions = extensions.GetExtension<IMicrosoftExtensions>();
    84.         m_UnityChannelExtensions = extensions.GetExtension<IUnityChannelExtensions>();
    85.         m_TransactionHistoryExtensions = extensions.GetExtension<ITransactionHistoryExtensions>();
    86. #if SUBSCRIPTION_MANAGER
    87.         m_GooglePlayStoreExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
    88. #endif
    89.  
    90.         InitUI(controller.products.all);
    91.  
    92.         // On Apple platforms we need to handle deferred purchases caused by Apple's Ask to Buy feature.
    93.         // On non-Apple platforms this will have no effect; OnDeferred will never be called.
    94.         m_AppleExtensions.RegisterPurchaseDeferredListener(OnDeferred);
    95.  
    96. #if SUBSCRIPTION_MANAGER
    97.         //Dictionary<string, string> introductory_info_dict = m_AppleExtensions.GetIntroductoryPriceDictionary();
    98. #endif
    99.         // This extension function returns a dictionary of the products' skuDetails from GooglePlay Store
    100.         // Key is product Id (Sku), value is the skuDetails json string
    101.         Dictionary<string, string> introductory_info_dict = m_GooglePlayStoreExtensions.GetProductJSONDictionary();
    102.  
    103.         Debug.Log("Available items:");
    104.         foreach (var item in controller.products.all)
    105.         {
    106.             if (item.availableToPurchase)
    107.             {
    108.                 Debug.Log(string.Join(" - ",
    109.                     new[]
    110.                     {
    111.                         item.metadata.localizedTitle,
    112.                         item.metadata.localizedDescription,
    113.                         item.metadata.isoCurrencyCode,
    114.                         item.metadata.localizedPrice.ToString(),
    115.                         item.metadata.localizedPriceString,
    116.                         item.transactionID,
    117.                         item.receipt
    118.                     }));
    119. #if INTERCEPT_PROMOTIONAL_PURCHASES
    120.                 // Set all these products to be visible in the user's App Store according to Apple's Promotional IAP feature
    121.                 // https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/PromotingIn-AppPurchases/PromotingIn-AppPurchases.html
    122.                 m_AppleExtensions.SetStorePromotionVisibility(item, AppleStorePromotionVisibility.Show);
    123. #endif
    124.  
    125. #if SUBSCRIPTION_MANAGER
    126.                 // this is the usage of SubscriptionManager class
    127.                 if (item.receipt != null) {
    128.                     if (item.definition.type == ProductType.Subscription) {
    129.                         if (checkIfProductIsAvailableForSubscriptionManager(item.receipt)) {
    130.                             string intro_json = (introductory_info_dict == null || !introductory_info_dict.ContainsKey(item.definition.storeSpecificId)) ? null : introductory_info_dict[item.definition.storeSpecificId];
    131.                             SubscriptionManager p = new SubscriptionManager(item, intro_json);
    132.                             SubscriptionInfo info = p.getSubscriptionInfo();
    133.                             Debug.Log("product id is: " + info.getProductId());
    134.                             Debug.Log("purchase date is: " + info.getPurchaseDate());
    135.                             Debug.Log("subscription next billing date is: " + info.getExpireDate());
    136.                             Debug.Log("is subscribed? " + info.isSubscribed().ToString());
    137.                             Debug.Log("is expired? " + info.isExpired().ToString());
    138.                             Debug.Log("is cancelled? " + info.isCancelled());
    139.                             Debug.Log("product is in free trial peroid? " + info.isFreeTrial());
    140.                             Debug.Log("product is auto renewing? " + info.isAutoRenewing());
    141.                             Debug.Log("subscription remaining valid time until next billing date is: " + info.getRemainingTime());
    142.                             Debug.Log("is this product in introductory price period? " + info.isIntroductoryPricePeriod());
    143.                             Debug.Log("the product introductory localized price is: " + info.getIntroductoryPrice());
    144.                             Debug.Log("the product introductory price period is: " + info.getIntroductoryPricePeriod());
    145.                             Debug.Log("the number of product introductory price period cycles is: " + info.getIntroductoryPricePeriodCycles());
    146.                         } else {
    147.                             Debug.Log("This product is not available for SubscriptionManager class, only products that are purchase by 1.19+ SDK can use this class.");
    148.                         }
    149.                     } else {
    150.                         Debug.Log("the product is not a subscription product");
    151.                     }
    152.                 } else {
    153.                     Debug.Log("the product should have a valid receipt");
    154.                 }
    155. #endif
    156.             }
    157.         }
    158.  
    159.         // Populate the product menu now that we have Products
    160.         AddProductUIs(m_Controller.products.all);
    161.  
    162.         LogProductDefinitions();
    163.     }
    164.  
    165. #if SUBSCRIPTION_MANAGER
    166.     private bool checkIfProductIsAvailableForSubscriptionManager(string receipt) {
    167.         var receipt_wrapper = (Dictionary<string, object>)MiniJson.JsonDecode(receipt);
    168.         if (!receipt_wrapper.ContainsKey("Store") || !receipt_wrapper.ContainsKey("Payload")) {
    169.             Debug.Log("The product receipt does not contain enough information");
    170.             return false;
    171.         }
    172.         var store = (string)receipt_wrapper ["Store"];
    173.         var payload = (string)receipt_wrapper ["Payload"];
    174.  
    175.         if (payload != null ) {
    176.             switch (store) {
    177.             case GooglePlay.Name:
    178.                 {
    179.                     var payload_wrapper = (Dictionary<string, object>)MiniJson.JsonDecode(payload);
    180.                     if (!payload_wrapper.ContainsKey("json")) {
    181.                         Debug.Log("The product receipt does not contain enough information, the 'json' field is missing");
    182.                         return false;
    183.                     }
    184.                     var original_json_payload_wrapper = (Dictionary<string, object>)MiniJson.JsonDecode((string)payload_wrapper["json"]);
    185.                     if (original_json_payload_wrapper == null || !original_json_payload_wrapper.ContainsKey("developerPayload")) {
    186.                         Debug.Log("The product receipt does not contain enough information, the 'developerPayload' field is missing");
    187.                         return false;
    188.                     }
    189.                     var developerPayloadJSON = (string)original_json_payload_wrapper["developerPayload"];
    190.                     var developerPayload_wrapper = (Dictionary<string, object>)MiniJson.JsonDecode(developerPayloadJSON);
    191.                     if (developerPayload_wrapper == null || !developerPayload_wrapper.ContainsKey("is_free_trial") || !developerPayload_wrapper.ContainsKey("has_introductory_price_trial")) {
    192.                         Debug.Log("The product receipt does not contain enough information, the product is not purchased using 1.19 or later");
    193.                         return false;
    194.                     }
    195.                     return true;
    196.                 }
    197.             case AppleAppStore.Name:
    198.             case AmazonApps.Name:
    199.             case MacAppStore.Name:
    200.                 {
    201.                     return true;
    202.                 }
    203.             default:
    204.                 {
    205.                     return false;
    206.                 }
    207.             }
    208.         }
    209.         return false;
    210.     }
    211. #endif
    212.  
    213.     /// <summary>
    214.     /// This will be called when a purchase completes.
    215.     /// </summary>
    216.     public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    217.     {
    218.         Debug.Log("Purchase OK: " + e.purchasedProduct.definition.id);
    219.         Debug.Log("Receipt: " + e.purchasedProduct.receipt);
    220.  
    221.         m_LastTransactionID = e.purchasedProduct.transactionID;
    222.         m_PurchaseInProgress = false;
    223.  
    224.         // Decode the UnityChannelPurchaseReceipt, extracting the gameOrderId
    225.         if (m_IsUnityChannelSelected)
    226.         {
    227.             var unifiedReceipt = JsonUtility.FromJson<UnifiedReceipt>(e.purchasedProduct.receipt);
    228.             if (unifiedReceipt != null && !string.IsNullOrEmpty(unifiedReceipt.Payload))
    229.             {
    230.                 var purchaseReceipt = JsonUtility.FromJson<UnityChannelPurchaseReceipt>(unifiedReceipt.Payload);
    231.                 Debug.LogFormat(
    232.                     "UnityChannel receipt: storeSpecificId = {0}, transactionId = {1}, orderQueryToken = {2}",
    233.                     purchaseReceipt.storeSpecificId, purchaseReceipt.transactionId, purchaseReceipt.orderQueryToken);
    234.             }
    235.         }
    236.  
    237. #if RECEIPT_VALIDATION // Local validation is available for GooglePlay, Apple, and UnityChannel stores
    238.         if (m_IsGooglePlayStoreSelected ||
    239.             (m_IsUnityChannelSelected && m_FetchReceiptPayloadOnPurchase) ||
    240.             Application.platform == RuntimePlatform.IPhonePlayer ||
    241.             Application.platform == RuntimePlatform.OSXPlayer ||
    242.             Application.platform == RuntimePlatform.tvOS) {
    243.             try {
    244.                 var result = validator.Validate(e.purchasedProduct.receipt);
    245.                 Debug.Log("Receipt is valid. Contents:");
    246.                 foreach (IPurchaseReceipt productReceipt in result) {
    247.                     Debug.Log(productReceipt.productID);
    248.                     Debug.Log(productReceipt.purchaseDate);
    249.                     Debug.Log(productReceipt.transactionID);
    250.  
    251.                     GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
    252.                     if (null != google) {
    253.                         Debug.Log(google.purchaseState);
    254.                         Debug.Log(google.purchaseToken);
    255.                     }
    256.  
    257.                     UnityChannelReceipt unityChannel = productReceipt as UnityChannelReceipt;
    258.                     if (null != unityChannel) {
    259.                         Debug.Log(unityChannel.productID);
    260.                         Debug.Log(unityChannel.purchaseDate);
    261.                         Debug.Log(unityChannel.transactionID);
    262.                     }
    263.  
    264.                     AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
    265.                     if (null != apple) {
    266.                         Debug.Log(apple.originalTransactionIdentifier);
    267.                         Debug.Log(apple.subscriptionExpirationDate);
    268.                         Debug.Log(apple.cancellationDate);
    269.                         Debug.Log(apple.quantity);
    270.                     }
    271.  
    272.                     // For improved security, consider comparing the signed
    273.                     // IPurchaseReceipt.productId, IPurchaseReceipt.transactionID, and other data
    274.                     // embedded in the signed receipt objects to the data which the game is using
    275.                     // to make this purchase.
    276.                 }
    277.             } catch (IAPSecurityException ex) {
    278.                 Debug.Log("Invalid receipt, not unlocking content. " + ex);
    279.                 return PurchaseProcessingResult.Complete;
    280.             }
    281.         }
    282. #endif
    283.  
    284.         // Unlock content from purchases here.
    285. #if USE_PAYOUTS
    286.         if (e.purchasedProduct.definition.payouts != null) {
    287.             Debug.Log("Purchase complete, paying out based on defined payouts");
    288.             foreach (var payout in e.purchasedProduct.definition.payouts) {
    289.                 Debug.Log(string.Format("Granting {0} {1} {2} {3}", payout.quantity, payout.typeString, payout.subtype, payout.data));
    290.             }
    291.         }
    292. #endif
    293.         // Indicate if we have handled this purchase.
    294.         //   PurchaseProcessingResult.Complete: ProcessPurchase will not be called
    295.         //     with this product again, until next purchase.
    296.         //   PurchaseProcessingResult.Pending: ProcessPurchase will be called
    297.         //     again with this product at next app launch. Later, call
    298.         //     m_Controller.ConfirmPendingPurchase(Product) to complete handling
    299.         //     this purchase. Use to transactionally save purchases to a cloud
    300.         //     game service.
    301. #if DELAY_CONFIRMATION
    302.         StartCoroutine(ConfirmPendingPurchaseAfterDelay(e.purchasedProduct));
    303.         return PurchaseProcessingResult.Pending;
    304. #else
    305.         UpdateProductUI(e.purchasedProduct);
    306.         return PurchaseProcessingResult.Complete;
    307. #endif
    308.     }
    309.  
    310. #if DELAY_CONFIRMATION
    311.     private HashSet<string> m_PendingProducts = new HashSet<string>();
    312.  
    313.     private IEnumerator ConfirmPendingPurchaseAfterDelay(Product p)
    314.     {
    315.         m_PendingProducts.Add(p.definition.id);
    316.         Debug.Log("Delaying confirmation of " + p.definition.id + " for 5 seconds.");
    317.  
    318.         var end = Time.time + 5f;
    319.  
    320.         while (Time.time < end) {
    321.             yield return null;
    322.             var remaining = Mathf.CeilToInt (end - Time.time);
    323.             UpdateProductPendingUI (p, remaining);
    324.         }
    325.  
    326.         Debug.Log("Confirming purchase of " + p.definition.id);
    327.         m_Controller.ConfirmPendingPurchase(p);
    328.         m_PendingProducts.Remove(p.definition.id);
    329.         UpdateProductUI (p);
    330.     }
    331. #endif
    332.  
    333.     /// <summary>
    334.     /// This will be called if an attempted purchase fails.
    335.     /// </summary>
    336.     public void OnPurchaseFailed(Product item, PurchaseFailureReason r)
    337.     {
    338.         Debug.Log("Purchase failed: " + item.definition.id);
    339.         Debug.Log(r);
    340.  
    341.         // Detailed debugging information
    342.         Debug.Log("Store specific error code: " + m_TransactionHistoryExtensions.GetLastStoreSpecificPurchaseErrorCode());
    343.         if (m_TransactionHistoryExtensions.GetLastPurchaseFailureDescription() != null)
    344.         {
    345.             Debug.Log("Purchase failure description message: " +
    346.                       m_TransactionHistoryExtensions.GetLastPurchaseFailureDescription().message);
    347.         }
    348.  
    349.         if (m_IsUnityChannelSelected)
    350.         {
    351.             var extra = m_UnityChannelExtensions.GetLastPurchaseError();
    352.             var purchaseError = JsonUtility.FromJson<UnityChannelPurchaseError>(extra);
    353.  
    354.             if (purchaseError != null && purchaseError.purchaseInfo != null)
    355.             {
    356.                 // Additional information about purchase failure.
    357.                 var purchaseInfo = purchaseError.purchaseInfo;
    358.                 Debug.LogFormat(
    359.                     "UnityChannel purchaseInfo: productCode = {0}, gameOrderId = {1}, orderQueryToken = {2}",
    360.                     purchaseInfo.productCode, purchaseInfo.gameOrderId, purchaseInfo.orderQueryToken);
    361.             }
    362.  
    363.             // Determine if the user already owns this item and that it can be added to
    364.             // their inventory, if not already present.
    365. #if UNITY_5_6_OR_NEWER
    366.             if (r == PurchaseFailureReason.DuplicateTransaction)
    367.             {
    368.                 // Unlock `item` in inventory if not already present.
    369.                 Debug.Log("Duplicate transaction detected, unlock this item");
    370.             }
    371. #else // Building using Unity strictly less than 5.6; e.g 5.3-5.5.
    372.             // In Unity 5.3 the enum PurchaseFailureReason.DuplicateTransaction
    373.             // may not be available (is available in 5.6 ... specifically
    374.             // 5.5.1p1+, 5.4.4p2+) and can be substituted with this call.
    375.             if (r == PurchaseFailureReason.Unknown)
    376.             {
    377.                 if (purchaseError != null && purchaseError.error != null &&
    378.                     purchaseError.error.Equals("DuplicateTransaction"))
    379.                 {
    380.                     // Unlock `item` in inventory if not already present.
    381.                     Debug.Log("Duplicate transaction detected, unlock this item");
    382.                 }
    383.             }
    384. #endif
    385.         }
    386.  
    387.         m_PurchaseInProgress = false;
    388.     }
    389.  
    390.     public void OnInitializeFailed(InitializationFailureReason error)
    391.     {
    392.         Debug.Log("Billing failed to initialize!");
    393.         switch (error)
    394.         {
    395.             case InitializationFailureReason.AppNotKnown:
    396.                 Debug.LogError("Is your App correctly uploaded on the relevant publisher console?");
    397.                 break;
    398.             case InitializationFailureReason.PurchasingUnavailable:
    399.                 // Ask the user if billing is disabled in device settings.
    400.                 Debug.Log("Billing disabled!");
    401.                 break;
    402.             case InitializationFailureReason.NoProductsAvailable:
    403.                 // Developer configuration error; check product metadata.
    404.                 Debug.Log("No products available for purchase!");
    405.                 break;
    406.         }
    407.     }
    408.  
    409.     [Serializable]
    410.     public class UnityChannelPurchaseError
    411.     {
    412.         public string error;
    413.         public UnityChannelPurchaseInfo purchaseInfo;
    414.     }
    415.  
    416.     [Serializable]
    417.     public class UnityChannelPurchaseInfo
    418.     {
    419.         public string productCode; // Corresponds to storeSpecificId
    420.         public string gameOrderId; // Corresponds to transactionId
    421.         public string orderQueryToken;
    422.     }
    423.  
    424.     public void Awake()
    425.     {
    426.         var module = StandardPurchasingModule.Instance();
    427.  
    428.         // The FakeStore supports: no-ui (always succeeding), basic ui (purchase pass/fail), and
    429.         // developer ui (initialization, purchase, failure code setting). These correspond to
    430.         // the FakeStoreUIMode Enum values passed into StandardPurchasingModule.useFakeStoreUIMode.
    431.         module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
    432.  
    433.         var builder = ConfigurationBuilder.Instance(module);
    434.  
    435.         // Set this to true to enable the Microsoft IAP simulator for local testing.
    436.         builder.Configure<IMicrosoftConfiguration>().useMockBillingSystem = false;
    437.  
    438.         m_IsGooglePlayStoreSelected =
    439.             Application.platform == RuntimePlatform.Android && module.appStore == AppStore.GooglePlay;
    440.  
    441.         // CloudMoolah Configuration setings
    442.         // All games must set the configuration. the configuration need to apply on the CloudMoolah Portal.
    443.         // CloudMoolah APP Key
    444.         builder.Configure<IMoolahConfiguration>().appKey = "d93f4564c41d463ed3d3cd207594ee1b";
    445.         // CloudMoolah Hash Key
    446.         builder.Configure<IMoolahConfiguration>().hashKey = "cc";
    447.         // This enables the CloudMoolah test mode for local testing.
    448.         // You would remove this, or set to CloudMoolahMode.Production, before building your release package.
    449.         builder.Configure<IMoolahConfiguration>().SetMode(CloudMoolahMode.AlwaysSucceed);
    450.         // This records whether we are using Cloud Moolah IAP.
    451.         // Cloud Moolah requires logging in to access your Digital Wallet, so:
    452.         // A) IAPDemo (this) displays the Cloud Moolah GUI button for Cloud Moolah
    453.         m_IsCloudMoolahStoreSelected =
    454.             Application.platform == RuntimePlatform.Android && module.appStore == AppStore.CloudMoolah;
    455.  
    456.         // UnityChannel, provides access to Xiaomi MiPay.
    457.         // Products are required to be set in the IAP Catalog window.  The file "MiProductCatalog.prop"
    458.         // is required to be generated into the project's
    459.         // Assets/Plugins/Android/assets folder, based off the contents of the
    460.         // IAP Catalog window, for MiPay.
    461.         m_IsUnityChannelSelected =
    462.             Application.platform == RuntimePlatform.Android && module.appStore == AppStore.XiaomiMiPay;
    463.         // UnityChannel supports receipt validation through a backend fetch.
    464.         builder.Configure<IUnityChannelConfiguration>().fetchReceiptPayloadOnPurchase = m_FetchReceiptPayloadOnPurchase;
    465.  
    466.         // Define our products.
    467.         // Either use the Unity IAP Catalog, or manually use the ConfigurationBuilder.AddProduct API.
    468.         // Use IDs from both the Unity IAP Catalog and hardcoded IDs via the ConfigurationBuilder.AddProduct API.
    469.  
    470.         // Use the products defined in the IAP Catalog GUI.
    471.         // E.g. Menu: "Window" > "Unity IAP" > "IAP Catalog", then add products, then click "App Store Export".
    472.         var catalog = ProductCatalog.LoadDefaultCatalog();
    473.  
    474.         foreach (var product in catalog.allValidProducts)
    475.         {
    476.             if (product.allStoreIDs.Count > 0)
    477.             {
    478.                 var ids = new IDs();
    479.                 foreach (var storeID in product.allStoreIDs)
    480.                 {
    481.                     ids.Add(storeID.id, storeID.store);
    482.                 }
    483.                 builder.AddProduct(product.id, product.type, ids);
    484.             }
    485.             else
    486.             {
    487.                 builder.AddProduct(product.id, product.type);
    488.             }
    489.         }
    490.  
    491.         // In this case our products have the same identifier across all the App stores,
    492.         // except on the Mac App store where product IDs cannot be reused across both Mac and
    493.         // iOS stores.
    494.         // So on the Mac App store our products have different identifiers,
    495.         // and we tell Unity IAP this by using the IDs class.
    496.         builder.AddProduct("100.gold.coins", ProductType.Consumable, new IDs
    497.             {
    498.                 {"com.unity3d.unityiap.unityiapdemo.100goldcoins.7", MacAppStore.Name},
    499.                 {"000000596586", TizenStore.Name},
    500.                 {"com.ff", MoolahAppStore.Name},
    501.                 {"100.gold.coins", AmazonApps.Name}
    502.             }
    503. #if USE_PAYOUTS
    504.         , new PayoutDefinition(PayoutType.Currency, "gold", 100)
    505. #endif //USE_PAYOUTS
    506.         );
    507.  
    508.         builder.AddProduct("500.gold.coins", ProductType.Consumable, new IDs
    509.             {
    510.                 {"com.unity3d.unityiap.unityiapdemo.500goldcoins.7", MacAppStore.Name},
    511.                 {"000000596581", TizenStore.Name},
    512.                 {"com.ee", MoolahAppStore.Name},
    513.                 {"500.gold.coins", AmazonApps.Name},
    514.             }
    515. #if USE_PAYOUTS
    516.         , new PayoutDefinition(PayoutType.Currency, "gold", 500)
    517. #endif //USE_PAYOUTS
    518.         );
    519.  
    520.         builder.AddProduct("sword", ProductType.NonConsumable, new IDs
    521.             {
    522.                 {"com.unity3d.unityiap.unityiapdemo.sword.7", MacAppStore.Name},
    523.                 {"000000596583", TizenStore.Name},
    524.                 {"sword", AmazonApps.Name}
    525.             }
    526. #if USE_PAYOUTS
    527.         , new List<PayoutDefinition> {
    528.             new PayoutDefinition(PayoutType.Item, "", 1, "item_id:76543"),
    529.             new PayoutDefinition(PayoutType.Currency, "gold", 50)
    530.         }
    531. #endif //USE_PAYOUTS
    532.         );
    533.  
    534.         // Write Amazon's JSON description of our products to storage when using Amazon's local sandbox.
    535.         // This should be removed from a production build.
    536.         //builder.Configure<IAmazonConfiguration>().WriteSandboxJSON(builder.products);
    537.  
    538.         // This enables simulated purchase success for Samsung IAP.
    539.         // You would remove this, or set to SamsungAppsMode.Production, before building your release package.
    540.         builder.Configure<ISamsungAppsConfiguration>().SetMode(SamsungAppsMode.AlwaysSucceed);
    541.         // This records whether we are using Samsung IAP. Currently ISamsungAppsExtensions.RestoreTransactions
    542.         // displays a blocking Android Activity, so:
    543.         // A) Unity IAP does not automatically restore purchases on Samsung Galaxy Apps
    544.         // B) IAPDemo (this) displays the "Restore" GUI button for Samsung Galaxy Apps
    545.         m_IsSamsungAppsStoreSelected =
    546.             Application.platform == RuntimePlatform.Android && module.appStore == AppStore.SamsungApps;
    547.  
    548.  
    549.         // This selects the GroupId that was created in the Tizen Store for this set of products
    550.         // An empty or non-matching GroupId here will result in no products available for purchase
    551.         builder.Configure<ITizenStoreConfiguration>().SetGroupId("100000085616");
    552.  
    553. #if INTERCEPT_PROMOTIONAL_PURCHASES
    554.         // On iOS and tvOS we can intercept promotional purchases that come directly from the App Store.
    555.         // On other platforms this will have no effect; OnPromotionalPurchase will never be called.
    556.         builder.Configure<IAppleConfiguration>().SetApplePromotionalPurchaseInterceptorCallback(OnPromotionalPurchase);
    557.         Debug.Log("Setting Apple promotional purchase interceptor callback");
    558. #endif
    559.  
    560. #if RECEIPT_VALIDATION
    561.         string appIdentifier;
    562. #if UNITY_5_6_OR_NEWER
    563.         appIdentifier = Application.identifier;
    564. #else
    565.         appIdentifier = Application.bundleIdentifier;
    566. #endif
    567.         validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(),
    568.             UnityChannelTangle.Data(), appIdentifier);
    569. #endif
    570.  
    571.         Action initializeUnityIap = () =>
    572.         {
    573.             // Now we're ready to initialize Unity IAP.
    574.             UnityPurchasing.Initialize(this, builder);
    575.         };
    576.  
    577.         bool needExternalLogin = m_IsUnityChannelSelected;
    578.  
    579.         if (!needExternalLogin)
    580.         {
    581.             initializeUnityIap();
    582.         }
    583.         else
    584.         {
    585.             // Call UnityChannel initialize and (later) login asynchronously
    586.  
    587.             // UnityChannel configuration settings. Required for Xiaomi MiPay.
    588.             // Collect this app configuration from the Unity Developer website at
    589.             // [2017-04-17 PENDING - Contact support representative]
    590.             // https://developer.cloud.unity3d.com/ providing your Xiaomi MiPay App
    591.             // ID, App Key, and App Secret. This permits Unity to proxy from the
    592.             // user's device into the MiPay system.
    593.             // IMPORTANT PRE-BUILD STEP: For mandatory Chinese Government app auditing
    594.             // and for MiPay testing, enable debug mode (test mode)
    595.             // using the `AppInfo.debug = true;` when initializing Unity Channel.
    596.  
    597.             AppInfo unityChannelAppInfo = new AppInfo();
    598.             unityChannelAppInfo.appId = "abc123appId";
    599.             unityChannelAppInfo.appKey = "efg456appKey";
    600.             unityChannelAppInfo.clientId = "hij789clientId";
    601.             unityChannelAppInfo.clientKey = "klm012clientKey";
    602.             unityChannelAppInfo.debug = false;
    603.  
    604.             // Shared handler for Unity Channel initialization, here, and login, later
    605.             unityChannelLoginHandler = new UnityChannelLoginHandler();
    606.             unityChannelLoginHandler.initializeFailedAction = (string message) =>
    607.             {
    608.                 Debug.LogError("Failed to initialize and login to UnityChannel: " + message);
    609.             };
    610.             unityChannelLoginHandler.initializeSucceededAction = () => { initializeUnityIap(); };
    611.  
    612.             StoreService.Initialize(unityChannelAppInfo, unityChannelLoginHandler);
    613.         }
    614.     }
    615.  
    616.     // For handling initialization and login of UnityChannel, returning control to our store after.
    617.     class UnityChannelLoginHandler : ILoginListener
    618.     {
    619.         internal Action initializeSucceededAction;
    620.         internal Action<string> initializeFailedAction;
    621.         internal Action<UserInfo> loginSucceededAction;
    622.         internal Action<string> loginFailedAction;
    623.  
    624.         public void OnInitialized()
    625.         {
    626.             initializeSucceededAction();
    627.         }
    628.  
    629.         public void OnInitializeFailed(string message)
    630.         {
    631.             initializeFailedAction(message);
    632.         }
    633.  
    634.         public void OnLogin(UserInfo userInfo)
    635.         {
    636.             loginSucceededAction(userInfo);
    637.         }
    638.  
    639.         public void OnLoginFailed(string message)
    640.         {
    641.             loginFailedAction(message);
    642.         }
    643.     }
    644.  
    645.     /// <summary>
    646.     /// This will be called after a call to IAppleExtensions.RestoreTransactions().
    647.     /// </summary>
    648.     private void OnTransactionsRestored(bool success)
    649.     {
    650.         Debug.Log("Transactions restored.");
    651.     }
    652.  
    653.     /// <summary>
    654.     /// iOS Specific.
    655.     /// This is called as part of Apple's 'Ask to buy' functionality,
    656.     /// when a purchase is requested by a minor and referred to a parent
    657.     /// for approval.
    658.     ///
    659.     /// When the purchase is approved or rejected, the normal purchase events
    660.     /// will fire.
    661.     /// </summary>
    662.     /// <param name="item">Item.</param>
    663.     private void OnDeferred(Product item)
    664.     {
    665.         Debug.Log("Purchase deferred: " + item.definition.id);
    666.     }
    667.  
    668. #if INTERCEPT_PROMOTIONAL_PURCHASES
    669.     private void OnPromotionalPurchase(Product item) {
    670.         Debug.Log("Attempted promotional purchase: " + item.definition.id);
    671.  
    672.         // Promotional purchase has been detected. Handle this event by, e.g. presenting a parental gate.
    673.         // Here, for demonstration purposes only, we will wait five seconds before continuing the purchase.
    674.         StartCoroutine(ContinuePromotionalPurchases());
    675.     }
    676.  
    677.     private IEnumerator ContinuePromotionalPurchases()
    678.     {
    679.         Debug.Log("Continuing promotional purchases in 5 seconds");
    680.         yield return new WaitForSeconds(5);
    681.         Debug.Log("Continuing promotional purchases now");
    682.         m_AppleExtensions.ContinuePromotionalPurchases (); // iOS and tvOS only; does nothing on Mac
    683.     }
    684. #endif
    685.  
    686.     private void InitUI(IEnumerable<Product> items)
    687.     {
    688.         // Show Restore, Register, Login, and Validate buttons on supported platforms
    689.         /*
    690.         restoreButton.gameObject.SetActive(NeedRestoreButton());
    691.         loginButton.gameObject.SetActive(NeedLoginButton());
    692.         validateButton.gameObject.SetActive(NeedValidateButton());
    693.  
    694.         ClearProductUIs();
    695.  
    696.         restoreButton.onClick.AddListener(RestoreButtonClick);
    697.         loginButton.onClick.AddListener(LoginButtonClick);
    698.         validateButton.onClick.AddListener(ValidateButtonClick);
    699.  
    700.  
    701.         versionText.text = "Unity version: " + Application.unityVersion + "\n" +
    702.                            "IAP version: " + StandardPurchasingModule.k_PackageVersion;
    703.                            */
    704.     }
    705.  
    706.  
    707.     public void PurchaseButtonClick(string productID)
    708.     {
    709.         if (m_PurchaseInProgress == true)
    710.         {
    711.             Debug.Log("Please wait, purchase in progress");
    712.             return;
    713.         }
    714.  
    715.         if (m_Controller == null)
    716.         {
    717.             Debug.LogError("Purchasing is not initialized");
    718.             return;
    719.         }
    720.  
    721.         if (m_Controller.products.WithID(productID) == null)
    722.         {
    723.             Debug.LogError("No product has id " + productID);
    724.             return;
    725.         }
    726.  
    727.         // For platforms needing Login, games utilizing a connected backend
    728.         // game server may wish to login.
    729.         // Standalone games may not need to login.
    730.         if (NeedLoginButton() && m_IsLoggedIn == false)
    731.         {
    732.             Debug.LogWarning("Purchase notifications will not be forwarded server-to-server. Login incomplete.");
    733.         }
    734.  
    735.         // Don't need to draw our UI whilst a purchase is in progress.
    736.         // This is not a requirement for IAP Applications but makes the demo
    737.         // scene tidier whilst the fake purchase dialog is showing.
    738.         m_PurchaseInProgress = true;
    739.         m_Controller.InitiatePurchase(m_Controller.products.WithID(productID), "aDemoDeveloperPayload");
    740.     }
    741.  
    742.     public void RestoreButtonClick()
    743.     {
    744.         if (m_IsCloudMoolahStoreSelected)
    745.         {
    746.             if (m_IsLoggedIn == false)
    747.             {
    748.                 Debug.LogError("CloudMoolah purchase restoration aborted. Login incomplete.");
    749.             }
    750.             else
    751.             {
    752.                 // Restore abnornal transaction identifer, if Client don't receive transaction identifer.
    753.                 m_MoolahExtensions.RestoreTransactionID((RestoreTransactionIDState restoreTransactionIDState) =>
    754.                 {
    755.                     Debug.Log("restoreTransactionIDState = " + restoreTransactionIDState.ToString());
    756.                     bool success =
    757.                         restoreTransactionIDState != RestoreTransactionIDState.RestoreFailed &&
    758.                         restoreTransactionIDState != RestoreTransactionIDState.NotKnown;
    759.                     OnTransactionsRestored(success);
    760.                 });
    761.             }
    762.         }
    763.         else if (m_IsSamsungAppsStoreSelected)
    764.         {
    765.             m_SamsungExtensions.RestoreTransactions(OnTransactionsRestored);
    766.         }
    767.         else if (Application.platform == RuntimePlatform.WSAPlayerX86 ||
    768.                  Application.platform == RuntimePlatform.WSAPlayerX64 ||
    769.                  Application.platform == RuntimePlatform.WSAPlayerARM)
    770.         {
    771.             m_MicrosoftExtensions.RestoreTransactions();
    772.         }
    773.         else
    774.         {
    775.             m_AppleExtensions.RestoreTransactions(OnTransactionsRestored);
    776.         }
    777.     }
    778.  
    779.     public void LoginButtonClick()
    780.     {
    781.         if (!m_IsUnityChannelSelected)
    782.         {
    783.             Debug.Log("Login is only required for the Xiaomi store");
    784.             return;
    785.         }
    786.  
    787.         unityChannelLoginHandler.loginSucceededAction = (UserInfo userInfo) =>
    788.         {
    789.             m_IsLoggedIn = true;
    790.             Debug.LogFormat("Succeeded logging into UnityChannel. channel {0}, userId {1}, userLoginToken {2} ",
    791.                 userInfo.channel, userInfo.userId, userInfo.userLoginToken);
    792.         };
    793.  
    794.         unityChannelLoginHandler.loginFailedAction = (string message) =>
    795.         {
    796.             m_IsLoggedIn = false;
    797.             Debug.LogError("Failed logging into UnityChannel. " + message);
    798.         };
    799.  
    800.         StoreService.Login(unityChannelLoginHandler);
    801.     }
    802.  
    803.     public void ValidateButtonClick()
    804.     {
    805.         // For local validation, see ProcessPurchase.
    806.  
    807.         if (!m_IsUnityChannelSelected)
    808.         {
    809.             Debug.Log("Remote purchase validation is only supported for the Xiaomi store");
    810.             return;
    811.         }
    812.  
    813.         string txId = m_LastTransactionID;
    814.         m_UnityChannelExtensions.ValidateReceipt(txId, (bool success, string signData, string signature) =>
    815.         {
    816.             Debug.LogFormat("ValidateReceipt transactionId {0}, success {1}, signData {2}, signature {3}",
    817.                 txId, success, signData, signature);
    818.  
    819.             // May use signData and signature results to validate server-to-server
    820.         });
    821.     }
    822.  
    823.     private void ClearProductUIs()
    824.     {
    825.         foreach (var productUIKVP in m_ProductUIs)
    826.         {
    827.             GameObject.Destroy(productUIKVP.Value.gameObject);
    828.         }
    829.         m_ProductUIs.Clear();
    830.     }
    831.  
    832.     private void AddProductUIs(Product[] products)
    833.     {
    834.        
    835.     }
    836.  
    837.     private void UpdateProductUI(Product p)
    838.     {
    839.         if (m_ProductUIs.ContainsKey(p.definition.id))
    840.         {
    841.             m_ProductUIs[p.definition.id].SetProduct(p, PurchaseButtonClick);
    842.         }
    843.     }
    844.  
    845.     private void UpdateProductPendingUI(Product p, int secondsRemaining)
    846.     {
    847.         if (m_ProductUIs.ContainsKey(p.definition.id))
    848.         {
    849.             m_ProductUIs[p.definition.id].SetPendingTime(secondsRemaining);
    850.         }
    851.     }
    852.  
    853.     private bool NeedRestoreButton()
    854.     {
    855.         return Application.platform == RuntimePlatform.IPhonePlayer ||
    856.                Application.platform == RuntimePlatform.OSXPlayer ||
    857.                Application.platform == RuntimePlatform.tvOS ||
    858.                Application.platform == RuntimePlatform.WSAPlayerX86 ||
    859.                Application.platform == RuntimePlatform.WSAPlayerX64 ||
    860.                Application.platform == RuntimePlatform.WSAPlayerARM ||
    861.                m_IsSamsungAppsStoreSelected ||
    862.                m_IsCloudMoolahStoreSelected;
    863.     }
    864.  
    865.  
    866.     private bool NeedLoginButton()
    867.     {
    868.         return m_IsUnityChannelSelected;
    869.     }
    870.  
    871.     private bool NeedValidateButton()
    872.     {
    873.         return m_IsUnityChannelSelected;
    874.     }
    875.  
    876.     private void LogProductDefinitions()
    877.     {
    878.         var products = m_Controller.products.all;
    879.         foreach (var product in products)
    880.         {
    881. #if UNITY_5_6_OR_NEWER
    882.             Debug.Log(string.Format("id: {0}\nstore-specific id: {1}\ntype: {2}\nenabled: {3}\n", product.definition.id, product.definition.storeSpecificId, product.definition.type.ToString(), product.definition.enabled ? "enabled" : "disabled"));
    883. #else
    884.             Debug.Log(string.Format("id: {0}\nstore-specific id: {1}\ntype: {2}\n", product.definition.id,
    885.                 product.definition.storeSpecificId, product.definition.type.ToString()));
    886. #endif
    887.         }
    888.     }
    889. }
    890.  
    891. #endif // UNITY_PURCHASING
    892.  
     
  5. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
    Here:s how I buy the subscription product in my minimal application.
    The in app id is blacked out.
    upload_2019-2-15_11-33-44.png
     
  6. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
    I have submitted a bug report Case 1127559 with the whole minimal project.
     
  7. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I will review the project. What version of IAP are you using? Are you testing with the latest (1.20.1)? Also, what version of Unity?
     
  8. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
    Thanks.
    I'm using Unity 2018.3.0f2 and In App Purchasing 2.0.5

    upload_2019-2-18_9-13-40.png
     
  9. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    That is the Package Manager version, you also need the Asset Store package. From the Window menu, choose Unity IAP/IAP Updates to get the version
     
  10. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
    Thank you. I just checked it's 1.20.1
     
  11. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    What device are you testing on? What Android version?
     
  12. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
  13. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Do these devices have Google Play installed?
     
  14. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
    Yes they do, that's how I could place a test order.
     
  15. tokyo-ramen

    tokyo-ramen

    Joined:
    Dec 4, 2018
    Posts:
    16
    I'm sorry it was a mistake in the settings on google play console.
     
  16. trooper

    trooper

    Joined:
    Aug 21, 2009
    Posts:
    748
    Was there ever any resolution for this? We're having the same issue in rare circumstances.
     
  17. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    My suspicion is that users that purchased a subscription previously with an IAP version like 1.18 or 1.19 may see this error with IAP 1.22. I am currently investigating, please test similarly if possible.
     
  18. OddScott

    OddScott

    Joined:
    Jul 17, 2019
    Posts:
    1
    For our case the IAP version (1.20.1) did not changed. The exception occurred just after the ProcessPurchase callback. Is it possible this could happen when the subscription price updates on Google Play Console between the IStoreController being initialized and the purchase?
     
  19. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    No, it's an issue with the subscription manager as mentioned, and we are looking into it. We have reproduced, and hope to have an update in the next release in a few weeks.
     
    Egil-Sandfeld likes this.
  20. trooper

    trooper

    Joined:
    Aug 21, 2009
    Posts:
    748
    What version of 2018 will this be hitting? You wouldn't happen to know if there is a page with all release notes on it, get's hard to search them when we are looking for a specific bug fix.
     
  21. JeffDUnity3D

    JeffDUnity3D

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

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Actually this fix looks to be going into the next release and should be available next week.
     
    Egil-Sandfeld likes this.
  23. Egil-Sandfeld

    Egil-Sandfeld

    Joined:
    Oct 8, 2012
    Posts:
    72
    I found a way around the issues of parsing the app store pricings to get intro prices For me, there's an issue with JSON parsing the received string. I've found a very hackish solution, but hope for a official one soon. This is how a skuProductDetail is received from Google Play:

    As you can see, some values are surrounded by quotation marks, and other are not. Then there's the ":" in the title. Google automatically joins the app title with the subscription title and joins them with a ":". I'm not sure, but I'm afraid that the json parser screws up when it gets here. So my very hacky solution is instead to parse and strip strings off quotation marks, commas:

    New GetSKUDetail method:

    Code (CSharp):
    1. private string GetSKUDetail(string productId, Dictionary<string, string> skuDetails, string detail)
    2. {
    3.  
    4.     if (!skuDetails.ContainsKey(productId))
    5.         return "";
    6.  
    7.     int indexStart = skuDetails[productId].IndexOf(detailWithQuotes);
    8.     string productSKUDetailSubstring = skuDetails[productId].Substring(indexStart + 1);
    9.     int indexEnd = productSKUDetailSubstring.IndexOf(",");
    10.  
    11.     if (productSKUDetailSubstring[indexEnd + 1].ToString() != "\"")
    12.         indexEnd = productSKUDetailSubstring.IndexOf(",", indexEnd + 1);
    13.  
    14.     string productSKUDetail = productSKUDetailSubstring.Remove(indexEnd);
    15.     string productSKUDetailValue = productSKUDetail.Replace("\"", "").Replace(detail, "").Replace(":", "");
    16.     return productSKUDetailValue;
    17. }
    Call it with eg.:

    Code (CSharp):
    1. MonthlyPriceString = GetSKUDetail(kProductNameSubscriptionMonthly, productJSONDetails, "price");
     
  24. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    We have found additional issues that need to be addressed, the next release is looking like late September
     
  25. Nyankoooo

    Nyankoooo

    Joined:
    May 21, 2016
    Posts:
    144
  26. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I am hearing 1-2 weeks for additional testing
     
    Egil-Sandfeld likes this.
  27. HieuNguyen90

    HieuNguyen90

    Joined:
    Oct 11, 2019
    Posts:
    1
    @JeffDUnity3D Hi is there any fix for this issue ? Im using IAP 1.22 on Mac / IOS build. In Editor testing, when i purchase subscription with a fake windows testing, it throws back the "only products that are purchase by 1.19+ SDK can use this class." Somehow the item.receipt fails the check for SubscriptionManager class
     
    Last edited: Oct 11, 2019
  28. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Were your products purchased prior to IAP version 1.19? There are no actual purchases performed in the fake store, it is for simulation only, please elaborate? Please provide steps to reproduce.
     
  29. Nyankoooo

    Nyankoooo

    Joined:
    May 21, 2016
    Posts:
    144
    @JeffDUnity3D I just updated to the latest IAP version and I still can't get the IntroductoryPrice with getSubscriptionInfo().

    Here is my code, in case I'm doing something wrong in general:

    Code (CSharp):
    1. Dictionary<string, string> introductory_info_dict = m_AppleExtensions.GetIntroductoryPriceDictionary();
    2. Dictionary<string, string> google_play_store_product_SKUDetails_json = m_GooglePlayStoreExtensions.GetProductJSONDictionary();
    3.  
    4. string intro_json = (introductory_info_dict == null || !introductory_info_dict.ContainsKey(product.definition.storeSpecificId)) ? null : introductory_info_dict[product.definition.storeSpecificId];
    5.  
    6. SubscriptionManager p = new SubscriptionManager(product, intro_json);
    7. if(p != null)
    8. {
    9.      SubscriptionInfo info = p.getSubscriptionInfo();
    10.      if(info != null)
    11.      {
    12.           return info.getIntroductoryPrice();
    13.      }
    14. }
     
    Last edited: Oct 22, 2019
  30. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Is this on Android or iOS? What is the result?
     
  31. Nyankoooo

    Nyankoooo

    Joined:
    May 21, 2016
    Posts:
    144
    @JeffDUnity3D This is on Android and the result is a NullPointerException when calling info.getIntroductoryPrice()
     
  32. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Can you share the exact error?
     
  33. Nyankoooo

    Nyankoooo

    Joined:
    May 21, 2016
    Posts:
    144
    @JeffDUnity3D Here you go:

    Code (CSharp):
    1. Error Unity: NullReceiptException: Exception of type 'UnityEngine.Purchasing.NullReceiptException' was thrown.
    2. Error Unity:   at UnityEngine.Purchasing.SubscriptionManager.getSubscriptionInfo () [0x00000] in <00000000000000000000000000000000>:0
    3. Error Unity:   at UnityEngine.Events.UnityAction.Invoke () [0x00000] in <00000000000000000000000000000000>:0
    4. Error Unity:   at UnityEngine.Events.UnityEvent.Invoke () [0x00000] in <00000000000000000000000000000000>:0
    5. Error Unity:   at UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1].Invoke (T1 handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <00000000000000000000000000000000>:0
    6. Error Unity:   at UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.Execute
    7.  
     
  34. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I admit that I haven't had a chance to test yet. Can you confirm that the logs also show IAP 1.23? I typically grep (search) the logcat logs with "-i unityiap"
     
  35. Nyankoooo

    Nyankoooo

    Joined:
    May 21, 2016
    Posts:
    144
    @JeffDUnity3D I'm using the logcat package directly in Unity, and when filtering the tags for "UnityIAP", no version number is shown, but I checked in the editor and I have the latest (1.23) version installed.

    Here is the output from logcat with the tag "UnityIAP":

    Code (CSharp):
    1. Info UnityIAP: IAB helper created.
    2. Info UnityIAP: Starting in-app billing setup.
    3. Info UnityIAP: Billing service connected.
    4. Info UnityIAP: invoking callback
    5. Info UnityIAP: Checking for in-app billing 3 support.
    6. Info UnityIAP: In-app billing version 3 supported for de.mycompany.myapp
    7. Info UnityIAP: Subscriptions AVAILABLE.
    8. Info UnityIAP: Subscription upgrade and downgrade are AVAILABLE.
    9. Info UnityIAP: Subscriptions information parse AVAILABLE.
    10. Info UnityIAP: VR supported.
    11. Info UnityIAP: onIabSetupFinished: 0
    12. Info UnityIAP: Requesting 5 products
    13. Info UnityIAP: QueryInventory: 5
    14. Info UnityIAP: invoking callback
    15. Info UnityIAP: Querying owned items, item type: inapp
    16. Info UnityIAP: Package name: de.mycompany.myapp
    17. Info UnityIAP: Calling getPurchases with continuation token: null
    18. Info UnityIAP: Owned items response: 0
    19. Info UnityIAP: Continuation token: null
    20. Info UnityIAP: Querying SKU details.
    21. Info UnityIAP: Querying owned items, item type: subs
    22. Info UnityIAP: Package name: de.mycompany.myapp
    23. Info UnityIAP: Calling getPurchases with continuation token: null
    24. Info UnityIAP: Owned items response: 0
    25. Info UnityIAP: Continuation token: null
    26. Info UnityIAP: Querying SKU details.
    27. Info UnityIAP: Querying owned items' purchase history, item type: subs
    28. Info UnityIAP: Package name: de.mycompany.myapp
    29. Info UnityIAP: Calling getPurchaseHistory with continuation token: null
    30. Info UnityIAP: Purchase history response: 0
    31. Info UnityIAP: Continuation token: null
    32. Info UnityIAP: Querying owned items' purchase history, item type: inapp
    33. Info UnityIAP: Package name: de.mycompany.myapp
    34. Info UnityIAP: Calling getPurchaseHistory with continuation token: null
    35. Info UnityIAP: Purchase history response: 0
    36. Info UnityIAP: Continuation token: null
    37. Info UnityIAP: onQueryInventoryFinished: true
    38. Info UnityIAP: Inventory refresh successful. (response: 0:OK)
     
  36. Nyankoooo

    Nyankoooo

    Joined:
    May 21, 2016
    Posts:
    144
  37. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I should have more information early next week
     
    Nyankoooo likes this.
  38. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    It is working for me, I get the introductory price. You need to make sure the product is a subscription, here is my code:

    Code (CSharp):
    1. public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    2.     {
    3.         MyDebug("OnInitialized: PASS");
    4.  
    5.         m_StoreController = controller;
    6.         m_StoreExtensionProvider = extensions;
    7.         m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();
    8.  
    9.         Dictionary<string, string> dict = m_AppleExtensions.GetIntroductoryPriceDictionary();
    10.  
    11.         MyDebug("Product count = " + controller.products.all.Length.ToString());
    12.  
    13.         foreach (Product item in controller.products.all)
    14.         {
    15.  
    16.             if (item.receipt != null)
    17.             {
    18.                 if (item.definition.type == ProductType.Subscription)
    19.                 {
    20.                     string intro_json = (dict == null || !dict.ContainsKey(item.definition.storeSpecificId)) ? null : dict[item.definition.storeSpecificId];
    21.                     SubscriptionManager p = new SubscriptionManager(item, intro_json);
    22.                     SubscriptionInfo info = p.getSubscriptionInfo();
    23.                     if (info != null)
    24.                         MyDebug("Intro Price : " + info.getIntroductoryPrice().ToString());
    25.                     else
    26.                         MyDebug("Info is null");
    27.                 }
    28.                 else
    29.                 {
    30.                     MyDebug("The product is not a subscription");
    31.                 }
    32.             }
    33.             else
    34.             {
    35.                 MyDebug("The receipt is null");
    36.             }
    37.         }
    38.     }
     
  39. Nyankoooo

    Nyankoooo

    Joined:
    May 21, 2016
    Posts:
    144
    @JeffDUnity3D I'm seeing you're using "m_AppleExtensions.GetIntroductoryPriceDictionary()" in your example, so does that mean this only works on iOS and not on Android?
    When only on iOS, how does this work on Android?
     
  40. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Yes, dict would be null, see the check in the code.
     
  41. Nyankoooo

    Nyankoooo

    Joined:
    May 21, 2016
    Posts:
    144
    @JeffDUnity3D When I'm doing your above code in the "OnInitialized" method, all my products don't have a receipt. Do I need to test this with a uploaded version to the Google Play Console to test?
     
  42. JeffDUnity3D

    JeffDUnity3D

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

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    What was the settings mistake?
     
    lmartin_unity763 likes this.