SDK_UnityMoney/Assets/Script/SDKManager/Purchase/IAPPurchaseManager.cs

581 lines
24 KiB
C#
Raw Normal View History

2025-09-18 10:30:57 +00:00
#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<IAPPurchaseManager>, 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<string, ProductType> products, Action<bool, string> 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<string, object>)MiniJson.JsonDecode(purchaseEvent.purchasedProduct.receipt);
var payload = (string)wrapper["Payload"];
var profileId = _gameExtraParam;
var _productName = "";
var payloadObj = (Dictionary<string, object>)MiniJson.JsonDecode(payload);
var o = (string)payloadObj["json"];
var payloadData = (Dictionary<string, object>)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<string, object>)MiniJson.JsonDecode(payload);
var gpJson = (string)gpDetails["json"];
var tokenJson = (Dictionary<string, object>)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<string, string>
{
{ "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<string, ProductType> ProductDic,
Action<bool, string> onProductsResult = null)
{
if (!IsInitialized())
{
_addProductsDic = ProductDic;
LoggerUtils.Debug("[iap] IAP not init.Now InitUnityPurchase");
InitUnityPurchase();
return;
}
if (_isFetchingAdditionalProducts)
{
LoggerUtils.Debug("[iap] Now fetching additional productsdon't call repeatedly");
if (onProductsResult != null)
{
onProductsResult(false, "Now fetching additional productsdon't call repeatedly");
}
return;
}
_isFetchingAdditionalProducts = true;
if (ProductDic != null)
{
var additional = new HashSet<ProductDefinition>();
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<InitializationFailureReason, string> 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<string> 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}.");
}
/// <summary>
/// 初始化IAP
/// </summary>
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<string, int>();
_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<IGooglePlayConfiguration>();
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<string, Dictionary<string, object>> _productArgs = new Dictionary<string, Dictionary<string, object>>();
private bool _serviceInit = false;
private Dictionary<string, ProductType> _addProductsDic;
private Dictionary<string, ProductType> _initProductDic;
private Dictionary<string, int> _repeatCountDic;
private IGooglePlayConfiguration _googlePlayConfiguration;
private bool _isFetchingAdditionalProducts = false;
#endregion
}
}
#endif