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)