[Closed] IAP attempts to process a non-existent pending purchase upon initialization

Hi all -

My IAPController.cs class seems to attempt to process a non-existent purchase every time I load it. I store my virtual currency balance in the cloud (using the PlayFab BaaS), which means I use PurchaseProcessingResult.Pending. The error persists even when I completely delete the user profile in the cloud, install it onto a new device, etc.

  • Install the game on a fresh device and with a fresh account
  • Call InitializePurchasing() on game load
  • Download the list of purchasable items from the server (using PlayFabClientAPI.GetStoreItems)
  • On successful download of the store contents, send confirmation that the controller has been initialized
  • EXPECTED: No further action occurs
  • ACTUAL: Begins processing purchase of “vc_bundle_xs”

IAPController.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
using System;
using PlayFab.ClientModels;
using PlayFab;
using LitJson;

public class IAPController : IStoreListener
{
    public static event Action<string> didPurchaseCurrencyBundle;
    public static event Action didInitializeIAPController;

    #region Variables

    public IStoreController controller;
    private IExtensionProvider extensions;
    private Product pendingPurchase;
    public List<StoreItem> store = new List<StoreItem>();

    public static IAPController Instance { get { if (instance == null) { instance = new IAPController(); } return instance; } }
    private static IAPController instance;

    /// <summary>The IDs of all golden cupcake purchases.</summary>
    public List<string> goldenCupcakeIDs = new List<string> { "vc_bundle_xs", "vc_bundle_s", "vc_bundle_m", "vc_bundle_l", "vc_bundle_xl", "vc_bundle_xxl", "vc_bundle_xxxl" };

    public Dictionary<string, string> goldenCupcakeNames = new Dictionary<string, string>()
    {
        { "vc_bundle_xs", "A Platter of Golden Cupcakes" },
        { "vc_bundle_s", "A Petite Box of Golden Cupcakes" },
        { "vc_bundle_m", "A Large Box of Golden Cupcakes" },
        { "vc_bundle_l", "A Tray of Golden Cupcakes" },
        { "vc_bundle_xl", "A Pyramid of Golden Cupcakes" },
        { "vc_bundle_xxl", "A Display of Golden Cupcakes" },
        { "vc_bundle_xxxl", "A Stand of Golden Cupcakes" },
    };

    /// <summary>Setting for whether to validate receipts via PlayFab or not.</summary>
    public enum ValidationMechanism { None, PlayFab }; public ValidationMechanism validationMechanism;

    #endregion

    public void InitializePurchasing()
    {
        if (IsInitialized())
        {
            Debug.Log("IAP Controller already initialized! Exiting");
            return;
        }

        validationMechanism = ValidationMechanism.PlayFab;


        Debug.Log("IAP: Downloading store information from PlayFab.");
        PlayFabClientAPI.GetStoreItems(new GetStoreItemsRequest { StoreId = "vcStore" }, result =>
        {
            store = result.Store;
            var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
            foreach (var item in store) { builder.AddProduct(item.ItemId, ProductType.Consumable); }
            UnityPurchasing.Initialize(this, builder);
        }, Utilities.PlayFabError, null);

    }

    /// <summary>Called when Unity IAP is ready to make purchases.</summary>
    public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { this.controller = controller; this.extensions = extensions;  if (didInitializeIAPController != null) { didInitializeIAPController(); Debug.Log("IAP: UNITY IAP Controller initialized!"); } }

    /// <summary>Called when Unity IAP encounters an unrecoverable initialization error. Note that this will not be called if Internet is unavailable; Unity IAP will attempt initialization until it becomes available.</summary>
    public void OnInitializeFailed(InitializationFailureReason error) { Debug.Log("UNITY IAP: Initialization failed! Reason: " + error.ToString()); }

    /// <summary> Called when a purchase fails.</summary>
    public void OnPurchaseFailed(Product i, PurchaseFailureReason p) { Debug.Log("UNITY IAP: Could not purchase " + i.metadata.localizedTitle + " due to " + p.ToString()); }

    private bool IsInitialized() { return controller != null && extensions != null; }

