Search Unity

[iOS] IAPs working fine in TestFlight, game rejected by Apple because of NoProductsAvailable

Discussion in 'Unity IAP' started by carles2m, May 25, 2020.

  1. carles2m

    carles2m

    Joined:
    Jan 6, 2016
    Posts:
    5
    • Description of your issue
      • We also have another game published with IAPs working. All tax, contact info, etc... documents required for IAPs to work are already filled out and active.
      • We are able to successfully load and purchase IAPs in device in development and with Ad Hoc build created from binary uploaded to Apple Store using a Sandbox account.

    • We are also able to successfully load and purchase IAPs in TestFlight, getting a note that because we are part of internal test group we won't be charged.
    • Other users in Beta group in TestFlight are also able to load and initialize the process of purchasing IAPs, they don't get the message they won't get charged, so they have not tested completing a purchase. Note they are able to see prices and get to the AppStore popup to enter their password to confirm the IAP purchase, like in the screenshot above.
    • Our game has been rejected by Apple multiple times now - they have attempted multiple times to purchase our IAPs in their sandbox environment but they are unable to see the prices. In our analytics we can see how the error is "NoProductsAvailable". The screenshot they've sent us confirm this, instead of seeing the IAP prices they see a fallback "Tap for Info" button we've implemented for this scenario. This is their message:


    Guideline 2.1 - Performance - App Completeness

    We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 13.4.1 on Wi-Fi.

    Specifically, we still received an error message when we attempted to take action on your in-app purchases when logged in with a sandbox account.

    Next Steps

    When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code "Sandbox receipt used in production," you should validate against the test environment instead.

    Resources

    You can learn more about testing in-app purchase products in your development sandbox environment in App Store Connect Developer Help.

    For more information on receipt validation, please see What url should I use to verify my receipt? in the In-App Purchase FAQ.

    Learn how to generate a receipt validation code in App Store Connect Developer Help.

    • Your purchasing script
    Code (CSharp):
    1. private static IStoreController StoreController;          // The Unity Purchasing system.
    2. private static IExtensionProvider StoreExtensionProvider; // The store-specific Purchasing subsystems.
    3.  
    4. public IEnumerable<PurchasableItem> PurchasableItems = new List<PurchasableItem>
    5. {
    6.     new PurchasableItem(Id: "points1"),
    7.     new PurchasableItem(Id: "points2"),
    8.     new PurchasableItem(Id: "points3"),
    9.     new PurchasableItem(Id: "points4"),
    10.     new PurchasableItem(Id: "points5"),
    11.     new PurchasableItem(Id: "points6"),
    12. };
    13.  
    14. public Task InitializePurchasing()
    15. {
    16.     // Create a builder, first passing in a suite of Unity provided stores.
    17.     ConfigurationBuilder builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    18.  
    19.     foreach (PurchasableItem item in PurchasableItems)
    20.     {
    21.         builder.AddProduct(item.Id, ProductType.Consumable);
    22.     }
    23.  
    24.     // Kick off the remainder of the set-up with an asynchronous call, passing the configuration and this class' instance. Expect a response either in OnInitialized or OnInitializeFailed.
    25.     UnityPurchasing.Initialize(this, builder);
    26.    
    27.     // returns a task waiting for OnInitialized or OnInitializeFailed to get called that times out after 15 secs
    28. }
    29.  
    30. public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    31. {
    32.     StoreController = controller;
    33.     StoreExtensionProvider = extensions;
    34. }
    35.  
    36. public void OnInitializeFailed(InitializationFailureReason error)
    37. {
    38.     Debug.LogError($"{nameof(Purchaser)}.{nameof(OnInitializeFailed)}: error '{error}'.");
    39. }
    40.  
    41. private void BuyProductInternal(string productId)
    42. {
    43.     BuyProductResult? result = null;
    44.  
    45.     try
    46.     {
    47.         // If Purchasing has been initialized ...
    48.         if (IsInitialized)
    49.         {
    50.             Product product = StoreController.products.WithID(productId);
    51.  
    52.             // If the look up found a product for this device's store and that product is ready to be sold ...
    53.             if (product != null && product.availableToPurchase)
    54.             {
    55.                 Debug.Log($"{nameof(Purchaser)}.{nameof(BuyProductInternal)}: Purchasing product '{product.definition.id}'.");
    56.  
    57.                 // ... buy the product. Expect a response either through ProcessPurchase or OnPurchaseFailed asynchronously.
    58.                 StoreController.InitiatePurchase(product);
    59.             }
    60.             else
    61.             {
    62.                 // ... otherwise report the product look-up failure situation
    63.                 result = BuyProductResult.UnknownError;
    64.             }
    65.         }
    66.         else
    67.         {
    68.             // ... otherwise report the fact Purchasing has not succeeded initializing yet.
    69.  
    70.             // retry initialization
    71.             InitializePurchasing().ContinueWith(t =>
    72.             {
    73.                 if (IsInitialized)
    74.                 {
    75.                     // yay! now we are initialized. Restart purchase call
    76.                     BuyProductInternal(productId);
    77.                 }
    78.                 else
    79.                 {
    80.                     // initialization failed again... give up
    81.                     result = BuyProductResult.PurchasesUnavailable);
    82.                 }
    83.             }, TaskContinuationOptions.ExecuteSynchronously); // run synchronously to avoid exception
    84.         }
    85.     }
    86.     catch (Exception exception)
    87.     {
    88.         Debug.LogException(exception);
    89.         result = BuyProductResult.UnknownError;
    90.     }
    91. }
    • Device logs
      • Since we are unable to repro this locally, we don't have logs. Most we can see are the errors being logged in Firebase.
    • Unity Version
      • 2019.3.9f1. Can't use latter than .9f1 because logging with Facebook freezes and also when closing the game it crashes.
    • Unity IAP version
      • 1.23.1
    • Platform you are building for
      • iOS and Android.
    • Store you are targeting
      • Apple AppStore
      • Google Play. No issues here. Issue only reproes in Apple AppStore.
    • Receipts
      • Apple has been unable to purchase, so no receipt, right?
    Thank you very much for your help!
     
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
  3. carles2m

    carles2m

    Joined:
    Jan 6, 2016
    Posts:
    5
    That makes no sense. Apple testers cannot retrieve the IAPs, cannot even start a purchase. Why is receipt validation required? Validation is done after purchase, right? That requires IAPs to be retrieved first - which is failing.
     
  4. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Good point, their message is a bit confusing in that regard. Couldn't hurt to include it, you should be checking receipts anyway.
     
  5. carles2m

    carles2m

    Joined:
    Jan 6, 2016
    Posts:
    5
    Sure. First things first - don't want to add things to something that's not working in all cases. Any clue on what's the problem and how to fix it? Thank you!
     
  6. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Receipt validation avoids purchase fraud and works in all cases. You are going to need to resubmit regardless, might as well add a few lines of code while you're at it. Did you provide the Sandbox password when you submitted? I'm not familiar with your specific code and could not debug it, you might compare to the Sample IAP Project here. Specifically I'm concerned about this line for example, you should remove it and find the cause of the exception.

    TaskContinuationOptions.ExecuteSynchronously); // run synchronously to avoid exception

    Sample IAP Project here https://forum.unity.com/threads/sample-iap-project.529555/
     
  7. carles2m

    carles2m

    Joined:
    Jan 6, 2016
    Posts:
    5
    Thank you Jeff for your suggestion. We'll implement receipt validation as soon as we get IAPs to work for Apple testers.

    What do you mean with "provide the Sandbox password"? Apple has their own Apple Sandbox accounts.

    The line you mention is a red herring - please ignore it.The moment that line executes we have already not been able to retrieve the IAPs anyway.

    The Sample IAP Project you reference is actually very close to our code. The "difference" is that when we detect IAPs are not initialized we retry a second time - and for Apple testers the retry also fails so we give up.
     
  8. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Last time I submitted to Apple, I believe they asked for a Sandbox testing password, perhaps it has changed since then. I believe Sandbox testers need to be approved first for IAP testing. But I've heard of other users getting the same rejection reason lately, and adding receipt validation allowed them to submit.
     
  9. carles2m

    carles2m

    Joined:
    Jan 6, 2016
    Posts:
    5
    To close on this... We got a call from Apple and they were finally able to retrieve and purchase our IAPs. We have no idea why they were not able to retrieve the IAPs before.
     
    JeffDUnity3D likes this.
  10. merovingiaan

    merovingiaan

    Joined:
    Nov 5, 2017
    Posts:
    4
    Hello, I have the same issue. Can you tell me what was the problem?
     
  11. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Can you describe your issue? Is it working in your TestFlight testing, but fails the Apple review?
     
  12. merovingiaan

    merovingiaan

    Joined:
    Nov 5, 2017
    Posts:
    4
    Yes it work fine in testflight.

    -Tax, deposit account, etc.
    above information has already been entered.
    I am already making another ios game and live.

    env:
    unity 2019.4.20 lts
    unity iap 2.2.2
     
  13. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Why did they fail the submission? Also, you'll definitely want to be using IAP 2.2.7 and not 2.2.2 (which has issues)
     
  14. merovingiaan

    merovingiaan

    Joined:
    Nov 5, 2017
    Posts:
    4
    How do I install 2.2.7?

    2.2.2 is the latest in the package manager.

    Specifically, your app’s In-App Purchases still do not show prices. =>

    It is caused by a failed initialization.
    OnInitializeFailed prints NoProductsAvailable.

    -------- from apple

    Hello,

    Thank you for your resubmission. However, upon further review, we found that your app is out of compliance with the following guideline(s):



    Guideline 2.1 - Performance - App Completeness



    We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad and iPhone running iOS 14.4 on Wi-Fi.

    Specifically, your app’s In-App Purchases still do not show prices.

    Next Steps

    When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code "Sandbox receipt used in production," you should validate against the test environment instead.

    Resources

    You can learn more about testing in-app purchase products in your development sandbox environment in App Store Connect Developer Help.

    For more information on receipt validation, please see What url should I use to verify my receipt? in the In-App Purchase FAQ.

    Learn how to generate a receipt validation code in App Store Connect Developer Help.
     
  15. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    @merovingiaan In the Unity Editor, you can use menu Window/Unity IAP/IAP Updates... to upgrade. Or you can upgrade via the Services window. Ensure to back up your project first prior to an upgrade. But I doubt it would apply in this case, it was a general recommendation. This has the appearance of an issue on the Apple side if it's working in TestFlight.
     
  16. merovingiaan

    merovingiaan

    Joined:
    Nov 5, 2017
    Posts:
    4
    Thank you for your kind and quick response. I asked Apple a question, so I'll leave the result here if it gets resolved.
     
    JeffDUnity3D likes this.
  17. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    Hi @merovingiaan, wondering if you got a response from them. I've been back and forth with Apple Review for 2 days now trying to replicate this same error on my end to no success. Each time, their response is:

    "We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 14.4 on Wi-Fi.

    Specifically, your app’s In-App Purchases have no price.

    Next Steps
    When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code "Sandbox receipt used in production," you should validate against the test environment instead."


    I'm using Unity IAP v3.0.1, codeless, and everything works fine in TestFlight and in all my devices. The prices show up and transactions go through just fine without any errors.
     
  18. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    Also in regards to this, does this mean we shouldn't use codeless IAP since it doesn't have receipt validation?
     
  19. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    That is correct. We have some work to do on Codeless. You could modify the Codeless scripts directly but I don't recommend that approach. If you are editing code, it is no longer codeless, and you might as well start using scripted IAP since it is more flexible.
     
  20. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    Thanks for confirming. I've started implementing the scripted IAP by copying the sample files found in the In App Purchasing Sample (imported from package manager). Worked fine until I tried enabling receipt validation by uncommenting #define RECEIPT_VALIDATION. It produces the error below:

    NotImplementedException: The method or operation is not implemented.
    UnityEngine.Purchasing.Security.GooglePlayTangle.Data () (at Assets/Scripts/UnityPurchasing/generated/GooglePlayTangle.cs:16)

    When I checked the line: 'GooglePlayTangle.cs:16', I found that it's the
    return Obfuscator.DeObfuscate(data, order, key) in the Tangle class.

    I thought I was just missing something from the demo, but I uncommented #define RECEIPT_VALIDATION in the IAPDemo.cs file itself and it produced the same error.

    Any clue as to why this is happening? Btw, I'm using Unity 2020.1.9f1


    EDIT: This error is also true with AppleTangle.cs when I passed null for GooglePlay in the validator definition:
    validator = new CrossPlatformValidator(null, AppleTangle.Data(), appIdentifier);
     
    Last edited: Mar 10, 2021
  21. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I was recommending the Sample IAP Project for this reason. It's simplified and it works without defines. It sounds like your tangle files may not be in the correct location, from the release notes for 3.0.1

    TangleFiles.png
     
  22. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    I did see that note. I believe they are in the correct folder, as shown in the error I pasted:

    NotImplementedException: The method or operation is not implemented.
    UnityEngine.Purchasing.Security.GooglePlayTangle.Data () (at Assets/Scripts/UnityPurchasing/generated/GooglePlayTangle.cs:16)
     
  23. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I added the following to the Sample IAP Project in IAPManager.cs in ProcessPurchase without error. Remember to add the following to the top of your file

    using UnityEngine.Purchasing.Security;

    Then I added

    Code (CSharp):
    1. public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    2.     {
    3.         test_product = args.purchasedProduct;
    4.  
    5.  
    6.         var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
    7.         var result = validator.Validate(args.purchasedProduct.receipt);
    8.         foreach (IPurchaseReceipt productReceipt in result)
    9.         {
    10.             MyDebug("Valid receipt for " + productReceipt.productID.ToString());
    11.         }
    12. ...
     
    Last edited: Mar 11, 2021
  24. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    Hi, the error persists. When I do cmd+click on Obfuscator.DeObfuscate in either GooglePlay or Apple Tangle.cs files, it takes me to the below read-only [metadata] script:

    Code (CSharp):
    1. #region Assembly UnityEngine.Purchasing.SecurityStub, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
    2. // UnityEngine.Purchasing.SecurityStub.dll
    3. #endregion
    4.  
    5. namespace UnityEngine.Purchasing.Security
    6. {
    7.     public static class Obfuscator
    8.     {
    9.         public static byte[] DeObfuscate(byte[] data, int[] order, int key);
    10.     }
    11. }

    which points to the Obfuscator.cs under SecurityStub (below) that's responsible for throwing the error:


    Code (CSharp):
    1. using System;
    2.  
    3. namespace UnityEngine.Purchasing.Security
    4. {
    5.     public static class Obfuscator
    6.     {
    7.         public static byte [] DeObfuscate (byte [] data, int [] order, int key)
    8.         {
    9.             throw new NotImplementedException ();
    10.         }
    11.     }
    12. }
    13.  

    I suspect it should be pointing to the Obfuscator.cs under Security, which I did find:

    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3. using System.Collections.Generic;
    4.  
    5. namespace UnityEngine.Purchasing.Security
    6. {
    7.     public static class Obfuscator
    8.     {
    9.         public static byte[] DeObfuscate(byte[] data, int[] order, int key)
    10.         {
    11.             var res = new byte[data.Length];
    12.             int slices = data.Length / 20 + 1;
    13.             bool hasRemainder = data.Length % 20 != 0;
    14.  
    15.             Array.Copy(data, res, data.Length);
    16.             for (int i = order.Length - 1; i >= 0; i--)
    17.             {
    18.                 var j = order[i];
    19.                 int sliceSize = (hasRemainder && j == slices - 1) ? (data.Length % 20) : 20;
    20.                 var tmp = res.Skip(i * 20).Take(sliceSize).ToArray(); // tmp = res[i*20 .. slice]
    21.                 Array.Copy(res, j * 20, res, i * 20, sliceSize);      // res[i] = res[j*20 .. slice]
    22.                 Array.Copy(tmp, 0, res, j * 20, sliceSize);              // res[j] = tmp
    23.             }
    24.             return res.Select(x => (byte)(x ^ key)).ToArray();
    25.         }
    26.     }
    27. }
    28.  

    Problem is I don't know how to make the read-only [metadata] to point to it. Please let me know if this is indeed the problem and, if it is, suggest any way to fix
     
  25. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    @francisIsFine Please elaborate, "still persists", what did you change from your original error? Are you using the Sample IAP Project? Please review the release notes regarding this method. I tested in the Sample IAP Project with the code pasted previously without error and it properly validates the receipt. If you are not familiar with #DEFINES and how to set them in Player Settings, I would suggest using the Sample IAP Project
     
    Last edited: Mar 11, 2021
  26. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    I mean that the same NotImplementedException keeps happening. I think you're confusing me for following IAPManager.cs file from the Sample IAP Project that you provide from this link: https://forum.unity.com/threads/sample-iap-project.529555/. But that's not where I copied my script from. The script I followed is the IAPDemo.cs file found in the "In App Purchasing Sample" offered directly in the package manager window (screenshot below). Screen Shot 2021-03-11 at 11.55.27 AM.png

    which has the following lines at the top:

    Code (CSharp):
    1. #if UNITY_ANDROID || UNITY_IPHONE || UNITY_STANDALONE_OSX || UNITY_TVOS
    2. // You must obfuscate your secrets using Window > Unity IAP > Receipt Validation Obfuscator
    3. // before receipt validation will compile in this sample.
    4. // #define RECEIPT_VALIDATION
    5. #endif
    From my understanding, I'd have to uncomment #define RECEIPT_VALIDATION to enable the RV parts of this sample along with Obfuscation. But doing all of that is what produces the NotImplementedException error message.

    In addition to this attempt, I also picked up the IAPManager.cs file from your Sample IAP package and added the following as you suggested:

    Code (CSharp):
    1. var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
    2.         var result = validator.Validate(args.purchasedProduct.receipt);
    3.         foreach (IPurchaseReceipt productReceipt in result)
    4.         {
    5.             MyDebug("Valid receipt for " + productReceipt.productID.ToString());
    6.         }
    But the same error happens because of this line:
    validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
     
  27. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Don't copy that script, as I've mentioned. It's outdated. Simply open the Sample IAP Project and start there.

    * Open the Sample IAP Project.
    * Relink the project to your organization
    * Upgrade to 3.0.1
    * Run the obfuscator from the Unity IAP editor menu
    * Ensure that AppleTangle.cs and GooglePlayTangle.cs exist at Assets/Scripts/UnityPurchasing/generated
    * Add the code I've mentioned above, including the using statement.

    I have followed these steps just now and have confirmed that it works properly.
     
  28. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    So I just followed those steps and repeated the whole thing multiple times still to no success.

    I did figure out a somewhat inelegant workaround however. In one of my previous responses, I mentioned I was convinced that including the namespace "UnityEngine.Purchasing.Security" was pointing to the .cs files under Packages > In App Purchasing > Runtime > SecurityStub when it should be running the classes under Packages > In App Purchasing > Runtime > Security. To confirm my suspicion, I duplicated the 'Security' and 'SecurityCore' folders and placed the copies into a folder outside Packages. I did not change any code in these files and so the "UnityEngine.Purchasing.Security" namespace is kept the same. Now the error has stopped appearing and Receipt Validation is finally working.

    Now my next step is to resubmit to App Review and see if this fixes the problem for them.
     
  29. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
  30. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    I'm still getting rejected by App Review and getting the same message from them:

    We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 14.4 on Wi-Fi.
    Specifically, your app’s In-App Purchases have no price. Also, upon further review, your app’s Buy button is unresponsive.


    A quick review of the things I've made sure to have done prior to submission:
    - Updated to Unity IAP v3.0.1 (Rejections started from when I was still using v2.2.2)
    - Receipt Validation is enabled
    - Since I initialize via script, "Automatically initialize Unity Purchasing" is unchecked in the IAP Catalog
    - Recreated a new set of IAPs
    - TestFlight shows no errors on all of my devices
    - Beta testers also show no problems
    - No contracts/agreements pending on App Store Connect
    - IAPs were in "Ready to Submit" status
    - IAPs attached to the binary upon submission

    I'm stumped. Any more suggestions?
     
  31. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    @francisIsFine It sounds like you have done everything correctly. You might need an iPad running iOS 14.4 to test. Have you been testing on 14.4? By the way, the previous NotImplementedException error might be fixed by deleting the /Library folder and allowing it to regenerate at project load. Make a project backup first.
     
  32. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    Thanks for the quick response. Yes I’ve also made sure my iPad is running iOS 14.4. I’ve also tried asking App Review to confirm if the error is only happening on the iPad but they’ve always ignored the question.

    I haven’t tried deleting the /Library folder yet. I’ll be sure to update you on that once I have. Right now, I’m still just looking around for answers as to why App Review keeps rejecting. If you become aware of more others experiencing the same issue, please let me know.
     
    JeffDUnity3D likes this.
  33. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    You mentioned you are using the IAP Catalog, yet are initializing IAP in script. Can you confirm that you are not using Codeless IAP Buttons? Granted it doesn't explain the rejections, but they are claiming that button is unresponsive.
     
  34. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    No, I'm not using the Codeless IAP Buttons anymore. There no instances of them left as I've replaced them with my scripted ones. I can confirm that the IAP Catalog is only used in the script for defining products prior to initialization. I also confirm that all these new buttons are indeed active and working in the editor and in Testflight.
     
    JeffDUnity3D likes this.
  35. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    Just tried the suggested deletion of the /Library folder. Unfortunately it did not fix it for me. NotImplementedException still appears after removing the folder and restarting Unity.
     
  36. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Where are your tangle files located, please show a screenshot. And you are using code from the IAP Sample Project from the forum, correct?
     
  37. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    Sure

    Screen Shot 2021-03-17 at 12.59.07 PM.png
     
  38. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    And you are not using code from IAPDemo, correct? It is outdated. And if you are building on Windows and using the fake store from IAP Demo, you would need to edit the tangle files and remove the #if and #endif statements
     
  39. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40

    No, I'm not building on Windows.

    Find IAPManager.cs script that I'm using. I've crossed reference what I've written in this script with the IAPDemo.cs file included in the Sample IAP Project as well as the Unity IAP tutorials found in the link below, taking only what I need:
    https://docs.unity3d.com/2020.1/Documentation/Manual/UnityIAP.html

    Code (CSharp):
    1. #if UNITY_ANDROID || UNITY_IPHONE || UNITY_IOS || UNITY_STANDALONE_OSX || UNITY_TVOS
    2. // You must obfuscate your secrets using Window > Unity IAP > Receipt Validation Obfuscator
    3. // before receipt validation will compile in this sample.
    4. #define RECEIPT_VALIDATION
    5. #endif
    6.  
    7. // #define INTERCEPT_PROMOTIONAL_PURCHASES // Enables intercepting promotional purchases that come directly from the Apple App Store
    8.  
    9. using System;
    10. using System.Collections;
    11. using System.Collections.Generic;
    12. using UnityEngine;
    13. using UnityEngine.Analytics;
    14. using UnityEngine.Purchasing;
    15.  
    16. #if RECEIPT_VALIDATION
    17. using UnityEngine.Purchasing.Security;
    18. #endif
    19.  
    20. public class IAPManager :  MonoBehaviour, IStoreListener
    21. {
    22.     // Unity IAP objects
    23.     private IStoreController m_Controller;
    24.  
    25.     private IAppleExtensions m_AppleExtensions;
    26.     private ISamsungAppsExtensions m_SamsungExtensions;
    27.     private IMicrosoftExtensions m_MicrosoftExtensions;
    28.     private ITransactionHistoryExtensions m_TransactionHistoryExtensions;
    29.     private IGooglePlayStoreExtensions m_GooglePlayStoreExtensions;
    30.  
    31. #pragma warning disable 0414
    32.     private bool m_IsGooglePlayStoreSelected;
    33. #pragma warning restore 0414
    34.     private bool m_IsSamsungAppsStoreSelected;
    35.  
    36.     private bool m_PurchaseInProgress;
    37.    
    38.     public GameObject processingBlocker;
    39.     public List<IAPCustomButton> customIAPButtons = new List<IAPCustomButton>();
    40.     private Dictionary<string, IAPCustomButton> m_ProductUIs = new Dictionary<string, IAPCustomButton>();
    41.  
    42. #if RECEIPT_VALIDATION
    43.     private CrossPlatformValidator validator;
    44. #endif
    45.  
    46.     public static event Action transactionComplete = delegate{};
    47.  
    48.  
    49.     /// <summary>
    50.     /// Called when Unity IAP is ready to make purchases.
    51.     /// </summary>
    52.     public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    53.     {
    54.         m_Controller = controller;
    55.         m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();
    56.         m_SamsungExtensions = extensions.GetExtension<ISamsungAppsExtensions>();
    57.         m_MicrosoftExtensions = extensions.GetExtension<IMicrosoftExtensions>();
    58.         m_TransactionHistoryExtensions = extensions.GetExtension<ITransactionHistoryExtensions>();
    59.         m_GooglePlayStoreExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
    60.  
    61.         // On Apple platforms we need to handle deferred purchases caused by Apple's Ask to Buy feature.
    62.         // On non-Apple platforms this will have no effect; OnDeferred will never be called.
    63.         m_AppleExtensions.RegisterPurchaseDeferredListener(OnDeferred);
    64.  
    65.         // Sample code for expose product sku details for apple store
    66.         Dictionary<string, string> product_details = m_AppleExtensions.GetProductDetails();
    67.         Debug.Log("MYDEBUG - product details: " + string.Join("\n", product_details));
    68.  
    69.  
    70.         Debug.Log("MYDEBUG - Available items:");
    71.         foreach (var item in controller.products.all)
    72.         {
    73.             if (item.availableToPurchase)
    74.             {
    75.                 Debug.Log(string.Join(" - ",
    76.                     new[]
    77.                     {
    78.                         item.metadata.localizedTitle,
    79.                         item.metadata.localizedDescription,
    80.                         item.metadata.isoCurrencyCode,
    81.                         item.metadata.localizedPrice.ToString(),
    82.                         item.metadata.localizedPriceString,
    83.                         item.transactionID,
    84.                         item.receipt
    85.                     }));
    86. #if INTERCEPT_PROMOTIONAL_PURCHASES
    87.                 // Set all these products to be visible in the user's App Store according to Apple's Promotional IAP feature
    88.                 // https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/PromotingIn-AppPurchases/PromotingIn-AppPurchases.html
    89.                 m_AppleExtensions.SetStorePromotionVisibility(item, AppleStorePromotionVisibility.Show);
    90. #endif
    91.             }
    92.         }
    93.  
    94.         // Populate the product menu now that we have Products
    95.         AddProductUIs(m_Controller.products.all);
    96.  
    97.         LogProductDefinitions();
    98.     }
    99.  
    100.     /// <summary>
    101.     /// This will be called when a purchase completes.
    102.     /// </summary>
    103.     public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    104.     {
    105.         Debug.Log("MYDEBUG - Purchase OK: " + e.purchasedProduct.definition.id);
    106.         Debug.Log("MYDEBUG - Receipt: " + e.purchasedProduct.receipt);
    107.  
    108.         m_PurchaseInProgress = false;
    109.         processingBlocker.SetActive(m_PurchaseInProgress);
    110.  
    111.         bool validPurchase = true; // Presume valid for platforms with no R.V.
    112.  
    113. #if RECEIPT_VALIDATION // Local validation is available for GooglePlay, and Apple stores
    114.         if (m_IsGooglePlayStoreSelected ||
    115.             Application.platform == RuntimePlatform.IPhonePlayer ||
    116.             Application.platform == RuntimePlatform.OSXPlayer ||
    117.             Application.platform == RuntimePlatform.tvOS) {
    118.             try {
    119.                 var result = validator.Validate(e.purchasedProduct.receipt);
    120.                 Debug.Log("MYDEBUG - Receipt is valid. Contents:");
    121.                 foreach (IPurchaseReceipt productReceipt in result) {
    122.                     Debug.Log(productReceipt.productID);
    123.                     Debug.Log(productReceipt.purchaseDate);
    124.                     Debug.Log(productReceipt.transactionID);
    125.  
    126.                     GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
    127.                     if (null != google) {
    128.                         Debug.Log(google.purchaseState);
    129.                         Debug.Log(google.purchaseToken);
    130.                     }
    131.  
    132.                     AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
    133.                     if (null != apple) {
    134.                         Debug.Log(apple.originalTransactionIdentifier);
    135.                         Debug.Log(apple.subscriptionExpirationDate);
    136.                         Debug.Log(apple.cancellationDate);
    137.                         Debug.Log(apple.quantity);
    138.                     }
    139.  
    140.                     // For improved security, consider comparing the signed
    141.                     // IPurchaseReceipt.productId, IPurchaseReceipt.transactionID, and other data
    142.                     // embedded in the signed receipt objects to the data which the game is using
    143.                     // to make this purchase.
    144.                 }
    145.             } catch (IAPSecurityException ex) {
    146.                 Debug.Log("MYDEBUG - Invalid receipt, not unlocking content. " + ex);
    147.                 validPurchase = false;
    148.                 return PurchaseProcessingResult.Complete;
    149.             }
    150.         }
    151. #endif
    152.  
    153.         // Unlock content from purchases here.
    154. #if USE_PAYOUTS
    155.         if (e.purchasedProduct.definition.payouts != null) {
    156.             Debug.Log("MYDEBUG - Purchase complete, paying out based on defined payouts");
    157.             foreach (var payout in e.purchasedProduct.definition.payouts) {
    158.                 Debug.Log(string.Format("Granting {0} {1} {2} {3}", payout.quantity, payout.typeString, payout.subtype, payout.data));
    159.             }
    160.         }
    161. #endif
    162.  
    163.         if (validPurchase)
    164.         {
    165.             // Unlock the appropriate content here.
    166.             RewardSuccessfulPurchase(e.purchasedProduct);
    167.         }
    168.  
    169.         transactionComplete?.Invoke();
    170.  
    171.         // Indicate if we have handled this purchase.
    172.         //   PurchaseProcessingResult.Complete: ProcessPurchase will not be called
    173.         //     with this product again, until next purchase.
    174.         //   PurchaseProcessingResult.Pending: ProcessPurchase will be called
    175.         //     again with this product at next app launch. Later, call
    176.         //     m_Controller.ConfirmPendingPurchase(Product) to complete handling
    177.         //     this purchase. Use to transactionally save purchases to a cloud
    178.         //     game service.
    179. #if DELAY_CONFIRMATION
    180.         StartCoroutine(ConfirmPendingPurchaseAfterDelay(e.purchasedProduct));
    181.         return PurchaseProcessingResult.Pending;
    182. #else
    183.         UpdateProductUI(e.purchasedProduct);
    184.         return PurchaseProcessingResult.Complete;
    185. #endif
    186.     }
    187.  
    188.     /// <summary>
    189.     /// This will be called if an attempted purchase fails.
    190.     /// </summary>
    191.     public void OnPurchaseFailed(Product product, PurchaseFailureReason r)
    192.     {
    193.         Debug.Log("MYDEBUG - Purchase failed: " + product.definition.id);
    194.         Debug.Log(r);
    195.  
    196.         // Detailed debugging information
    197.         Debug.Log("MYDEBUG - Store specific error code: " + m_TransactionHistoryExtensions.GetLastStoreSpecificPurchaseErrorCode());
    198.         if (m_TransactionHistoryExtensions.GetLastPurchaseFailureDescription() != null)
    199.         {
    200.             Debug.Log("MYDEBUG - Purchase failure description message: " +
    201.                       m_TransactionHistoryExtensions.GetLastPurchaseFailureDescription().message);
    202.         }
    203.  
    204.         m_PurchaseInProgress = false;
    205.         processingBlocker.SetActive(m_PurchaseInProgress);
    206.  
    207.         Debug.Log("MYDEBUG - PURCHASE FAILED");
    208.  
    209.         string failReasonText = "";
    210.  
    211.         if (r == PurchaseFailureReason.DuplicateTransaction)
    212.         {
    213.             failReasonText = "PURCHASE ERROR: DuplicateTransaction";
    214.             Debug.Log(failReasonText);
    215.             RewardSuccessfulPurchase(product); //this error happens even after the credit card has been charged but the app could not report back that transaction is complete. hence, reward player with item to be safe.
    216.         }
    217.  
    218.         if (r == PurchaseFailureReason.Unknown)
    219.         {
    220.             failReasonText = "PURCHASE ERROR: Unknown";
    221.             Debug.Log(failReasonText);
    222.         }
    223.  
    224.         if (r == PurchaseFailureReason.UserCancelled)
    225.         {
    226.             failReasonText = "PURCHASE ERROR: UserCancelled";
    227.             Debug.Log(failReasonText);
    228.         }
    229.  
    230.         if (r == PurchaseFailureReason.ExistingPurchasePending)
    231.         {
    232.             failReasonText = "PURCHASE ERROR: ExistingPurchasePending";
    233.             Debug.Log(failReasonText);
    234.         }
    235.  
    236.  
    237.         Analytics.CustomEvent("playerPurchaseFailed", new Dictionary<string, object>
    238.         {
    239.             { "itemFailedToPurchase", product.definition.id },
    240.             { "reason", failReasonText }
    241.         });
    242.  
    243.         transactionComplete?.Invoke();
    244.     }
    245.  
    246.     /// <summary>
    247.     /// Called when Unity IAP encounters an unrecoverable initialization error.
    248.     ///
    249.     /// Note that this will not be called if Internet is unavailable; Unity IAP
    250.     /// will attempt initialization until it becomes available.
    251.     /// </summary>
    252.     public void OnInitializeFailed (InitializationFailureReason error)
    253.     {
    254.         Debug.Log("MYDEBUG - IAPManager Error: Failed to initialize.");
    255.  
    256.         switch (error)
    257.         {
    258.             case InitializationFailureReason.AppNotKnown:
    259.                 Debug.LogError("Is your App correctly uploaded on the relevant publisher console?");
    260.                 break;
    261.             case InitializationFailureReason.PurchasingUnavailable:
    262.                 // Ask the user if billing is disabled in device settings.
    263.                 Debug.Log("MYDEBUG - Billing disabled!");
    264.                 break;
    265.             case InitializationFailureReason.NoProductsAvailable:
    266.                 // Developer configuration error; check product metadata.
    267.                 Debug.Log("MYDEBUG - No products available for purchase!");
    268.                 break;
    269.         }
    270.     }
    271.  
    272.     private void Awake()
    273.     {
    274.         var module = StandardPurchasingModule.Instance();
    275.  
    276.         // The FakeStore supports: no-ui (always succeeding), basic ui (purchase pass/fail), and
    277.         // developer ui (initialization, purchase, failure code setting). These correspond to
    278.         // the FakeStoreUIMode Enum values passed into StandardPurchasingModule.useFakeStoreUIMode.
    279.         module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
    280.  
    281.         var builder = ConfigurationBuilder.Instance(module);
    282.  
    283.         // Set this to true to enable the Microsoft IAP simulator for local testing.
    284.         builder.Configure<IMicrosoftConfiguration>().useMockBillingSystem = false;
    285.  
    286.         m_IsGooglePlayStoreSelected =
    287.             Application.platform == RuntimePlatform.Android && module.appStore == AppStore.GooglePlay;
    288.  
    289.         // Use the products defined in the IAP Catalog GUI.
    290.         // E.g. Menu: "Window" > "Unity IAP" > "IAP Catalog", then add products, then click "App Store Export".
    291.         var catalog = ProductCatalog.LoadDefaultCatalog();
    292.  
    293.         foreach (var product in catalog.allValidProducts)
    294.         {
    295.             if (product.allStoreIDs.Count > 0)
    296.             {
    297.                 var ids = new IDs();
    298.                 foreach (var storeID in product.allStoreIDs)
    299.                 {
    300.                     ids.Add(storeID.id, storeID.store);
    301.                 }
    302.                 builder.AddProduct(product.id, product.type, ids);
    303.             }
    304.             else
    305.             {
    306.                 builder.AddProduct(product.id, product.type);
    307.             }
    308.         }
    309.  
    310.         // Write Amazon's JSON description of our products to storage when using Amazon's local sandbox.
    311.         // This should be removed from a production build.
    312.         //builder.Configure<IAmazonConfiguration>().WriteSandboxJSON(builder.products);
    313.  
    314.         // This enables simulated purchase success for Samsung IAP.
    315.         // You would remove this, or set to SamsungAppsMode.Production, before building your release package.
    316.         builder.Configure<ISamsungAppsConfiguration>().SetMode(SamsungAppsMode.AlwaysSucceed);
    317.         // This records whether we are using Samsung IAP. Currently ISamsungAppsExtensions.RestoreTransactions
    318.         // displays a blocking Android Activity, so:
    319.         // A) Unity IAP does not automatically restore purchases on Samsung Galaxy Apps
    320.         // B) IAPDemo (this) displays the "Restore" GUI button for Samsung Galaxy Apps
    321.         m_IsSamsungAppsStoreSelected =
    322.             Application.platform == RuntimePlatform.Android && module.appStore == AppStore.SamsungApps;
    323.  
    324. #if INTERCEPT_PROMOTIONAL_PURCHASES
    325.         // On iOS and tvOS we can intercept promotional purchases that come directly from the App Store.
    326.         // On other platforms this will have no effect; OnPromotionalPurchase will never be called.
    327.         builder.Configure<IAppleConfiguration>().SetApplePromotionalPurchaseInterceptorCallback(OnPromotionalPurchase);
    328.         Debug.Log("MYDEBUG - Setting Apple promotional purchase interceptor callback");
    329. #endif
    330.  
    331. #if RECEIPT_VALIDATION
    332.         string appIdentifier;
    333.         #if UNITY_5_6_OR_NEWER
    334.         appIdentifier = Application.identifier;
    335.         #else
    336.         appIdentifier = Application.bundleIdentifier;
    337.         #endif
    338.         validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), appIdentifier);
    339. #endif
    340.  
    341.         // Now we're ready to initialize Unity IAP.
    342.         UnityPurchasing.Initialize(this, builder);
    343.     }
    344.  
    345.     /// <summary>
    346.     /// This will be called after a call to IAppleExtensions.RestoreTransactions().
    347.     /// </summary>
    348.     private void OnTransactionsRestored(bool success)
    349.     {
    350.         processingBlocker.SetActive(false);
    351.  
    352.         transactionComplete?.Invoke();
    353.  
    354.         Debug.Log("MYDEBUG - Transactions restored." + success);
    355.     }
    356.  
    357.     /// <summary>
    358.     /// iOS Specific.
    359.     /// This is called as part of Apple's 'Ask to buy' functionality,
    360.     /// when a purchase is requested by a minor and referred to a parent
    361.     /// for approval.
    362.     ///
    363.     /// When the purchase is approved or rejected, the normal purchase events
    364.     /// will fire.
    365.     /// </summary>
    366.     /// <param name="item">Item.</param>
    367.     private void OnDeferred(Product item)
    368.     {
    369.         Debug.Log("MYDEBUG - Purchase deferred: " + item.definition.id);
    370.     }
    371.  
    372.     private void LogProductDefinitions()
    373.     {
    374.         var products = m_Controller.products.all;
    375.         foreach (var product in products)
    376.         {
    377. #if UNITY_5_6_OR_NEWER
    378.             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"));
    379. #else
    380.             Debug.Log(string.Format("id: {0}\nstore-specific id: {1}\ntype: {2}\n", product.definition.id,
    381.                 product.definition.storeSpecificId, product.definition.type.ToString()));
    382. #endif
    383.         }
    384.     }
    385.  
    386.     public void PurchaseButtonClick(string productID)
    387.     {
    388.         if (m_PurchaseInProgress == true)
    389.         {
    390.             Debug.Log("MYDEBUG - Please wait, purchase in progress");
    391.             return;
    392.         }
    393.  
    394.         if (m_Controller == null)
    395.         {
    396.             Debug.LogError("Purchasing is not initialized");
    397.             return;
    398.         }
    399.  
    400.         if (m_Controller.products.WithID(productID) == null)
    401.         {
    402.             Debug.LogError("No product has id " + productID);
    403.             return;
    404.         }
    405.  
    406.         // Don't need to draw our UI whilst a purchase is in progress.
    407.         // This is not a requirement for IAP Applications but makes the demo
    408.         // scene tidier whilst the fake purchase dialog is showing.
    409.         m_PurchaseInProgress = true;
    410.         processingBlocker.SetActive(m_PurchaseInProgress);
    411.  
    412.         //Sample code how to add accountId in developerPayload to pass it to getBuyIntentExtraParams
    413.         //Dictionary<string, string> payload_dictionary = new Dictionary<string, string>();
    414.         //payload_dictionary["accountId"] = "Faked account id";
    415.         //payload_dictionary["developerPayload"] = "Faked developer payload";
    416.         //m_Controller.InitiatePurchase(m_Controller.products.WithID(productID), MiniJson.JsonEncode(payload_dictionary));
    417.         m_Controller.InitiatePurchase(m_Controller.products.WithID(productID), "developerPayload");
    418.  
    419.     }
    420.  
    421.     public void RestoreButtonClick()
    422.     {
    423.         if (m_IsSamsungAppsStoreSelected)
    424.         {
    425.             m_SamsungExtensions.RestoreTransactions(OnTransactionsRestored);
    426.             processingBlocker.SetActive(true);
    427.         }
    428.         else if (Application.platform == RuntimePlatform.WSAPlayerX86 ||
    429.                  Application.platform == RuntimePlatform.WSAPlayerX64 ||
    430.                  Application.platform == RuntimePlatform.WSAPlayerARM)
    431.         {
    432.             m_MicrosoftExtensions.RestoreTransactions();
    433.         }
    434.         else if (m_IsGooglePlayStoreSelected)
    435.         {
    436.             m_GooglePlayStoreExtensions.RestoreTransactions(OnTransactionsRestored);
    437.             processingBlocker.SetActive(true);
    438.         }
    439.         else
    440.         {
    441.             m_AppleExtensions.RestoreTransactions(OnTransactionsRestored);
    442.             processingBlocker.SetActive(true);
    443.         }
    444.     }
    445.  
    446.     private void AddProductUIs(Product[] products)
    447.     {
    448.         foreach (var p in products)
    449.         {
    450.             var newProductUI = customIAPButtons.Find(cb => cb.productID == p.definition.id);
    451.  
    452.             if (newProductUI != null)
    453.             {
    454.                 newProductUI.SetProduct(p, PurchaseButtonClick);
    455.                 m_ProductUIs[p.definition.id] = newProductUI;
    456.             }
    457.             else
    458.                 Debug.Log("MYDEBUG - CustomIAPButton of " + p.definition.id + " could not be found!");
    459.         }
    460.     }
    461.  
    462.     private void UpdateProductUI(Product p)
    463.     {
    464.         if (m_ProductUIs.ContainsKey(p.definition.id))
    465.         {
    466.             m_ProductUIs[p.definition.id].SetProduct(p, PurchaseButtonClick);
    467.         }
    468.     }
    469.  
    470.     private void RewardSuccessfulPurchase(Product product)
    471.     {
    472.         Debug.Log("MYDEBUG - PURCHASE COMPLETE: " + product.definition.id);
    473.  
    474.         switch (product.definition.id)
    475.         {
    476.             case "com.francisEntereso.FlutterBoost.flutterBoostFullVersion":
    477.                 Player.instance.savedProgress.EnableFullVersion(true);
    478.  
    479.                 Player.instance.savedProgress.UpdateTotalGameGold(50000);
    480.  
    481.                 break;
    482.             case "com.francisEntereso.FlutterBoost.flutterBoostBuy100000g":
    483.                 Player.instance.savedProgress.UpdateTotalGameGold(100000);
    484.                 break;
    485.             case "com.francisEntereso.FlutterBoost.flutterBoostBuy250000g":
    486.                 Player.instance.savedProgress.UpdateTotalGameGold(250000);
    487.                 break;
    488.         }
    489.        
    490.         AudioManager.instance.Play(Types.SoundName.goldPaid);
    491.  
    492.         Analytics.CustomEvent("playerPurchaseSuccess", new Dictionary<string, object>
    493.         {
    494.             { "itemPurchased", product.definition.id }
    495.         });
    496.     }
    497.  
    498. #if INTERCEPT_PROMOTIONAL_PURCHASES
    499.     private void OnPromotionalPurchase(Product item) {
    500.         Debug.Log("MYDEBUG - Attempted promotional purchase: " + item.definition.id);
    501.  
    502.         // Promotional purchase has been detected. Handle this event by, e.g. presenting a parental gate.
    503.         // Here, for demonstration purposes only, we will wait five seconds before continuing the purchase.
    504.         StartCoroutine(ContinuePromotionalPurchases());
    505.     }
    506.  
    507.     private IEnumerator ContinuePromotionalPurchases()
    508.     {
    509.         Debug.Log("MYDEBUG - Continuing promotional purchases in 5 seconds");
    510.         yield return new WaitForSeconds(5);
    511.         Debug.Log("MYDEBUG - Continuing promotional purchases now");
    512.         m_AppleExtensions.ContinuePromotionalPurchases (); // iOS and tvOS only; does nothing on Mac
    513.     }
    514. #endif
    515. }
     
  40. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    As mentioned several times, start with the Sample IAP Project, upgrade to IAP 3.0.1 test and compare. The code you posted is outdated (and has unnecessary #if statements)
     
  41. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    Thanks for unlocking this thread. I apologize if I seem like I'm not following your suggestions but I did mention, several times as well, that I did pick up your Sample IAP project and did the steps you provided multiple times still to no success. When I found it wasn't working in your Sample Project, I just didn't bother to think to re-implement things in my code after I found RV working with my inelegant solution. Please keep updating me on whatever else you find on this matter.

    But going back to this thread's original problem. I'm now working with Apple DTS and investigating on why the app works perfectly on Testflight but is reporting NoProductsAvailable only during App Review. Also, this issue seems inconsistent as my IAP items do load successfully sometimes during "In Review". The DTS personnel did find a potential bug that may need to be addressed to App Review engineers but he's still in the process of finding out for sure. I'll post an update once it's confirmed.

    One of the things he was speculating — and maybe something you can confirm for me as well because it's related to the internals of UnityIAP — is that the call to "addTransactionObserver" might not be happening early enough after app launch as it should. Does this have to do with when I initialize IAP in my code? And if so, do I have to make sure that initializing UnityIAP is one of the things my app should do first when it starts? Or is it correct to initialize when the player arrives on the IAP screen?
     
  42. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    @francisIsFine Typically if the app is working in TestFlight but not live, then it may be considered an Apple issue. There should be no difference. If the Sample IAP Project isn't working for you either, then I doubt it would be the order of your initialization, correct? But the initialization timing should not matter.
     
  43. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    App Review finally approved the app yesterday but unfortunately, I never got to find out what was causing the issue. Even Apple DTS wasn't able to pinpoint exactly but they suspected it was on their end. Their system status page (link here and screenshot below) updated shortly after my app got approved. It indicated that some maintenance was done to Testflight and AppStore.

    Screen Shot 2021-03-30 at 2.25.48 PM.png

    Since I'm not sure what got fixed exactly or what may have contributed to it, I thought I'd list down some more of the things I tried on my end in case others might find it helpful:

    1. I made sure UnityIAP initialized as early as possible. I placed my IAP script in the first scene and among the first ones to load in the script execution order.

    2. In Xcode after building from Unity, I noticed that "In-App Purchase" is missing under "Capabilities"—wierd—so I added that in.

    3. On App Store Connect, under YourAppName > App Store > In-App Purchases > Manage, I made sure that each item is either on "Ready to Submit" or "Waiting for Review" prior to every submission. To get these statuses, make sure there are no red dots (typically on the Localization section) existing on each of the items' metadata.

    4. If your IAPs are in "Ready to Submit", make sure to attach them to the binary before submitting. I'm including this here so others won't get confused: After you've done this step once, App Store Connect will no longer display the "In App Purchase" section under YourAppName > App Store > YourAppVersion and your IAPs can only get to a "Waiting for Review" status for all your subsequent resubmissions.
     
  44. francisIsFine

    francisIsFine

    Joined:
    Jun 26, 2018
    Posts:
    40
    Also about the NotImplementedException topic, I managed to resolve this using the try/catch solution mentioned here. Thanks @JeffDUnity3D for looking into this.
     
    JeffDUnity3D likes this.
  45. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    @francisIsFine Likely an App
    Looks like an Apple issue, not a configuration issue (not your fault!)
     
    francisIsFine likes this.
  46. akashc

    akashc

    Joined:
    Jan 9, 2021
    Posts:
    15
    hi @JeffDUnity3D ,

    My app is also getting rejected by apple with following reason :

    The "YES" button they mentioned is the IAP Button which is working fine in the unity editor 2020.2.3f1.
    Kindly help me.
     
  47. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Is it working in your TestFlight testing? Are you getting ProductsUnavailable during your testing also (the topic of this thread). Unity IAP does not actually connect to any stores when running in the Editor so is not a valid test before publishing. You need to test via TestFlight for Apple or Google closed testing (or internal testing)
     
  48. akashc

    akashc

    Joined:
    Jan 9, 2021
    Posts:
    15
    No it is not even working in Test Flight and the IAP button was not showing anything, but I thought not working in Test Flight is normal as it only works when app gets published thats why I ignored.

    Also,
    I just noticed that we haven't accepted the Paid Apps Agreement of Apple, could it be the cause of not working even in Test Flight?
     
  49. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Yes! You'll always want to ensure your app works in TestFlight before publishing live.
     
  50. akashc

    akashc

    Joined:
    Jan 9, 2021
    Posts:
    15
    Okay it is working now, the problem was that we hadn't accepted the Paid Apps Agreement of Apple at App Store Connect, After accepting that it started working.

    Anyone else who is also facing the issue where IAP button works fine in Unity Editor but doesn't response in Test Flight, Firstly, Just make sure you have accepted both Free and Paid Apps Agreements at App Store connect also you need to add Bank and Tax information as well to get the status of both as Active .[See the Attached Image]
    Note that only Account holder can accept the Terms.
     

    Attached Files: