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

1326 lines
44 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;
public abstract class IAPServiceBase<T>: IStoreListener where T: IAPServiceBase<T> , new()
{
public static readonly int OrderRequestTimeout = 10;
public static readonly int OrderRequestRetryTimes = 3;
#region 属性定义
private const string Tag = "[IAP]";
private const string DefaultCategory = "Store";
private static bool _showLog;
private static string _userId;
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;
/// <summary>
/// 是否是首次购买
/// </summary>
public int PurchaseCount
{
get => _model.PurchaseCount;
set => _model.PurchaseCount = value;
}
/// <summary>
/// 是否是首个IAP
/// </summary>
public bool IsFirstIAP => PurchaseCount == 0;
private byte[] _googlePublicKey;
private byte[] _appleRootCert;
/// <summary>
/// 服务初始化回调
/// </summary>
public event Action<bool> OnInitResult;
/// <summary>
/// 恢复购买回调
/// </summary>
public event Action<bool, string> OnRestored;
public event Action<string> OnBuyStart;
public event Action<string, bool> OnBuyEnd;
public event Action<string, string> OnBuyFailed;
public event Action<string, string, bool> OnGetProductReceipt;
#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>
public virtual void Initialize(string userId, bool showLog = false)
{
_userId = userId;
_showLog = showLog;
InitPurchasing();
}
/// <summary>
/// 带有校验器的初始化
/// </summary>
/// <param name="userId"></param>
/// <param name="googlePublicKey"></param>
/// <param name="appleRootCert"></param>
/// <param name="showLog"></param>
public virtual void InitWithKeys(string userId, byte[] googlePublicKey, byte[] appleRootCert, bool showLog = false)
{
_googlePublicKey = googlePublicKey;
_appleRootCert = appleRootCert;
InitModel();
Initialize(userId, showLog);
}
/// <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;
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
}
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
}
if (emptyIDs)
{
continue;
}
_configBuilder.AddProduct(item.ProductId, item.Type, ids); // 添加商品
// 建立本地的商品信息列表
if (string.IsNullOrEmpty(item.Category)) item.Category = DefaultCategory;
_products[item.ProductId] = new ProductInfo(item);
}
}
// 调用插件初始化
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;
if (string.IsNullOrEmpty(_userId)) _userId = IPMConfig.IPM_UID;
LogI($"--- IAP Initialized Success. With UID: {_userId}");
#if UNITY_IOS
_appleExtensions = extensions.GetExtension<IAppleExtensions>();
_appleExtensions.SetApplicationUsername(_userId); // SetUp UID
// 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
_configBuilder.Configure<IGooglePlayConfiguration>().SetObfuscatedAccountId(_userId); // SetUp UID
_googlePlayStoreExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
// _googlePlayStoreExtensions.SetObfuscatedAccountId(IPMConfig.IPM_UID);
//添加安装游戏后第一次初试化进行恢复购买的回调 只有安卓才有
_googlePlayStoreExtensions.RestoreTransactions(OnRestoreHandle);
#endif
foreach (var product in _storeController.products.all)
{
if (!product.availableToPurchase)
{
continue;
}
if (_products.ContainsKey(product.definition.id))
{
_products[product.definition.id].SetProduct(product);
}
}
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;
}
#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}"));
}
}
catch (NotImplementedException exception)
{
LogE("Cross Platform Validator Not Implemented: " + exception);
}
}
}
#endregion
#region 恢复购买
/// <summary>
/// 恢复购买
/// </summary>
/// <param name="success"></param>
/// <param name="msg"></param>
protected virtual void OnRestoreHandle(bool success, string msg)
{
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);
}
/// <summary>
/// 恢复购买道具
/// </summary>
public virtual void Restore()
{
if (!IsInitialized) return;
#if UNITY_IOS
_appleExtensions.RestoreTransactions(OnRestoreHandle);
#elif UNITY_ANDROID
_googlePlayStoreExtensions.RestoreTransactions(OnRestoreHandle);
#endif
}
#endregion
#region 购买流程
/// <summary>
/// 购买商品
/// </summary>
/// <param name="productName"></param>
public virtual T Buy(string productName, string category = "")
{
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;
_storeController.InitiatePurchase(product);
_curProductInfo = info;
_curProductCategory = category;
Analytics.IAPClick(_curProductCategory, product.definition.id);
// Analytics.IAPImp(_curProductCategory, product.definition.id); // <--- Client should report this Event
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)
{
success = true;
productName = info.Name;
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
{
string msg = $"{Tag} --- Purchased end, but can't find ProductInfo with ID: {productId}";
Debug.LogError(msg);
Analytics.LogCrashlytics(new Exception(msg));
}
OnBuyEnd?.Invoke(productName, success);
OnPurchaseOver(success, productName); // 支付成功处理逻辑
ClearCurPurchasingProduct(); // 清除购买缓存
return PurchaseProcessingResult.Complete; // 直接Consume 掉当前的商品
}
/// <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());
LogI($"{Tag} --- OnPurchaseFailed :: failureReason = {failureReason}");
// 失败的处理逻辑
OnPurchaseOver(false, info.Name);
OnBuyEnd?.Invoke(info.Name, false);
// 失败原因
OnBuyFailed?.Invoke(info.Name, failureReason.ToString());
ClearCurPurchasingProduct(); // 清除购买缓存
}
private void ClearCurPurchasingProduct()
{
_curProductInfo = null;
_curProductCategory = "";
}
/// <summary>
/// 获取商品的本地化价格字符串
/// 如果商品不存在或者 IAP 尚未初始化完成则显示 "Loading" 字样
/// </summary>
/// <param name="productName"></param>
/// <returns></returns>
public string GetLocalizedPriceString(string productName)
{
return GetInfo(productName)?.LocalizedPriceString ?? "Loading";
}
#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 支付上报逻辑
/// <summary>
/// 支付结果上报
/// </summary>
protected virtual bool ReportPurchaseResult(PurchaseEventArgs args)
{
// 验证器判定
if (_validator == null)
{
// Debug.Log($"############ --- Validator is null");
LogE($"{Tag} --- Validator is null. Report Order failed.");
Analytics.LogCrashlytics(new Exception($"IAPService can not report order because Validator is null!"));
return false;
}
//---------------- 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 --------------------
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;
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);
// ---- Android 订单验证, 上报打点信息 ----
string offerId = "";
string basePlanId = "";
foreach (var receipt in allReceipts)
{
if (receipt == null) continue;
if (receipt is GooglePlayReceipt googleReceipt)
{
ReportGoogleOrder(orderType,
googleReceipt.productID,
googleReceipt.purchaseToken,
googleReceipt.orderID,
googleReceipt.purchaseDate,
level,
userCurrency, payPrice, scene, isFree,
offerId, basePlanId);
}
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);
}
}
}
catch (Exception ex)
{
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;
}
LogI("--- All Receipt is valid ---");
return true;
}
#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
#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
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)
{
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)
{
if(!IsInitialized) return false;
var smgr = GetSubManager(productName);
if (smgr != null)
{
return smgr.getSubscriptionInfo().isCancelled() == Result.True;
}
return false;
}
public bool IsSubscriptionAvailable(string productName)
{
if(!IsInitialized) return false;
var smgr = GetSubManager(productName);
if (smgr != null)
{
return smgr.getSubscriptionInfo().isSubscribed() == Result.True;
}
return false;
}
public bool IsSubscriptionExpired(string productName)
{
if(!IsInitialized) return false;
var smgr = GetSubManager(productName);
if (smgr != null)
{
return smgr.getSubscriptionInfo().isExpired() == Result.True;
}
return false;
}
public bool IsSubscriptionAutoRenewing(string productName)
{
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)
{
if(!IsInitialized) return false;
var smgr = GetSubManager(productName);
if (smgr != null)
{
return smgr.getSubscriptionInfo().isIntroductoryPricePeriod() == Result.True;
}
return false;
}
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;
}
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
}
}