#nullable enable using System; using System.Collections.Generic; using UnityEngine.Purchasing.Extension; using UnityEngine.Purchasing.Interfaces; using UnityEngine.Purchasing.Models; using UnityEngine.Purchasing.Security; using UnityEngine.Purchasing.Telemetry; namespace UnityEngine.Purchasing { class GooglePlayStoreExtensions : IGooglePlayStoreExtensions, IGooglePlayStoreExtensionsInternal { readonly IGooglePlayStoreService m_GooglePlayStoreService; readonly IGooglePlayStoreFinishTransactionService m_GooglePlayStoreFinishTransactionService; readonly ITelemetryDiagnostics m_TelemetryDiagnostics; readonly ILogger m_Logger; IStoreCallback? m_StoreCallback; readonly Action? m_DeferredPurchaseAction; readonly Action? m_DeferredProrationUpgradeDowngradeSubscriptionAction; internal GooglePlayStoreExtensions(IGooglePlayStoreService googlePlayStoreService, IGooglePlayStoreFinishTransactionService googlePlayStoreFinishTransactionService, ILogger logger, ITelemetryDiagnostics telemetryDiagnostics) { m_GooglePlayStoreService = googlePlayStoreService; m_GooglePlayStoreFinishTransactionService = googlePlayStoreFinishTransactionService; m_Logger = logger; m_TelemetryDiagnostics = telemetryDiagnostics; } public void UpgradeDowngradeSubscription(string oldSku, string newSku) { UpgradeDowngradeSubscription(oldSku, newSku, GooglePlayProrationMode.ImmediateWithoutProration); } public void UpgradeDowngradeSubscription(string oldSku, string newSku, int desiredProrationMode) { UpgradeDowngradeSubscription(oldSku, newSku, (GooglePlayProrationMode)desiredProrationMode); } public virtual void UpgradeDowngradeSubscription(string oldSku, string newSku, GooglePlayProrationMode desiredProrationMode) { var product = m_StoreCallback.FindProductById(newSku); var oldProduct = m_StoreCallback.FindProductById(oldSku); if (product == null || product.definition.type != ProductType.Subscription || oldProduct == null || oldProduct.definition.type != ProductType.Subscription) { m_StoreCallback?.OnPurchaseFailed( new PurchaseFailureDescription( newSku ?? "", PurchaseFailureReason.ProductUnavailable, "Please verify that the products are subscriptions and are not null.")); } else if (string.IsNullOrEmpty(oldProduct.transactionID)) { m_StoreCallback?.OnPurchaseFailed( new PurchaseFailureDescription( newSku ?? "", PurchaseFailureReason.ProductUnavailable, "Invalid transaction id for old product: " + oldProduct.definition.id)); } else { m_GooglePlayStoreService.Purchase(product.definition, oldProduct, desiredProrationMode); } } [Obsolete("RestoreTransactions(Action callback) is deprecated, please use RestoreTransactions(Action callback) instead.")] public virtual void RestoreTransactions(Action? callback) { if (callback == null) { m_Logger.LogIAPError("RestoreTransactions called with a null callback. Please provide a callback to avoid null pointer exceptions"); } m_GooglePlayStoreService.FetchPurchases(_ => { callback?.Invoke(true); }); } public virtual void RestoreTransactions(Action? callback) { if (callback == null) { m_Logger.LogIAPError("RestoreTransactions called with a null callback. Please provide a callback to avoid null pointer exceptions"); } m_GooglePlayStoreService.FetchPurchases(_ => { callback?.Invoke(true, null); }); } public void ConfirmSubscriptionPriceChange(string productId, Action callback) { var product = m_StoreCallback.FindProductById(productId); if (product != null) { m_GooglePlayStoreService.ConfirmSubscriptionPriceChange(product.definition, result => { callback(result.responseCode == GoogleBillingResponseCode.Ok); }); } } public void SetStoreCallback(IStoreCallback storeCallback) { m_StoreCallback = storeCallback; } public bool IsPurchasedProductDeferred(Product product) { if (product == null) { m_Logger.LogIAPWarning("IsPurchasedProductDeferred: the product is null."); return false; } try { return TryIsPurchasedProductDeferred(product); } catch (Exception ex) { m_TelemetryDiagnostics.SendDiagnostic(TelemetryDiagnosticNames.ParseReceiptTransactionError, ex); m_Logger.LogIAPWarning("Cannot parse Google receipt for transaction " + product.transactionID); return false; } } bool TryIsPurchasedProductDeferred(Product product) { var purchaseState = GetPurchaseState(product); //PurchaseState codes: https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.products return purchaseState == GooglePurchaseState.Refunded || purchaseState == GooglePurchaseState.Deferred; } public GooglePurchaseState GetPurchaseState(Product product) { var unifiedReceipt = MiniJson.JsonDecode(product.receipt) as Dictionary; var payloadStr = unifiedReceipt!["Payload"] as string; var payload = MiniJson.JsonDecode(payloadStr) as Dictionary; var jsonStr = payload!["json"] as string; var jsonDic = MiniJson.JsonDecode(jsonStr) as Dictionary; var purchaseState = (long)jsonDic!["purchaseState"]; return (GooglePurchaseState)purchaseState; } } }