#if UNITY_PURCHASE using System; using System.Collections; using System.Collections.Generic; using NUnit.Framework.Constraints; using Unity.Services.Core; using Unity.Services.Core.Environments; using UnityEngine; using UnityEngine.Purchasing; using UnityEngine.Purchasing.Extension; using static WZ.IAPOrderManager; namespace WZ { public class IAPPurchaseManager : D_MonoSingleton, IDetailedStoreListener { #region API public void BuyProductByID(string productId, string productName, string gameExtraParam) { #if !UNITY_EDITOR RushSDKManager.Instance.OnPurchaseComplete(new PurchaseInfo( productName: productName, productID: productId, orderID: "", currency: "USD", price: "", gameExtra: gameExtraParam, failReason: "", orderAlreadyExists: false, purchaseResult: true, resultType: IAPResultType.PurchasingSuccess, productType: "" )); #else _productName = productName; _gameExtraParam = gameExtraParam; Product m_p = GetProductInfoByID(productId); var currencyCode = ""; var localizedPrice = ""; if (m_p != null) { currencyCode = m_p.metadata.isoCurrencyCode; localizedPrice = m_p.metadata.localizedPrice.ToString(); } IAPEvent.LogIAPButtonClick(new PurchaseInfo( productName: _productName, productID: productId, orderID: "", currency: currencyCode, price: localizedPrice, gameExtra: _gameExtraParam, failReason: "", orderAlreadyExists: false, purchaseResult: false, resultType: IAPResultType.NULL, productType: "")); if (IsInitialized()) { if (_inPurchaseProgress) { LoggerUtils.Debug("[iap] The payment is in progress, please do not initiate the payment repeatedly."); return; } Product product = _storeController.products.WithID(productId); if (product != null && product.availableToPurchase) { _inPurchaseProgress = true; LoggerUtils.Debug( string.Format("[iap] Purchasing product asychronously: '{0}'", product.definition.id)); if (_googlePlayConfiguration != null) { if (!string.IsNullOrEmpty(productName)) { _googlePlayConfiguration.SetObfuscatedAccountId(productName); } _googlePlayConfiguration.SetObfuscatedProfileId(gameExtraParam); LoggerUtils.Debug($"[iap] [BuyProductByID] 设置成功 userId = {productName} profileId = {gameExtraParam}"); } _storeController.InitiatePurchase(product); } else { IAPEvent.LogPurchaseFail(new PurchaseInfo( productName: _productName, productID: productId, orderID: "", currency: currencyCode, price: localizedPrice, gameExtra: _gameExtraParam, failReason: "BuyProductID FAIL. Not purchasing product, either is not found or is not available for purchase", orderAlreadyExists: false, purchaseResult: false, resultType: IAPResultType.NULL, productType: product.definition.type.ToString())); LoggerUtils.Debug("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase"); } } else { RushSDKManager.Instance.OnPurchaseComplete(new PurchaseInfo( productName: productName, productID: productId, orderID: "", currency: "", price: "", gameExtra: _gameExtraParam, failReason: "BuyProductID FAIL. IAP Not initialized or Not add product.", orderAlreadyExists: false, purchaseResult: false, resultType: IAPResultType.PurchasingUnavailable, productType: "")); IAPEvent.LogPurchaseFail(new PurchaseInfo( productName: _productName, productID: productId, orderID: "", currency: currencyCode, price: localizedPrice, gameExtra: _gameExtraParam, failReason: "BuyProductID FAIL. Not purchasing product, either is not found or is not available for purchase", orderAlreadyExists: false, purchaseResult: false, resultType: IAPResultType.NULL, productType: "")); LoggerUtils.Debug("[iap] BuyProductID FAIL. IAP Not initialized or Not add product."); LoggerUtils.Debug("[iap] OnPurchaseFailed -> productId : " + productId + " , transactionID : " + "" + " , localizedPrice : " + "" + " , isoCurrencyCode : " + ""); } #endif } public Product GetProductInfoByID(string pID) { if (_storeController == null && _storeExtensionProvider == null) return null; for (int i = 0; i < _storeController.products.all.Length; i++) { Product tItem = _storeController.products.all[i]; if (tItem.definition.id.Equals(pID)) { return tItem; } } return null; } public void AddProducts(Dictionary products, Action onProductsResult = null) { _initProductDic = products; FetchAdditionalProducts(products, onProductsResult); } #endregion #region 购买成功 public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent) { _inPurchaseProgress = false; LoggerUtils.Debug("[iap] Purchase OK: " + purchaseEvent.purchasedProduct.definition.id); var wrapper = (Dictionary)MiniJson.JsonDecode(purchaseEvent.purchasedProduct.receipt); var payload = (string)wrapper["Payload"]; var profileId = _gameExtraParam; var _productName = ""; var payloadObj = (Dictionary)MiniJson.JsonDecode(payload); var o = (string)payloadObj["json"]; var payloadData = (Dictionary)MiniJson.JsonDecode(o); if (payloadData.TryGetValue("obfuscatedAccountId", out var obfuscatedAccountIdValue)) { var obfuscatedAccountId = (string)obfuscatedAccountIdValue; if (!string.IsNullOrEmpty(obfuscatedAccountId)) { _productName = obfuscatedAccountId; } } if (payloadData.TryGetValue("obfuscatedProfileId", out var value)) { var obfuscatedProfileId = (string)value; if (!string.IsNullOrEmpty(obfuscatedProfileId)) { profileId = obfuscatedProfileId; } } LoggerUtils.Debug("[iap] productName" + _productName + " profileId:" + profileId); string token = ""; string orderId = ""; if (Application.platform == RuntimePlatform.Android) { var gpDetails = (Dictionary)MiniJson.JsonDecode(payload); var gpJson = (string)gpDetails["json"]; var tokenJson = (Dictionary)MiniJson.JsonDecode(gpJson); token = (string)tokenJson["purchaseToken"]; orderId = (string)tokenJson["orderId"]; LoggerUtils.Debug("[iap] ClientIAPSuccess productId : " + purchaseEvent.purchasedProduct.definition.id + " , transactionID : " + orderId + " , token : " + token + " , localizedPrice : " + purchaseEvent.purchasedProduct.metadata.localizedPriceString + " , isoCurrencyCode : " + purchaseEvent.purchasedProduct.metadata.isoCurrencyCode); } IAPDataConfig newData = new IAPDataConfig(); newData.info = new Dictionary { { "price", purchaseEvent.purchasedProduct.metadata.localizedPrice.ToString() }, { "product_id", purchaseEvent.purchasedProduct.definition.id }, { "product_name", (string)_productName }, { "purchase_token", token }, { "order_id", orderId }, { "currency", purchaseEvent.purchasedProduct.metadata.isoCurrencyCode }, { "payment_method", "googleplay" }, { "product_type", purchaseEvent.purchasedProduct.definition.type.ToString() }, { "mGameExtraParam", (string)profileId } }; newData.state = IAPDataStateType.def; IAPEvent.LogClientComplete(new PurchaseInfo( productName: (string)_productName, productID: purchaseEvent.purchasedProduct.definition.id, orderID: orderId, currency: purchaseEvent.purchasedProduct.metadata.isoCurrencyCode, price: purchaseEvent.purchasedProduct.metadata.localizedPrice.ToString(), gameExtra: _gameExtraParam, failReason: "", orderAlreadyExists: false, purchaseResult: true, resultType: IAPResultType.PurchasingSuccess, productType: purchaseEvent.purchasedProduct.definition.type.ToString() )); IAPOrderManager.Instance.SaveOrder(newData); IAPOrderManager.Instance.VerifyPurchase(newData); return PurchaseProcessingResult.Complete; } #endregion #region 动态添加商品 private void FetchAdditionalProducts(Dictionary ProductDic, Action onProductsResult = null) { if (!IsInitialized()) { _addProductsDic = ProductDic; LoggerUtils.Debug("[iap] IAP not init.Now InitUnityPurchase"); InitUnityPurchase(); return; } if (_isFetchingAdditionalProducts) { LoggerUtils.Debug("[iap] Now fetching additional products,don't call repeatedly"); if (onProductsResult != null) { onProductsResult(false, "Now fetching additional products,don't call repeatedly"); } return; } _isFetchingAdditionalProducts = true; if (ProductDic != null) { var additional = new HashSet(); foreach (string tID in ProductDic.Keys) { additional.Add(new ProductDefinition(tID, ProductDic[tID])); } Action onSuccess = () => { _isFetchingAdditionalProducts = false; LoggerUtils.Debug("[iap] Fetched successfully!"); RushSDKManager.Instance.OnGetProductsInfo?.Invoke(_storeController.products.all); foreach (var product in _storeController.products.all) { LoggerUtils.Debug("[iap]" + product.metadata.localizedTitle + "|" + product.metadata.localizedPriceString + "|" + product.metadata.localizedDescription + "|" + product.metadata.isoCurrencyCode); } if (onProductsResult != null) { onProductsResult(true, "Fetched successfully!"); } }; Action onFailure = (InitializationFailureReason i, string msg) => { _isFetchingAdditionalProducts = false; if (onProductsResult != null) { onProductsResult(true, "Fetching failed for the specified reason: " + i + " msg: " + msg); } LoggerUtils.Debug("[iap] Fetching failed for the specified reason: " + i + " msg: " + msg); }; _storeController.FetchAdditionalProducts(additional, onSuccess, onFailure); } } #endregion #region 初始化 public void PreInitialize() { LoggerUtils.Debug("[iap] PreInitialize() mServiceInit: " + _serviceInit); if (!_serviceInit) { InitializeUnityServices(OnSuccess, OnError); } } private void InitializeUnityServices(Action onSuccess, Action onError) { try { var options = new InitializationOptions().SetEnvironmentName("production"); UnityServices.InitializeAsync(options).ContinueWith(task => onSuccess()); } catch (Exception exception) { onError(exception.Message); } } private void OnSuccess() { LoggerUtils.Debug("[iap] Congratulations!\nUnity Gaming Services has been successfully initialized."); _serviceInit = true; IAPEvent.LogPurchaseInit(); } private void OnError(string message) { LoggerUtils.Debug($"[iap] Unity Gaming Services failed to initialize with error: {message}."); } /// /// 初始化IAP /// public void Initialize() { LoggerUtils.Debug("[iap] IAP Initialize() _storeController.Debug:" + _storeController + " m_StoreExtensionProvider: " + _storeExtensionProvider); if (_storeController == null && _storeExtensionProvider == null) InitUnityPurchase(); } private void InitUnityPurchase() { LoggerUtils.Debug("[iap] IAP InitUnityPurchase() IsInitialized: " + IsInitialized()); if (IsInitialized()) return; _repeatCountDic = new Dictionary(); _module ??= StandardPurchasingModule.Instance(); // 配置模式; _builder ??= ConfigurationBuilder.Instance(_module); int productsNum = 0; if (_initProductDic != null && _initProductDic.Count > 0) { foreach (string tID in _initProductDic.Keys) { productsNum++; if (!string.IsNullOrEmpty(tID)) { LoggerUtils.Debug($"[iap] Add InitProductDic APProducts: {tID}"); _builder.AddProduct(tID, _initProductDic[tID]); } } } if (_addProductsDic != null && _addProductsDic.Count > 0) { foreach (string tID in _addProductsDic.Keys) { productsNum++; if (!string.IsNullOrEmpty(tID)) { LoggerUtils.Debug($"[iap] Add AddProductsDic IAPProducts: {tID}"); _builder.AddProduct(tID, _addProductsDic[tID]); } } } _googlePlayConfiguration = _builder.Configure(); if (productsNum > 0) { UnityPurchasing.Initialize(this, _builder); } else { LoggerUtils.Debug( "[iap] UnityPurchasing will not initialize.Products is empty,please add product."); } } private bool IsInitialized() { return _storeController != null && _storeExtensionProvider != null; } #endregion #region 初始化成功 public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { LoggerUtils.Debug("[iap] IAP initialize Succeed!"); _storeController = controller; _storeExtensionProvider = extensions; RushSDKManager.Instance.OnGetProductsInfo?.Invoke(_storeController.products.all); foreach (var product in _storeController.products.all) { LoggerUtils.Debug("[iap] " + product.metadata.localizedTitle + "|" + product.metadata.localizedPriceString + "|" + product.metadata.localizedDescription + "|" + product.metadata.isoCurrencyCode + "|" + product.definition.id + "|" + product.definition.type); } // CheckSubscribeReceipt(); RushSDKManager.Instance.OnGetProductsInfo?.Invoke(_storeController.products.all); RushSDKManager.Instance.OnPurchaseInitComplete?.Invoke(true, ""); IAPEvent.LogPurchaseInit(true); } #endregion #region 初始化失败 public void OnInitializeFailed(InitializationFailureReason error) { OnInitializeFailed(error, ""); RushSDKManager.Instance.OnPurchaseInitComplete?.Invoke(false, error.ToString()); } public void OnInitializeFailed(InitializationFailureReason error, string message) { LoggerUtils.Debug("[iap] IAP OnInitializeFailed error:" + error.ToString() + " msg:" + message); switch (error) { case InitializationFailureReason.AppNotKnown: LoggerUtils.Debug("[iap] Is your App correctly uploaded on the relevant publisher console?"); break; case InitializationFailureReason.PurchasingUnavailable: LoggerUtils.Debug("[iap] Billing disabled! Ask the user if billing is disabled in device settings."); break; case InitializationFailureReason.NoProductsAvailable: LoggerUtils.Debug("[iap] No products available for purchase! Developer configuration error; check product metadata!"); break; } IAPEvent.LogPurchaseInit(false, error.ToString()); } #endregion #region 购买失败 public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription) { LoggerUtils.Debug("[iap] OnPurchaseFailed productId : " + failureDescription.productId + " , transactionID : " + product.transactionID + " , localizedPrice : " + product.metadata.localizedPriceString + " , isoCurrencyCode : " + product.metadata.isoCurrencyCode + "failureDescription" + failureDescription.message); // 失败打点 IAPEvent.LogPurchaseFail(new PurchaseInfo( productName: product.metadata.localizedTitle, productID: product.definition.id, orderID: "", currency: product.metadata.isoCurrencyCode, price: product.metadata.localizedPriceString, gameExtra: _gameExtraParam, failReason: failureDescription.message, orderAlreadyExists: false, purchaseResult: false, resultType: (IAPResultType)failureDescription.reason, productType: product.definition.type.ToString() )); // 购买失败回调 RushSDKManager.Instance.OnPurchaseComplete(new PurchaseInfo( productName: product.metadata.localizedTitle, productID: product.definition.id, orderID: "", currency: product.metadata.isoCurrencyCode, price: product.metadata.localizedPriceString, gameExtra: _gameExtraParam, failReason: failureDescription.message, orderAlreadyExists: false, purchaseResult: false, resultType: (IAPResultType)failureDescription.reason, productType: product.definition.type.ToString() )); } public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) { // m_PurchaseInProgress = false; LoggerUtils.Debug("[iap] OnPurchaseFailed productId -> : " + product.definition.id + " , transactionID : " + product.transactionID + " , localizedPrice : " + product.metadata.localizedPriceString + " , isoCurrencyCode : " + product.metadata.isoCurrencyCode + " , failureReason : " + failureReason.ToString()); // 失败打点 IAPEvent.LogPurchaseFail(new PurchaseInfo( productName: product.metadata.localizedTitle, productID: product.definition.id, orderID: "", currency: product.metadata.isoCurrencyCode, price: product.metadata.localizedPriceString, gameExtra: _gameExtraParam, productType: "", failReason: failureReason.ToString(), orderAlreadyExists: false, purchaseResult: false, resultType: (IAPResultType)failureReason) ); // 购买失败回调 RushSDKManager.Instance.OnPurchaseComplete(new PurchaseInfo( productName: product.metadata.localizedTitle, productID: product.definition.id, orderID: "", currency: product.metadata.isoCurrencyCode, price: product.metadata.localizedPriceString, gameExtra: _gameExtraParam, failReason: failureReason.ToString(), orderAlreadyExists: false, purchaseResult: false, resultType: (IAPResultType)failureReason, productType: product.definition.type.ToString() )); // ReadFailOrderId(); } #endregion #region Properties private StandardPurchasingModule _module; private ConfigurationBuilder _builder; private static IStoreController _storeController; private static IExtensionProvider _storeExtensionProvider; public string _gameExtraParam = ""; private static string _productName = ""; private bool _inPurchaseProgress = false; public Dictionary> _productArgs = new Dictionary>(); private bool _serviceInit = false; private Dictionary _addProductsDic; private Dictionary _initProductDic; private Dictionary _repeatCountDic; private IGooglePlayConfiguration _googlePlayConfiguration; private bool _isFetchingAdditionalProducts = false; #endregion } } #endif