581 lines
24 KiB
C#
581 lines
24 KiB
C#
#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 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<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 |