    /// <summary>Called when a purchase completes. May be called at any time after OnInitialized().</summary>
    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    {
        Debug.Log("IAP: Processing purchase " + e.purchasedProduct.definition.id);

        // Test edge case where product is unknown
        if (e.purchasedProduct == null)
        {
            Debug.LogWarning("Attempted to process purchasewith unknown product. Ignoring");
            return PurchaseProcessingResult.Complete;
        }

        // Test edge case where purchase has no receipt
        if (string.IsNullOrEmpty(e.purchasedProduct.receipt))
        {
            Debug.LogWarning("Attempted to process purchase with no receipt: ignoring");
            return PurchaseProcessingResult.Complete;
        }

        if (validationMechanism == ValidationMechanism.None)
        {
            Debug.Log("UNITY IAP: Successfully purchased" + e.purchasedProduct.metadata.localizedTitle + " with an ID of " + e.purchasedProduct.definition.id);
            didPurchaseCurrencyBundle(e.purchasedProduct.definition.id);
            return PurchaseProcessingResult.Complete;
        }

        else
        {
            Debug.Log("UNITY IAP: Initiating validation of purchase" + e.purchasedProduct.metadata.localizedTitle);
            JsonData receipt = JsonMapper.ToObject(e.purchasedProduct.receipt);
            ValidateIOSReceiptRequest request = new ValidateIOSReceiptRequest
            {
                CurrencyCode = e.purchasedProduct.metadata.isoCurrencyCode,
                PurchasePrice = (int)e.purchasedProduct.metadata.localizedPrice * 100,
                ReceiptData = receipt["Payload"].ToString()
            };

            pendingPurchase = e.purchasedProduct;
            Debug.Log("The pending purchase is " + pendingPurchase.metadata.localizedTitle);

            PlayFabClientAPI.ValidateIOSReceipt(request, result =>
            {
                Debug.Log("UNITY IAP: Receipt successfully validated by PlayFab!");
                controller.ConfirmPendingPurchase(pendingPurchase);
                pendingPurchase = null;
                PlayerController.Instance.GetUserInventory();
            }, (error)=> { Debug.Log("There was an error validating the receipt!"); Debug.Log(error.GenerateErrorReport()); }, null);
            return PurchaseProcessingResult.Pending;
        }
    }

    /// <summary>Start the sequence to purchase this product.</summary>
    public void Purchase(string id) { controller.InitiatePurchase(id); }

}

XCode Debug Log
Click to Show

IAP: Downloading store information from PlayFab.
IAPController:InitializePurchasing()
PlayerController:Launch()
BraincloudController:ReceivedGameConfigurationFromPlayFab(GetTitleDataResult)
PlayFab.Internal.c__AnonStorey01:<>m__1() PlayFab.Internal.<MakeApiCall>c__AnonStorey1:<>m__0(String) PlayFab.Internal.<Post>c__Iterator0:MoveNext() UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) System.IComparable1:CompareTo(T)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

UnloadTime: 4.435750 ms

Unloading 10 unused Assets to reduce memory usage. Loaded Objects now: 12336.
Total: 149.163333 ms (FindLiveObjects: 1.608458 ms CreateObjectMapping: 0.769500 ms MarkObjects: 145.293041 ms DeleteObjects: 1.491750 ms)

UnityIAP StandardPurchasingModule Version: 1.15.0
UnityEngine.Purchasing.StandardPurchasingModule:Instance(AppStore)
IAPController:m__0(GetStoreItemsResult)
PlayFab.Internal.c__AnonStorey01:<>m__1() PlayFab.Internal.<MakeApiCall>c__AnonStorey1:<>m__0(String) PlayFab.Internal.<Post>c__Iterator0:MoveNext() UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) System.IComparable1:CompareTo(T)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

