Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Switched from codeless iap to scripted and now getting play services error

Discussion in 'Getting Started' started by taylorMeow, Aug 2, 2022.

  1. taylorMeow

    taylorMeow

    Joined:
    Apr 16, 2022
    Posts:
    30
    Hi, I started to go down the codeless iap route and then changed my mind after reading about the apple receipt validation requirement. I used the demo script provided in these forums and it seems to work ok except I'm getting the error "Unity IAP: Unity In-App Purchasing requires Unity Gaming Services to have been initialized before use.". When I search this error it seems it comes from mixing codeless and scripted iap. So something from my codeless implementation is leftover I guess, I just don't know where. I deleted my IAPButtons and all products from the catalog. Any ideas? Thanks.
     
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    If you look closely at the warning message, it contains a URL link to get started. It doesn't have anything to do with Codeless, but Unity Gaming Services initialization. This should help too https://docs.unity.com/analytics/GetStarted.html
     
  3. taylorMeow

    taylorMeow

    Joined:
    Apr 16, 2022
    Posts:
    30
    Ok I think I'm making progress... it seems like no matter what tutorial I follow nothing ever just goes smooth for me haha I guess that's what happens when you're learning. So currently I have the IAPManager script that I sort of hacked away from using the demo in one of your previous comments on another post. It's not fully functional yet as you can see, I'm still figuring it out. I visited the url regarding my error and I couldn't tell if I'm supposed to mix that initialization WITH my iap script, or on the side. I made a 2nd script, just called it "GameServicesInitialize" and copied the documentation into it. I attached it as a 2nd script to my IAPManager object. So the scene object has both the IAPManager script and GameServicesInitialize script. I feel like this can't be right though because it's still doing it... but only intermittently. I can't reliably reproduce it now it will happen maybe 1 of every 3 or 4 times I test the game using the same actions. I'll try to mix it into my IAPManager script and see if that helps it to be called first.

    Code (CSharp):
    1. using System;
    2. public class IAPManager : MonoBehaviour, IStoreListener
    3. {
    4.     public static IAPManager instance;
    5.  
    6.     private static IStoreController m_StoreController;
    7.     private static IExtensionProvider m_StoreExtensionProvider;
    8.  
    9.     //Step 1 create your products
    10.     private string removeAds = "remove_ads";
    11.     private string mainBuy5 = "main_buy_5";
    12.     private string mainBuy25 = "main_buy_25";
    13.  
    14.     //************************** Adjust these methods **************************************
    15.     public void InitializePurchasing()
    16.     {
    17.         if (IsInitialized()) { return; }
    18.         var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    19.  
    20.         //Step 2 choose if your product is a consumable or non consumable
    21.         builder.AddProduct(removeAds, ProductType.NonConsumable);
    22.         builder.AddProduct(mainBuy5, ProductType.Consumable);
    23.         builder.AddProduct(mainBuy25, ProductType.Consumable);
    24.  
    25.         UnityPurchasing.Initialize(this, builder);
    26.     }
    27.  
    28.  
    29.     private bool IsInitialized()
    30.     {
    31.         return m_StoreController != null && m_StoreExtensionProvider != null;
    32.     }
    33.  
    34.  
    35.     //Step 3 Create methods
    36.     public void buyRemoveAds(){
    37.         BuyProductID(removeAds);
    38.     }
    39.     public void buyMain5(){
    40.         BuyProductID(mainBuy5);
    41.     }
    42.     public void buyMain25(){
    43.         BuyProductID(mainBuy25);
    44.     }
    45.  
    46.     //Step 4 modify purchasing
    47.     public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    48.     {
    49.  
    50.         // put validation code here i think?
    51.  
    52.         if (String.Equals(args.purchasedProduct.definition.id, removeAds, StringComparison.Ordinal)) {
    53.             Debug.Log("Ads removed successfully");
    54.             FindObjectOfType<FrontPage>().BuyNoAds();
    55.         } else if (String.Equals(args.purchasedProduct.definition.id, mainBuy5, StringComparison.Ordinal)) {
    56.             Debug.Log("5 boosts purchased successfully");
    57.             FindObjectOfType<mainBoosterManager>().mainBoostBuy5();
    58.         } else if (String.Equals(args.purchasedProduct.definition.id, mainBuy25, StringComparison.Ordinal)) {
    59.             Debug.Log("25 boosts purchased successfully");
    60.             FindObjectOfType<mainBoosterManager>().mainBoostBuy25();
    61.         } else {
    62.             Debug.Log("Error");
    63.         }
    64.         return PurchaseProcessingResult.Complete;
    65.     }
    66.  
    67.     private void Awake()
    68.     {
    69.         TestSingleton();
    70.     }
    71.  
    72.     void Start()
    73.     {
    74.         if (m_StoreController == null) { InitializePurchasing(); }
    75.     }
    76.  
    77.     private void TestSingleton()
    78.     {
    79.         if (instance != null) { Destroy(gameObject); return; }
    80.         instance = this;
    81.         DontDestroyOnLoad(gameObject);
    82.     }
    83.  
    84.     void BuyProductID(string productId)
    85.     {
    86.         if (IsInitialized())
    87.         {
    88.             Product product = m_StoreController.products.WithID(productId);
    89.             if (product != null && product.availableToPurchase)
    90.             {
    91.                 Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));
    92.                 m_StoreController.InitiatePurchase(product);
    93.             }
    94.             else
    95.             {
    96.                 Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
    97.             }
    98.         }
    99.         else
    100.         {
    101.             Debug.Log("BuyProductID FAIL. Not initialized.");
    102.         }
    103.     }
    104.  
    105.     public void RestorePurchases()
    106.     {
    107.         if (!IsInitialized())
    108.         {
    109.             Debug.Log("RestorePurchases FAIL. Not initialized.");
    110.             FindObjectOfType<FrontPage>().restoreFailed();
    111.             return;
    112.         }
    113.  
    114.         if (Application.platform == RuntimePlatform.IPhonePlayer ||
    115.             Application.platform == RuntimePlatform.OSXPlayer)
    116.         {
    117.             Debug.Log("RestorePurchases started ...");
    118.  
    119.             var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
    120.             apple.RestoreTransactions((result) => {
    121.                 Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
    122.             });
    123.             FindObjectOfType<FrontPage>().restoreComplete();
    124.         }
    125.         else
    126.         {
    127.             Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
    128.             FindObjectOfType<FrontPage>().restoreFailed();
    129.         }
    130.     }
    131.  
    132.     public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    133.     {
    134.         //Debug.Log("OnInitialized: PASS");
    135.         m_StoreController = controller;
    136.         m_StoreExtensionProvider = extensions;
    137.     }
    138.  
    139.  
    140.     public void OnInitializeFailed(InitializationFailureReason error)
    141.     {
    142.         //Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    143.     }
    144.  
    145.     public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    146.     {
    147.         Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
    148.         if (product.definition.id == removeAds){
    149.             FindObjectOfType<FrontPage>().buyAdsFailed();
    150.         } else if (product.definition.id == mainBuy5){
    151.             FindObjectOfType<mainBoosterManager>().boostPurchaseFailed();
    152.         } else if (product.definition.id == mainBuy25){
    153.             FindObjectOfType<mainBoosterManager>().boostPurchaseFailed();
    154.         }
    155.     }
    156. }

    And then here is the game initialize script separately.

    Code (CSharp):
    1. using System;
    2.  
    3. public class InitializeGameServices : MonoBehaviour
    4. {
    5.     public string environment = "production";
    6.  
    7.     async void Start()
    8.     {
    9.         try
    10.         {
    11.             var options = new InitializationOptions()
    12.                 .SetEnvironmentName(environment);
    13.  
    14.             await UnityServices.InitializeAsync(options);
    15.         }
    16.         catch (Exception exception)
    17.         {
    18.             Debug.Log("Unity game services failed to initialize :( " + exception);
    19.         }
    20.     }
    21. }
    22.  
     
  4. taylorMeow

    taylorMeow

    Joined:
    Apr 16, 2022
    Posts:
    30
    Oh wait, I replied too fast I should have read yours more carefully (long time character flaw of mine sorry). Are you saying I have to sign up for analytics (and therefore unity game services) to make the error go away even if I created the initialization script like I showed above?
     
  5. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Yes, you need to provide a valid payment method in your dashboard to sign up for UGS services. If you stay under the prescribed limits (25M events/month for Analytics), then you won't be charged. And I might recommend placing a Debug.Log statements to ensure the code is actually being executed. You have a log for the exception handler, but consider Debug.Log statements if successful also. And instead of randomly copying code, I would highly recommend taking a step back, and publish the Sample IAP Project v3 just like it is to Google Play as a separate test project. Once you get it working, then apply lessons learned to your game. This way, your game is never "broken"
     
    Last edited: Aug 2, 2022
  6. taylorMeow

    taylorMeow

    Joined:
    Apr 16, 2022
    Posts:
    30
    Great I'll do that now. Thank you so much for the instructions I really appreciate it. I combined the 2 scripts into the following, any chance you could give it a once over and tell me if it looks ok? Is it alright to make the start method async like I did? I'm hoping to upload my first builds today for internal testing. Since my game is very simple and only has in app purchases of $3 or less, I put the validation code only for iOS since google doesn't require it.

    Code (CSharp):
    1. public class IAPManager : MonoBehaviour, IStoreListener
    2. {
    3.     public static IAPManager instance;
    4.  
    5.     private static IStoreController m_StoreController;
    6.     private static IExtensionProvider m_StoreExtensionProvider;
    7.  
    8.     //Step 1 create your products
    9.     private string removeAds = "remove_ads";
    10.     private string mainBuy5 = "main_buy_5";
    11.     private string mainBuy25 = "main_buy_25";
    12.     public string environment = "production";
    13.  
    14.  
    15.     //************************** Adjust these methods **************************************
    16.     public void InitializePurchasing()
    17.     {
    18.         if (IsInitialized()) { return; }
    19.         var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    20.  
    21.         //Step 2 choose if your product is a consumable or non consumable
    22.         builder.AddProduct(removeAds, ProductType.NonConsumable);
    23.         builder.AddProduct(mainBuy5, ProductType.Consumable);
    24.         builder.AddProduct(mainBuy25, ProductType.Consumable);
    25.  
    26.         UnityPurchasing.Initialize(this, builder);
    27.     }
    28.  
    29.  
    30.     private bool IsInitialized()
    31.     {
    32.         return m_StoreController != null && m_StoreExtensionProvider != null;
    33.     }
    34.  
    35.  
    36.     //Step 3 Create methods
    37.     public void buyRemoveAds(){
    38.         BuyProductID(removeAds);
    39.     }
    40.     public void buyMain5(){
    41.         BuyProductID(mainBuy5);
    42.     }
    43.     public void buyMain25(){
    44.         BuyProductID(mainBuy25);
    45.     }
    46.  
    47.     //Step 4 modify purchasing
    48.     public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    49.     {
    50.         bool validPurchase = true; // Presume valid for platforms with no R.V.
    51.             // Unity IAP's validation logic is only included on these platforms.
    52.         #if UNITY_IOS || UNITY_STANDALONE_OSX
    53.         //#if UNITY_IOS || UNITY_STANDALONE_OSX || UNITY_ANDROID
    54.             try {
    55.                 var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
    56.                     AppleTangle.Data(), Application.identifier);
    57.                 try {
    58.                     var result = validator.Validate(args.purchasedProduct.receipt);
    59.                     Debug.Log("Receipt is valid.");
    60.                 } catch (IAPSecurityException) {
    61.                     Debug.Log("Invalid receipt, not unlocking content");
    62.                     validPurchase = false;
    63.                 }
    64.             } catch (Exception e) {
    65.                 Debug.Log("Error, unable to validate.");
    66.             }
    67.         #endif
    68.  
    69.             if (validPurchase) {
    70.                 if (String.Equals(args.purchasedProduct.definition.id, removeAds, StringComparison.Ordinal)) {
    71.                     FindObjectOfType<FrontPage>().BuyNoAds();
    72.                 } else if (String.Equals(args.purchasedProduct.definition.id, mainBuy5, StringComparison.Ordinal)) {
    73.                     FindObjectOfType<mainBoosterManager>().mainBoostBuy5();
    74.                 } else if (String.Equals(args.purchasedProduct.definition.id, mainBuy25, StringComparison.Ordinal)) {
    75.                     FindObjectOfType<mainBoosterManager>().mainBoostBuy25();
    76.                 }
    77.             } else {
    78.                 if (String.Equals(args.purchasedProduct.definition.id, removeAds, StringComparison.Ordinal)) {
    79.                     Debug.Log("Receipt not valid, content denied");
    80.                     FindObjectOfType<FrontPage>().buyAdsFailed();
    81.                 } else if (String.Equals(args.purchasedProduct.definition.id, mainBuy5, StringComparison.Ordinal)) {
    82.                     Debug.Log("Receipt not valid, content denied");
    83.                     FindObjectOfType<mainBoosterManager>().boostPurchaseFailed();
    84.                 } else if (String.Equals(args.purchasedProduct.definition.id, mainBuy25, StringComparison.Ordinal)) {
    85.                     Debug.Log("Receipt not valid, content denied");
    86.                     FindObjectOfType<mainBoosterManager>().boostPurchaseFailed();
    87.                 }
    88.             }
    89.         return PurchaseProcessingResult.Complete;
    90.     }
    91.  
    92.  
    93.     //**************************** Dont worry about these methods ***********************************
    94.     private void Awake()
    95.     {
    96.         TestSingleton();
    97.     }
    98.  
    99.     async void Start()
    100.     {
    101.         try
    102.         {
    103.             var options = new InitializationOptions()
    104.                 .SetEnvironmentName(environment);
    105.  
    106.             await UnityServices.InitializeAsync(options);
    107.         }
    108.         catch (Exception exception)
    109.         {
    110.             //Debug.Log("Unity game services failed to initialize :( " + exception);
    111.         }
    112.         if (m_StoreController == null) { InitializePurchasing(); }
    113.     }
    114.  
    115.     private void TestSingleton()
    116.     {
    117.         if (instance != null) { Destroy(gameObject); return; }
    118.         instance = this;
    119.         DontDestroyOnLoad(gameObject);
    120.     }
    121.  
    122.     void BuyProductID(string productId)
    123.     {
    124.         if (IsInitialized())
    125.         {
    126.             Product product = m_StoreController.products.WithID(productId);
    127.             if (product != null && product.availableToPurchase)
    128.             {
    129.                 //Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));
    130.                 m_StoreController.InitiatePurchase(product);
    131.             }
    132.             else
    133.             {
    134.                 Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
    135.             }
    136.         }
    137.         else
    138.         {
    139.             Debug.Log("BuyProductID FAIL. Not initialized.");
    140.         }
    141.     }
    142.  
    143.     public void RestorePurchases()
    144.     {
    145.         if (!IsInitialized())
    146.         {
    147.             Debug.Log("RestorePurchases FAIL. Not initialized.");
    148.             FindObjectOfType<FrontPage>().restoreFailed();
    149.             return;
    150.         }
    151.  
    152.         if (Application.platform == RuntimePlatform.IPhonePlayer ||
    153.             Application.platform == RuntimePlatform.OSXPlayer)
    154.         {
    155.             //Debug.Log("RestorePurchases started ...");
    156.  
    157.             var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
    158.             apple.RestoreTransactions((result) => {
    159.                 //Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
    160.             });
    161.             FindObjectOfType<FrontPage>().restoreComplete();
    162.         }
    163.         else
    164.         {
    165.             Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
    166.             FindObjectOfType<FrontPage>().restoreFailed();
    167.         }
    168.     }
    169.  
    170.     public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    171.     {
    172.         //Debug.Log("OnInitialized: PASS");
    173.         m_StoreController = controller;
    174.         m_StoreExtensionProvider = extensions;
    175.     }
    176.  
    177.  
    178.     public void OnInitializeFailed(InitializationFailureReason error)
    179.     {
    180.         //Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    181.     }
    182.  
    183.     public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    184.     {
    185.         Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
    186.         if (product.definition.id == removeAds){
    187.             FindObjectOfType<FrontPage>().buyAdsFailed();
    188.         } else if (product.definition.id == mainBuy5){
    189.             FindObjectOfType<mainBoosterManager>().boostPurchaseFailed();
    190.         } else if (product.definition.id == mainBuy25){
    191.             FindObjectOfType<mainBoosterManager>().boostPurchaseFailed();
    192.         }
    193.     }
    194. }
     
  7. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Looks appropriate. But you'll definitely want to check Google receipts too, fraud on Android is very common (and unfortunately easy)
     
  8. taylorMeow

    taylorMeow

    Joined:
    Apr 16, 2022
    Posts:
    30
    IM SO NERVOUS THIS IS MY FIRST APP! I'll come back and say whether and when it gets approved or if I run into anymore problems. Thank you again this place has been a tremendous source of help and the only reason I've gotten this far.
     
  9. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Do not publish your app live yet until you are finished testing. You'll want to create a separate tester account, and test first. I would recommend Closed testing on Google Play https://docs.unity3d.com/Packages/com.unity.purchasing@4.4/manual/UnityIAPGoogleConfiguration.html
     
  10. taylorMeow

    taylorMeow

    Joined:
    Apr 16, 2022
    Posts:
    30
    Oh don't worry I will do an internal track test first! I'm watching a video about it now. I won't jump in the deep end then yell "But Jeff from Unity said my code looked appropriate!!" while drowning ;). I've just worked so hard on it and opening something you created up to the world is intimidating. Flattering myself to think it will even get seen much, that is. Anyway... starting to ramble... thanks again
     
  11. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    No, not internal testing for IAP! Use Closed testing.
     
  12. taylorMeow

    taylorMeow

    Joined:
    Apr 16, 2022
    Posts:
    30
    Closed testing! Ah! Ok.
     
  13. cotevelino

    cotevelino

    Joined:
    Jul 3, 2020
    Posts:
    5
    What is the difference between internal and closed testing regarding IAP functionality?
     
  14. arkon

    arkon

    Joined:
    Jun 27, 2011
    Posts:
    1,122
    I've followed all the instructions on a game that used to work fine but having to upgrade Unity to the latest meant now having to initialise services to use IAP purchasing, but it still doesn't work, Should I wait for the services to initialise before initialising purchasing? I do services first but it seems to finish after I've then initialised purchasing.