Game/Library/PackageCache/com.unity.purchasing@2.1.1/Runtime/managed/Purchasing/PurchasingManager.cs
2021-01-16 18:29:12 +01:00

288 lines
11 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Purchasing.Extension;
namespace UnityEngine.Purchasing
{
/// <summary>
/// The main controller for Applications using Unity Purchasing.
/// </summary>
internal class PurchasingManager : IStoreCallback, IStoreController
{
private IStore m_Store;
private IInternalStoreListener m_Listener;
private ILogger m_Logger;
private TransactionLog m_TransactionLog;
private string m_StoreName;
private Action m_AdditionalProductsCallback;
private Action<InitializationFailureReason> m_AdditionalProductsFailCallback;
/// <summary>
/// Stores may opt to disable Unity IAP's transaction log.
/// </summary>
public bool useTransactionLog { get; set; }
internal PurchasingManager(TransactionLog tDb, ILogger logger, IStore store, string storeName)
{
m_TransactionLog = tDb;
m_Store = store;
m_Logger = logger;
m_StoreName = storeName;
useTransactionLog = true;
}
public void InitiatePurchase(Product product)
{
InitiatePurchase(product, string.Empty);
}
public void InitiatePurchase(string productId)
{
InitiatePurchase(productId, string.Empty);
}
public void InitiatePurchase(Product product, string developerPayload)
{
if (null == product)
{
m_Logger.Log("Trying to purchase null Product");
return;
}
if (!product.availableToPurchase)
{
m_Listener.OnPurchaseFailed(product, PurchaseFailureReason.ProductUnavailable);
return;
}
m_Store.Purchase(product.definition, developerPayload);
m_Logger.Log("purchase({0})", product.definition.id);
}
public void InitiatePurchase(string purchasableId, string developerPayload)
{
Product product = products.WithID(purchasableId);
if (null == product)
m_Logger.LogWarning("Unable to purchase unknown product with id: {0}", purchasableId);
InitiatePurchase(product, developerPayload);
}
/// <summary>
/// Where an Application returned ProcessingResult.Pending they can manually
/// finish the transaction by calling this method.
/// </summary>
public void ConfirmPendingPurchase(Product product)
{
if (null == product)
{
m_Logger.Log("Unable to confirm purchase with null Product");
return;
}
if (string.IsNullOrEmpty(product.transactionID))
{
m_Logger.Log("Unable to confirm purchase; Product has missing or empty transactionID");
return;
}
if (useTransactionLog)
m_TransactionLog.Record(product.transactionID);
m_Store.FinishTransaction(product.definition, product.transactionID);
}
public ProductCollection products { get; private set; }
/// <summary>
/// Called by our IStore when a purchase succeeds.
/// </summary>
public void OnPurchaseSucceeded(string id, string receipt, string transactionId)
{
var product = products.WithStoreSpecificID(id);
if (null == product)
{
// If is possible for stores to tell us about products we have not yet
// requested details of.
// We should still tell the App in this scenario, albeit with incomplete information.
var definition = new ProductDefinition(id, ProductType.NonConsumable);
product = new Product(definition, new ProductMetadata());
}
var unifiedReceipt = FormatUnifiedReceipt(receipt, transactionId);
product.receipt = unifiedReceipt;
product.transactionID = transactionId;
ProcessPurchaseIfNew(product);
}
public void OnSetupFailed(InitializationFailureReason reason)
{
if (initialized)
{
if (null != m_AdditionalProductsFailCallback)
m_AdditionalProductsFailCallback(reason);
}
else
m_Listener.OnInitializeFailed(reason);
}
public void OnPurchaseFailed(PurchaseFailureDescription description)
{
var product = products.WithStoreSpecificID(description.productId);
if (null == product)
{
m_Logger.LogError("Failed to purchase unknown product {0}", description.productId);
return;
}
m_Logger.Log("onPurchaseFailedEvent({0})", product.definition.id);
m_Listener.OnPurchaseFailed(product, description.reason);
}
/// <summary>
/// Called back by our IStore when it has fetched the latest product data.
/// </summary>
public void OnProductsRetrieved(List<ProductDescription> products)
{
var unknownProducts = new HashSet<Product>();
foreach (var product in products)
{
var matchedProduct = this.products.WithStoreSpecificID(product.storeSpecificId);
if (null == matchedProduct)
{
var definition = new ProductDefinition(product.storeSpecificId,
product.storeSpecificId, product.type);
matchedProduct = new Product(definition, product.metadata);
unknownProducts.Add(matchedProduct);
}
matchedProduct.availableToPurchase = true;
matchedProduct.metadata = product.metadata;
matchedProduct.transactionID = product.transactionId;
if (!string.IsNullOrEmpty(product.receipt))
{
matchedProduct.receipt = FormatUnifiedReceipt(product.receipt, product.transactionId);
}
}
if (unknownProducts.Count > 0)
{
this.products.AddProducts(unknownProducts);
}
// Fire our initialisation events if this is a first poll.
CheckForInitialization();
// Notify the application of any purchases it
// has not yet processed.
// Note that this must be done *after* initialisation, above.
foreach (var product in this.products.set)
{
if (!string.IsNullOrEmpty(product.receipt) && !string.IsNullOrEmpty(product.transactionID))
ProcessPurchaseIfNew(product);
}
}
public void FetchAdditionalProducts(HashSet<ProductDefinition> products, Action successCallback,
Action<InitializationFailureReason> failCallback)
{
m_AdditionalProductsCallback = successCallback;
m_AdditionalProductsFailCallback = failCallback;
this.products.AddProducts(products.Select(x => new Product(x, new ProductMetadata())));
m_Store.RetrieveProducts(new ReadOnlyCollection<ProductDefinition>(products.ToList()));
}
/// <summary>
/// Checks the product's transaction ID for uniqueness
/// against the transaction log and calls the Application's
/// ProcessPurchase method if so.
/// </summary>
private void ProcessPurchaseIfNew(Product product)
{
if (useTransactionLog && m_TransactionLog.HasRecordOf(product.transactionID))
{
m_Logger.Log("Already recorded transaction " + product.transactionID);
m_Store.FinishTransaction(product.definition, product.transactionID);
return;
}
var p = new PurchaseEventArgs(product);
// Applications may elect to delay confirmations of purchases,
// such as when persisting purchase state asynchronously.
if (PurchaseProcessingResult.Complete == m_Listener.ProcessPurchase(p))
ConfirmPendingPurchase(product);
}
private bool initialized;
private void CheckForInitialization()
{
if (!initialized)
{
// Determine if any products are purchasable.
var available = false;
foreach (var product in this.products.set)
{
if (!product.availableToPurchase)
m_Logger.LogFormat(LogType.Warning, "Unavailable product {0} -{1}", product.definition.id, product.definition.storeSpecificId);
else
available = true;
}
// One or more products is available.
if (available)
m_Listener.OnInitialized(this);
else
OnSetupFailed(InitializationFailureReason.NoProductsAvailable);
initialized = true;
}
else
{
if (null != m_AdditionalProductsCallback)
m_AdditionalProductsCallback();
}
}
public void Initialize(IInternalStoreListener listener, HashSet<ProductDefinition> products)
{
m_Listener = listener;
m_Store.Initialize(this);
var prods = products.Select(x => new Product(x, new ProductMetadata())).ToArray();
this.products = new ProductCollection(prods);
var productCollection = new ReadOnlyCollection<ProductDefinition>(products.ToList());
// Start the initialisation process by fetching product metadata.
m_Store.RetrieveProducts(productCollection);
}
#if UNITY_5_4_OR_NEWER
[Serializable]
class UnifiedReceipt
{
public string Store, TransactionID, Payload;
}
#endif
private string FormatUnifiedReceipt(string platformReceipt, string transactionId)
{
#if UNITY_5_4_OR_NEWER
UnifiedReceipt ur = new UnifiedReceipt()
{
Store = this.m_StoreName,
TransactionID = transactionId,
Payload = platformReceipt
};
return JsonUtility.ToJson(ur);
# else
var dic = new Dictionary<string, object>();
dic["Store"] = this.m_StoreName;
dic["TransactionID"] = transactionId ?? string.Empty;
dic["Payload"] = platformReceipt ?? string.Empty;
return SimpleJson.SimpleJson.SerializeObject(dic);
#endif
}
}
}