2017-12-16 12:52:48.345684+0100 CandyClickerTest[2964:546212] UnityIAP:Requesting 7 products
2017-12-16 12:52:48.347528+0100 CandyClickerTest[2964:546212] UnityIAP:Requesting product data…
2017-12-16 12:52:49.448775+0100 CandyClickerTest[2964:546212] UnityIAP:Received 7 products
IAP: UNITY IAP Controller initialized!
UnityEngine.Purchasing.PurchasingManager:CheckForInitialization()
UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List1) UnityEngine.Purchasing.AppleStoreImpl:OnProductsRetrieved(String) UnityEngine.Purchasing.Extension.UnityUtil:Update() System.IComparable1:CompareTo(T)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

IAP: Processing purchase vc_bundle_xs /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
IAPController:ProcessPurchase(PurchaseEventArgs)
UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List1) UnityEngine.Purchasing.AppleStoreImpl:OnProductsRetrieved(String) UnityEngine.Purchasing.Extension.UnityUtil:Update() System.IComparable1:CompareTo(T)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

UNITY IAP: Initiating validation of purchase10 Golden Cupcakes /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
IAPController:ProcessPurchase(PurchaseEventArgs)
UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List1) UnityEngine.Purchasing.AppleStoreImpl:OnProductsRetrieved(String) UnityEngine.Purchasing.Extension.UnityUtil:Update() System.IComparable1:CompareTo(T)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

The pending purchase is 10 Golden Cupcakes /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
IAPController:ProcessPurchase(PurchaseEventArgs)
UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List1) UnityEngine.Purchasing.AppleStoreImpl:OnProductsRetrieved(String) UnityEngine.Purchasing.Extension.UnityUtil:Update() System.IComparable1:CompareTo(T)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

2017-12-16 12:52:49.523944+0100 CandyClickerTest[2964:546212] UnityIAP:addTransactionObserver
2017-12-16 12:52:49.524085+0100 CandyClickerTest[2964:546212] UnityIAP:UpdatedTransactions
2017-12-16 12:52:49.529667+0100 CandyClickerTest[2964:546212] UnityIAP UnityEarlyTransactionObserver: Request to initiate queued payments
IAP: Processing purchase vc_bundle_xs /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
IAPController:ProcessPurchase(PurchaseEventArgs)
UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
UnityEngine.Purchasing.JSONStore:OnPurchaseSucceeded(String, String, String)
UnityEngine.Purchasing.Extension.UnityUtil:Update()
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)
System.IComparable`1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

UNITY IAP: Initiating validation of purchase10 Golden Cupcakes /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
IAPController:ProcessPurchase(PurchaseEventArgs)
UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
UnityEngine.Purchasing.JSONStore:OnPurchaseSucceeded(String, String, String)
UnityEngine.Purchasing.Extension.UnityUtil:Update()
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)
System.IComparable`1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

The pending purchase is 10 Golden Cupcakes /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
IAPController:ProcessPurchase(PurchaseEventArgs)
UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
UnityEngine.Purchasing.JSONStore:OnPurchaseSucceeded(String, String, String)
UnityEngine.Purchasing.Extension.UnityUtil:Update()
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)
System.IComparable`1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

There was an error validating the receipt! /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
IAPController:m__2(PlayFabError)
PlayFab.Internal.c__AnonStorey1:<>m__0(String)
PlayFab.Internal.c__Iterator0:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)
System.IComparable`1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

Receipt already used /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
PlayFab.Internal.c__AnonStorey1:<>m__0(String)
PlayFab.Internal.c__Iterator0:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)
System.IComparable`1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

There was an error validating the receipt! /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
IAPController:m__2(PlayFabError)
PlayFab.Internal.c__AnonStorey1:<>m__0(String)
PlayFab.Internal.c__Iterator0:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)
System.IComparable`1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

Receipt already used /// ← DEV COMMENT: THIS SHOULD NOT BE HAPPENING
PlayFab.Internal.c__AnonStorey1:<>m__0(String)
PlayFab.Internal.c__Iterator0:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
System.IComparable1:CompareTo(T) System.IComparable1:CompareTo(T)
System.IComparable`1:CompareTo(T)

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

@GeorgeCH_1 I understand that you are using PurchaseProcessingResult.Pending, when do you finalize the transaction with PurchaseProcessingResult.Complete? Also, what store are you targeting?