com.guru.unity.sdk.core/Runtime/GuruIAP/Runtime/Code/IAPServiceBase.cs

1326 lines
44 KiB
C#
Raw Normal View History

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;
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()
{
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;
private ProductInfo _curProductInfo = null;
private string _curProductCategory = "";
public string CurrentBuyingProductId
{
get
{
if (_curProductInfo != null)
{
return _curProductInfo.Id;
}
return "";
}
}
private IAPModel _model;
2023-12-26 04:22:19 +00:00
/// <summary>
/// 是否是首次购买
/// </summary>
public int PurchaseCount
{
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();
}
2023-12-26 04:22:19 +00:00
/// <summary>
/// 带有校验器的初始化
/// </summary>
/// <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;
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;
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);
}
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);
}
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
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;
_products[item.ProductId] = new ProductInfo(item);
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;
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
foreach (var product in _storeController.products.all)
2023-12-26 04:22:19 +00:00
{
if (!product.availableToPurchase)
2023-12-26 04:22:19 +00:00
{
continue;
}
if (_products.ContainsKey(product.definition.id))
2023-12-26 04:22:19 +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;
}
}
/// <summary>
/// 获取 IAP 内置商品
/// </summary>
/// <param name="productName"></param>
/// <returns></returns>
public Product GetProduct(string productName)
{
if (_storeController != null && _storeController.products != null)
{
var info = GetInfo(productName);
if (info != null)
{
return _storeController.products.WithID(info.Id);
}
Debug.LogError($"[IAP] --- can't find <ProductInfo> with name {productName}");
}
// 商品不存在则返回 NULL
Debug.LogError($"[IAP] --- _storeController is null or products is null or products.all.Length == 0");
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
{
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>
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)
{
// #if UNITY_ANDROID
// _configBuilder
// .Configure<IGooglePlayConfiguration>()
// .SetObfuscatedAccountId(IPMConfig.IPM_UID);
// #endif
if (string.IsNullOrEmpty(category)) category = info.Category;
2023-12-26 04:22:19 +00:00
_storeController.InitiatePurchase(product);
_curProductInfo = info;
_curProductCategory = category;
Analytics.IAPClick(_curProductCategory, product.definition.id);
// Analytics.IAPImp(_curProductCategory, product.definition.id); // <--- Client should report this Event
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);
bool success = false;
string productName = "";
if (null != info)
2023-12-26 04:22:19 +00:00
{
success = true;
productName = info.Name;
2024-01-23 03:05:51 +00:00
SetIsIAPUser(success); // 设置用户属性标记
// 只有实际发生购买后才会有订单上报. 启动时的 Restore 操作自动调用支付成功. 这里做一个判定, 过滤掉订单的物品
if (_curProductInfo != null)
{
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++; // 记录支付次数
}
else
2024-01-19 12:45:10 +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);
Analytics.LogCrashlytics(new Exception(msg));
2024-01-19 12:45:10 +00:00
}
OnBuyEnd?.Invoke(productName, success);
OnPurchaseOver(success, productName); // 支付成功处理逻辑
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);
//上报点位,用户购买失败的原因
Analytics.IAPRetFalse(_curProductCategory, product.definition.id, failureReason.ToString());
2023-12-26 04:22:19 +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());
ClearCurPurchasingProduct(); // 清除购买缓存
}
private void ClearCurPurchasingProduct()
{
_curProductInfo = null;
_curProductCategory = "";
2023-12-26 04:22:19 +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 支付上报逻辑
2023-12-26 04:22:19 +00:00
/// <summary>
/// 支付结果上报
/// </summary>
protected virtual bool ReportPurchaseResult(PurchaseEventArgs args)
2023-12-26 04:22:19 +00:00
{
// 验证器判定
2023-12-26 04:22:19 +00:00
if (_validator == null)
{
// Debug.Log($"############ --- Validator is null");
2024-01-18 03:48:28 +00:00
LogE($"{Tag} --- Validator is null. Report Order failed.");
Analytics.LogCrashlytics(new Exception($"IAPService can not report order because Validator is null!"));
return false;
2023-12-26 04:22:19 +00:00
}
//---------------- All Report Information --------------------
int level = GetBLevel();
int orderType = args.purchasedProduct.definition.type == ProductType.Subscription ? 1 : 0;
string productId = args.purchasedProduct.definition.id;
// 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
LogI($"--- Report b_level:[{level}] with product id:{args.purchasedProduct.definition.id} ");
#if UNITY_EDITOR
// // Editor 不做上报逻辑
LogI($"--- Editor Validate Success. But Editor can't report order.");
return true;
#endif
IPurchaseReceipt[] allReceipts = null;
2023-12-26 04:22:19 +00:00
try
{
/*
#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
*/
allReceipts = _validator.Validate(args.purchasedProduct.receipt);
2024-01-19 12:45:10 +00:00
// ---- Android 订单验证, 上报打点信息 ----
string offerId = "";
string basePlanId = "";
2024-01-18 03:48:28 +00:00
foreach (var receipt in allReceipts)
2023-12-26 04:22:19 +00:00
{
if (receipt == null) continue;
if (receipt is GooglePlayReceipt googleReceipt)
2023-12-26 04:22:19 +00:00
{
ReportGoogleOrder(orderType,
googleReceipt.productID,
googleReceipt.purchaseToken,
googleReceipt.orderID,
googleReceipt.purchaseDate,
level,
userCurrency, payPrice, scene, isFree,
offerId, basePlanId);
2023-12-26 04:22:19 +00:00
}
else if (receipt is AppleInAppPurchaseReceipt appleReceipt)
{
ReportAppleOrder(orderType,
appleReceipt.productID,
args.purchasedProduct.receipt,
appleReceipt.transactionID,
appleReceipt.purchaseDate,
level,
userCurrency, payPrice, scene, isFree);
}
else
{
string exmsg = $"--- [{productId}] :: Unknown Receipt Type: {receipt.GetType()} can't report order.";
Analytics.LogCrashlytics(exmsg);
LogE(exmsg);
}
}
2023-12-26 04:22:19 +00:00
}
catch (Exception ex)
2023-12-26 04:22:19 +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
}
LogI("--- All Receipt is valid ---");
return true;
2023-12-26 04:22:19 +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
#region 数据初始化
private void InitModel()
{
_model = IAPModel.Load(); // 初始化 Model
// 启动时查询
if(_orderRequests == null)
_orderRequests = new Queue<RequestBase>(20);
// #if UNITY_EDITOR
// Debug.Log($"----- IAP Model init -----");
// #elif UNITY_ANDROID
#if UNITY_ANDROID
if (_model.HasUnreportedGoogleOrder)
{
int i = 0;
while (_model.googleOrders.Count > 0
&& i < _model.googleOrders.Count)
{
var o = _model.googleOrders[i];
ReportGoogleOrder(o);
i++;
}
}
#elif UNITY_IOS
if (_model.HasUnreportedAppleOrder)
{
int i = 0;
while (_model.appleOrders.Count > 0
&& i < _model.appleOrders.Count)
{
var o = _model.appleOrders[i];
ReportAppleOrder(o);
i++;
}
}
#endif
}
#endregion
#region 订单上报队列
private bool isOrderSending = false;
private Queue<RequestBase> _orderRequests = new Queue<RequestBase>(20);
/// <summary>
/// 上报 Google Order Request
/// </summary>
/// <param name="orderType"></param>
/// <param name="productId"></param>
/// <param name="subscriptionId"></param>
/// <param name="token"></param>
/// <param name="date"></param>
/// <param name="level"></param>
/// <param name="offerId"></param>
/// <param name="basePlanId"></param>
/// <param name="orderId"></param>
private void ReportGoogleOrder(int orderType, string productId, string token,
string orderId, DateTime date, int level,
string userCurrency, double payPrice, string scene, bool isFree = false,
string offerId = "", string basePlanId = "")
{
var payedDate = TimeUtil.GetTimeStampString(date);
var request = GoogleOrderRequest.Build(orderType, productId, token, orderId, payedDate, level,
userCurrency, payPrice, scene, isFree, offerId, basePlanId);
ReportNextOrder(request);
}
private void ReportGoogleOrder(GoogleOrderData data)
{
var request = GoogleOrderRequest.Build(data);
ReportNextOrder(request);
}
/// <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,
string orderId, DateTime date,int level, string userCurrency, double payPrice, string scene, bool isFree = false,
string offerId = "", string basePlanId = "")
{
var payedDate = TimeUtil.GetTimeStampString(date);
var request = AppleOrderRequest.Build(orderType, productId, receipt, orderId, payedDate, level,
userCurrency, payPrice, scene, isFree);
ReportNextOrder(request);
}
private void ReportAppleOrder(AppleOrderData data)
{
var request = AppleOrderRequest.Build(data);
ReportNextOrder(request);
}
private void ReportNextOrder(RequestBase request)
{
if(_orderRequests == null) _orderRequests = new Queue<RequestBase>(20);
_orderRequests.Enqueue(request);
if(isOrderSending) return;
isOrderSending = true;
OnSendNextOrder();
}
/// <summary>
/// 上报下一个订单数据
/// </summary>
private void OnSendNextOrder()
{
if (_orderRequests != null && _orderRequests.Count > 0)
{
// 如果上报队列不为空, 则尝试上报
isOrderSending = true;
var request = _orderRequests.Dequeue();
if (request == null)
{
// 跳过空请求
OnSendNextOrder();
return;
}
GoogleOrderRequest googleReq = request as GoogleOrderRequest;
AppleOrderRequest appReq = request as AppleOrderRequest;
if (googleReq != null)
{
if (_model.IsTokenExists(googleReq.token))
{
OnSendNextOrder(); // 跳过上报过的 Google 订单
return;
}
_model.AddGoogleOrder(googleReq.orderData); // 缓存当前 orderData 等待上报后再消除
}
else if( appReq != null)
{
if (_model.IsReceiptExist(appReq.receipt))
{
OnSendNextOrder(); // 跳过上报过的 Apple 订单
return;
}
_model.AddAppleOrder(appReq.orderData); // 缓存当前 orderData 等待上报后再消除
}
request.SetTimeOut(OrderRequestTimeout)
.SetRetryTimes(OrderRequestRetryTimes)
.SetSuccessCallBack(() =>
{
//---------------- Success ------------------------
if (googleReq != null)
{
_model.AddToken(googleReq.token); // 记录当前的 Google 订单
_model.RemoveGoogleOrder(googleReq.orderData); // 成功后清除缓存 orderData
}
else if (appReq != null)
{
_model.AddReceipt(appReq.receipt); // 记录当前的 Apple 订单
_model.RemoveAppleOrder(appReq.orderData); // 成功后清除缓存 orderData
}
OnSendNextOrder(); // NEXT Order
})
.SetFailCallBack(() =>
{
//---------------- Fail ------------------------
if (googleReq != null)
{
ReportGoogleOrderLost(googleReq.orderData); // 上报 Google 订单缺失打点
}
else if (appReq != null)
{
ReportAppleOrderLost(appReq.orderData); // 上报 Apple 订单缺失打点
}
OnSendNextOrder(); // NEXT Order
})
.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
#region Subscription
2024-05-07 13:48:47 +00:00
public static DateTime DefaultSubscriptionDate = new DateTime(1970, 1,1,0,0,0);
private SubscriptionManager GetSubManager(string productName)
{
var product = GetProduct(productName);
if (product != null && product.definition.type == ProductType.Subscription)
{
return new SubscriptionManager(product, null);
}
return null;
}
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;
}
public bool IsSubscriptionFreeTrail(string productName)
{
2024-05-07 13:48:47 +00:00
if(!IsInitialized) return false;
var smgr = GetSubManager(productName);
if (smgr != null)
{
return smgr.getSubscriptionInfo().isFreeTrial() == Result.True;
}
return false;
}
public bool IsSubscriptionFreeTrailById(string productId)
{
if(!IsInitialized) return false;
var smgr = GetSubManagerById(productId);
if (smgr != null)
{
return smgr.getSubscriptionInfo().isFreeTrial() == Result.True;
}
return false;
}
public bool IsSubscriptionCancelled(string productName)
{
2024-05-07 13:48:47 +00:00
if(!IsInitialized) return false;
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;
var smgr = GetSubManager(productName);
if (smgr != null)
{
return smgr.getSubscriptionInfo().isSubscribed() == Result.True;
}
return false;
}
public bool IsSubscriptionExpired(string productName)
{
2024-05-07 13:48:47 +00:00
if(!IsInitialized) return false;
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;
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;
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 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 "";
}
#endregion
2023-12-26 04:22:19 +00:00
}
}