update: 支付插件初始化修改, orders 上报缓存, IAP 数据中台接口改造
							parent
							
								
									277bf0bfba
								
							
						
					
					
						commit
						0c9b2a77fe
					
				|  | @ -136,6 +136,11 @@ namespace Guru | |||
|         { | ||||
|             CacheUserProperty(Analytics.PropertyIDFV, idfv); | ||||
|         } | ||||
|          | ||||
|         public static void SetIDFA(string idfa) | ||||
|         { | ||||
|             CacheUserProperty(Analytics.PropertyIDFA, idfa); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|  |  | |||
|  | @ -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 关键属性上报 | ||||
|  |  | |||
|  | @ -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 状态 | ||||
|  |  | |||
|  | @ -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 | |||
| 			 | ||||
| 		} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// 设置FirebaseId | ||||
| 		/// </summary> | ||||
|  | @ -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(); | ||||
| 		} | ||||
|  |  | |||
|  | @ -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); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| } | ||||
|  | @ -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<string, object> 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<string, object> | ||||
|             { | ||||
|                 ["level"] = level | ||||
|             }; | ||||
|             userInfo = new Dictionary<string, object> { ["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}"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 0b44b9252ff741f58c6489223c0d5dca | ||||
| timeCreated: 1710411579 | ||||
|  | @ -6,27 +6,56 @@ namespace Guru | |||
|     [Serializable] | ||||
|     public class GoogleOrderData | ||||
|     { | ||||
|         public int orderType; | ||||
|         public string packageName; | ||||
|         public string productId; | ||||
|         public string token; | ||||
|         public Dictionary<string, object> 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<string, object> 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<string, object> | ||||
|             { | ||||
|                 ["level"] = level | ||||
|             }; | ||||
|             this.offerId = offerId; | ||||
|             this.basePlanId = basePlanId; | ||||
|             this.level = level; | ||||
|             userInfo = new Dictionary<string, object> { ["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<string, object> { ["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}"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<string, object> userInfo; | ||||
|         public EventConfig eventConfig; | ||||
|          | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 构建订单数据 | ||||
|         /// </summary> | ||||
|         /// <param name="platform"></param> | ||||
|         /// <param name="orderType"></param> | ||||
|         /// <param name="receipt"></param> | ||||
|         /// <param name="level"></param> | ||||
|         /// <param name="productId"></param> | ||||
|         /// <param name="subscriptionId"></param> | ||||
|         /// <param name="offerId"></param> | ||||
|         /// <param name="basePlanId"></param> | ||||
|         /// <returns></returns> | ||||
|         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<string, object>() | ||||
|                 { | ||||
|                     {"level", level} | ||||
|                 }, | ||||
|                 eventConfig = new EventConfig() | ||||
|                 { | ||||
|                     firebaseAppInstanceId = IPMConfig.FIREBASE_ID, | ||||
|                     idfa = IPMConfig.ADJUST_IDFA, | ||||
|                     adid = IPMConfig.ADJUST_ID, | ||||
|                     gpsAdid = IPMConfig.ADJUST_ADID, | ||||
|                 } | ||||
|             }; | ||||
|             return orderData; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 9e4562ba60204b85bca8777b771c73e5 | ||||
| timeCreated: 1710410036 | ||||
|  | @ -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<OrderResponse> responseData = JsonUtility.FromJson<ResponseData<OrderResponse>>(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); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -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; | ||||
|         } | ||||
|          | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -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<string> androidTokens; | ||||
|         public List<string> iosReceipts; | ||||
|         public List<GoogleOrderData> googleOrders; | ||||
|         public List<AppleOrderData> appleOrders; | ||||
|          | ||||
| 
 | ||||
|      | ||||
|         /// <summary> | ||||
|         /// 是否还有未上报的 Google Order | ||||
|         /// </summary> | ||||
|         public bool HasUnreportedGoogleOrder => (googleOrders?.Count ?? 0) > 0; | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 是否还有未上报的 Apple Order | ||||
|         /// </summary> | ||||
|         public bool HasUnreportedAppleOrder => (appleOrders?.Count ?? 0) > 0; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         public IAPModel() | ||||
|         { | ||||
|             androidTokens = new List<string>(20); | ||||
|             iosReceipts = new List<string>(20); | ||||
|             googleOrders = new List<GoogleOrderData>(20); | ||||
|             appleOrders = new List<AppleOrderData>(20); | ||||
|         } | ||||
| 
 | ||||
|         public static IAPModel Load() | ||||
|         { | ||||
|             IAPModel model = null; | ||||
|             var json = LoadModelData(); | ||||
|             if (!string.IsNullOrEmpty(json)) | ||||
|             { | ||||
|                 model = JsonConvert.DeserializeObject<IAPModel>(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<string>(20); | ||||
|             if(string.IsNullOrEmpty(token)) return; | ||||
|             androidTokens.Add(token); | ||||
|             Save(); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 添加收据 | ||||
|         /// </summary> | ||||
|         /// <param name="receipt"></param> | ||||
|         /// <param name="platform"></param> | ||||
|         public void AddReceipt(string receipt) | ||||
|         { | ||||
|             if (iosReceipts == null) iosReceipts = new List<string>(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 | ||||
|          | ||||
|          | ||||
|          | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 52e82348c38f470c8d9f247451ca5ce5 | ||||
| timeCreated: 1710408457 | ||||
|  | @ -12,6 +12,9 @@ namespace Guru | |||
|     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]"; | ||||
|  | @ -33,7 +36,8 @@ namespace Guru | |||
|         public bool IsInitialized => _storeController != null && _storeExtensionProvider != null; | ||||
| 
 | ||||
|         private Product _curPurchasingProduct = null; | ||||
|          | ||||
| 
 | ||||
|         private IAPModel _model; | ||||
|         /// <summary> | ||||
|         /// 是否是首次购买 | ||||
|         /// </summary> | ||||
|  | @ -129,6 +133,7 @@ namespace Guru | |||
|         { | ||||
|             _googlePublicKey = googlePublicKey; | ||||
|             _appleRootCert = appleRootCert; | ||||
|             InitModel(); | ||||
|             Initialize(showLog); | ||||
|         } | ||||
| 
 | ||||
|  | @ -681,7 +686,7 @@ namespace Guru | |||
|         #endregion | ||||
|          | ||||
|         #region 支付上报逻辑 | ||||
| 
 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 支付结果上报 | ||||
|         /// </summary> | ||||
|  | @ -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<RequestBase>(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<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="level"></param> | ||||
|         /// <param name="offerId"></param> | ||||
|         /// <param name="basePlanId"></param> | ||||
|         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(); | ||||
|         } | ||||
|          | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 上报下一个 Google 订单 | ||||
|         /// </summary> | ||||
|         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<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 | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
		Loading…
	
		Reference in New Issue