2024-03-17 14:22:58 +00:00
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
namespace Guru
|
|
|
|
|
|
{
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.Purchasing;
|
|
|
|
|
|
using UnityEngine.Purchasing.Security;
|
|
|
|
|
|
using System.Collections.Generic;
|
2024-03-17 14:22:58 +00:00
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
|
|
|
|
|
|
public abstract class IAPServiceBase<T>: IStoreListener where T: IAPServiceBase<T> , new()
|
|
|
|
|
|
{
|
|
|
|
|
|
|
2024-03-14 12:21:26 +00:00
|
|
|
|
public static readonly int OrderRequestTimeout = 10;
|
|
|
|
|
|
public static readonly int OrderRequestRetryTimes = 3;
|
|
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
#region 属性定义
|
|
|
|
|
|
|
|
|
|
|
|
private const string Tag = "[IAP]";
|
|
|
|
|
|
private const string DefaultCategory = "Store";
|
|
|
|
|
|
|
|
|
|
|
|
private static bool _showLog;
|
2024-05-07 13:48:47 +00:00
|
|
|
|
private static string _userId;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
|
|
|
|
|
|
private ConfigurationBuilder _configBuilder; // 商店配置创建器
|
|
|
|
|
|
|
|
|
|
|
|
private IStoreController _storeController;
|
|
|
|
|
|
private IExtensionProvider _storeExtensionProvider;
|
|
|
|
|
|
private IAppleExtensions _appleExtensions;
|
|
|
|
|
|
private IGooglePlayStoreExtensions _googlePlayStoreExtensions;
|
|
|
|
|
|
|
|
|
|
|
|
private CrossPlatformValidator _validator;
|
|
|
|
|
|
private Dictionary<string, ProductInfo> _products;
|
|
|
|
|
|
protected Dictionary<string, ProductInfo> Products => _products;
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsInitialized => _storeController != null && _storeExtensionProvider != null;
|
2024-03-12 07:05:06 +00:00
|
|
|
|
|
2024-05-13 09:00:07 +00:00
|
|
|
|
private ProductInfo _curProductInfo = null;
|
2024-03-18 01:06:22 +00:00
|
|
|
|
private string _curProductCategory = "";
|
2024-03-14 12:21:26 +00:00
|
|
|
|
|
2024-05-13 09:00:07 +00:00
|
|
|
|
public string CurrentBuyingProductId
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_curProductInfo != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return _curProductInfo.Id;
|
|
|
|
|
|
}
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-14 12:21:26 +00:00
|
|
|
|
private IAPModel _model;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否是首次购买
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public int PurchaseCount
|
|
|
|
|
|
{
|
2024-03-15 03:23:39 +00:00
|
|
|
|
get => _model.PurchaseCount;
|
|
|
|
|
|
set => _model.PurchaseCount = value;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否是首个IAP
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool IsFirstIAP => PurchaseCount == 0;
|
|
|
|
|
|
|
|
|
|
|
|
private byte[] _googlePublicKey;
|
|
|
|
|
|
private byte[] _appleRootCert;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 服务初始化回调
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public event Action<bool> OnInitResult;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 恢复购买回调
|
|
|
|
|
|
/// </summary>
|
2024-01-23 03:05:51 +00:00
|
|
|
|
public event Action<bool, string> OnRestored;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
|
|
|
|
|
|
public event Action<string> OnBuyStart;
|
|
|
|
|
|
public event Action<string, bool> OnBuyEnd;
|
2023-12-28 11:29:08 +00:00
|
|
|
|
public event Action<string, string> OnBuyFailed;
|
2024-01-19 12:45:10 +00:00
|
|
|
|
public event Action<string, string, bool> OnGetProductReceipt;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
|
|
|
|
|
|
#if UNITY_IOS
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// AppStore 支付, 处理苹果支付延迟反应
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public Action<Product> OnAppStorePurchaseDeferred;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 单利模式
|
|
|
|
|
|
|
|
|
|
|
|
protected static T _instance;
|
|
|
|
|
|
private static object _locker = new object();
|
|
|
|
|
|
|
|
|
|
|
|
public static T Instance
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
if (null == _instance)
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_locker)
|
|
|
|
|
|
{
|
|
|
|
|
|
_instance = Activator.CreateInstance<T>();
|
|
|
|
|
|
_instance.OnCreatedInit();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return _instance;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 组件创建初始化
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected virtual void OnCreatedInit()
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.Log("--- IAPService Init");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 初始化
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化支付服务
|
|
|
|
|
|
/// </summary>
|
2024-05-07 13:48:47 +00:00
|
|
|
|
public virtual void Initialize(string userId, bool showLog = false)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
2024-05-07 13:48:47 +00:00
|
|
|
|
_userId = userId;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
_showLog = showLog;
|
|
|
|
|
|
InitPurchasing();
|
|
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 带有校验器的初始化
|
|
|
|
|
|
/// </summary>
|
2024-05-09 12:25:13 +00:00
|
|
|
|
/// <param name="userId"></param>
|
2023-12-26 04:22:19 +00:00
|
|
|
|
/// <param name="googlePublicKey"></param>
|
|
|
|
|
|
/// <param name="appleRootCert"></param>
|
|
|
|
|
|
/// <param name="showLog"></param>
|
2024-05-07 13:48:47 +00:00
|
|
|
|
public virtual void InitWithKeys(string userId, byte[] googlePublicKey, byte[] appleRootCert, bool showLog = false)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
|
|
|
|
|
_googlePublicKey = googlePublicKey;
|
|
|
|
|
|
_appleRootCert = appleRootCert;
|
2024-03-14 12:21:26 +00:00
|
|
|
|
InitModel();
|
2024-05-07 13:48:47 +00:00
|
|
|
|
Initialize(userId, showLog);
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化支付插件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected virtual void InitPurchasing()
|
|
|
|
|
|
{
|
|
|
|
|
|
_configBuilder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
|
|
|
|
|
|
// 注入初始商品产品列表
|
|
|
|
|
|
var settings = GetProductSettings();
|
|
|
|
|
|
if (null != settings)
|
|
|
|
|
|
{
|
|
|
|
|
|
int len = settings.Length;
|
|
|
|
|
|
|
|
|
|
|
|
if(_products != null) _products.Clear();
|
|
|
|
|
|
_products = new Dictionary<string, ProductInfo>(len);
|
|
|
|
|
|
|
|
|
|
|
|
ProductSetting item;
|
|
|
|
|
|
IDs ids;
|
2024-03-08 12:15:33 +00:00
|
|
|
|
bool emptyIDs = false;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
for (int i = 0; i < len; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
item = settings[i];
|
|
|
|
|
|
ids = new IDs();
|
|
|
|
|
|
if (!string.IsNullOrEmpty(item.GooglePlayProductId))
|
|
|
|
|
|
{
|
|
|
|
|
|
ids.Add(item.GooglePlayProductId, GooglePlay.Name);
|
|
|
|
|
|
}
|
2024-03-08 12:15:33 +00:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
#if UNITY_ADNROID
|
|
|
|
|
|
emptyIDs = true;
|
|
|
|
|
|
LogE($"[IAP] --- GoogleProductId is empty, please check the product setting: {item.ProductName}");
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(item.AppStoreProductId))
|
|
|
|
|
|
{
|
|
|
|
|
|
ids.Add(item.AppStoreProductId, AppleAppStore.Name);
|
|
|
|
|
|
}
|
2024-03-08 12:15:33 +00:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
#if UNITY_IOS
|
|
|
|
|
|
emptyIDs = true;
|
|
|
|
|
|
LogE($"[IAP] --- AppleProductId is empty, please check the product setting: {item.ProductName}");
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
2023-12-26 04:22:19 +00:00
|
|
|
|
|
2024-03-08 12:15:33 +00:00
|
|
|
|
if (emptyIDs)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
_configBuilder.AddProduct(item.ProductId, item.Type, ids); // 添加商品
|
|
|
|
|
|
|
|
|
|
|
|
// 建立本地的商品信息列表
|
|
|
|
|
|
if (string.IsNullOrEmpty(item.Category)) item.Category = DefaultCategory;
|
2024-04-08 14:59:23 +00:00
|
|
|
|
_products[item.ProductId] = new ProductInfo(item);
|
2024-05-07 12:35:20 +00:00
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 调用插件初始化
|
|
|
|
|
|
UnityPurchasing.Initialize(this, _configBuilder);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化成功
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="controller"></param>
|
|
|
|
|
|
/// <param name="extensions"></param>
|
|
|
|
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
|
|
|
|
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
|
|
|
|
|
|
{
|
|
|
|
|
|
_storeController = controller;
|
|
|
|
|
|
_storeExtensionProvider = extensions;
|
2024-05-07 13:48:47 +00:00
|
|
|
|
if (string.IsNullOrEmpty(_userId)) _userId = IPMConfig.IPM_UID;
|
2024-05-09 12:25:13 +00:00
|
|
|
|
|
|
|
|
|
|
LogI($"--- IAP Initialized Success. With UID: {_userId}");
|
|
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
#if UNITY_IOS
|
|
|
|
|
|
_appleExtensions = extensions.GetExtension<IAppleExtensions>();
|
2024-05-07 13:48:47 +00:00
|
|
|
|
_appleExtensions.SetApplicationUsername(_userId); // SetUp UID
|
2023-12-26 04:22:19 +00:00
|
|
|
|
// On Apple platforms we need to handle deferred purchases caused by Apple's Ask to Buy feature.
|
|
|
|
|
|
// On non-Apple platforms this will have no effect; OnDeferred will never be called.
|
|
|
|
|
|
_appleExtensions.RegisterPurchaseDeferredListener(item =>
|
|
|
|
|
|
{
|
|
|
|
|
|
LogI("Purchase deferred: " + item.definition.id);
|
|
|
|
|
|
OnAppStorePurchaseDeferred?.Invoke(item);
|
|
|
|
|
|
});
|
|
|
|
|
|
#elif UNITY_ANDROID
|
2024-05-07 13:48:47 +00:00
|
|
|
|
_configBuilder.Configure<IGooglePlayConfiguration>().SetObfuscatedAccountId(_userId); // SetUp UID
|
2023-12-26 04:22:19 +00:00
|
|
|
|
_googlePlayStoreExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
|
|
|
|
|
|
// _googlePlayStoreExtensions.SetObfuscatedAccountId(IPMConfig.IPM_UID);
|
|
|
|
|
|
//添加安装游戏后第一次初试化进行恢复购买的回调 只有安卓才有
|
|
|
|
|
|
_googlePlayStoreExtensions.RestoreTransactions(OnRestoreHandle);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
foreach (var product in _storeController.products.all)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
2024-05-07 12:35:20 +00:00
|
|
|
|
if (!product.availableToPurchase)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
if (_products.ContainsKey(product.definition.id))
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
2024-05-07 12:35:20 +00:00
|
|
|
|
_products[product.definition.id].SetProduct(product);
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
InitValidator(); // 初始化订单验证器
|
|
|
|
|
|
OnInitResult?.Invoke(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化失败
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="error"></param>
|
|
|
|
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
|
|
|
|
public void OnInitializeFailed(InitializationFailureReason error)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogE($"--- IAP Initialized Fail: {error}");
|
|
|
|
|
|
OnInitResult?.Invoke(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化失败
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="error"></param>
|
|
|
|
|
|
/// <param name="message"></param>
|
|
|
|
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
|
|
|
|
public void OnInitializeFailed(InitializationFailureReason error, string message)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogE($"--- IAP Initialized Fail: {error} msg: {message}");
|
|
|
|
|
|
OnInitResult?.Invoke(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 数据查询
|
|
|
|
|
|
|
|
|
|
|
|
// <summary>
|
|
|
|
|
|
/// 获取商品Info
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="productName">商品名称</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public ProductInfo GetInfo(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(null == Products || Products.Count == 0 ) return null;
|
|
|
|
|
|
return Products.Values.FirstOrDefault(c => c.Name == productName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通过商品ID获取对应的信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="productId">商品ID</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public ProductInfo GetInfoById(string productId)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(null == Products || Products.Count == 0 ) return null;
|
|
|
|
|
|
return Products.Values.FirstOrDefault(c => c.Id == productId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取道具价格
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public double GetProductPrice(string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_storeController == null || _storeController.products == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Fallback();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ProductInfo info = GetInfo(name);
|
|
|
|
|
|
var product = _storeController.products.WithID(info.Id);
|
|
|
|
|
|
if (product == null)
|
|
|
|
|
|
return Fallback();
|
|
|
|
|
|
|
|
|
|
|
|
return (double)product.metadata.localizedPrice;
|
|
|
|
|
|
|
|
|
|
|
|
double Fallback()
|
|
|
|
|
|
{
|
|
|
|
|
|
ProductInfo info = GetInfo(name);
|
|
|
|
|
|
return info?.Price ?? 0.0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取道具价格(带单位 $0.01)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="name"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public string GetProductPriceString(string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_storeController == null || _storeController.products == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Fallback();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ProductInfo info = GetInfo(name);
|
|
|
|
|
|
var product = _storeController.products.WithID(info.Id);
|
|
|
|
|
|
if (product == null)
|
|
|
|
|
|
return Fallback();
|
|
|
|
|
|
|
|
|
|
|
|
return product.metadata.localizedPriceString;
|
|
|
|
|
|
|
|
|
|
|
|
string Fallback()
|
|
|
|
|
|
{
|
|
|
|
|
|
ProductInfo info = GetInfo(name);
|
|
|
|
|
|
var pr = info?.Price ?? 0.0;
|
|
|
|
|
|
return "$" + pr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-22 07:15:26 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取 IAP 内置商品
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="productName"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2024-01-19 11:00:30 +00:00
|
|
|
|
public Product GetProduct(string productName)
|
|
|
|
|
|
{
|
2024-01-22 07:15:26 +00:00
|
|
|
|
if (_storeController != null && _storeController.products != null)
|
2024-01-19 11:00:30 +00:00
|
|
|
|
{
|
|
|
|
|
|
var info = GetInfo(productName);
|
|
|
|
|
|
if (info != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return _storeController.products.WithID(info.Id);
|
|
|
|
|
|
}
|
2024-01-22 07:15:26 +00:00
|
|
|
|
Debug.LogError($"[IAP] --- can't find <ProductInfo> with name {productName}");
|
2024-01-19 11:00:30 +00:00
|
|
|
|
}
|
2024-01-22 07:15:26 +00:00
|
|
|
|
|
|
|
|
|
|
// 商品不存在则返回 NULL
|
|
|
|
|
|
Debug.LogError($"[IAP] --- _storeController is null or products is null or products.all.Length == 0");
|
2024-01-19 11:00:30 +00:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前的商品是否已经持有收据了
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="productName"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public bool IsProductHasReceipt(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
var product = GetProduct(productName);
|
|
|
|
|
|
if (product != null) return product.hasReceipt;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 订单验证器
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否支持订单校验
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
private bool IsCurrentStoreSupportedByValidator()
|
|
|
|
|
|
=> IsGooglePlayStoreSelected() || IsAppleAppStoreSelected();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Google 商店支持
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
private bool IsGooglePlayStoreSelected()
|
|
|
|
|
|
{
|
|
|
|
|
|
var currentAppStore = StandardPurchasingModule.Instance().appStore;
|
|
|
|
|
|
return currentAppStore == AppStore.GooglePlay;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Apple 商店支持
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
private bool IsAppleAppStoreSelected()
|
|
|
|
|
|
{
|
|
|
|
|
|
var currentAppStore = StandardPurchasingModule.Instance().appStore;
|
|
|
|
|
|
return currentAppStore == AppStore.AppleAppStore || currentAppStore == AppStore.MacAppStore;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化订单校验器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected virtual void InitValidator()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsCurrentStoreSupportedByValidator())
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_googlePublicKey != null && _appleRootCert != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_validator = new CrossPlatformValidator(_googlePublicKey, _appleRootCert, Application.identifier);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-03-17 14:22:58 +00:00
|
|
|
|
Analytics.LogCrashlytics(new Exception($"[IAP] Init Validator failed -> googlePublicKey: {_googlePublicKey} appleRootCert: {_appleRootCert}"));
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (NotImplementedException exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogE("Cross Platform Validator Not Implemented: " + exception);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 恢复购买
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 恢复购买
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="success"></param>
|
2024-01-23 03:05:51 +00:00
|
|
|
|
/// <param name="msg"></param>
|
|
|
|
|
|
protected virtual void OnRestoreHandle(bool success, string msg)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
2024-01-23 03:05:51 +00:00
|
|
|
|
LogI($"--- Restore complete: {success}: msg:{msg}" );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool isIAPUser = false;
|
|
|
|
|
|
// 扫描所有商品, 追加用户属性
|
|
|
|
|
|
for (int i = 0; i < _storeController.products.all.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var product = _storeController.products.all[i];
|
|
|
|
|
|
if (product.hasReceipt)
|
|
|
|
|
|
{
|
|
|
|
|
|
isIAPUser = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
SetIsIAPUser(isIAPUser);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
OnRestored?.Invoke(success, msg);
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 恢复购买道具
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual void Restore()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!IsInitialized) return;
|
|
|
|
|
|
|
|
|
|
|
|
#if UNITY_IOS
|
|
|
|
|
|
_appleExtensions.RestoreTransactions(OnRestoreHandle);
|
|
|
|
|
|
#elif UNITY_ANDROID
|
|
|
|
|
|
_googlePlayStoreExtensions.RestoreTransactions(OnRestoreHandle);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
2024-01-23 03:05:51 +00:00
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 购买流程
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 购买商品
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="productName"></param>
|
2024-03-18 01:06:22 +00:00
|
|
|
|
public virtual T Buy(string productName, string category = "")
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (!IsInitialized)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogE("Buy FAIL. Not initialized.");
|
|
|
|
|
|
OnBuyEnd?.Invoke(productName, false);
|
|
|
|
|
|
return (T)this;
|
|
|
|
|
|
}
|
|
|
|
|
|
ProductInfo info = GetInfo(productName);
|
|
|
|
|
|
if (info == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogE($"Buy FAIL. No product with name: {productName}");
|
|
|
|
|
|
OnBuyEnd?.Invoke(productName, false);
|
|
|
|
|
|
return (T)this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Product product = _storeController.products.WithID(info.Setting.ProductId);
|
|
|
|
|
|
if (product != null && product.availableToPurchase)
|
|
|
|
|
|
{
|
2024-05-15 01:58:42 +00:00
|
|
|
|
// #if UNITY_ANDROID
|
|
|
|
|
|
// _configBuilder
|
|
|
|
|
|
// .Configure<IGooglePlayConfiguration>()
|
|
|
|
|
|
// .SetObfuscatedAccountId(IPMConfig.IPM_UID);
|
|
|
|
|
|
// #endif
|
2024-03-18 01:06:22 +00:00
|
|
|
|
if (string.IsNullOrEmpty(category)) category = info.Category;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
_storeController.InitiatePurchase(product);
|
2024-05-13 09:00:07 +00:00
|
|
|
|
_curProductInfo = info;
|
2024-03-18 01:06:22 +00:00
|
|
|
|
_curProductCategory = category;
|
|
|
|
|
|
|
|
|
|
|
|
Analytics.IAPClick(_curProductCategory, product.definition.id);
|
2024-05-13 09:00:07 +00:00
|
|
|
|
// Analytics.IAPImp(_curProductCategory, product.definition.id); // <--- Client should report this Event
|
2024-03-18 03:04:24 +00:00
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
OnBuyStart?.Invoke(productName);
|
|
|
|
|
|
return (T)this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 找不到商品
|
|
|
|
|
|
LogE($"Can't find product by name: {productName}, pay canceled.");
|
|
|
|
|
|
OnPurchaseOver(false, productName);
|
|
|
|
|
|
OnBuyEnd?.Invoke(productName, false);
|
|
|
|
|
|
|
|
|
|
|
|
return (T)this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理支付流程
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="purchaseEvent"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
|
|
|
|
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
|
|
|
|
|
|
{
|
|
|
|
|
|
string productId = purchaseEvent.purchasedProduct.definition.id;
|
|
|
|
|
|
ProductInfo info = GetInfoById(productId);
|
2024-01-17 12:55:36 +00:00
|
|
|
|
bool success = false;
|
2024-03-18 03:04:24 +00:00
|
|
|
|
string productName = "";
|
2024-01-17 12:55:36 +00:00
|
|
|
|
if (null != info)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
2024-01-17 12:55:36 +00:00
|
|
|
|
success = true;
|
2024-03-18 03:04:24 +00:00
|
|
|
|
productName = info.Name;
|
2024-01-23 03:05:51 +00:00
|
|
|
|
SetIsIAPUser(success); // 设置用户属性标记
|
2024-03-18 03:04:24 +00:00
|
|
|
|
|
2024-03-18 01:06:22 +00:00
|
|
|
|
// 只有实际发生购买后才会有订单上报. 启动时的 Restore 操作自动调用支付成功. 这里做一个判定, 过滤掉订单的物品
|
2024-05-13 09:00:07 +00:00
|
|
|
|
if (_curProductInfo != null)
|
2024-03-18 03:04:24 +00:00
|
|
|
|
{
|
|
|
|
|
|
ReportPurchaseResult(purchaseEvent); // 订单上报
|
|
|
|
|
|
|
|
|
|
|
|
// 真实购买后上报对应的事件
|
|
|
|
|
|
if (IsFirstIAP) Analytics.FirstIAP(info.Id, info.Price, info.CurrencyCode); // 上报首次支付打点
|
|
|
|
|
|
Analytics.ProductIAP(info.Id,info.Id, info.Price, info.CurrencyCode);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pp = purchaseEvent.purchasedProduct;
|
|
|
|
|
|
if ( pp == null || string.IsNullOrEmpty(pp.receipt))
|
|
|
|
|
|
{
|
|
|
|
|
|
string msg = $"{Tag} --- Purchased product is null or has no receipt!!";
|
|
|
|
|
|
Debug.LogError(msg);
|
|
|
|
|
|
Analytics.LogCrashlytics(new Exception(msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
OnGetProductReceipt?.Invoke(pp.definition.id, pp.receipt, pp.appleProductIsRestored);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LogI($"{Tag} --- OnPurchaseSuccess :: purchase count: {PurchaseCount} productName: {productName}");
|
|
|
|
|
|
PurchaseCount++; // 记录支付次数
|
2024-03-12 07:05:06 +00:00
|
|
|
|
}
|
2024-03-18 03:04:24 +00:00
|
|
|
|
else
|
2024-01-19 12:45:10 +00:00
|
|
|
|
{
|
2024-03-18 03:04:24 +00:00
|
|
|
|
string msg = $"{Tag} --- Purchased end, but can't find ProductInfo with ID: {productId}";
|
2024-01-19 12:45:10 +00:00
|
|
|
|
Debug.LogError(msg);
|
2024-03-17 14:22:58 +00:00
|
|
|
|
Analytics.LogCrashlytics(new Exception(msg));
|
2024-01-19 12:45:10 +00:00
|
|
|
|
}
|
2024-01-17 12:55:36 +00:00
|
|
|
|
|
|
|
|
|
|
OnBuyEnd?.Invoke(productName, success);
|
2024-03-18 03:04:24 +00:00
|
|
|
|
OnPurchaseOver(success, productName); // 支付成功处理逻辑
|
2024-03-18 01:06:22 +00:00
|
|
|
|
ClearCurPurchasingProduct(); // 清除购买缓存
|
|
|
|
|
|
|
2024-01-18 08:44:52 +00:00
|
|
|
|
return PurchaseProcessingResult.Complete; // 直接Consume 掉当前的商品
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 支付失败
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="product"></param>
|
|
|
|
|
|
/// <param name="failureReason"></param>
|
|
|
|
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
|
|
|
|
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
|
|
|
|
|
|
{
|
|
|
|
|
|
string productId = product.definition.id;
|
|
|
|
|
|
ProductInfo info = GetInfoById(productId);
|
|
|
|
|
|
|
|
|
|
|
|
//上报点位,用户购买失败的原因
|
2024-05-13 09:00:07 +00:00
|
|
|
|
Analytics.IAPRetFalse(_curProductCategory, product.definition.id, failureReason.ToString());
|
2023-12-26 04:22:19 +00:00
|
|
|
|
|
2024-01-17 12:55:36 +00:00
|
|
|
|
LogI($"{Tag} --- OnPurchaseFailed :: failureReason = {failureReason}");
|
2023-12-26 04:22:19 +00:00
|
|
|
|
// 失败的处理逻辑
|
|
|
|
|
|
OnPurchaseOver(false, info.Name);
|
|
|
|
|
|
OnBuyEnd?.Invoke(info.Name, false);
|
2023-12-28 11:29:08 +00:00
|
|
|
|
// 失败原因
|
|
|
|
|
|
OnBuyFailed?.Invoke(info.Name, failureReason.ToString());
|
2024-03-18 01:06:22 +00:00
|
|
|
|
ClearCurPurchasingProduct(); // 清除购买缓存
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ClearCurPurchasingProduct()
|
|
|
|
|
|
{
|
2024-05-13 09:00:07 +00:00
|
|
|
|
_curProductInfo = null;
|
2024-03-18 01:06:22 +00:00
|
|
|
|
_curProductCategory = "";
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
2024-01-29 06:42:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取商品的本地化价格字符串
|
|
|
|
|
|
/// 如果商品不存在或者 IAP 尚未初始化完成则显示 "Loading" 字样
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="productName"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public string GetLocalizedPriceString(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return GetInfo(productName)?.LocalizedPriceString ?? "Loading";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Log 输出
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 日志输出
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="msg"></param>
|
|
|
|
|
|
private static void LogI(object msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_showLog)
|
|
|
|
|
|
Debug.Log($"{Tag} {msg}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void LogE(object msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_showLog)
|
|
|
|
|
|
Debug.LogError($"{Tag} {msg}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void LogW(object msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_showLog)
|
|
|
|
|
|
Debug.LogWarning($"{Tag} {msg}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 实现接口
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 需要游戏侧继承并完成Blevel的取值上报
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
protected abstract int GetBLevel();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取商品品配置列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
protected virtual ProductSetting[] GetProductSettings()
|
|
|
|
|
|
=> GuruSettings.Instance.Products;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 支付回调
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="success">是否成功</param>
|
|
|
|
|
|
/// <param name="productName">商品名称</param>
|
|
|
|
|
|
protected abstract void OnPurchaseOver(bool success, string productName);
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 支付上报逻辑
|
2024-03-14 12:21:26 +00:00
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 支付结果上报
|
|
|
|
|
|
/// </summary>
|
2024-05-09 12:25:13 +00:00
|
|
|
|
protected virtual bool ReportPurchaseResult(PurchaseEventArgs args)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
// 验证器判定
|
2023-12-26 04:22:19 +00:00
|
|
|
|
if (_validator == null)
|
|
|
|
|
|
{
|
2024-03-15 03:41:49 +00:00
|
|
|
|
// Debug.Log($"############ --- Validator is null");
|
2024-01-18 03:48:28 +00:00
|
|
|
|
LogE($"{Tag} --- Validator is null. Report Order failed.");
|
2024-03-17 14:22:58 +00:00
|
|
|
|
Analytics.LogCrashlytics(new Exception($"IAPService can not report order because Validator is null!"));
|
2024-05-09 12:25:13 +00:00
|
|
|
|
return false;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
2024-05-10 13:31:14 +00:00
|
|
|
|
|
|
|
|
|
|
//---------------- All Report Information --------------------
|
2024-05-09 12:25:13 +00:00
|
|
|
|
int level = GetBLevel();
|
|
|
|
|
|
int orderType = args.purchasedProduct.definition.type == ProductType.Subscription ? 1 : 0;
|
|
|
|
|
|
string productId = args.purchasedProduct.definition.id;
|
2024-05-10 13:31:14 +00:00
|
|
|
|
// string appleReceiptString = "";
|
|
|
|
|
|
string userCurrency = args.purchasedProduct.metadata.isoCurrencyCode;
|
|
|
|
|
|
double payPrice = decimal.ToDouble(args.purchasedProduct.metadata.localizedPrice);
|
|
|
|
|
|
string scene = _curProductCategory ?? "Store";
|
|
|
|
|
|
bool isFree = false;
|
|
|
|
|
|
if (orderType == 1) isFree = IsSubscriptionFreeTrailById(productId);
|
|
|
|
|
|
//---------------- All Report Information --------------------
|
2023-12-26 04:22:19 +00:00
|
|
|
|
|
2024-05-10 13:31:14 +00:00
|
|
|
|
LogI($"--- Report b_level:[{level}] with product id:{args.purchasedProduct.definition.id} ");
|
2024-05-09 12:25:13 +00:00
|
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
|
// // Editor 不做上报逻辑
|
|
|
|
|
|
LogI($"--- Editor Validate Success. But Editor can't report order.");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
IPurchaseReceipt[] allReceipts = null;
|
2024-05-09 12:34:47 +00:00
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2024-05-09 12:34:47 +00:00
|
|
|
|
/*
|
2024-05-09 12:25:13 +00:00
|
|
|
|
#if UNITY_IOS
|
|
|
|
|
|
// ---- iOS 订单验证, 上报打点信息 ----
|
|
|
|
|
|
var jsonData = JsonConvert.DeserializeObject<JObject>(args.purchasedProduct.receipt);
|
|
|
|
|
|
if (jsonData != null && jsonData.TryGetValue("Payload", out var recp))
|
|
|
|
|
|
{
|
|
|
|
|
|
appleReceiptString = recp.ToString();
|
|
|
|
|
|
LogI($"--- [{productId}] iOS receipt: {appleReceiptString}");
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2024-05-09 12:34:47 +00:00
|
|
|
|
*/
|
2024-05-09 12:25:13 +00:00
|
|
|
|
allReceipts = _validator.Validate(args.purchasedProduct.receipt);
|
2024-01-19 12:45:10 +00:00
|
|
|
|
|
2024-05-09 12:25:13 +00:00
|
|
|
|
// ---- Android 订单验证, 上报打点信息 ----
|
|
|
|
|
|
string offerId = "";
|
|
|
|
|
|
string basePlanId = "";
|
2024-01-18 03:48:28 +00:00
|
|
|
|
|
2024-05-09 12:25:13 +00:00
|
|
|
|
foreach (var receipt in allReceipts)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
if (receipt == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (receipt is GooglePlayReceipt googleReceipt)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
ReportGoogleOrder(orderType,
|
|
|
|
|
|
googleReceipt.productID,
|
|
|
|
|
|
googleReceipt.purchaseToken,
|
|
|
|
|
|
googleReceipt.orderID,
|
|
|
|
|
|
googleReceipt.purchaseDate,
|
2024-05-10 13:31:14 +00:00
|
|
|
|
level,
|
|
|
|
|
|
userCurrency, payPrice, scene, isFree,
|
|
|
|
|
|
offerId, basePlanId);
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
else if (receipt is AppleInAppPurchaseReceipt appleReceipt)
|
|
|
|
|
|
{
|
|
|
|
|
|
ReportAppleOrder(orderType,
|
|
|
|
|
|
appleReceipt.productID,
|
2024-05-09 12:34:47 +00:00
|
|
|
|
args.purchasedProduct.receipt,
|
2024-05-09 12:25:13 +00:00
|
|
|
|
appleReceipt.transactionID,
|
2024-05-10 13:31:14 +00:00
|
|
|
|
appleReceipt.purchaseDate,
|
|
|
|
|
|
level,
|
|
|
|
|
|
userCurrency, payPrice, scene, isFree);
|
2024-05-09 12:25:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
string exmsg = $"--- [{productId}] :: Unknown Receipt Type: {receipt.GetType()} can't report order.";
|
|
|
|
|
|
Analytics.LogCrashlytics(exmsg);
|
|
|
|
|
|
LogE(exmsg);
|
|
|
|
|
|
}
|
2024-03-17 14:22:58 +00:00
|
|
|
|
}
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
catch (Exception ex)
|
2023-12-26 04:22:19 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
LogE($" [IAPManager.RevenueUpload] got Exception: {ex.Message}");
|
|
|
|
|
|
Analytics.LogCrashlytics(new Exception($"[IAP] Unity report purchase data with b_level={level} got error: {ex.Message}"));
|
|
|
|
|
|
return false;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
|
|
|
|
|
|
LogI("--- All Receipt is valid ---");
|
|
|
|
|
|
return true;
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
|
2023-12-26 04:22:19 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region IOS Orders Collection
|
|
|
|
|
|
|
|
|
|
|
|
private HashSet<string> iOSReceipts;
|
|
|
|
|
|
public HashSet<string> IOSReceiptCollection
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
// 读取订单信息
|
|
|
|
|
|
if (iOSReceipts == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
iOSReceipts = new HashSet<string>();
|
|
|
|
|
|
string raw = PlayerPrefs.GetString(nameof(IOSReceiptCollection), "");
|
|
|
|
|
|
if (!string.IsNullOrEmpty(raw))
|
|
|
|
|
|
{
|
|
|
|
|
|
var arr = raw.Split(',');
|
|
|
|
|
|
for (int i = 0; i < arr.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
iOSReceipts.Add(arr[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return iOSReceipts;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
// 保存订单信息
|
|
|
|
|
|
iOSReceipts = value;
|
|
|
|
|
|
PlayerPrefs.SetString(nameof(IOSReceiptCollection), string.Join(",", iOSReceipts));
|
|
|
|
|
|
PlayerPrefs.Save();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 添加订单信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="receipt"></param>
|
|
|
|
|
|
public void AddReceipt(string receipt)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!HasReceipt(receipt))
|
|
|
|
|
|
{
|
|
|
|
|
|
IOSReceiptCollection.Add(receipt);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否包含订单
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="receipt"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public bool HasReceipt(string receipt)
|
|
|
|
|
|
{
|
|
|
|
|
|
return IOSReceiptCollection.Contains(receipt);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2024-01-23 03:05:51 +00:00
|
|
|
|
#region 用户标志位设置
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 标记是否为付费用户
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="value"></param>
|
|
|
|
|
|
public static void SetIsIAPUser(bool value = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
Analytics.SetUserProperty(Analytics.PropertyIsIAPUser, value ? "true" : "false");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2024-03-14 12:21:26 +00:00
|
|
|
|
|
|
|
|
|
|
#region 数据初始化
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void InitModel()
|
|
|
|
|
|
{
|
|
|
|
|
|
_model = IAPModel.Load(); // 初始化 Model
|
|
|
|
|
|
|
|
|
|
|
|
// 启动时查询
|
|
|
|
|
|
if(_orderRequests == null)
|
|
|
|
|
|
_orderRequests = new Queue<RequestBase>(20);
|
2024-03-15 03:23:39 +00:00
|
|
|
|
|
|
|
|
|
|
// #if UNITY_EDITOR
|
|
|
|
|
|
// Debug.Log($"----- IAP Model init -----");
|
|
|
|
|
|
// #elif UNITY_ANDROID
|
|
|
|
|
|
|
|
|
|
|
|
#if UNITY_ANDROID
|
2024-03-14 12:21:26 +00:00
|
|
|
|
if (_model.HasUnreportedGoogleOrder)
|
|
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
int i = 0;
|
|
|
|
|
|
while (_model.googleOrders.Count > 0
|
|
|
|
|
|
&& i < _model.googleOrders.Count)
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
var o = _model.googleOrders[i];
|
2024-03-14 12:21:26 +00:00
|
|
|
|
ReportGoogleOrder(o);
|
2024-04-08 14:59:23 +00:00
|
|
|
|
i++;
|
2024-03-14 12:21:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#elif UNITY_IOS
|
|
|
|
|
|
if (_model.HasUnreportedAppleOrder)
|
|
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
int i = 0;
|
|
|
|
|
|
while (_model.appleOrders.Count > 0
|
|
|
|
|
|
&& i < _model.appleOrders.Count)
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
var o = _model.appleOrders[i];
|
2024-03-14 12:21:26 +00:00
|
|
|
|
ReportAppleOrder(o);
|
2024-04-08 14:59:23 +00:00
|
|
|
|
i++;
|
2024-03-14 12:21:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 订单上报队列
|
|
|
|
|
|
|
|
|
|
|
|
private bool isOrderSending = false;
|
|
|
|
|
|
private Queue<RequestBase> _orderRequests = new Queue<RequestBase>(20);
|
2024-05-09 12:25:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
2024-03-14 12:21:26 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 上报 Google Order Request
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="orderType"></param>
|
|
|
|
|
|
/// <param name="productId"></param>
|
|
|
|
|
|
/// <param name="subscriptionId"></param>
|
|
|
|
|
|
/// <param name="token"></param>
|
2024-05-09 12:25:13 +00:00
|
|
|
|
/// <param name="date"></param>
|
2024-03-14 12:21:26 +00:00
|
|
|
|
/// <param name="level"></param>
|
|
|
|
|
|
/// <param name="offerId"></param>
|
|
|
|
|
|
/// <param name="basePlanId"></param>
|
2024-05-09 12:25:13 +00:00
|
|
|
|
/// <param name="orderId"></param>
|
|
|
|
|
|
private void ReportGoogleOrder(int orderType, string productId, string token,
|
2024-05-10 13:31:14 +00:00
|
|
|
|
string orderId, DateTime date, int level,
|
|
|
|
|
|
string userCurrency, double payPrice, string scene, bool isFree = false,
|
|
|
|
|
|
string offerId = "", string basePlanId = "")
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
var payedDate = TimeUtil.GetTimeStampString(date);
|
2024-05-10 13:31:14 +00:00
|
|
|
|
var request = GoogleOrderRequest.Build(orderType, productId, token, orderId, payedDate, level,
|
|
|
|
|
|
userCurrency, payPrice, scene, isFree, offerId, basePlanId);
|
2024-03-14 12:21:26 +00:00
|
|
|
|
ReportNextOrder(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
private void ReportGoogleOrder(GoogleOrderData data)
|
|
|
|
|
|
{
|
|
|
|
|
|
var request = GoogleOrderRequest.Build(data);
|
|
|
|
|
|
ReportNextOrder(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-09 12:25:13 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 上报 Apple Order Request
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="orderType"></param>
|
|
|
|
|
|
/// <param name="productId"></param>
|
|
|
|
|
|
/// <param name="receipt"></param>
|
|
|
|
|
|
/// <param name="orderId"></param>
|
|
|
|
|
|
/// <param name="date"></param>
|
|
|
|
|
|
/// <param name="level"></param>
|
|
|
|
|
|
/// <param name="offerId"></param>
|
|
|
|
|
|
/// <param name="basePlanId"></param>
|
|
|
|
|
|
private void ReportAppleOrder(int orderType, string productId, string receipt,
|
2024-05-10 13:31:14 +00:00
|
|
|
|
string orderId, DateTime date,int level, string userCurrency, double payPrice, string scene, bool isFree = false,
|
|
|
|
|
|
string offerId = "", string basePlanId = "")
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
var payedDate = TimeUtil.GetTimeStampString(date);
|
2024-05-10 13:31:14 +00:00
|
|
|
|
var request = AppleOrderRequest.Build(orderType, productId, receipt, orderId, payedDate, level,
|
|
|
|
|
|
userCurrency, payPrice, scene, isFree);
|
2024-03-14 12:21:26 +00:00
|
|
|
|
ReportNextOrder(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ReportAppleOrder(AppleOrderData data)
|
|
|
|
|
|
{
|
|
|
|
|
|
var request = AppleOrderRequest.Build(data);
|
|
|
|
|
|
ReportNextOrder(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ReportNextOrder(RequestBase request)
|
|
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
if(_orderRequests == null) _orderRequests = new Queue<RequestBase>(20);
|
2024-03-14 12:21:26 +00:00
|
|
|
|
_orderRequests.Enqueue(request);
|
|
|
|
|
|
|
|
|
|
|
|
if(isOrderSending) return;
|
|
|
|
|
|
isOrderSending = true;
|
|
|
|
|
|
|
|
|
|
|
|
OnSendNextOrder();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2024-03-15 09:20:24 +00:00
|
|
|
|
/// 上报下一个订单数据
|
2024-03-14 12:21:26 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void OnSendNextOrder()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_orderRequests != null && _orderRequests.Count > 0)
|
|
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
// 如果上报队列不为空, 则尝试上报
|
2024-03-14 12:21:26 +00:00
|
|
|
|
isOrderSending = true;
|
|
|
|
|
|
var request = _orderRequests.Dequeue();
|
2024-03-15 09:20:24 +00:00
|
|
|
|
if (request == null)
|
|
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
// 跳过空请求
|
2024-03-15 09:20:24 +00:00
|
|
|
|
OnSendNextOrder();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-09 12:25:13 +00:00
|
|
|
|
GoogleOrderRequest googleReq = request as GoogleOrderRequest;
|
|
|
|
|
|
AppleOrderRequest appReq = request as AppleOrderRequest;
|
2024-03-14 12:21:26 +00:00
|
|
|
|
|
2024-05-09 12:25:13 +00:00
|
|
|
|
if (googleReq != null)
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
if (_model.IsTokenExists(googleReq.token))
|
2024-03-15 09:20:24 +00:00
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
OnSendNextOrder(); // 跳过上报过的 Google 订单
|
2024-03-15 09:20:24 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
_model.AddGoogleOrder(googleReq.orderData); // 缓存当前 orderData 等待上报后再消除
|
2024-03-14 12:21:26 +00:00
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
else if( appReq != null)
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
if (_model.IsReceiptExist(appReq.receipt))
|
2024-03-15 09:20:24 +00:00
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
OnSendNextOrder(); // 跳过上报过的 Apple 订单
|
2024-03-15 09:20:24 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
_model.AddAppleOrder(appReq.orderData); // 缓存当前 orderData 等待上报后再消除
|
2024-03-14 12:21:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
request.SetTimeOut(OrderRequestTimeout)
|
|
|
|
|
|
.SetRetryTimes(OrderRequestRetryTimes)
|
|
|
|
|
|
.SetSuccessCallBack(() =>
|
|
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
//---------------- Success ------------------------
|
2024-05-09 12:25:13 +00:00
|
|
|
|
if (googleReq != null)
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
_model.AddToken(googleReq.token); // 记录当前的 Google 订单
|
|
|
|
|
|
_model.RemoveGoogleOrder(googleReq.orderData); // 成功后清除缓存 orderData
|
2024-03-14 12:21:26 +00:00
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
else if (appReq != null)
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
_model.AddReceipt(appReq.receipt); // 记录当前的 Apple 订单
|
|
|
|
|
|
_model.RemoveAppleOrder(appReq.orderData); // 成功后清除缓存 orderData
|
2024-03-14 12:21:26 +00:00
|
|
|
|
}
|
2024-04-08 14:59:23 +00:00
|
|
|
|
OnSendNextOrder(); // NEXT Order
|
2024-03-14 12:21:26 +00:00
|
|
|
|
})
|
|
|
|
|
|
.SetFailCallBack(() =>
|
|
|
|
|
|
{
|
2024-04-08 14:59:23 +00:00
|
|
|
|
//---------------- Fail ------------------------
|
2024-05-09 12:25:13 +00:00
|
|
|
|
if (googleReq != null)
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
ReportGoogleOrderLost(googleReq.orderData); // 上报 Google 订单缺失打点
|
2024-03-14 12:21:26 +00:00
|
|
|
|
}
|
2024-05-09 12:25:13 +00:00
|
|
|
|
else if (appReq != null)
|
2024-03-14 12:21:26 +00:00
|
|
|
|
{
|
2024-05-09 12:25:13 +00:00
|
|
|
|
ReportAppleOrderLost(appReq.orderData); // 上报 Apple 订单缺失打点
|
2024-03-14 12:21:26 +00:00
|
|
|
|
}
|
2024-04-08 14:59:23 +00:00
|
|
|
|
OnSendNextOrder(); // NEXT Order
|
2024-03-14 12:21:26 +00:00
|
|
|
|
})
|
|
|
|
|
|
.Send();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
isOrderSending = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ReportGoogleOrderLost(GoogleOrderData data)
|
|
|
|
|
|
{
|
|
|
|
|
|
Analytics.LogEvent("google_order_lost", new Dictionary<string, dynamic>()
|
|
|
|
|
|
{
|
|
|
|
|
|
["data"] = data.ToString(),
|
|
|
|
|
|
}, new Analytics.EventSetting()
|
|
|
|
|
|
{
|
|
|
|
|
|
EnableFirebaseAnalytics = true,
|
|
|
|
|
|
EnableFacebookAnalytics = true,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ReportAppleOrderLost(AppleOrderData data)
|
|
|
|
|
|
{
|
|
|
|
|
|
Analytics.LogEvent("apple_order_lost", new Dictionary<string, dynamic>()
|
|
|
|
|
|
{
|
|
|
|
|
|
["data"] = data.ToString(),
|
|
|
|
|
|
}, new Analytics.EventSetting()
|
|
|
|
|
|
{
|
|
|
|
|
|
EnableFirebaseAnalytics = true,
|
|
|
|
|
|
EnableFacebookAnalytics = true,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
#region Subscription
|
|
|
|
|
|
|
2024-05-07 13:48:47 +00:00
|
|
|
|
|
|
|
|
|
|
public static DateTime DefaultSubscriptionDate = new DateTime(1970, 1,1,0,0,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
private SubscriptionManager GetSubManager(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
var product = GetProduct(productName);
|
|
|
|
|
|
if (product != null && product.definition.type == ProductType.Subscription)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new SubscriptionManager(product, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2024-05-10 13:31:14 +00:00
|
|
|
|
|
|
|
|
|
|
private SubscriptionManager GetSubManagerById(string productId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var product = _storeController.products.WithID(productId);
|
|
|
|
|
|
if (product != null && product.definition.type == ProductType.Subscription)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new SubscriptionManager(product, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2024-05-07 12:35:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsSubscriptionFreeTrail(string productName)
|
|
|
|
|
|
{
|
2024-05-07 13:48:47 +00:00
|
|
|
|
if(!IsInitialized) return false;
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().isFreeTrial() == Result.True;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2024-05-10 13:31:14 +00:00
|
|
|
|
|
|
|
|
|
|
public bool IsSubscriptionFreeTrailById(string productId)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!IsInitialized) return false;
|
|
|
|
|
|
|
|
|
|
|
|
var smgr = GetSubManagerById(productId);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().isFreeTrial() == Result.True;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2024-05-07 12:35:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsSubscriptionCancelled(string productName)
|
|
|
|
|
|
{
|
2024-05-07 13:48:47 +00:00
|
|
|
|
if(!IsInitialized) return false;
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().isCancelled() == Result.True;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsSubscriptionAvailable(string productName)
|
|
|
|
|
|
{
|
2024-05-07 13:48:47 +00:00
|
|
|
|
if(!IsInitialized) return false;
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().isSubscribed() == Result.True;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-14 12:21:26 +00:00
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
public bool IsSubscriptionExpired(string productName)
|
|
|
|
|
|
{
|
2024-05-07 13:48:47 +00:00
|
|
|
|
if(!IsInitialized) return false;
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().isExpired() == Result.True;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsSubscriptionAutoRenewing(string productName)
|
|
|
|
|
|
{
|
2024-05-07 13:48:47 +00:00
|
|
|
|
if(!IsInitialized) return false;
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().isAutoRenewing() == Result.True;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// IntroductioryPrice Period
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="productName"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public bool IsSubscriptionIntroductoryPricePeriod(string productName)
|
|
|
|
|
|
{
|
2024-05-07 13:48:47 +00:00
|
|
|
|
if(!IsInitialized) return false;
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().isIntroductoryPricePeriod() == Result.True;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2024-05-07 13:48:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public DateTime GetSubscriptionExpireDate(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!IsInitialized) return DefaultSubscriptionDate;
|
|
|
|
|
|
|
|
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo()?.getExpireDate() ?? DateTime.Now;
|
|
|
|
|
|
}
|
|
|
|
|
|
return DefaultSubscriptionDate;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public DateTime GetSubscriptionPurchaseDate(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!IsInitialized) return DefaultSubscriptionDate;
|
|
|
|
|
|
|
|
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().getPurchaseDate();
|
|
|
|
|
|
}
|
|
|
|
|
|
return DefaultSubscriptionDate;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public DateTime GetSubscriptionCancelDate(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!IsInitialized) return DefaultSubscriptionDate;
|
|
|
|
|
|
|
|
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().getCancelDate();
|
|
|
|
|
|
}
|
|
|
|
|
|
return DefaultSubscriptionDate;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
|
2024-05-07 13:48:47 +00:00
|
|
|
|
public TimeSpan GetSubscriptionRemainingTime(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!IsInitialized) return TimeSpan.Zero;
|
|
|
|
|
|
|
|
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().getRemainingTime();
|
|
|
|
|
|
}
|
|
|
|
|
|
return TimeSpan.Zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public TimeSpan GetSubscriptionIntroductoryPricePeriod(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!IsInitialized) return TimeSpan.Zero;
|
|
|
|
|
|
|
|
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().getIntroductoryPricePeriod();
|
|
|
|
|
|
}
|
|
|
|
|
|
return TimeSpan.Zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public TimeSpan GetSubscriptionFreeTrialPeriod(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!IsInitialized) return TimeSpan.Zero;
|
|
|
|
|
|
|
|
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().getFreeTrialPeriod();
|
|
|
|
|
|
}
|
|
|
|
|
|
return TimeSpan.Zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string GetSubscriptionInfoJsonString(string productName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!IsInitialized) return "";
|
|
|
|
|
|
|
|
|
|
|
|
var smgr = GetSubManager(productName);
|
|
|
|
|
|
if (smgr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return smgr.getSubscriptionInfo().getSubscriptionInfoJsonString();
|
|
|
|
|
|
}
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-07 12:35:20 +00:00
|
|
|
|
#endregion
|
2023-12-26 04:22:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|