using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using UnityEngine.Purchasing.Extension; namespace UnityEngine.Purchasing { /// /// The various kinds of Fake Store UI presentations. /// Requires UIFakeStore variant of FakeStore to function. /// public enum FakeStoreUIMode { /// /// FakeStore by default displays no dialogs. /// Default, /// /// Simple dialog is shown when Purchasing. /// StandardUser, /// /// Dialogs with failure reason code selection when /// Initializing/Retrieving Products and when Purchasing. /// DeveloperUser } internal class FakeStore : JSONStore, IFakeExtensions, INativeStore { protected enum DialogType { Purchase, RetrieveProducts, } public const string Name = "fake"; private IStoreCallback m_Biller; private readonly List m_PurchasedProducts = new List(); public bool purchaseCalled; public bool restoreCalled; public string unavailableProductId { get; set; } public FakeStoreUIMode UIMode = FakeStoreUIMode.Default; // Requires UIFakeStore public override void Initialize(IStoreCallback biller) { m_Biller = biller; base.Initialize(biller); SetNativeStore(this); } // INativeStore public void RetrieveProducts(string json) { var jsonList = (List)MiniJson.JsonDecode(json); var productDefinitions = jsonList.DecodeJSON(Name); StoreRetrieveProducts(new ReadOnlyCollection(productDefinitions.ToList())); } // This is now being used by the INativeStore implementation public void StoreRetrieveProducts(ReadOnlyCollection productDefinitions) { var products = new List(); foreach (var product in productDefinitions) { if (unavailableProductId != product.id) { var metadata = new ProductMetadata("$0.01", "Fake title for " + product.id, "Fake description", "USD", 0.01m); var catalog = ProductCatalog.LoadDefaultCatalog(); if (catalog != null) { foreach (var item in catalog.allProducts) { if (item.id == product.id) { metadata = new ProductMetadata(item.googlePrice.value.ToString(), item.defaultDescription.Title, item.defaultDescription.Description, "USD", item.googlePrice.value); } } } products.Add(new ProductDescription(product.storeSpecificId, metadata)); } } void handleAllowInitializeOrRetrieveProducts(bool allow, InitializationFailureReason failureReason) { if (allow) { m_Biller.OnProductsRetrieved(products); } else { m_Biller.OnSetupFailed(failureReason, null); } } // To mimic typical store behavior, only display RetrieveProducts dialog for developers if (!(UIMode == FakeStoreUIMode.DeveloperUser && StartUI(productDefinitions, DialogType.RetrieveProducts, handleAllowInitializeOrRetrieveProducts))) { // Default non-UI FakeStore RetrieveProducts behavior is to succeed handleAllowInitializeOrRetrieveProducts(true, InitializationFailureReason.AppNotKnown); } } // INativeStore public void Purchase(string productJSON, string developerPayload) { var dic = (Dictionary)MiniJson.JsonDecode(productJSON); string id, storeId, type; ProductType itemType; dic.TryGetValue("id", out var obj); id = obj.ToString(); dic.TryGetValue("storeSpecificId", out obj); storeId = obj.ToString(); dic.TryGetValue("type", out obj); type = obj.ToString(); itemType = Enum.IsDefined(typeof(ProductType), type) ? (ProductType)Enum.Parse(typeof(ProductType), type) : ProductType.Consumable; // This doesn't currently deal with "enabled" and "payouts" that could be included in the JSON var product = new ProductDefinition(id, storeId, itemType); FakePurchase(product, developerPayload); } void FakePurchase(ProductDefinition product, string developerPayload) { purchaseCalled = true; // Our billing systems should only keep track of non consumables. if (product.type != ProductType.Consumable) { m_PurchasedProducts.Add(product.storeSpecificId); } void handleAllowPurchase(bool allow, PurchaseFailureReason failureReason) { if (allow) { base.OnPurchaseSucceeded(product.storeSpecificId, "ThisIsFakeReceiptData", Guid.NewGuid().ToString()); } else { if (failureReason == (PurchaseFailureReason)Enum.Parse(typeof(PurchaseFailureReason), "Unknown")) { failureReason = PurchaseFailureReason.UserCancelled; } var failureDescription = new PurchaseFailureDescription(product.storeSpecificId, failureReason, "failed a fake store purchase"); OnPurchaseFailed(failureDescription); } } if (!StartUI(product, DialogType.Purchase, handleAllowPurchase)) { // Default non-UI FakeStore purchase behavior is to succeed handleAllowPurchase(true, (PurchaseFailureReason)Enum.Parse(typeof(PurchaseFailureReason), "Unknown")); } } public void RestoreTransactions(Action callback) { restoreCalled = true; foreach (var product in m_PurchasedProducts) { m_Biller.OnPurchaseSucceeded(product, /*lang=json,strict*/ "{ \"this\" : \"is a fake receipt\" }", "1"); } callback?.Invoke(true, null); } // INativeStore public void FinishTransaction(string productJSON, string transactionID) { // we need this for INativeStore but won't be using } public override void FinishTransaction(ProductDefinition product, string transactionId) { } public void RegisterPurchaseForRestore(string productId) { m_PurchasedProducts.Add(productId); } /// /// Implemented by UIFakeStore derived class /// /// true, if UI was started, false otherwise. protected virtual bool StartUI(object model, DialogType dialogType, Action callback) { return false; } } }