Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

IAP 2.2.7 iOS Sandbox Consumable item says "Already bought"

Discussion in 'Unity IAP' started by dfarrow, Apr 16, 2021.

  1. dfarrow

    dfarrow

    Joined:
    Jan 10, 2015
    Posts:
    3
    Hi, I have a Unity App using Unity IAP 2.2.7. I was having no trouble testing this app on iOS and purchasing "Consumable" products repeatedly until 2021.04.15. Now, when I login to a new device I can make 1 purchase of each item but after that I see "This item is already bought..it will be restored automatically". I can then move to another device, use the same Apple ID and get the consumable again, but then will not be able to purchase on that device (even after reboot/reinstall of app).

    However, when I then close the app and re-open, the application appears to be finalizing any transactions at start up. All of my purchases are for Consumable IAP and I return PurchaseProcessingResult.Complete; immediately in the ProcessPurchase() function, so I am not sure why purchases are still waiting to complete after I restart the app. I see many of these lines at start in the logs:

    2021-04-16 16:03:50.320834-0400 avatars[448:27326] UnityIAP: Add transaction observer
    2021-04-16 16:03:50.320943-0400 avatars[448:27326] UnityIAP: UpdatedTransactions
    2021-04-16 16:03:50.324387-0400 avatars[448:27326] UnityIAP UnityEarlyTransactionObserver:
    Request to initiate queued payments
    Already recorded transaction 1000000801606451
    UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)
    UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
    UnityEngine.Logger:Log(Object)
    UnityEngine.Purchasing.PurchasingManager:processPurchaseIfNew(Product)
    UnityEngine.Purchasing.PurchasingManager:OnPurchaseSucceeded(String, String, String)
    UnityEngine.Purchasing.JSONStore:OnPurchaseSucceeded(String, String, String)
    UnityEngine.Purchasing.AppleStoreImpl:OnPurchaseSucceeded(String, String, String)
    UnityEngine.Purchasing.AppleStoreImpl:processMessage(String, String, String, String)
    UnityEngine.Purchasing.<>c__DisplayClass37_0:<MessageCallback>b__0()
    System.Action:Invoke()
    UnityEngine.Purchasing.Extension.UnityUtil:Update()
    (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    2021-04-16 16:03:50.349388-0400 avatars[448:27326] UnityIAP: Finishing transaction
    1000000801606451
    Already recorded transaction 1000000801843965
    UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)
    UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
    UnityEngine.Logger:Log(Object)
    UnityEngine.Purchasing.PurchasingManager:processPurchaseIfNew(Product)
    UnityEngine.Purchasing.PurchasingManager:OnPurchaseSucceeded(String, String, String)
    UnityEngine.Purchasing.JSONStore:OnPurchaseSucceeded(String, String, String)
    UnityEngine.Purchasing.AppleStoreImpl:OnPurchaseSucceeded(String, String, String)
    UnityEngine.Purchasing.AppleStoreImpl:processMessage(String, String, String, String)
    UnityEngine.Purchasing.<>c__DisplayClass37_0:<MessageCallback>b__0()
    System.Action:Invoke()
    UnityEngine.Purchasing.Extension.UnityUtil:Update()
    2021-04-16 16:03:50.351335-0400 avatars[448:27326] UnityIAP: Finishing transaction
    1000000801843965


    and then if I try to make a purchase, it seems the same transaction IDs are still hanging around:

    2021-04-16 16:05:02.427319-0400 avatars[448:27326] UnityIAP: UpdatedTransactions
    -> applicationWillResignActive()
    -> applicationDidBecomeActive()
    -> applicationWillResignActive()
    2021-04-16 16:05:25.795599-0400 avatars[448:27326] UnityIAP: UpdatedTransactions
    2021-04-16 16:05:25.795824-0400 avatars[448:27326] UnityIAP: DuplicateTransaction error with product
    400c and transactionId 1000000801843965
    -> applicationDidBecomeActive()
    onPurchaseFailedEvent({0}): 400c
    UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)
    UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
    UnityEngine.Logger:Log(String, Object)
    UnityEngine.Purchasing.PurchasingManager:OnPurchaseFailed(PurchaseFailureDescription)
    UnityEngine.Purchasing.JSONStore:OnPurchaseFailed(PurchaseFailureDescription, String)
    UnityEngine.Purchasing.JSONStore:OnPurchaseFailed(String)
    UnityEngine.Purchasing.AppleStoreImpl:processMessage(String, String, String, String)
    UnityEngine.Purchasing.<>c__DisplayClass37_0:<MessageCallback>b__0()



    My code looks like this:

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6. using System.Linq.Expressions;
    7. using Celery.Composer;
    8. using UnityEngine;
    9. using UnityEngine.Purchasing;
    10.  
    11. namespace Celery.Store
    12. {
    13.     public class InAppPurchaseStoreListener : MonoBehaviour, IStoreListener {
    14.  
    15.         public static InAppPurchaseStoreListener Instance = null;
    16.         public IStoreController Controller;
    17.         public PurchaseEvent PurchaseEvent = new PurchaseEvent();
    18.  
    19.         private IExtensionProvider extensions;
    20.         private IAppleExtensions m_AppleExtensions;
    21.         private IGooglePlayStoreExtensions m_GooglePlayStoreExtensions;
    22. #pragma warning disable 0414
    23.         private bool m_IsGooglePlayStoreSelected;
    24. #pragma warning restore 0414
    25.  
    26.         public void Awake() {
    27.  
    28.             if (Instance != null && Instance != this) {
    29.                 Destroy(this.gameObject);
    30.                 return;
    31.             } else {
    32.                 Instance = this;
    33.             }
    34.  
    35.             var module = StandardPurchasingModule.Instance();
    36.             // The FakeStore supports: no-ui (always succeeding), basic ui (purchase pass/fail), and
    37.             // developer ui (initialization, purchase, failure code setting). These correspond to
    38.             // the FakeStoreUIMode Enum values passed into StandardPurchasingModule.useFakeStoreUIMode.
    39.             module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
    40.  
    41.             var builder = ConfigurationBuilder.Instance(module);
    42.             // Set this to true to enable the Microsoft IAP simulator for local testing.
    43.             builder.Configure<IMicrosoftConfiguration>().useMockBillingSystem = false;
    44.  
    45.             m_IsGooglePlayStoreSelected = Application.platform == RuntimePlatform.Android && module.appStore == AppStore.GooglePlay;
    46.  
    47.             var catalog = ProductCatalog.LoadDefaultCatalog();
    48.             SetupProducts(catalog, builder);
    49.  
    50.             try {
    51.                 UnityPurchasing.Initialize(this, builder);
    52.             } catch (Exception ex)
    53.             {
    54.                 Debug.Log("An error occured calling UnityPurchasing.Initialize() -> " + ex.Message);
    55.             }
    56.  
    57.         }
    58.  
    59.         /// <summary>
    60.         /// Use the products defined in the IAP Catalog GUI.
    61.         ///  E.g. Menu: "Window" > "Unity IAP" > "IAP Catalog", then add products...
    62.         ///  These should correspond to what is set up in the online App stores (Apple, Google, etc.)
    63.         /// (you can also click "App Store Export" to create store manifests).
    64.         /// </summary>
    65.         /// <param name="catalog">ProductCatalog</param>
    66.         /// <param name="builder">ConfigurationBuilder</param>
    67.         private void SetupProducts(ProductCatalog catalog, ConfigurationBuilder builder) {
    68.             foreach (var product in catalog.allValidProducts) {
    69.                 if (product.allStoreIDs.Count > 0) {
    70.                     var ids = new IDs();
    71.                     foreach (var storeID in product.allStoreIDs) {
    72.                         ids.Add(storeID.id, storeID.store);
    73.                     }
    74.                     builder.AddProduct(product.id, product.type, ids);
    75.                 } else {
    76.                     builder.AddProduct(product.id, product.type);
    77.                 }
    78.             }
    79.         }
    80.  
    81.         /// <summary>
    82.         /// Called when Unity IAP is ready to make purchases.
    83.         /// </summary>
    84.         public void OnInitialized(IStoreController controller, IExtensionProvider extensions) {
    85.             this.Controller = controller;
    86.             this.extensions = extensions;
    87.  
    88.             m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();
    89.             m_GooglePlayStoreExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
    90.  
    91.             // On Apple platforms we need to handle deferred purchases caused by Apple's Ask to Buy feature.
    92.             // On non-Apple platforms this will have no effect; OnDeferred will never be called.
    93.             m_AppleExtensions.RegisterPurchaseDeferredListener(OnDeferred);
    94.      
    95.         }
    96.  
    97.         /// <summary>
    98.         /// Restore purchases previously made by this customer. Some platforms automatically restore purchases.
    99.         /// Apple currently requires explicit purchase restoration for IAP.
    100.         /// </summary>
    101.         public void RestorePurchases() {
    102.             // If Purchasing has not yet been set up ...
    103.             if (Controller == null) {
    104.                 // ... report the situation and stop restoring. Consider either waiting longer, or retrying initialization.
    105.  
    106.                 return;
    107.             }
    108.      
    109.             // If we are running on an Apple device ...
    110.             if (Application.platform == RuntimePlatform.IPhonePlayer ||
    111.                 Application.platform == RuntimePlatform.OSXPlayer) {
    112.                 // ... begin restoring purchases
    113.                 // Fetch the Apple store-specific subsystem.
    114.                 var apple = extensions.GetExtension<IAppleExtensions>();
    115.                 // Begin the asynchronous process of restoring purchases. Expect a confirmation response in the Action<bool> below, and ProcessPurchase if there are previously purchased products to restore.
    116.                 apple.RestoreTransactions((result) => {
    117.                     // The first phase of restoration. If no more responses are received on ProcessPurchase then no purchases are available to be restored.
    118.                     Debug.Log("RestoreTransactions " + result.ToString());
    119.  
    120.  
    121.                 });
    122.             } else {
    123.                 // Otherwise ...We are not running on an Apple device. No work is necessary to restore purchases.
    124.  
    125.             }
    126.         }
    127.  
    128.         /// <summary>
    129.         /// This will be called after a call to IAppleExtensions.RestoreTransactions().
    130.         /// </summary>
    131.         private void OnTransactionsRestored(bool success) {
    132.             Debug.Log("OnTransactionsRestored " );
    133.         }
    134.  
    135.         /// <summary>
    136.         /// iOS Specific.
    137.         /// This is called as part of Apple's 'Ask to buy' functionality,
    138.         /// when a purchase is requested by a minor and referred to a parent
    139.         /// for approval.
    140.         ///
    141.         /// When the purchase is approved or rejected, the normal purchase events
    142.         /// will fire.
    143.         /// </summary>
    144.         /// <param name="item">Item.</param>
    145.         private void OnDeferred(Product item) {
    146.  
    147.         }
    148.  
    149.         /// <summary>
    150.         /// Called when Unity IAP encounters an unrecoverable initialization error.
    151.         ///
    152.         /// Note that this will not be called if Internet is unavailable; Unity IAP
    153.         /// will attempt initialization until it becomes available.
    154.         /// </summary>
    155.         public void OnInitializeFailed(InitializationFailureReason error) {
    156.  
    157.         }
    158.  
    159.         /// <summary>
    160.         /// Method called when the user presses a 'buy' button to start the purchase process.
    161.         /// </summary>
    162.         public void OnPurchaseClicked(string productId) {
    163.  
    164.             Controller.InitiatePurchase(productId);
    165.         }
    166.  
    167.         /// <summary>
    168.         /// Called when a purchase completes.
    169.         ///
    170.         /// May be called at any time after OnInitialized().
    171.         /// </summary>
    172.         public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e) {
    173.             PurchaseEvent.Invoke(true);
    174.             Debug.Log("IAP ProcessPurchase");
    175.  
    176.             return PurchaseProcessingResult.Complete;
    177.         }
    178.  
    179.         /// <summary>
    180.         /// Called when a purchase fails.
    181.         /// </summary>
    182.         public void OnPurchaseFailed(Product product, PurchaseFailureReason reason) {
    183.             Debug.Log("IAP OnPurchaseFailed");
    184.             PurchaseEvent.Invoke(false);
    185.         }
    186.  
    187.     }
    188. }
    Android works fine. I see many similar threads to this but none seem to address what I am experiencing...how do I make the queued items complete successfully? Also, I should mention this is in the Sandbox environment, we haven't released this due to this issue. I did see this thread mentioning Sandbox IAP issues for other developers... https://developer.apple.com/forums/thread/678070?page=1
     
    Last edited: Apr 19, 2021
  2. dfarrow

    dfarrow

    Joined:
    Jan 10, 2015
    Posts:
    3
    Well it appears the problem was on the Apple side...it seems to be fixed as of 2pm ET on 2021/04/19
     
  3. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    It started working for me yesterday 4/18