From 0c9b2a77fe1d2b3a211b2b33d59bddc48d274ce0 Mon Sep 17 00:00:00 2001 From: huyufei Date: Thu, 14 Mar 2024 20:21:26 +0800 Subject: [PATCH] =?UTF-8?q?update:=20=E6=94=AF=E4=BB=98=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E4=BF=AE=E6=94=B9,=20orders=20?= =?UTF-8?q?=E4=B8=8A=E6=8A=A5=E7=BC=93=E5=AD=98,=20IAP=20=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=B8=AD=E5=8F=B0=E6=8E=A5=E5=8F=A3=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runtime/Script/GuruAnalytics.cs | 5 + .../GuruCore/Runtime/Adjust/AdjustService.cs | 7 +- .../Runtime/Analytics/Analytics.Const.cs | 1 + .../Runtime/Analytics/Analytics.Custom.cs | 25 +++ .../GuruCore/Runtime/IPM/Scripts/IPMConfig.cs | 10 +- .../IPM/Scripts/RequestData/AppleOrderData.cs | 23 +- .../IPM/Scripts/RequestData/EventConfig.cs | 28 +++ .../Scripts/RequestData/EventConfig.cs.meta | 3 + .../Scripts/RequestData/GoogleOrderData.cs | 51 ++++- .../IPM/Scripts/RequestData/IAPOrderData.cs | 74 +++++++ .../Scripts/RequestData/IAPOrderData.cs.meta | 3 + .../IPM/Scripts/Requests/AppleOrderRequest.cs | 37 +++- .../Scripts/Requests/GoogleOrderRequest.cs | 81 +++++-- Runtime/GuruIAP/Runtime/Code/IAPModel.cs | 159 ++++++++++++++ Runtime/GuruIAP/Runtime/Code/IAPModel.cs.meta | 3 + .../GuruIAP/Runtime/Code/IAPServiceBase.cs | 199 +++++++++++++++++- 16 files changed, 648 insertions(+), 61 deletions(-) create mode 100644 Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/EventConfig.cs create mode 100644 Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/EventConfig.cs.meta create mode 100644 Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/IAPOrderData.cs create mode 100644 Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/IAPOrderData.cs.meta create mode 100644 Runtime/GuruIAP/Runtime/Code/IAPModel.cs create mode 100644 Runtime/GuruIAP/Runtime/Code/IAPModel.cs.meta diff --git a/Runtime/GuruAnalytics/Runtime/Script/GuruAnalytics.cs b/Runtime/GuruAnalytics/Runtime/Script/GuruAnalytics.cs index fc11003..403fece 100644 --- a/Runtime/GuruAnalytics/Runtime/Script/GuruAnalytics.cs +++ b/Runtime/GuruAnalytics/Runtime/Script/GuruAnalytics.cs @@ -136,6 +136,11 @@ namespace Guru { CacheUserProperty(Analytics.PropertyIDFV, idfv); } + + public static void SetIDFA(string idfa) + { + CacheUserProperty(Analytics.PropertyIDFA, idfa); + } /// diff --git a/Runtime/GuruCore/Runtime/Adjust/AdjustService.cs b/Runtime/GuruCore/Runtime/Adjust/AdjustService.cs index 50d32f2..cb1e7bc 100644 --- a/Runtime/GuruCore/Runtime/Adjust/AdjustService.cs +++ b/Runtime/GuruCore/Runtime/Adjust/AdjustService.cs @@ -10,7 +10,7 @@ namespace Guru public static class AdjustService { - public const string Version = "1.6.0"; + public const string Version = "1.6.1"; public static readonly string LOG_TAG = "Adjust"; public static readonly float DelayTime = 1f; // 延迟启动时间 @@ -28,6 +28,8 @@ namespace Guru } } + public static string IDFA => Adjust.getIdfa(); + private static string _adjustId = ""; public static string AdjustId { @@ -111,6 +113,9 @@ namespace Guru } } + + + #endregion #region 关键属性上报 diff --git a/Runtime/GuruCore/Runtime/Analytics/Analytics.Const.cs b/Runtime/GuruCore/Runtime/Analytics/Analytics.Const.cs index f1d9dcf..8cc6b2a 100644 --- a/Runtime/GuruCore/Runtime/Analytics/Analytics.Const.cs +++ b/Runtime/GuruCore/Runtime/Analytics/Analytics.Const.cs @@ -106,6 +106,7 @@ namespace Guru public static readonly string PropertyHp = "hp"; // 生命值/体力 public static readonly string PropertyAndroidID = "android_id"; // Android 平台 AndroidID public static readonly string PropertyIDFV = "idfv"; // iOS 平台 IDFV + public static readonly string PropertyIDFA = "idfa"; // iOS 平台 IDFA public static readonly string PropertyPicture = "picture"; // 玩家在主线的mapid public static readonly string PropertyNoAds = "no_ads"; // 玩家是否去广告 public static readonly string PropertyATTStatus = "att_status"; // ATT 状态 diff --git a/Runtime/GuruCore/Runtime/Analytics/Analytics.Custom.cs b/Runtime/GuruCore/Runtime/Analytics/Analytics.Custom.cs index a3fa7c7..c556f48 100644 --- a/Runtime/GuruCore/Runtime/Analytics/Analytics.Custom.cs +++ b/Runtime/GuruCore/Runtime/Analytics/Analytics.Custom.cs @@ -14,6 +14,7 @@ namespace Guru { private static bool _hasGotFirebaseId; //已取得FirebaseId private static bool _hasGotAdId; // 已取得AdId + private static bool _hasGotIDFA; // 已取得IDFA private static bool _hasGotAdjustId; // 已取得AdjustId private static bool _hasGotDeviceId; // 已取得DeviceId private static bool _hasGotUid; // 已取得UID @@ -21,6 +22,7 @@ namespace Guru private static double _reportSuccessInterval; // 上报频率 private const string VALUE_NOT_FOR_IOS = "not_support_for_ios"; + private const string VALUE_ONLY_FOR_IOS = "idfa_only_for_ios"; public static bool IsDebug { get; set; } = false; @@ -148,6 +150,10 @@ namespace Guru } + + + + /// /// 设置FirebaseId /// @@ -200,6 +206,24 @@ namespace Guru { GuruAnalytics.SetIDFV(DeviceIDHelper.IDFV); } + + private static void SetIDFA() + { + if(_hasGotIDFA) return; + var idfa = AdjustService.IDFA; + + if (!string.IsNullOrEmpty(idfa)) + { + // Debug.Log($"---[ANA] ADID: {adId}"); + IPMConfig.ADJUST_IDFA = idfa; + } + + if (!string.IsNullOrEmpty(IPMConfig.ADJUST_IDFA)) + { + GuruAnalytics.SetIDFA(IPMConfig.ADJUST_IDFA); + _hasGotIDFA = true; + } + } #endif @@ -231,6 +255,7 @@ namespace Guru #if UNITY_IOS SetATTStatus(); SetIDFV(); + SetIDFA(); #endif ReportEventSuccessRate(); } diff --git a/Runtime/GuruCore/Runtime/IPM/Scripts/IPMConfig.cs b/Runtime/GuruCore/Runtime/IPM/Scripts/IPMConfig.cs index a15e3d4..b21d813 100644 --- a/Runtime/GuruCore/Runtime/IPM/Scripts/IPMConfig.cs +++ b/Runtime/GuruCore/Runtime/IPM/Scripts/IPMConfig.cs @@ -211,7 +211,13 @@ namespace Guru get => PlayerPrefs.GetString(nameof(ADJUST_ADID), ""); set => PlayerPrefs.SetString(nameof(ADJUST_ADID), value); } - - + + + public static string ADJUST_IDFA + { + get => PlayerPrefs.GetString(nameof(ADJUST_IDFA), ""); + set => PlayerPrefs.SetString(nameof(ADJUST_IDFA), value); + } + } } \ No newline at end of file diff --git a/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/AppleOrderData.cs b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/AppleOrderData.cs index b9a475c..d3deb6d 100644 --- a/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/AppleOrderData.cs +++ b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/AppleOrderData.cs @@ -1,30 +1,37 @@ -using System; -using System.Collections.Generic; + namespace Guru { + using System; + using System.Collections.Generic; + [Serializable] public class AppleOrderData { + public int orderType; + public string productId; public string bundleId; public string receipt; public string country; + public int level; public Dictionary userInfo; + public EventConfig eventConfig; - public AppleOrderData(string receipt, int level) + public AppleOrderData(int orderType, string productId, string receipt, int level) { + this.orderType = orderType; + this.productId = productId; this.receipt = receipt; + this.level = level; bundleId = GuruSettings.Instance.GameIdentifier; country = IPMConfig.IPM_COUNTRY_CODE; - userInfo = new Dictionary - { - ["level"] = level - }; + userInfo = new Dictionary { ["level"] = level }; + eventConfig = EventConfig.Build(); } public override string ToString() { - return $"{nameof(bundleId)}: {bundleId}, {nameof(receipt)}: {receipt}, {nameof(country)}: {country}"; + return $"{nameof(orderType)}: {orderType}, {nameof(productId)}: {productId}, {nameof(bundleId)}: {bundleId}, {nameof(receipt)}: {receipt}, {nameof(country)}: {country}"; } } } \ No newline at end of file diff --git a/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/EventConfig.cs b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/EventConfig.cs new file mode 100644 index 0000000..6b8bd4a --- /dev/null +++ b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/EventConfig.cs @@ -0,0 +1,28 @@ + +namespace Guru +{ + using System; + + [Serializable] + public class EventConfig + { + public string firebaseAppInstanceId; // firebase实例id,用于标识一个用户 + public string idfa; // adjust广告id(ios) + public string adid; // adjust设备id(ios) + public string gpsAdid; // adjust广告id + + + public static EventConfig Build() + { + var config = new EventConfig() + { + firebaseAppInstanceId = IPMConfig.FIREBASE_ID, + idfa = IPMConfig.ADJUST_IDFA, + adid = IPMConfig.ADJUST_ID, + gpsAdid = IPMConfig.ADJUST_ADID, + }; + + return config; + } + } +} \ No newline at end of file diff --git a/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/EventConfig.cs.meta b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/EventConfig.cs.meta new file mode 100644 index 0000000..14aaf97 --- /dev/null +++ b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/EventConfig.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0b44b9252ff741f58c6489223c0d5dca +timeCreated: 1710411579 \ No newline at end of file diff --git a/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/GoogleOrderData.cs b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/GoogleOrderData.cs index 01833a5..7aefa26 100644 --- a/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/GoogleOrderData.cs +++ b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/GoogleOrderData.cs @@ -6,27 +6,56 @@ namespace Guru [Serializable] public class GoogleOrderData { - public int orderType; - public string packageName; - public string productId; - public string token; - public Dictionary userInfo; + public int orderType; // 订单类型,可选值:0:IAP订单 1: 订阅订单 + public string packageName; //应用包名 + public string productId = ""; // 商品ID 当orderType=0时,传递该参数 + public string subscriptionId = ""; // 订阅ID // 当orderType=1时,传递该参数 + public string token; // 应用商店里面的购买token + public int level; + public Dictionary userInfo; // 当前用户信息。目前包含: level: 用户属性中的"b_level"的值 + public string basePlanId; // 订阅商品的planId + public string offerId; // 订阅商品的offerId + public EventConfig eventConfig; // 事件打点所需信息 - public GoogleOrderData(int orderType, string productId, string token, int level) + public GoogleOrderData(int orderType, string productId, string subscriptionId, string token, int level, + string offerId = "", string basePlanId = "") { this.orderType = orderType; this.packageName = GuruSettings.Instance.GameIdentifier; this.productId = productId; + this.subscriptionId = subscriptionId; this.token = token; - userInfo = new Dictionary - { - ["level"] = level - }; + this.offerId = offerId; + this.basePlanId = basePlanId; + this.level = level; + userInfo = new Dictionary { ["level"] = level }; + eventConfig = EventConfig.Build(); } + public GoogleOrderData(ProductInfo productInfo, string token, int level, + string offerId = "", string basePlanId = "") + { + this.orderType = productInfo.Type == "subscription" ? 1 : 0; + if (orderType == 1) + { + subscriptionId = productInfo.Id; + } + else + { + productId = productInfo.Id; + } + this.packageName = GuruSettings.Instance.GameIdentifier; + this.token = token; + this.offerId = offerId; + this.basePlanId = basePlanId; + userInfo = new Dictionary { ["level"] = level }; + eventConfig = EventConfig.Build(); + } + + public override string ToString() { - return $"{nameof(orderType)}: {orderType}, {nameof(packageName)}: {packageName}, {nameof(productId)}: {productId}, {nameof(token)}: {token}"; + return $"{nameof(orderType)}: {orderType}, {nameof(packageName)}: {packageName}, {nameof(productId)}: {productId}, {nameof(subscriptionId)}: {subscriptionId}, {nameof(token)}: {token}"; } } } \ No newline at end of file diff --git a/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/IAPOrderData.cs b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/IAPOrderData.cs new file mode 100644 index 0000000..e9666ef --- /dev/null +++ b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/IAPOrderData.cs @@ -0,0 +1,74 @@ +using UnityEngine; + +namespace Guru +{ + using System; + using System.Collections.Generic; + + + [Serializable] + public class IAPOrderData + { + public string platform; // 平台类型 android 或 ios + public int orderType = 0; // 订单类型,可选值:0:IAP订单 1: 订阅订单 + public string productId = ""; // 商品ID 当orderType=0时,传递该参数 + public string subscriptionId = ""; // 订阅ID // 当orderType=1时,传递该参数 + public string receipt = ""; // 应用商店里面的购买token + public int level = 0; // 用户属性中的"b_level"的值 + public string packageName = ""; // 应用包名 + public string offerId = ""; // 订阅商品的offerId (Android) + public string basePlanId = ""; // 订阅商品的planId (Android) + + public Dictionary userInfo; + public EventConfig eventConfig; + + + /// + /// 构建订单数据 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static IAPOrderData Build(string platform, int orderType = 0, string receipt= "", int level = 0, + string productId = "", string subscriptionId = "", + string offerId = "", string basePlanId = "") + { + IAPOrderData orderData = new IAPOrderData + { + platform = platform, + orderType = orderType, + productId = productId, + subscriptionId = subscriptionId, + receipt = receipt, + level = level, + packageName = Application.identifier, + offerId = offerId, + basePlanId = basePlanId, + userInfo = new Dictionary() + { + {"level", level} + }, + eventConfig = new EventConfig() + { + firebaseAppInstanceId = IPMConfig.FIREBASE_ID, + idfa = IPMConfig.ADJUST_IDFA, + adid = IPMConfig.ADJUST_ID, + gpsAdid = IPMConfig.ADJUST_ADID, + } + }; + return orderData; + } + + + } + + + + +} \ No newline at end of file diff --git a/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/IAPOrderData.cs.meta b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/IAPOrderData.cs.meta new file mode 100644 index 0000000..d657a9b --- /dev/null +++ b/Runtime/GuruCore/Runtime/IPM/Scripts/RequestData/IAPOrderData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9e4562ba60204b85bca8777b771c73e5 +timeCreated: 1710410036 \ No newline at end of file diff --git a/Runtime/GuruCore/Runtime/IPM/Scripts/Requests/AppleOrderRequest.cs b/Runtime/GuruCore/Runtime/IPM/Scripts/Requests/AppleOrderRequest.cs index 28299b7..ea3f372 100644 --- a/Runtime/GuruCore/Runtime/IPM/Scripts/Requests/AppleOrderRequest.cs +++ b/Runtime/GuruCore/Runtime/IPM/Scripts/Requests/AppleOrderRequest.cs @@ -11,23 +11,34 @@ namespace Guru public string productId; public string receipt; public int level; + public AppleOrderData orderData; public AppleOrderRequest(){} - public AppleOrderRequest(int orderType, string productId, string receipt, int level) + public static AppleOrderRequest Build(int orderType, string productId, string receipt, int level) { - this.orderType = orderType; - this.productId = productId; - this.receipt = receipt; - this.level = level; + var request = new AppleOrderRequest() + { + orderType = orderType, + productId = productId, + receipt = receipt, + level = level, + }; + return request; } + public static AppleOrderRequest Build(AppleOrderData orderData) + { + return Build(orderData.orderType, orderData.productId, orderData.receipt, orderData.level); + } + + + protected override string RequestURL => IPMConfig.IPM_URL + "order/api/v1/orders/ios"; protected override UnityWebRequest CreateRequest() { - AppleOrderData appleOrderData = new AppleOrderData(receipt, level); - this.Log($"send orderData:{appleOrderData}"); + this.Log($"send orderData:{orderData}"); var request = new UnityWebRequest(RequestURL, "POST"); - request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(JsonMapper.ToJson(appleOrderData))); + request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(JsonMapper.ToJson(orderData))); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader(IPMConfig.Header_Param_APPID, IPMConfig.IPM_X_APP_ID); request.SetRequestHeader(IPMConfig.Header_Param_UID, IPMConfig.IPM_UID); @@ -41,11 +52,17 @@ namespace Guru ResponseData responseData = JsonUtility.FromJson>(response); if (responseData != null && responseData.data != null) { - // Analytics.Tch001IAPRev(responseData.data.usdPrice); double usdPrice = responseData.data.usdPrice; Analytics.Tch001IAPRev(usdPrice); Analytics.Tch02IAPRev(usdPrice); - AdjustService.TrackIAPPurchase(usdPrice, productId); + if (orderType == 0) + { + AdjustService.TrackIAPPurchase(usdPrice, productId); + } + else if (orderType == 1) + { + AdjustService.TrackSubPurchase(usdPrice, productId); + } Analytics.IAPPurchase(usdPrice, productId); } } diff --git a/Runtime/GuruCore/Runtime/IPM/Scripts/Requests/GoogleOrderRequest.cs b/Runtime/GuruCore/Runtime/IPM/Scripts/Requests/GoogleOrderRequest.cs index 1c6c173..0aa0b40 100644 --- a/Runtime/GuruCore/Runtime/IPM/Scripts/Requests/GoogleOrderRequest.cs +++ b/Runtime/GuruCore/Runtime/IPM/Scripts/Requests/GoogleOrderRequest.cs @@ -7,17 +7,20 @@ namespace Guru { public class GoogleOrderRequest : RequestBase { - public int orderType; + public int orderType; // 订单类型,可选值:0:IAP订单 1: 订阅订单 public string productId; public string subscriptionId; public string token; public string packageName; public int level; + public string basePlanId; + public string offerId; + public GoogleOrderData orderData; public GoogleOrderRequest(){} public GoogleOrderRequest(int orderType, string productId, string subscriptionId, - string token, int level) + string token, int level, string offerId = "", string basePlanId = "") { this.orderType = orderType; this.packageName = Application.identifier; @@ -25,12 +28,14 @@ namespace Guru this.subscriptionId = subscriptionId; this.token = token; this.level = level; - } + orderData = new GoogleOrderData(orderType, productId, subscriptionId, token, level, offerId, basePlanId); + } + protected override string RequestURL => IPMConfig.IPM_URL + "order/api/v1/orders/android"; protected override UnityWebRequest CreateRequest() { - GoogleOrderData googleOrderData = new GoogleOrderData(orderType, productId, token, level); + GoogleOrderData googleOrderData = orderData; this.Log($"send orderData:{googleOrderData}"); var request = new UnityWebRequest(RequestURL, "POST"); request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(JsonMapper.ToJson(googleOrderData))); @@ -51,27 +56,59 @@ namespace Guru double usdPrice = responseData.data.usdPrice; Analytics.Tch001IAPRev(usdPrice); Analytics.Tch02IAPRev(usdPrice); - AdjustService.TrackIAPPurchase(usdPrice, productId); - Analytics.IAPPurchase(usdPrice, productId); + + string pid = ""; + + if (!string.IsNullOrEmpty(productId)) + { + pid = productId; + AdjustService.TrackIAPPurchase(usdPrice, productId); // 上报 IAP 支付事件 + } + + if (!string.IsNullOrEmpty(subscriptionId)) + { + pid = subscriptionId; + AdjustService.TrackSubPurchase(usdPrice, productId); // 上报 订阅 支付事件 + } + + Analytics.IAPPurchase(usdPrice, pid); return; } - if (!string.IsNullOrEmpty(response)) - { - if (response.Contains("500") || response.Contains("Internal Server Error")) - { - Debug.LogError( - $"[IAP] GoogleOrderRequest failed: contact with guru backend server team. Make sure they'v connect order report to this project!"); - } - else - { - Debug.LogError($"[IAP] GoogleOrderRequest failed: {response}"); - } - } - else - { - Debug.LogError($"[IAP] GoogleOrderRequest failed: response is null or empty"); - } } + + + public static GoogleOrderRequest Build(int orderType, string productId, string subscriptionId, + string token, int level, string offerId = "", string basePlanId = "") + { + var request = new GoogleOrderRequest() + { + orderData = new GoogleOrderData(orderType, productId, subscriptionId, token, level, offerId, basePlanId), + orderType = orderType, + packageName = Application.identifier, + productId = productId, + subscriptionId = subscriptionId, + token = token, + level = level, + }; + return request; + } + + public static GoogleOrderRequest Build(GoogleOrderData data) + { + var request = new GoogleOrderRequest() + { + orderData = data, + orderType = data.orderType, + packageName = data.packageName, + productId = data.productId, + subscriptionId = data.subscriptionId, + token = data.token, + level = data.level, + }; + return request; + } + + } } \ No newline at end of file diff --git a/Runtime/GuruIAP/Runtime/Code/IAPModel.cs b/Runtime/GuruIAP/Runtime/Code/IAPModel.cs new file mode 100644 index 0000000..3cb2193 --- /dev/null +++ b/Runtime/GuruIAP/Runtime/Code/IAPModel.cs @@ -0,0 +1,159 @@ + +using System.Collections.Generic; +using UnityEngine; + +namespace Guru +{ + using System; + using Newtonsoft.Json; + + public class IAPModel + { + public static readonly float SaveInterval = 3; + + public static readonly string PlatformAndroid = "android"; + public static readonly string PlatformIOS = "ios"; + + public int buyCount = 0; + public List androidTokens; + public List iosReceipts; + public List googleOrders; + public List appleOrders; + + + + /// + /// 是否还有未上报的 Google Order + /// + public bool HasUnreportedGoogleOrder => (googleOrders?.Count ?? 0) > 0; + + /// + /// 是否还有未上报的 Apple Order + /// + public bool HasUnreportedAppleOrder => (appleOrders?.Count ?? 0) > 0; + + + + public IAPModel() + { + androidTokens = new List(20); + iosReceipts = new List(20); + googleOrders = new List(20); + appleOrders = new List(20); + } + + public static IAPModel Load() + { + IAPModel model = null; + var json = LoadModelData(); + if (!string.IsNullOrEmpty(json)) + { + model = JsonConvert.DeserializeObject(json); + } + if(null != model) return model; + return new IAPModel(); + } + + + public void Save() + { + SaveModelData(this); + } + + + private static string LoadModelData() + { + return PlayerPrefs.GetString(nameof(IAPModel), ""); + } + + private static void SaveModelData(IAPModel model) + { + var json = JsonConvert.SerializeObject(model); + PlayerPrefs.SetString(nameof(IAPModel), json); + } + + + #region Receipt + + public void AddToken(string token) + { + if (androidTokens == null) androidTokens = new List(20); + if(string.IsNullOrEmpty(token)) return; + androidTokens.Add(token); + Save(); + } + + /// + /// 添加收据 + /// + /// + /// + public void AddReceipt(string receipt) + { + if (iosReceipts == null) iosReceipts = new List(20); + if(string.IsNullOrEmpty(receipt)) return; + iosReceipts.Add(receipt); + Save(); + } + + public bool IsTokenExists(string token) + { + if (androidTokens == null) return false; + return androidTokens.Contains(token); + } + + public bool IsReceiptExist(string receipt) + { + if (iosReceipts == null) return false; + return iosReceipts.Contains(receipt); + } + + #endregion + + #region Orders + + public void AddGoogleOrder(GoogleOrderData order) + { + googleOrders.Add(order); + Save(); + } + + public void RemoveGoogleOrder(GoogleOrderData order) + { + googleOrders.Remove(order); + Save(); + } + public void ClearGoogleOrders() + { + googleOrders.Clear(); + Save(); + } + + public void AddAppleOrder(AppleOrderData order) + { + appleOrders.Add(order); + Save(); + } + + public void RemoveAppleOrder(AppleOrderData order) + { + appleOrders.Remove(order); + Save(); + } + + public void ClearAppleOrders() + { + appleOrders.Clear(); + Save(); + } + #endregion + + + + } + + + + + +} \ No newline at end of file diff --git a/Runtime/GuruIAP/Runtime/Code/IAPModel.cs.meta b/Runtime/GuruIAP/Runtime/Code/IAPModel.cs.meta new file mode 100644 index 0000000..9f015a6 --- /dev/null +++ b/Runtime/GuruIAP/Runtime/Code/IAPModel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 52e82348c38f470c8d9f247451ca5ce5 +timeCreated: 1710408457 \ No newline at end of file diff --git a/Runtime/GuruIAP/Runtime/Code/IAPServiceBase.cs b/Runtime/GuruIAP/Runtime/Code/IAPServiceBase.cs index 92ff0d0..f42cd65 100644 --- a/Runtime/GuruIAP/Runtime/Code/IAPServiceBase.cs +++ b/Runtime/GuruIAP/Runtime/Code/IAPServiceBase.cs @@ -12,6 +12,9 @@ namespace Guru public abstract class IAPServiceBase: IStoreListener where T: IAPServiceBase , new() { + public static readonly int OrderRequestTimeout = 10; + public static readonly int OrderRequestRetryTimes = 3; + #region 属性定义 private const string Tag = "[IAP]"; @@ -33,7 +36,8 @@ namespace Guru public bool IsInitialized => _storeController != null && _storeExtensionProvider != null; private Product _curPurchasingProduct = null; - + + private IAPModel _model; /// /// 是否是首次购买 /// @@ -129,6 +133,7 @@ namespace Guru { _googlePublicKey = googlePublicKey; _appleRootCert = appleRootCert; + InitModel(); Initialize(showLog); } @@ -681,7 +686,7 @@ namespace Guru #endregion #region 支付上报逻辑 - + /// /// 支付结果上报 /// @@ -716,15 +721,13 @@ namespace Guru string subscriptionID = orderType == 1 ? args.purchasedProduct.definition.id : ""; LogI($"{Tag} --- Report Android IAP Order -> orderType:{orderType} productID:{productID} blevel:{blevel}"); - - Debug.Log($"############ --- result.Count: {result.Length}"); - foreach (var productReceipt in result) { if (productReceipt is GooglePlayReceipt google) { Debug.Log($"{Tag} --- Report Android IAP Order -> orderType:{orderType} productID:{productID} blevel:{blevel}"); - new GoogleOrderRequest(orderType, productID, subscriptionID, google.purchaseToken, blevel).SetTimeOut(5).Send(); + // new GoogleOrderRequest(orderType, productID, subscriptionID, google.purchaseToken, blevel).SetTimeOut(5).Send(); + ReportGoogleOrder(orderType, productID, subscriptionID, google.purchaseToken, blevel); } } #elif UNITY_IOS @@ -737,7 +740,8 @@ namespace Guru return; } AddReceipt(receipt); - new AppleOrderRequest(orderType, args.purchasedProduct.definition.id, receipt,blevel).Send(); + // new AppleOrderRequest(orderType, args.purchasedProduct.definition.id, receipt,blevel).Send(); + ReportAppleOrder(orderType, args.purchasedProduct.definition.id, receipt,blevel); Debug.Log($"{Tag} --- Report iOS IAP Order -> orderType:{orderType} productID:{args.purchasedProduct.definition.id} blevel:{blevel}"); #endif } @@ -821,6 +825,187 @@ namespace Guru #endregion + + #region 数据初始化 + + + private void InitModel() + { + _model = IAPModel.Load(); // 初始化 Model + + // 启动时查询 + if(_orderRequests == null) + _orderRequests = new Queue(20); +#if UNITY_ANDROID + + if (_model.HasUnreportedGoogleOrder) + { + foreach (var o in _model.googleOrders) + { + ReportGoogleOrder(o); + } + _model.ClearGoogleOrders(); + } +#elif UNITY_IOS + if (_model.HasUnreportedAppleOrder) + { + foreach (var o in _model.appleOrders) + { + ReportAppleOrder(o); + } + _model.ClearAppleOrders(); + } +#endif + + + } + + + + + + + #endregion + + #region 订单上报队列 + + private bool isOrderSending = false; + private Queue _orderRequests = new Queue(20); + + + /// + /// 上报 Google Order Request + /// + /// + /// + /// + /// + /// + /// + /// + private void ReportGoogleOrder(int orderType, string productId, string subscriptionId, string token, int level, + string offerId = "", string basePlanId = "") + { + var request = GoogleOrderRequest.Build(orderType, productId, subscriptionId, token, level, offerId, basePlanId); + ReportNextOrder(request); + } + private void ReportGoogleOrder(GoogleOrderData data) + { + var request = GoogleOrderRequest.Build(data); + ReportNextOrder(request); + } + + private void ReportAppleOrder(int orderType, string productId, string receipt, int level) + { + var request = AppleOrderRequest.Build(orderType, productId, receipt, level); + ReportNextOrder(request); + } + + private void ReportAppleOrder(AppleOrderData data) + { + var request = AppleOrderRequest.Build(data); + ReportNextOrder(request); + } + + private void ReportNextOrder(RequestBase request) + { + _orderRequests.Enqueue(request); + + if(isOrderSending) return; + isOrderSending = true; + + OnSendNextOrder(); + } + + + /// + /// 上报下一个 Google 订单 + /// + private void OnSendNextOrder() + { + if (_orderRequests != null && _orderRequests.Count > 0) + { + isOrderSending = true; + var request = _orderRequests.Dequeue(); + GoogleOrderRequest go = request as GoogleOrderRequest; + AppleOrderRequest ao = request as AppleOrderRequest; + + if (go != null && _model.IsTokenExists(go.token)) + { + OnSendNextOrder(); + return; + } + + if( ao != null && _model.IsReceiptExist(ao.receipt)) + { + OnSendNextOrder(); + return; + } + + request.SetTimeOut(OrderRequestTimeout) + .SetRetryTimes(OrderRequestRetryTimes) + .SetSuccessCallBack(() => + { + if (go != null) + { + _model.AddToken(go.token); + } + else if (ao != null) + { + _model.AddReceipt(ao.receipt); + } + OnSendNextOrder(); + }) + .SetFailCallBack(() => + { + if (go != null) + { + _model.AddGoogleOrder(go.orderData); + ReportGoogleOrderLost(go.orderData); + } + else if (ao != null) + { + _model.AddAppleOrder(ao.orderData); + ReportAppleOrderLost(ao.orderData); + } + + OnSendNextOrder(); + }) + .Send(); + } + else + { + isOrderSending = false; + } + } + + private void ReportGoogleOrderLost(GoogleOrderData data) + { + Analytics.LogEvent("google_order_lost", new Dictionary() + { + ["data"] = data.ToString(), + }, new Analytics.EventSetting() + { + EnableFirebaseAnalytics = true, + EnableFacebookAnalytics = true, + }); + } + + private void ReportAppleOrderLost(AppleOrderData data) + { + Analytics.LogEvent("apple_order_lost", new Dictionary() + { + ["data"] = data.ToString(), + }, new Analytics.EventSetting() + { + EnableFirebaseAnalytics = true, + EnableFacebookAnalytics = true, + }); + } + + #endregion + + } } \ No newline at end of file