新增内购
This commit is contained in:
parent
c89fe09c1a
commit
5201363037
|
@ -121,6 +121,14 @@ namespace WZ
|
|||
{
|
||||
StaticValue.AdmobCollapsibleBannerId = valueTemp;
|
||||
}
|
||||
else if (_configs[i].Key.ToLower() == KEY_TF_IAP_CHECK_ORDER.ToLower())
|
||||
{
|
||||
StaticValue.CheckOrderUrl = valueTemp;
|
||||
}
|
||||
else if (_configs[i].Key.ToLower() == KEY_TF_IAP_REPORT.ToLower())
|
||||
{
|
||||
StaticValue.VerifyPurchaseUrl = valueTemp;
|
||||
}
|
||||
else if (_configs[i].Key.ToLower() == KEY_Admob_NormalBannerId.ToLower())
|
||||
{
|
||||
StaticValue.AdmobNormalBannerId = valueTemp;
|
||||
|
@ -323,6 +331,8 @@ namespace WZ
|
|||
|
||||
}
|
||||
|
||||
public const string KEY_TF_IAP_CHECK_ORDER = "TF_IAP_CHECK_ORDER";
|
||||
public const string KEY_TF_IAP_REPORT = "TF_IAP_REPORT";
|
||||
public const string KEY_Admob_CollapsibleBannerId = "Admob_Banner_ID1";
|
||||
public const string KEY_Admob_NormalBannerId = "Admob_Banner_ID2";
|
||||
public const string KEY_Admob_SplashId = "Admob_APPOPEN_ID";
|
||||
|
|
|
@ -7,6 +7,8 @@ namespace WZ
|
|||
|
||||
public static class StaticValue
|
||||
{
|
||||
public static string CheckOrderUrl = "";
|
||||
public static string VerifyPurchaseUrl = "";
|
||||
public static string AdmobCollapsibleBannerId = "";
|
||||
|
||||
public static string AdmobNormalBannerId = "";
|
||||
|
|
|
@ -5,6 +5,8 @@ using System.Threading.Tasks;
|
|||
using AdjustSdk;
|
||||
using ThinkingData.Analytics;
|
||||
using Unity.VisualScripting;
|
||||
using Unity.VisualScripting.Antlr3.Runtime.Tree;
|
||||
using UnityEditor.ShaderKeywordFilter;
|
||||
using UnityEngine;
|
||||
using WZ;
|
||||
|
||||
|
@ -18,6 +20,11 @@ public class AdjustManager : D_MonoSingleton<AdjustManager>
|
|||
private string callbackNetwork = "";
|
||||
bool m_start = false;
|
||||
private int callGetTimes = 0;
|
||||
private string _adjustNetwork = "_adjustNetwork";
|
||||
private string _adjustCampaign = "_adjustCampaign";
|
||||
private string _adjustAdgroup = "_adjustAdgroup";
|
||||
private string _adjustCreative = "_adjustCreative";
|
||||
private string _adjustClickLabel = "_adjustClickLabel";
|
||||
|
||||
public void Init()
|
||||
{
|
||||
|
@ -172,8 +179,37 @@ public class AdjustManager : D_MonoSingleton<AdjustManager>
|
|||
});
|
||||
|
||||
RushSDKManager.Instance.OnUserSourceListener?.Invoke(IsOrganic(network), network);
|
||||
PlayerPrefsUtils.SavePlayerPrefsString(_adjustNetwork, network);
|
||||
PlayerPrefsUtils.SavePlayerPrefsString(_adjustCampaign, campaign);
|
||||
PlayerPrefsUtils.SavePlayerPrefsString(_adjustAdgroup, adgroup);
|
||||
PlayerPrefsUtils.SavePlayerPrefsString(_adjustCreative, creative);
|
||||
PlayerPrefsUtils.SavePlayerPrefsString(_adjustClickLabel, attribution?.ClickLabel);
|
||||
}
|
||||
|
||||
public string GetAdjustNetwork()
|
||||
{
|
||||
return PlayerPrefsUtils.GetPlayerPrefsString(_adjustNetwork);
|
||||
}
|
||||
|
||||
public string GetAdjustCampaign()
|
||||
{
|
||||
return PlayerPrefsUtils.GetPlayerPrefsString(_adjustCampaign);
|
||||
}
|
||||
|
||||
public string GetAdjustAdgroup()
|
||||
{
|
||||
return PlayerPrefsUtils.GetPlayerPrefsString(_adjustAdgroup);
|
||||
}
|
||||
|
||||
public string GetAdjustCreative()
|
||||
{
|
||||
return PlayerPrefsUtils.GetPlayerPrefsString(_adjustCreative);
|
||||
}
|
||||
|
||||
public string GetAdjustClickLabel()
|
||||
{
|
||||
return PlayerPrefsUtils.GetPlayerPrefsString(_adjustClickLabel);
|
||||
}
|
||||
|
||||
private bool IsOrganic(string _network)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2ebf62044324047e78f251d34185ff48
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ca2f9d75394fd4e859f89e488d422e80
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,16 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WZ
|
||||
{
|
||||
public enum IAPDataStateType
|
||||
{
|
||||
// 未处理
|
||||
def,
|
||||
// 处理中
|
||||
deal
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0595ab8f5ac6241689d314aec6c7a3ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,25 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WZ
|
||||
{
|
||||
|
||||
public enum IAPResultType
|
||||
{
|
||||
PurchasingUnavailable, // 无法使用系统购买功能
|
||||
ExistingPurchasePending, // 请求新购买时正在进行前一项购买
|
||||
ProductUnavailable, // 无法在商店购买商品
|
||||
SignatureInvalid, // 购买收据的签名验证失败
|
||||
UserCancelled, // 用户选择取消而不是继续购买
|
||||
PaymentDeclined, // 付款出现问题
|
||||
DuplicateTransaction, // 当交易已经成功完成时出现的重复交易错误
|
||||
Unknown, // 未识别的购买问题的通用原因
|
||||
ServerRequestFailed, // 客户端购买成功请求服务端失败
|
||||
ServerAuthenticationFailed, // 客户端购买成功服务端验证失败
|
||||
PurchasingSuccess,
|
||||
NULL
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2aa385284497442cebdff40e44bad707
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,236 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_PURCHASE
|
||||
namespace WZ
|
||||
{
|
||||
public static class IAPEvent
|
||||
{
|
||||
public static void LogPurchaseInit()
|
||||
{
|
||||
var eventName = "Purchase_Init";
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName);
|
||||
ShuShuEvent.Instance.Track(eventName);
|
||||
}
|
||||
|
||||
public static void LogPurchaseInit(bool success, string message = "")
|
||||
{
|
||||
var eventName = success ? "Purchase_Init_Success" : "Purchase_Init_Fail";
|
||||
if (success)
|
||||
{
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName);
|
||||
ShuShuEvent.Instance.Track(eventName);
|
||||
}
|
||||
else
|
||||
{
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName, new Dictionary<string, object> { { "fail_reason", message } });
|
||||
ShuShuEvent.Instance.Track(eventName, new Dictionary<string, object> { { "fail_reason", message } });
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogPurchaseSuccess(PurchaseInfo info,string environment)
|
||||
{
|
||||
// 总支付次数
|
||||
int payTimes = PlayerPrefsUtils.GetPlayerPrefsInt("PAY_TIME", 0);
|
||||
if (!info.orderAlreadyExists)
|
||||
{
|
||||
payTimes += 1;
|
||||
}
|
||||
float fPrice;
|
||||
if (!float.TryParse(info.price, out fPrice))
|
||||
{
|
||||
fPrice = 0.0f;
|
||||
}
|
||||
var args = new Dictionary<string, object>
|
||||
{
|
||||
{ "IAP", info.productName },
|
||||
{ "product_id", info.productID },
|
||||
{ "currency", info.currency },
|
||||
{ "Price", fPrice },
|
||||
{ "order_id", info.orderID },
|
||||
{ "game_extra", info.gameExtra },
|
||||
{ "is_first", PlayerPrefsUtils.GetPlayerPrefsInt(First_Purchase, 0) == 0},
|
||||
{ "payment_type", "GooglePlay" },
|
||||
{ "product_type", info.productType },
|
||||
{ "orderAlreadyExists", info.orderAlreadyExists }
|
||||
};
|
||||
if (string.IsNullOrEmpty(environment)) environment = "";
|
||||
var eventName = environment.Equals("sandbox") ? "IAP_Success_Sandbox" : "IAP_Success";
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName, args);
|
||||
ShuShuEvent.Instance.Track(eventName, args);
|
||||
// 首次订单 设置支付次数/累计支付次数/累计支付金额
|
||||
if (!info.orderAlreadyExists)
|
||||
{
|
||||
fPrice = fPrice + PlayerPrefsUtils.GetPlayerPrefsFloat(PURCHASE_PRICE, 0.0f);
|
||||
PlayerPrefsUtils.SavePlayerPrefsFloat(PURCHASE_PRICE, fPrice);
|
||||
|
||||
ShuShuEvent.Instance.UserSet(new Dictionary<string, object>()
|
||||
{
|
||||
{ "total_pay_amount", fPrice },
|
||||
{ "total_pay_times", payTimes }
|
||||
});
|
||||
|
||||
ShuShuEvent.Instance.SetSuperProperties(new Dictionary<string, object>()
|
||||
{
|
||||
{ "pay_times", payTimes }
|
||||
});
|
||||
PlayerPrefsUtils.SavePlayerPrefsInt(First_Purchase, 1);
|
||||
ShuShuEvent.Instance.UserSet(new Dictionary<string, object>()
|
||||
{
|
||||
{ "first_pay_time", DateTime.Now }
|
||||
}, true);
|
||||
}
|
||||
|
||||
PlayerPrefsUtils.SavePlayerPrefsInt(PAY_TIME, payTimes);
|
||||
}
|
||||
|
||||
public static void LogPurchaseFail(PurchaseInfo info)
|
||||
{
|
||||
var eventName = "IAP_Fail";
|
||||
float fPrice;
|
||||
if (!float.TryParse(info.price, out fPrice))
|
||||
{
|
||||
fPrice = 0.0f;
|
||||
}
|
||||
var para = new Dictionary<string, object>
|
||||
{
|
||||
{ "IAP", info.productName },
|
||||
{ "product_id", info.productID },
|
||||
{ "currency", info.currency },
|
||||
{ "pay_amount", fPrice },
|
||||
{ "order_id", info.orderID },
|
||||
{ "game_extra", info.gameExtra ?? ""},
|
||||
{ "payment_type", "GooglePlay" },
|
||||
{ "fail_reason", info.failReason }
|
||||
};
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName, para);
|
||||
ShuShuEvent.Instance.Track(eventName, para);
|
||||
}
|
||||
|
||||
public static void LogIAPButtonClick(PurchaseInfo info)
|
||||
{
|
||||
var eventName = "IAP_Button_Click";
|
||||
float fPrice;
|
||||
if (!float.TryParse(info.price, out fPrice))
|
||||
{
|
||||
fPrice = 0.0f;
|
||||
}
|
||||
var args = new Dictionary<string, object>
|
||||
{
|
||||
{ "IAP", info.productName},
|
||||
{ "product_id", info.productID},
|
||||
{ "currency", info.currency },
|
||||
{ "Price", fPrice },
|
||||
{ "game_extra", info.gameExtra},
|
||||
{ "payment_type", "GooglePlay" },
|
||||
};
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName, args);
|
||||
ShuShuEvent.Instance.Track(eventName, args);
|
||||
}
|
||||
|
||||
public static void LogClientComplete(PurchaseInfo info)
|
||||
{
|
||||
var eventName = "Client_Finish_Channel";
|
||||
float fPrice;
|
||||
if (!float.TryParse(info.price, out fPrice))
|
||||
{
|
||||
fPrice = 0.0f;
|
||||
}
|
||||
var para = new Dictionary<string, object>
|
||||
{
|
||||
{ "IAP", info.productName },
|
||||
{ "product_id", info.productID },
|
||||
{ "currency", info.currency },
|
||||
{ "Price", fPrice },
|
||||
{ "order_id", info.orderID },
|
||||
{ "game_extra", info.gameExtra ?? ""},
|
||||
{ "payment_type", "GooglePlay" },
|
||||
{ "product_type",info.productType},
|
||||
{ "is_first", PlayerPrefsUtils.GetPlayerPrefsInt(First_Purchase, 0) == 0 }
|
||||
};
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName, para);
|
||||
ShuShuEvent.Instance.Track(eventName, para);
|
||||
}
|
||||
|
||||
public static void LogSaveOrderBySdk(PurchaseInfo info)
|
||||
{
|
||||
var eventName = "IAP_SDK_Save_Order";
|
||||
float fPrice;
|
||||
if (!float.TryParse(info.price, out fPrice))
|
||||
{
|
||||
fPrice = 0.0f;
|
||||
}
|
||||
var para = new Dictionary<string, object>
|
||||
{
|
||||
{ "IAP", info.productName },
|
||||
{ "product_id", info.productID },
|
||||
{ "currency", info.currency },
|
||||
{ "price", fPrice },
|
||||
{ "order_id", info.orderID },
|
||||
{ "game_extra", info.gameExtra ?? ""},
|
||||
{ "payment_type", "GooglePlay" },
|
||||
{ "product_type",info.productType},
|
||||
{ "is_first", PlayerPrefsUtils.GetPlayerPrefsInt(First_Purchase, 0) == 0 }
|
||||
};
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName, para);
|
||||
ShuShuEvent.Instance.Track(eventName, para);
|
||||
}
|
||||
|
||||
public static void LogStarVerifyOrderBySdk(PurchaseInfo info)
|
||||
{
|
||||
var eventName = "IAP_Cache_Order";
|
||||
float fPrice;
|
||||
if (!float.TryParse(info.price, out fPrice))
|
||||
{
|
||||
fPrice = 0.0f;
|
||||
}
|
||||
var para = new Dictionary<string, object>
|
||||
{
|
||||
{ "IAP", info.productName },
|
||||
{ "product_id", info.productID },
|
||||
{ "currency", info.currency },
|
||||
{ "price", fPrice },
|
||||
{ "order_id", info.orderID },
|
||||
{ "game_extra", info.gameExtra},
|
||||
{ "payment_type", "GooglePlay" },
|
||||
{ "product_type",info.productType},
|
||||
{ "is_first", PlayerPrefsUtils.GetPlayerPrefsInt(First_Purchase, 0) == 0 }
|
||||
};
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName, para);
|
||||
ShuShuEvent.Instance.Track(eventName, para);
|
||||
}
|
||||
|
||||
public static void LogRemoveOrderBySdk(PurchaseInfo info)
|
||||
{
|
||||
var eventName = "IAP_SDK_Remove_Order";
|
||||
float fPrice;
|
||||
if (!float.TryParse(info.price, out fPrice))
|
||||
{
|
||||
fPrice = 0.0f;
|
||||
}
|
||||
|
||||
var para = new Dictionary<string, object>
|
||||
{
|
||||
{ "price", fPrice },
|
||||
{ "product_id", info.productID },
|
||||
{ "IAP", info.productName },
|
||||
{ "order_id", info.orderID },
|
||||
{ "currency", info.currency },
|
||||
{ "payment_method", "googleplay" },
|
||||
{ "game_extra", info.gameExtra},
|
||||
{ "product_type", info.productType}
|
||||
};
|
||||
FireBaseAnalyticsManager.Instance.LogEvent(eventName, para);
|
||||
ShuShuEvent.Instance.Track(eventName, para);
|
||||
}
|
||||
|
||||
public static string First_Purchase = "First_purchase";
|
||||
public static string PAY_TIME = "PAY_TIME";
|
||||
public static string PURCHASE_PRICE = "PURCHASE_PRICE";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 273e000ea028247038e5e7f8f756fbc0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,39 @@
|
|||
#if UNITY_PURCHASE
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
namespace WZ
|
||||
{
|
||||
public struct PurchaseInfo
|
||||
{
|
||||
public string productName;
|
||||
public string productID;
|
||||
public string orderID;
|
||||
public string currency;
|
||||
public string price;
|
||||
public string gameExtra;
|
||||
public string productType;
|
||||
public string failReason;
|
||||
public bool orderAlreadyExists;
|
||||
public bool purchaseResult;
|
||||
public IAPResultType resultType;
|
||||
public PurchaseInfo(string productName, string productID, string orderID,
|
||||
string currency, string price, string gameExtra,
|
||||
string productType, string failReason, bool orderAlreadyExists, bool purchaseResult,
|
||||
IAPResultType resultType)
|
||||
{
|
||||
this.productName = productName;
|
||||
this.productID = productID;
|
||||
this.orderID = orderID;
|
||||
this.currency = currency;
|
||||
this.price = price;
|
||||
this.gameExtra = gameExtra;
|
||||
this.productType = productType;
|
||||
this.failReason = failReason;
|
||||
this.orderAlreadyExists = orderAlreadyExists;
|
||||
this.purchaseResult = purchaseResult;
|
||||
this.resultType = resultType;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6d2378dd12f2d41c190fc63f6016b4d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,395 @@
|
|||
#if UNITY_PURCHASE
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WZ
|
||||
{
|
||||
|
||||
public class IAPOrderManager : D_MonoSingleton<IAPOrderManager>
|
||||
{
|
||||
private static string _purchaseOrderMap = "PurchaseOrderMap";
|
||||
private List<int> NotRetryCode = new List<int> { 0, 430, 900, 901, 902, 903, 904, 905 };
|
||||
private Dictionary<string, int> mRepeatCountDic;
|
||||
|
||||
#region 订单验证
|
||||
public void VerifyPurchase(IAPDataConfig args)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] Start VerifyPurchase orderid:" + args.info["order_id"] + "status:" + args.state);
|
||||
if (args.state == IAPDataStateType.def)
|
||||
{
|
||||
// 记录订单处理次数,处理5次还是失败就不处理了
|
||||
if (mRepeatCountDic.ContainsKey(args.info["order_id"]))
|
||||
{
|
||||
mRepeatCountDic[args.info["order_id"]] = mRepeatCountDic[args.info["order_id"]] + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
mRepeatCountDic.Add(args.info["order_id"], 1);
|
||||
}
|
||||
|
||||
// 开始处理,设置正在处理的状态
|
||||
RefreshOrderStatue(args, IAPDataStateType.deal);
|
||||
args.info.TryGetValue("mGameExtraParam", out var gameExtraParam);
|
||||
|
||||
if (string.IsNullOrEmpty(gameExtraParam))
|
||||
{
|
||||
gameExtraParam = IAPPurchaseManager.Instance._gameExtraParam;
|
||||
}
|
||||
|
||||
LoggerUtils.Debug("[iap] IAP VerifyPurchase start");
|
||||
var requestArgs = args.info.ToDictionary(kv => kv.Key, kv => (object)kv.Value);
|
||||
ServerMgr.Instance.VerifyPurchase(requestArgs, (code, msg, data) =>
|
||||
{
|
||||
RefreshOrderStatue(args, IAPDataStateType.def);
|
||||
LoggerUtils.Debug("IAP VerifyPurchase CODE:" + code + " illegal:" + data.illegal_order + " env:" + data.environment);
|
||||
LoggerUtils.Debug("IAP VerifyPurchase msg:" + msg);
|
||||
|
||||
if ((code == 0 || code == 430) && data.illegal_order == 0)
|
||||
{
|
||||
// 将订阅商品信息存储到本地
|
||||
if (!IAPPurchaseManager.Instance._productArgs.ContainsKey(args.info["product_id"].ToString()) && args.info["product_type"].ToString().Equals("Subscription"))
|
||||
{
|
||||
IAPPurchaseManager.Instance._productArgs.Add(args.info["product_id"].ToString(), requestArgs);
|
||||
|
||||
}
|
||||
else if (IAPPurchaseManager.Instance._productArgs.ContainsKey(args.info["product_id"].ToString()) && args.info["product_type"].ToString().Equals("Subscription"))
|
||||
{
|
||||
IAPPurchaseManager.Instance._productArgs[args.info["product_id"].ToString()] = requestArgs;
|
||||
}
|
||||
RushSDKManager.Instance.OnPurchaseComplete?.Invoke(new PurchaseInfo(
|
||||
productName: args.info["product_name"],
|
||||
productID: args.info["product_id"],
|
||||
orderID: args.info["order_id"],
|
||||
currency: args.info["currency"],
|
||||
price: args.info["price"],
|
||||
gameExtra: gameExtraParam,
|
||||
failReason: "",
|
||||
orderAlreadyExists: code == 430,
|
||||
purchaseResult: code == 0,
|
||||
resultType: IAPResultType.PurchasingSuccess,
|
||||
productType: args.info["product_type"]
|
||||
));
|
||||
LogVerifySuccessOrder(args, data.environment, gameExtraParam, code == 430);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 不需要重试的错误码
|
||||
if (!NotRetryCode.Contains(code))
|
||||
{
|
||||
SaveVerifyFailOrderId(args);
|
||||
}
|
||||
|
||||
// 主动删除过期订单
|
||||
if (!string.IsNullOrEmpty(data.illegal_msg) && args.info["product_type"].ToString().Equals("Subscription"))
|
||||
{
|
||||
if (data.illegal_msg.Contains("product expired"))
|
||||
{
|
||||
RemoveVerifySuccessOrder(args.info["product_id"]);
|
||||
}
|
||||
}
|
||||
if (code == -1)
|
||||
{
|
||||
RushSDKManager.Instance.OnPurchaseComplete?.Invoke(new PurchaseInfo(
|
||||
productName: args.info["product_name"],
|
||||
productID: args.info["product_id"],
|
||||
orderID: args.info["order_id"],
|
||||
currency: args.info["currency"],
|
||||
price: args.info["price"],
|
||||
gameExtra: gameExtraParam,
|
||||
failReason: "",
|
||||
orderAlreadyExists: code == 430,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.NULL,
|
||||
productType: args.info["product_type"]
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
RushSDKManager.Instance.OnPurchaseComplete?.Invoke(new PurchaseInfo(
|
||||
productName: args.info["product_name"],
|
||||
productID: args.info["product_id"],
|
||||
orderID: args.info["order_id"],
|
||||
currency: args.info["currency"],
|
||||
price: args.info["price"],
|
||||
gameExtra: gameExtraParam,
|
||||
failReason: "",
|
||||
orderAlreadyExists: code == 430,
|
||||
purchaseResult: false,
|
||||
resultType: code == 430 ? IAPResultType.PurchasingSuccess : IAPResultType.ServerAuthenticationFailed,
|
||||
productType: args.info["product_type"]
|
||||
));
|
||||
}
|
||||
|
||||
// msg 包含 illegal_order_0 说明是正常的缓存订单上报
|
||||
// 卸载重装后,一次性商品
|
||||
if (msg.Contains("illegal_order_0"))
|
||||
{
|
||||
LogVerifySuccessOrder(args, msg.Contains("sandbox") ? "sandbox" : "production", gameExtraParam, code == 430);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogVerifyFailOrder(args, gameExtraParam, $"Purchase service code : {code} msg : {msg} dataMsg:{data.illegal_msg} illegal_order:{data.illegal_order} environment:{(msg.Contains("sandbox") ? "sandbox" : "production")}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void LogVerifySuccessOrder(IAPDataConfig _productInfo, string _enviroment, string _gameExtraParam, bool _orderAlreadyExists)
|
||||
{
|
||||
IAPEvent.LogPurchaseSuccess(new PurchaseInfo(
|
||||
productName: _productInfo.info["product_name"],
|
||||
productID: _productInfo.info["product_id"],
|
||||
orderID: _productInfo.info["order_id"],
|
||||
currency: _productInfo.info["currency"],
|
||||
price: _productInfo.info["price"],
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: "",
|
||||
orderAlreadyExists: _orderAlreadyExists,
|
||||
purchaseResult: !_orderAlreadyExists,
|
||||
resultType: IAPResultType.NULL,
|
||||
productType: _productInfo.info["product_type"]
|
||||
), _enviroment);
|
||||
}
|
||||
|
||||
private void LogVerifyFailOrder(IAPDataConfig _productInfo, string _gameExtraParam, string _errInfo)
|
||||
{
|
||||
|
||||
IAPEvent.LogPurchaseFail(new PurchaseInfo(
|
||||
productName: _productInfo.info["product_name"],
|
||||
productID: _productInfo.info["product_id"],
|
||||
orderID: _productInfo.info["order_id"],
|
||||
currency: _productInfo.info["currency"],
|
||||
price: _productInfo.info["price"],
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: _errInfo,
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.NULL,
|
||||
productType: _productInfo.info["product_type"]
|
||||
));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 重置订单状态
|
||||
private void RefreshOrderStatue(IAPDataConfig _productInfo, IAPDataStateType state)
|
||||
{
|
||||
if (ES3.KeyExists("FailOrderCacheData"))
|
||||
{
|
||||
List<IAPDataConfig> list = ES3.Load(_purchaseOrderMap) as List<IAPDataConfig>;
|
||||
var value = _productInfo.info["order_id"];
|
||||
var want = list.Find(e => e.info.ContainsValue(value));
|
||||
if (want != null)
|
||||
{
|
||||
want.state = state;
|
||||
ES3.Save(_purchaseOrderMap, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 存储订单
|
||||
public void SaveOrder(IAPDataConfig _productInfo)
|
||||
{
|
||||
if (ES3.KeyExists(_purchaseOrderMap))
|
||||
{
|
||||
List<IAPDataConfig> list = ES3.Load(_purchaseOrderMap) as List<IAPDataConfig>;
|
||||
var want = list.Find(e => e.info.ContainsValue(_productInfo.info["order_id"]));
|
||||
if (want == null)
|
||||
{
|
||||
list.Add(_productInfo);
|
||||
ES3.Save(_purchaseOrderMap, list);
|
||||
IAPEvent.LogSaveOrderBySdk(new PurchaseInfo(
|
||||
productName: _productInfo.info["product_name"],
|
||||
productID: _productInfo.info["product_id"],
|
||||
orderID: _productInfo.info["order_id"],
|
||||
currency: _productInfo.info["currency"],
|
||||
price: _productInfo.info["price"],
|
||||
gameExtra: _productInfo.info["mGameExtraParam"],
|
||||
productType: _productInfo.info["product_type"],
|
||||
failReason: null,
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.NULL
|
||||
));
|
||||
|
||||
LoggerUtils.Debug("[iap] IAP SaveOrder 已经有表了,添加订单,订单ID:" + _productInfo.info["order_id"] + " 未验证订单个数:" + list.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
List<IAPDataConfig> tem = new List<IAPDataConfig>
|
||||
{
|
||||
_productInfo
|
||||
};
|
||||
ES3.Save(_purchaseOrderMap, tem);
|
||||
IAPEvent.LogSaveOrderBySdk(new PurchaseInfo(
|
||||
productName: _productInfo.info["product_name"],
|
||||
productID: _productInfo.info["product_id"],
|
||||
orderID: _productInfo.info["order_id"],
|
||||
currency: _productInfo.info["currency"],
|
||||
price: _productInfo.info["price"],
|
||||
gameExtra: _productInfo.info["mGameExtraParam"],
|
||||
productType: _productInfo.info["product_type"],
|
||||
failReason: null,
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.NULL
|
||||
));
|
||||
LoggerUtils.Debug("[iap] IAP SaveOrder 第一次存储表 添加订单ID:" + _productInfo.info["order_id"] + " 商品类型:" + _productInfo.info["product_type"] + " 订单个数:" + tem.Count);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 保存验证失败订单
|
||||
private bool inDealFailOrder;
|
||||
private void SaveVerifyFailOrderId(IAPDataConfig _productInfo)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] iap save verify fail order,orderId:" + _productInfo.info["order_id"] + " 商品类型:" + _productInfo.info["product_type"]);
|
||||
if (ES3.KeyExists(_purchaseOrderMap))
|
||||
{
|
||||
List<IAPDataConfig> list = ES3.Load(_purchaseOrderMap) as List<IAPDataConfig>;
|
||||
|
||||
var value = _productInfo.info["order_id"];
|
||||
var want = list.Find(e => e.info.ContainsValue(value));
|
||||
if (want == null)
|
||||
{
|
||||
list.Add(_productInfo);
|
||||
ES3.Save(_purchaseOrderMap, list);
|
||||
ReadFailOrderId();
|
||||
LoggerUtils.Debug("[iap] IAP SaveVerifyFailOrderId 已经有表了,添加失败订单,订单ID:" + _productInfo.info["order_id"] + " 商品类型:" + _productInfo.info["product_type"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!inDealFailOrder)
|
||||
{
|
||||
inDealFailOrder = true;
|
||||
InvokeRepeating(nameof(ReadFailOrderId), 0, 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
List<IAPDataConfig> tem = new List<IAPDataConfig>
|
||||
{
|
||||
_productInfo
|
||||
};
|
||||
ES3.Save(_purchaseOrderMap, tem);
|
||||
ReadFailOrderId();
|
||||
LoggerUtils.Debug("[iap] IAP SaveVerifyFailOrderId 第一次存储表 添加失败订单 订单个数:" + tem.Count);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 再次验证订单
|
||||
public void ReadFailOrderId()
|
||||
{
|
||||
if (ES3.KeyExists(_purchaseOrderMap))
|
||||
{
|
||||
List<IAPDataConfig> list = ES3.Load(_purchaseOrderMap) as List<IAPDataConfig>;
|
||||
LoggerUtils.Debug("[iap] IAP ReadFailOrderId 读取失败订单列表,count:" + list.Count + " mRepeatCountDic" + mRepeatCountDic.Count);
|
||||
if (list.Count > 0)
|
||||
{
|
||||
// 当前进程每条订单校验5次,如果失败则不再处理
|
||||
// 重复校验时判断次数是否大于5,大于5则不再处理
|
||||
IAPDataConfig tempData = null;
|
||||
if (mRepeatCountDic.Count > 0)
|
||||
{
|
||||
foreach (var data in list)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] iap start verify fail order with repeat dic" + data.info["order_id"] + " count:" + mRepeatCountDic[data.info["order_id"]]);
|
||||
if (mRepeatCountDic.ContainsKey(data.info["order_id"]) && mRepeatCountDic[data.info["order_id"]] <= 5)
|
||||
{
|
||||
tempData = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果还有次数不超过5次的订单,则继续校验,否则就不再处理
|
||||
if (tempData != null)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] iap start verify fail order with repeat temp dic" + tempData.info["order_id"] + " count:" + mRepeatCountDic[tempData.info["order_id"]]);
|
||||
VerifyPurchase(tempData);
|
||||
IAPEvent.LogStarVerifyOrderBySdk(new PurchaseInfo(
|
||||
productName: tempData.info["product_name"],
|
||||
productID: tempData.info["product_id"],
|
||||
orderID: tempData.info["order_id"],
|
||||
currency: tempData.info["currency"],
|
||||
price: tempData.info["price"],
|
||||
gameExtra: tempData.info["mGameExtraParam"],
|
||||
productType: tempData.info["product_type"],
|
||||
failReason: null,
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.NULL));
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerUtils.Debug("[iap] iap cancel verify fail order");
|
||||
CancelInvoke(nameof(ReadFailOrderId));
|
||||
inDealFailOrder = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CancelInvoke(nameof(ReadFailOrderId));
|
||||
inDealFailOrder = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 移除订单
|
||||
// 校验成功后移除订单
|
||||
public void RemoveVerifySuccessOrder(string orderId)
|
||||
{
|
||||
if (ES3.KeyExists(_purchaseOrderMap))
|
||||
{
|
||||
List<IAPDataConfig> list = ES3.Load(_purchaseOrderMap) as List<IAPDataConfig>;
|
||||
var want = list.Find(e => e.info.ContainsValue(orderId));
|
||||
if (want != null)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] IAP RemoveVerifySuccessOrder 找到并移除订单,订单ID:" + orderId);
|
||||
list.Remove(want);
|
||||
IAPEvent.LogRemoveOrderBySdk(new PurchaseInfo(
|
||||
productName: want.info["product_name"],
|
||||
productID: want.info["product_id"],
|
||||
orderID: want.info["order_id"],
|
||||
currency: want.info["currency"],
|
||||
price: want.info["price"],
|
||||
gameExtra: want.info["mGameExtraParam"],
|
||||
productType: want.info["product_type"],
|
||||
failReason: null,
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.NULL));
|
||||
ES3.Save(_purchaseOrderMap, list);
|
||||
}
|
||||
|
||||
LoggerUtils.Debug("[iap] IAP 订单还剩余:" + list.Count + " 条未校验");
|
||||
if (list.Count > 0)
|
||||
{
|
||||
ReadFailOrderId();
|
||||
}
|
||||
else
|
||||
{
|
||||
inDealFailOrder = false;
|
||||
CancelInvoke(nameof(ReadFailOrderId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
public class IAPDataConfig
|
||||
{
|
||||
public Dictionary<string, string> info;
|
||||
public IAPDataStateType state;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8c0364ee309a94459af3113c481b3206
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,581 @@
|
|||
#if UNITY_PURCHASE
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework.Constraints;
|
||||
using Unity.Services.Core;
|
||||
using Unity.Services.Core.Environments;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Purchasing;
|
||||
using UnityEngine.Purchasing.Extension;
|
||||
using static WZ.IAPOrderManager;
|
||||
|
||||
|
||||
namespace WZ
|
||||
{
|
||||
public class IAPPurchaseManager : D_MonoSingleton<IAPPurchaseManager>, IDetailedStoreListener
|
||||
{
|
||||
#region API
|
||||
public void BuyProductByID(string productId, string productName, string gameExtraParam)
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
RushSDKManager.Instance.OnPurchaseComplete(new PurchaseInfo(
|
||||
productName: productName,
|
||||
productID: productId,
|
||||
orderID: "",
|
||||
currency: "USD",
|
||||
price: "",
|
||||
gameExtra: gameExtraParam,
|
||||
failReason: "",
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: true,
|
||||
resultType: IAPResultType.PurchasingSuccess,
|
||||
productType: ""
|
||||
));
|
||||
#else
|
||||
_productName = productName;
|
||||
_gameExtraParam = gameExtraParam;
|
||||
Product m_p = GetProductInfoByID(productId);
|
||||
var currencyCode = "";
|
||||
var localizedPrice = "";
|
||||
if (m_p != null)
|
||||
{
|
||||
currencyCode = m_p.metadata.isoCurrencyCode;
|
||||
localizedPrice = m_p.metadata.localizedPrice.ToString();
|
||||
}
|
||||
|
||||
IAPEvent.LogIAPButtonClick(new PurchaseInfo(
|
||||
productName: _productName,
|
||||
productID: productId,
|
||||
orderID: "",
|
||||
currency: currencyCode,
|
||||
price: localizedPrice,
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: "",
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.NULL,
|
||||
productType: ""));
|
||||
|
||||
if (IsInitialized())
|
||||
{
|
||||
if (_inPurchaseProgress)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] The payment is in progress, please do not initiate the payment repeatedly.");
|
||||
return;
|
||||
}
|
||||
|
||||
Product product = _storeController.products.WithID(productId);
|
||||
if (product != null && product.availableToPurchase)
|
||||
{
|
||||
_inPurchaseProgress = true;
|
||||
LoggerUtils.Debug(
|
||||
string.Format("[iap] Purchasing product asychronously: '{0}'", product.definition.id));
|
||||
|
||||
if (_googlePlayConfiguration != null)
|
||||
{
|
||||
|
||||
if (!string.IsNullOrEmpty(productName))
|
||||
{
|
||||
_googlePlayConfiguration.SetObfuscatedAccountId(productName);
|
||||
}
|
||||
_googlePlayConfiguration.SetObfuscatedProfileId(gameExtraParam);
|
||||
LoggerUtils.Debug($"[iap] [BuyProductByID] 设置成功 userId = {productName} profileId = {gameExtraParam}");
|
||||
}
|
||||
_storeController.InitiatePurchase(product);
|
||||
}
|
||||
else
|
||||
{
|
||||
IAPEvent.LogPurchaseFail(new PurchaseInfo(
|
||||
productName: _productName,
|
||||
productID: productId,
|
||||
orderID: "",
|
||||
currency: currencyCode,
|
||||
price: localizedPrice,
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: "BuyProductID FAIL. Not purchasing product, either is not found or is not available for purchase",
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.NULL,
|
||||
productType: product.definition.type.ToString()));
|
||||
LoggerUtils.Debug("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RushSDKManager.Instance.OnPurchaseComplete(new PurchaseInfo(
|
||||
productName: productName,
|
||||
productID: productId,
|
||||
orderID: "",
|
||||
currency: "",
|
||||
price: "",
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: "BuyProductID FAIL. IAP Not initialized or Not add product.",
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.PurchasingUnavailable,
|
||||
productType: ""));
|
||||
|
||||
IAPEvent.LogPurchaseFail(new PurchaseInfo(
|
||||
productName: _productName,
|
||||
productID: productId,
|
||||
orderID: "",
|
||||
currency: currencyCode,
|
||||
price: localizedPrice,
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: "BuyProductID FAIL. Not purchasing product, either is not found or is not available for purchase",
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: IAPResultType.NULL,
|
||||
productType: ""));
|
||||
LoggerUtils.Debug("[iap] BuyProductID FAIL. IAP Not initialized or Not add product.");
|
||||
LoggerUtils.Debug("[iap] OnPurchaseFailed -> productId : " + productId + " , transactionID : " + "" + " , localizedPrice : " + "" + " , isoCurrencyCode : " + "");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public Product GetProductInfoByID(string pID)
|
||||
{
|
||||
if (_storeController == null && _storeExtensionProvider == null)
|
||||
return null;
|
||||
for (int i = 0; i < _storeController.products.all.Length; i++)
|
||||
{
|
||||
Product tItem = _storeController.products.all[i];
|
||||
if (tItem.definition.id.Equals(pID))
|
||||
{
|
||||
return tItem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void AddProducts(Dictionary<string, ProductType> products, Action<bool, string> onProductsResult = null)
|
||||
{
|
||||
_initProductDic = products;
|
||||
|
||||
FetchAdditionalProducts(products, onProductsResult);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 购买成功
|
||||
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
|
||||
{
|
||||
_inPurchaseProgress = false;
|
||||
|
||||
LoggerUtils.Debug("[iap] Purchase OK: " + purchaseEvent.purchasedProduct.definition.id);
|
||||
|
||||
var wrapper = (Dictionary<string, object>)MiniJson.JsonDecode(purchaseEvent.purchasedProduct.receipt);
|
||||
var payload = (string)wrapper["Payload"];
|
||||
var profileId = _gameExtraParam;
|
||||
var _productName = "";
|
||||
var payloadObj = (Dictionary<string, object>)MiniJson.JsonDecode(payload);
|
||||
var o = (string)payloadObj["json"];
|
||||
var payloadData = (Dictionary<string, object>)MiniJson.JsonDecode(o);
|
||||
|
||||
if (payloadData.TryGetValue("obfuscatedAccountId", out var obfuscatedAccountIdValue))
|
||||
{
|
||||
var obfuscatedAccountId = (string)obfuscatedAccountIdValue;
|
||||
if (!string.IsNullOrEmpty(obfuscatedAccountId))
|
||||
{
|
||||
_productName = obfuscatedAccountId;
|
||||
}
|
||||
}
|
||||
|
||||
if (payloadData.TryGetValue("obfuscatedProfileId", out var value))
|
||||
{
|
||||
var obfuscatedProfileId = (string)value;
|
||||
if (!string.IsNullOrEmpty(obfuscatedProfileId))
|
||||
{
|
||||
profileId = obfuscatedProfileId;
|
||||
}
|
||||
}
|
||||
LoggerUtils.Debug("[iap] productName" + _productName + " profileId:" + profileId);
|
||||
|
||||
string token = "";
|
||||
string orderId = "";
|
||||
if (Application.platform == RuntimePlatform.Android)
|
||||
{
|
||||
var gpDetails = (Dictionary<string, object>)MiniJson.JsonDecode(payload);
|
||||
var gpJson = (string)gpDetails["json"];
|
||||
var tokenJson = (Dictionary<string, object>)MiniJson.JsonDecode(gpJson);
|
||||
token = (string)tokenJson["purchaseToken"];
|
||||
orderId = (string)tokenJson["orderId"];
|
||||
|
||||
LoggerUtils.Debug("[iap] ClientIAPSuccess productId : " + purchaseEvent.purchasedProduct.definition.id
|
||||
+ " , transactionID : " + orderId
|
||||
+ " , token : " + token
|
||||
+ " , localizedPrice : " + purchaseEvent.purchasedProduct.metadata.localizedPriceString
|
||||
+ " , isoCurrencyCode : " + purchaseEvent.purchasedProduct.metadata.isoCurrencyCode);
|
||||
}
|
||||
|
||||
IAPDataConfig newData = new IAPDataConfig();
|
||||
newData.info = new Dictionary<string, string>
|
||||
{
|
||||
{ "price", purchaseEvent.purchasedProduct.metadata.localizedPrice.ToString() },
|
||||
{ "product_id", purchaseEvent.purchasedProduct.definition.id },
|
||||
{ "product_name", (string)_productName },
|
||||
{ "purchase_token", token },
|
||||
{ "order_id", orderId },
|
||||
{ "currency", purchaseEvent.purchasedProduct.metadata.isoCurrencyCode },
|
||||
{ "payment_method", "googleplay" },
|
||||
{ "product_type", purchaseEvent.purchasedProduct.definition.type.ToString() },
|
||||
{ "mGameExtraParam", (string)profileId }
|
||||
};
|
||||
newData.state = IAPDataStateType.def;
|
||||
|
||||
IAPEvent.LogClientComplete(new PurchaseInfo(
|
||||
productName: (string)_productName,
|
||||
productID: purchaseEvent.purchasedProduct.definition.id,
|
||||
orderID: orderId,
|
||||
currency: purchaseEvent.purchasedProduct.metadata.isoCurrencyCode,
|
||||
price: purchaseEvent.purchasedProduct.metadata.localizedPrice.ToString(),
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: "",
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: true,
|
||||
resultType: IAPResultType.PurchasingSuccess,
|
||||
productType: purchaseEvent.purchasedProduct.definition.type.ToString()
|
||||
));
|
||||
|
||||
IAPOrderManager.Instance.SaveOrder(newData);
|
||||
IAPOrderManager.Instance.VerifyPurchase(newData);
|
||||
return PurchaseProcessingResult.Complete;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 动态添加商品
|
||||
private void FetchAdditionalProducts(Dictionary<string, ProductType> ProductDic,
|
||||
Action<bool, string> onProductsResult = null)
|
||||
{
|
||||
if (!IsInitialized())
|
||||
{
|
||||
_addProductsDic = ProductDic;
|
||||
LoggerUtils.Debug("[iap] IAP not init.Now InitUnityPurchase");
|
||||
InitUnityPurchase();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isFetchingAdditionalProducts)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] Now fetching additional products,don't call repeatedly");
|
||||
if (onProductsResult != null)
|
||||
{
|
||||
onProductsResult(false, "Now fetching additional products,don't call repeatedly");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_isFetchingAdditionalProducts = true;
|
||||
if (ProductDic != null)
|
||||
{
|
||||
var additional = new HashSet<ProductDefinition>();
|
||||
foreach (string tID in ProductDic.Keys)
|
||||
{
|
||||
additional.Add(new ProductDefinition(tID, ProductDic[tID]));
|
||||
}
|
||||
|
||||
Action onSuccess = () =>
|
||||
{
|
||||
_isFetchingAdditionalProducts = false;
|
||||
|
||||
LoggerUtils.Debug("[iap] Fetched successfully!");
|
||||
RushSDKManager.Instance.OnGetProductsInfo?.Invoke(_storeController.products.all);
|
||||
|
||||
foreach (var product in _storeController.products.all)
|
||||
{
|
||||
LoggerUtils.Debug("[iap]" + product.metadata.localizedTitle
|
||||
+ "|" + product.metadata.localizedPriceString
|
||||
+ "|" + product.metadata.localizedDescription
|
||||
+ "|" + product.metadata.isoCurrencyCode);
|
||||
}
|
||||
|
||||
if (onProductsResult != null)
|
||||
{
|
||||
onProductsResult(true, "Fetched successfully!");
|
||||
}
|
||||
};
|
||||
|
||||
Action<InitializationFailureReason, string> onFailure = (InitializationFailureReason i, string msg) =>
|
||||
{
|
||||
_isFetchingAdditionalProducts = false;
|
||||
if (onProductsResult != null)
|
||||
{
|
||||
onProductsResult(true, "Fetching failed for the specified reason: " + i + " msg: " + msg);
|
||||
}
|
||||
|
||||
LoggerUtils.Debug("[iap] Fetching failed for the specified reason: " + i + " msg: " + msg);
|
||||
};
|
||||
|
||||
_storeController.FetchAdditionalProducts(additional, onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 初始化
|
||||
public void PreInitialize()
|
||||
{
|
||||
LoggerUtils.Debug("[iap] PreInitialize() mServiceInit: " + _serviceInit);
|
||||
if (!_serviceInit)
|
||||
{
|
||||
InitializeUnityServices(OnSuccess, OnError);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeUnityServices(Action onSuccess, Action<string> onError)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new InitializationOptions().SetEnvironmentName("production");
|
||||
|
||||
UnityServices.InitializeAsync(options).ContinueWith(task => onSuccess());
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
onError(exception.Message);
|
||||
}
|
||||
}
|
||||
private void OnSuccess()
|
||||
{
|
||||
LoggerUtils.Debug("[iap] Congratulations!\nUnity Gaming Services has been successfully initialized.");
|
||||
_serviceInit = true;
|
||||
IAPEvent.LogPurchaseInit();
|
||||
}
|
||||
|
||||
private void OnError(string message)
|
||||
{
|
||||
LoggerUtils.Debug($"[iap] Unity Gaming Services failed to initialize with error: {message}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化IAP
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
LoggerUtils.Debug("[iap] IAP Initialize() _storeController.Debug:" + _storeController + " m_StoreExtensionProvider: " + _storeExtensionProvider);
|
||||
if (_storeController == null && _storeExtensionProvider == null)
|
||||
InitUnityPurchase();
|
||||
}
|
||||
|
||||
private void InitUnityPurchase()
|
||||
{
|
||||
LoggerUtils.Debug("[iap] IAP InitUnityPurchase() IsInitialized: " + IsInitialized());
|
||||
if (IsInitialized()) return;
|
||||
|
||||
_repeatCountDic = new Dictionary<string, int>();
|
||||
|
||||
_module ??= StandardPurchasingModule.Instance();
|
||||
|
||||
// 配置模式;
|
||||
_builder ??= ConfigurationBuilder.Instance(_module);
|
||||
|
||||
int productsNum = 0;
|
||||
|
||||
if (_initProductDic != null && _initProductDic.Count > 0)
|
||||
{
|
||||
foreach (string tID in _initProductDic.Keys)
|
||||
{
|
||||
productsNum++;
|
||||
if (!string.IsNullOrEmpty(tID))
|
||||
{
|
||||
LoggerUtils.Debug($"[iap] Add InitProductDic APProducts: {tID}");
|
||||
_builder.AddProduct(tID, _initProductDic[tID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_addProductsDic != null && _addProductsDic.Count > 0)
|
||||
{
|
||||
foreach (string tID in _addProductsDic.Keys)
|
||||
{
|
||||
productsNum++;
|
||||
if (!string.IsNullOrEmpty(tID))
|
||||
{
|
||||
LoggerUtils.Debug($"[iap] Add AddProductsDic IAPProducts: {tID}");
|
||||
_builder.AddProduct(tID, _addProductsDic[tID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_googlePlayConfiguration = _builder.Configure<IGooglePlayConfiguration>();
|
||||
if (productsNum > 0)
|
||||
{
|
||||
UnityPurchasing.Initialize(this, _builder);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerUtils.Debug(
|
||||
"[iap] UnityPurchasing will not initialize.Products is empty,please add product.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsInitialized()
|
||||
{
|
||||
return _storeController != null && _storeExtensionProvider != null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 初始化成功
|
||||
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] IAP initialize Succeed!");
|
||||
|
||||
_storeController = controller;
|
||||
_storeExtensionProvider = extensions;
|
||||
|
||||
RushSDKManager.Instance.OnGetProductsInfo?.Invoke(_storeController.products.all);
|
||||
foreach (var product in _storeController.products.all)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] " + product.metadata.localizedTitle
|
||||
+ "|" + product.metadata.localizedPriceString
|
||||
+ "|" + product.metadata.localizedDescription
|
||||
+ "|" + product.metadata.isoCurrencyCode
|
||||
+ "|" + product.definition.id
|
||||
+ "|" + product.definition.type);
|
||||
}
|
||||
|
||||
// CheckSubscribeReceipt();
|
||||
RushSDKManager.Instance.OnGetProductsInfo?.Invoke(_storeController.products.all);
|
||||
RushSDKManager.Instance.OnPurchaseInitComplete?.Invoke(true, "");
|
||||
IAPEvent.LogPurchaseInit(true);
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 初始化失败
|
||||
public void OnInitializeFailed(InitializationFailureReason error)
|
||||
{
|
||||
OnInitializeFailed(error, "");
|
||||
RushSDKManager.Instance.OnPurchaseInitComplete?.Invoke(false, error.ToString());
|
||||
}
|
||||
|
||||
public void OnInitializeFailed(InitializationFailureReason error, string message)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] IAP OnInitializeFailed error:" + error.ToString() + " msg:" + message);
|
||||
switch (error)
|
||||
{
|
||||
case InitializationFailureReason.AppNotKnown:
|
||||
LoggerUtils.Debug("[iap] Is your App correctly uploaded on the relevant publisher console?");
|
||||
break;
|
||||
case InitializationFailureReason.PurchasingUnavailable:
|
||||
LoggerUtils.Debug("[iap] Billing disabled! Ask the user if billing is disabled in device settings.");
|
||||
break;
|
||||
case InitializationFailureReason.NoProductsAvailable:
|
||||
LoggerUtils.Debug("[iap] No products available for purchase! Developer configuration error; check product metadata!");
|
||||
break;
|
||||
}
|
||||
IAPEvent.LogPurchaseInit(false, error.ToString());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 购买失败
|
||||
public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
|
||||
{
|
||||
LoggerUtils.Debug("[iap] OnPurchaseFailed productId : " + failureDescription.productId
|
||||
+ " , transactionID : " + product.transactionID
|
||||
+ " , localizedPrice : " + product.metadata.localizedPriceString
|
||||
+ " , isoCurrencyCode : " + product.metadata.isoCurrencyCode
|
||||
+ "failureDescription" + failureDescription.message);
|
||||
|
||||
|
||||
// 失败打点
|
||||
IAPEvent.LogPurchaseFail(new PurchaseInfo(
|
||||
productName: product.metadata.localizedTitle,
|
||||
productID: product.definition.id,
|
||||
orderID: "",
|
||||
currency: product.metadata.isoCurrencyCode,
|
||||
price: product.metadata.localizedPriceString,
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: failureDescription.message,
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: (IAPResultType)failureDescription.reason,
|
||||
productType: product.definition.type.ToString()
|
||||
));
|
||||
|
||||
// 购买失败回调
|
||||
RushSDKManager.Instance.OnPurchaseComplete(new PurchaseInfo(
|
||||
productName: product.metadata.localizedTitle,
|
||||
productID: product.definition.id,
|
||||
orderID: "",
|
||||
currency: product.metadata.isoCurrencyCode,
|
||||
price: product.metadata.localizedPriceString,
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: failureDescription.message,
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: (IAPResultType)failureDescription.reason,
|
||||
productType: product.definition.type.ToString()
|
||||
));
|
||||
}
|
||||
|
||||
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
|
||||
{
|
||||
// m_PurchaseInProgress = false;
|
||||
LoggerUtils.Debug("[iap] OnPurchaseFailed productId -> : " + product.definition.id
|
||||
+ " , transactionID : " + product.transactionID
|
||||
+ " , localizedPrice : " + product.metadata.localizedPriceString
|
||||
+ " , isoCurrencyCode : " + product.metadata.isoCurrencyCode
|
||||
+ " , failureReason : " + failureReason.ToString());
|
||||
|
||||
|
||||
// 失败打点
|
||||
IAPEvent.LogPurchaseFail(new PurchaseInfo(
|
||||
productName: product.metadata.localizedTitle,
|
||||
productID: product.definition.id,
|
||||
orderID: "",
|
||||
currency: product.metadata.isoCurrencyCode,
|
||||
price: product.metadata.localizedPriceString,
|
||||
gameExtra: _gameExtraParam,
|
||||
productType: "",
|
||||
failReason: failureReason.ToString(),
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: (IAPResultType)failureReason)
|
||||
);
|
||||
|
||||
// 购买失败回调
|
||||
RushSDKManager.Instance.OnPurchaseComplete(new PurchaseInfo(
|
||||
productName: product.metadata.localizedTitle,
|
||||
productID: product.definition.id,
|
||||
orderID: "",
|
||||
currency: product.metadata.isoCurrencyCode,
|
||||
price: product.metadata.localizedPriceString,
|
||||
gameExtra: _gameExtraParam,
|
||||
failReason: failureReason.ToString(),
|
||||
orderAlreadyExists: false,
|
||||
purchaseResult: false,
|
||||
resultType: (IAPResultType)failureReason,
|
||||
productType: product.definition.type.ToString()
|
||||
|
||||
));
|
||||
// ReadFailOrderId();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
private StandardPurchasingModule _module;
|
||||
private ConfigurationBuilder _builder;
|
||||
private static IStoreController _storeController;
|
||||
private static IExtensionProvider _storeExtensionProvider;
|
||||
public string _gameExtraParam = "";
|
||||
private static string _productName = "";
|
||||
private bool _inPurchaseProgress = false;
|
||||
public Dictionary<string, Dictionary<string, object>> _productArgs = new Dictionary<string, Dictionary<string, object>>();
|
||||
private bool _serviceInit = false;
|
||||
private Dictionary<string, ProductType> _addProductsDic;
|
||||
private Dictionary<string, ProductType> _initProductDic;
|
||||
private Dictionary<string, int> _repeatCountDic;
|
||||
private IGooglePlayConfiguration _googlePlayConfiguration;
|
||||
private bool _isFetchingAdditionalProducts = false;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ffce1a3398ade48a9907b1f3ebb82f8d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,44 @@
|
|||
#if UNITY_PURCHASE
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WZ
|
||||
{
|
||||
|
||||
public struct SubscribeInfo
|
||||
{
|
||||
|
||||
public int Illegal_order; // 0:代表合法订单,1:代表非法订单
|
||||
public string Illegal_msg; // 非法订单信息
|
||||
public string Environment; // production & sandbox
|
||||
public string Purchase_time; //订阅时间,单位毫秒
|
||||
public int Is_subscribed; //是否订阅过 0:未订阅过,1:订阅过
|
||||
public int Is_expired; //是否过期 0未过期,1:已过期
|
||||
public int Is_cancelled; // 0:未取消,1:已取消
|
||||
public int Is_free_trial; // 0:不是免费试用,1:是免费试用
|
||||
public int Is_auto_renewing; //是否自动续订 0:非自动,1:自动
|
||||
public string Remaining_time; //订阅到期剩余时间,单位毫秒
|
||||
public string Expiry_time; //过期时间,单位毫秒
|
||||
public string Latest_order_id; //当前订阅的最新订单号
|
||||
public string Product_id; //产品ID
|
||||
|
||||
public SubscribeInfo(int illegal_order, string illegal_msg, string environment, string purchase_time, int is_subscribed, int is_expired, int is_cancelled, int is_free_trial, int is_auto_renewing, string remaining_time, string expiry_time, string latest_order_id,string product_id)
|
||||
{
|
||||
Illegal_order = illegal_order;
|
||||
Illegal_msg = illegal_msg;
|
||||
Environment = environment;
|
||||
Purchase_time = purchase_time;
|
||||
Is_subscribed = is_subscribed;
|
||||
Is_expired = is_expired;
|
||||
Is_cancelled = is_cancelled;
|
||||
Is_free_trial = is_free_trial;
|
||||
Is_auto_renewing = is_auto_renewing;
|
||||
Remaining_time = remaining_time;
|
||||
Expiry_time = expiry_time;
|
||||
Latest_order_id = latest_order_id;
|
||||
Product_id = product_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 33709546096a84c849505413000f3a28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1edbb658f8a39411aa0d6508c05142b3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,113 @@
|
|||
#if UNITY_PURCHASE
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace WZ
|
||||
{
|
||||
|
||||
public class RequestHandler : MonoBehaviour
|
||||
{
|
||||
private static RequestHandler _instance;
|
||||
|
||||
public static RequestHandler Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance != null) return _instance;
|
||||
_instance = FindObjectOfType<RequestHandler>();
|
||||
if (_instance != null) return _instance;
|
||||
var obj = new GameObject();
|
||||
_instance = obj.AddComponent<RequestHandler>();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = this;
|
||||
}
|
||||
else if (_instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
public void SendPostRequest(string url, string jsonRequestBody, Dictionary<string, string> headers = null, Action<int, string> callback = null)
|
||||
{
|
||||
StartCoroutine(PostRequestCoroutine(url, jsonRequestBody, headers, callback));
|
||||
}
|
||||
|
||||
private static IEnumerator PostRequestCoroutine(string url, string jsonRequestBody, Dictionary<string, string> headers, Action<int, string> callback)
|
||||
{
|
||||
using var request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST);
|
||||
request.timeout = 15;
|
||||
request.downloadHandler = new DownloadHandlerBuffer();
|
||||
request.SetRequestHeader("Content-Type", "application/json");
|
||||
|
||||
if (!string.IsNullOrEmpty(jsonRequestBody))
|
||||
{
|
||||
request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(jsonRequestBody));
|
||||
request.uploadHandler.contentType = "application/json";
|
||||
}
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
{
|
||||
request.SetRequestHeader(header.Key, header.Value);
|
||||
}
|
||||
}
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
var startTime = Time.realtimeSinceStartup;
|
||||
#endif
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
var endTime = Time.realtimeSinceStartup;
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"POST Request URL: {url}");
|
||||
sb.AppendLine($"POST Request Headers: {GetHeadersAsString(headers)}");
|
||||
sb.AppendLine($"POST Request Body: {jsonRequestBody}");
|
||||
sb.AppendLine($"Response Code: {request.responseCode}");
|
||||
sb.AppendLine($"Response Time: {endTime - startTime:F2} seconds");
|
||||
sb.AppendLine($"Response Headers: \n {GetHeadersAsString(request.GetResponseHeaders())}");
|
||||
var downloadHandlerText = request.downloadHandler != null ? request.downloadHandler.text : "";
|
||||
sb.AppendLine($"Response: {downloadHandlerText}");
|
||||
#endif
|
||||
|
||||
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
callback?.Invoke(0, request.downloadHandler?.text);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback?.Invoke(-1, request.error ?? $"request fail, result = {request.result}");
|
||||
}
|
||||
|
||||
request.disposeDownloadHandlerOnDispose = true;
|
||||
request.disposeUploadHandlerOnDispose = true;
|
||||
}
|
||||
|
||||
private static string GetHeadersAsString(Dictionary<string, string> headers)
|
||||
{
|
||||
if (headers == null || headers.Count == 0)
|
||||
{
|
||||
return "\tN/A";
|
||||
}
|
||||
|
||||
return headers.Aggregate("", (current, header) => current + "\t" + header.Key + ": " + header.Value + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d0a1895b264d84fb888ce190870a1fc7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,272 @@
|
|||
#if UNITY_PURCHASE
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Google.MiniJSON;
|
||||
using Newtonsoft.Json;
|
||||
using ThinkingData.Analytics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WZ
|
||||
{
|
||||
public class ServerMgr : D_MonoSingleton<ServerMgr>
|
||||
{
|
||||
private const string XXTEA_KEY = "tkbff&(gBUjX#$s0710";
|
||||
private const string secretKey = "tk~!@#$%^&*()_+0708";
|
||||
|
||||
public void CheckOrder(Dictionary<string, object> args, Action<int,string, SubscriptionInfo> callback)
|
||||
{
|
||||
Post(StaticValue.CheckOrderUrl, args,callback);
|
||||
}
|
||||
|
||||
public void VerifyPurchase(Dictionary<string, object> args, Action<int, string, DataInfo> callback)
|
||||
{
|
||||
float fPrice;
|
||||
if (!float.TryParse(args["price"].ToString(), out fPrice))
|
||||
{
|
||||
fPrice = 0.0f;
|
||||
}
|
||||
var ssProperties = new Dictionary<string, object>
|
||||
{
|
||||
{ "is_first", PlayerPrefsUtils.GetPlayerPrefsInt("First_Purchase", 0) == 0 },
|
||||
{ "IAP", args["iap_name"].ToString() },
|
||||
{ "product_id", args["product_id"].ToString() },
|
||||
{ "payment_type", "GooglePlay" },
|
||||
{ "Price", fPrice }
|
||||
};
|
||||
try
|
||||
{
|
||||
if (TDAnalytics.GetSuperProperties().Count > 0)
|
||||
{
|
||||
ssProperties = ssProperties.Concat(TDAnalytics.GetSuperProperties()).ToDictionary(postParK => postParK.Key, PostParV => PostParV.Value);
|
||||
}
|
||||
|
||||
if (TDAnalytics.GetPresetProperties().ToDictionary().Count > 0)
|
||||
{
|
||||
ssProperties = ssProperties.Concat(TDAnalytics.GetPresetProperties().ToDictionary()).ToDictionary(postParK => postParK.Key, PostParV => PostParV.Value);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
args.Add("ss_super_properties", JsonConvert.SerializeObject(ssProperties));
|
||||
Post(StaticValue.VerifyPurchaseUrl, args, callback);
|
||||
}
|
||||
|
||||
public void Post<T>(string url, Dictionary<string, object> args, Action<int, string, T> callback,
|
||||
Dictionary<string, string> headers = null) where T : new()
|
||||
{
|
||||
args ??= new Dictionary<string, object>();
|
||||
|
||||
headers ??= new Dictionary<string, string>();
|
||||
|
||||
AddBaseParameters(args);
|
||||
|
||||
EncryptionParameters(args);
|
||||
var requestBody = ConvertDictionaryToJson(args);
|
||||
LoggerUtils.Debug("[server] url=> " + url +" requestBody=>"+requestBody);
|
||||
var encryptBody = EncryptRequestBody(requestBody);
|
||||
if (!string.IsNullOrEmpty(encryptBody))
|
||||
{
|
||||
headers["params"] = encryptBody;
|
||||
headers["encrypt"] = true.ToString().ToLower();
|
||||
requestBody = "";
|
||||
}
|
||||
headers.AddIfNotExists("Is-Dev", "0");
|
||||
|
||||
try
|
||||
{
|
||||
headers.AddIfNotExists("unity-platform", Application.platform.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
RequestHandler.Instance.SendPostRequest(url, requestBody, headers,
|
||||
(code, res) =>
|
||||
{
|
||||
LoggerUtils.Debug($"[server] res ====> code : {code} res : {res}");
|
||||
if (code == 0)
|
||||
{
|
||||
Response<T> resp = null;
|
||||
var errorMsg = "";
|
||||
|
||||
try
|
||||
{
|
||||
res = DecryptResponseBody(res);
|
||||
resp = JsonUtility.FromJson<Response<T>>(res);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
errorMsg = $"[server] Data conversion exception。{e.Message} {res}";
|
||||
LoggerUtils.Debug(errorMsg);
|
||||
}
|
||||
|
||||
LoggerUtils.Debug($"[server] resp ====> code : {resp.code} res : {resp.data}");
|
||||
if (resp != null)
|
||||
{
|
||||
callback(resp.code, resp.msg, resp.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.Invoke(-1, errorMsg, new T());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.Invoke(code, res, new T());
|
||||
}
|
||||
|
||||
LoggerUtils.Debug("[server]" + "[res] " + res);
|
||||
});
|
||||
}
|
||||
|
||||
private static string EncryptRequestBody(string requestBody)
|
||||
{
|
||||
var encryptRequestBodyBytes = XXTEA.Encrypt(Encoding.UTF8.GetBytes(requestBody), Encoding.UTF8.GetBytes(XXTEA_KEY));
|
||||
var encryptRequestBody = EncryptionUtils.BytesToHexString(encryptRequestBodyBytes, false);
|
||||
LoggerUtils.Debug($"[[server]] [EncryptRequestBody] requestBody = {requestBody} encryptRequestBody = {encryptRequestBody}");
|
||||
return encryptRequestBody;
|
||||
}
|
||||
|
||||
private static string DecryptResponseBody(string responseBody)
|
||||
{
|
||||
var decryptResponseBodyBytes = XXTEA.Decrypt(EncryptionUtils.HexStringToBytes(responseBody), Encoding.UTF8.GetBytes(XXTEA_KEY));
|
||||
var decryptResponseBody = Encoding.UTF8.GetString(decryptResponseBodyBytes);
|
||||
LoggerUtils.Debug($"[[server]] [DecryptResponseBody] decryptResponseBody = {decryptResponseBody}");
|
||||
return decryptResponseBody;
|
||||
}
|
||||
|
||||
public static string GetMD5Hash(string input)
|
||||
{
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
var inputBytes = Encoding.ASCII.GetBytes(input);
|
||||
var hashBytes = md5.ComputeHash(inputBytes);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
foreach (var t in hashBytes)
|
||||
{
|
||||
builder.Append(t.ToString("x2")); // Convert byte to hexadecimal string
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static void EncryptionParameters(Dictionary<string, object> args)
|
||||
{
|
||||
var signString =
|
||||
$"{secretKey}platform={args["platform"]}packagename={args["package_name"]}channel={args["channel"]}appversion={args["app_version"]}appversioncode={args["app_version_code"]}language={args["language"]}ip={args["ip"]}ts={args["ts"]}";
|
||||
var sign = GetMD5Hash(signString);
|
||||
args["sign"] = sign;
|
||||
}
|
||||
|
||||
private static void AddBaseParameters(IDictionary<string, object> args)
|
||||
{
|
||||
|
||||
args.AddIfNotExists("unity_sdk_version", RushSDKManager.GetSDKVersion());
|
||||
|
||||
args.AddIfNotExists("package_name", Application.identifier);
|
||||
args.AddIfNotExists("platform", "android");
|
||||
args.AddIfNotExists("platform_os", "android");
|
||||
args.AddIfNotExists("channel", "googleplay");
|
||||
args.AddIfNotExists("device_type", "Android");
|
||||
args.AddIfNotExists("platform_channel", "gp");
|
||||
args.AddIfNotExists("app_version", Application.version);
|
||||
args.AddIfNotExists("app_version_code", DataUtils.AndroidVersionCode());
|
||||
args.AddIfNotExists("language", "ZH");
|
||||
args.AddIfNotExists("ip", "");
|
||||
args.AddIfNotExists("device_id", AdjustManager.Instance.GetGdid());
|
||||
args.AddIfNotExists("adjust_adid", AdjustManager.Instance.GetAdid());
|
||||
args.AddIfNotExists("app_u8id", "");
|
||||
args.AddIfNotExists("app_name", Application.productName);
|
||||
args.AddIfNotExists("model", "");
|
||||
args.AddIfNotExists("screen_size", "");
|
||||
args.AddIfNotExists("network_type", "");
|
||||
args.AddIfNotExists("ua", "");
|
||||
args.AddIfNotExists("idfa", "");
|
||||
args.AddIfNotExists("idfv", "");
|
||||
args.AddIfNotExists("gaid", AdjustManager.Instance.GetGdid());
|
||||
args.AddIfNotExists("oaid", "");
|
||||
args.AddIfNotExists("android_id", "");
|
||||
args.AddIfNotExists("adid", AdjustManager.Instance.GetAdid());
|
||||
args.AddIfNotExists("fire_adid", "");
|
||||
args.AddIfNotExists("ad_network", AdjustManager.Instance.GetAdjustNetwork());
|
||||
args.AddIfNotExists("campaign", AdjustManager.Instance.GetAdjustCampaign());
|
||||
args.AddIfNotExists("adgroup", AdjustManager.Instance.GetAdjustAdgroup());
|
||||
args.AddIfNotExists("creative", AdjustManager.Instance.GetAdjustCreative());
|
||||
args.AddIfNotExists("clickLabel", AdjustManager.Instance.GetAdjustClickLabel());
|
||||
args.AddIfNotExists("referrer", "");
|
||||
args.AddIfNotExists("memory", "");
|
||||
args.AddIfNotExists("memory_usage", "");
|
||||
args.AddIfNotExists("country", "");
|
||||
args.AddIfNotExists("user_id", "");
|
||||
args.AddIfNotExists("user_type", "");
|
||||
args.AddIfNotExists("ss_distinct_id", TDAnalytics.GetDistinctId() ?? "");
|
||||
args.AddIfNotExists("ss_account_id", "");
|
||||
args.AddIfNotExists("ts", "" + TimeUtils.CurrentTimestamp());
|
||||
}
|
||||
|
||||
|
||||
public static string ConvertDictionaryToJson(Dictionary<string, object> dictionary)
|
||||
{
|
||||
return Json.Serialize(dictionary);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Response<T>
|
||||
{
|
||||
public int code = -1;
|
||||
public string msg;
|
||||
public T data;
|
||||
public int ts;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class SensitiveDataInfo
|
||||
{
|
||||
public bool has_sensitive;
|
||||
public string content;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TranslateDataInfo
|
||||
{
|
||||
public string content;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class DataInfo
|
||||
{
|
||||
public string environment;
|
||||
public int illegal_order = -1;
|
||||
public string illegal_msg;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class SubscriptionInfo
|
||||
{
|
||||
public int illegal_order;
|
||||
public string illegal_msg;
|
||||
public string environment;
|
||||
public string purchase_time;
|
||||
public int is_subscribed;
|
||||
public int is_expired;
|
||||
public int is_cancelled;
|
||||
public int is_free_trial;
|
||||
public int is_auto_renewing;
|
||||
public string remaining_time;
|
||||
public string expiry_time;
|
||||
public string latest_order_id;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: af510513690fd4631b3b57b1d427f4c7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -67,6 +67,14 @@ public class RushSDKManager : D_MonoSingleton<RushSDKManager>
|
|||
OnGetProductsInfo += _action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支付回调
|
||||
/// </summary>
|
||||
public Action<PurchaseInfo> OnPurchaseComplete;
|
||||
public void RegisterPurchaseCompletionHandler(Action<PurchaseInfo> _action)
|
||||
{
|
||||
OnPurchaseComplete = _action;
|
||||
}
|
||||
#endregion
|
||||
#endif
|
||||
|
||||
|
|
|
@ -27,16 +27,19 @@ public class Test : MonoBehaviour
|
|||
|
||||
public void OnShowAd()
|
||||
{
|
||||
// RushSDKManager.Instance.ShowBanner();
|
||||
// RushSDKManager.Instance.InitializeSdk(null, true);
|
||||
// KwaiAdsManager.Instance.ShowRewardAd();
|
||||
AdsSDKManager.Instance.ShowRewardAd("getcoin",(state,revenue)=>
|
||||
{
|
||||
LoggerUtils.Debug("[kwai] OnRewardedVideoAdShowed revenue:"+revenue+"+ state:"+state);
|
||||
});
|
||||
// AdsSDKManager.Instance.ShowRewardAd("getcoin",(state,revenue)=>
|
||||
// {
|
||||
// LoggerUtils.Debug("[kwai] OnRewardedVideoAdShowed revenue:"+revenue+"+ state:"+state);
|
||||
// });
|
||||
RushSDKManager.Instance.ShowBanner(BannerType.Standard,BannerAlignType.CenterTop);
|
||||
}
|
||||
|
||||
public void OnShowInterstitial()
|
||||
{
|
||||
RushSDKManager.Instance.ShowBanner(BannerType.Collapsible,BannerAlignType.CenterBottom);
|
||||
// KwaiAdsManager.Instance.OnInterstitialCallback();
|
||||
// var small = gameObject.transform.Find("NativeAd-small").GetComponent<RectTransform>();
|
||||
// var medium = gameObject.transform.Find("NativeAd-medium").GetComponent<RectTransform>();
|
||||
|
@ -63,11 +66,11 @@ public class Test : MonoBehaviour
|
|||
// {
|
||||
// Debug.LogWarning("luojian admob native ad start show fail,not ready.");
|
||||
// }
|
||||
AdsSDKManager.Instance.ShowInterstitialAd("endgame",IvType.IV1, (revenue) =>
|
||||
{
|
||||
LoggerUtils.Debug("oninter show call revenue:"+revenue);
|
||||
// AdsSDKManager.Instance.ShowInterstitialAd("endgame",IvType.IV1, (revenue) =>
|
||||
// {
|
||||
// LoggerUtils.Debug("oninter show call revenue:"+revenue);
|
||||
|
||||
});
|
||||
// });
|
||||
}
|
||||
|
||||
public void OnInterShow()
|
||||
|
@ -81,12 +84,14 @@ public class Test : MonoBehaviour
|
|||
|
||||
public void ToponDebug()
|
||||
{
|
||||
ATSDKAPI.showDebuggerUI();
|
||||
// ATSDKAPI.showDebuggerUI();
|
||||
RushSDKManager.Instance.HideBanner(BannerType.Standard);
|
||||
}
|
||||
|
||||
public void MaxDebug()
|
||||
{
|
||||
MaxSdk.ShowMediationDebugger();
|
||||
// MaxSdk.ShowMediationDebugger();
|
||||
RushSDKManager.Instance.HideBanner(BannerType.Collapsible);
|
||||
}
|
||||
|
||||
public void Native1Show()
|
||||
|
|
|
@ -13,6 +13,16 @@ namespace WZ
|
|||
public T[] items;
|
||||
}
|
||||
|
||||
public static int AndroidVersionCode()
|
||||
{
|
||||
AndroidJavaClass contextCls = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
|
||||
AndroidJavaObject context = contextCls.GetStatic<AndroidJavaObject>("currentActivity");
|
||||
AndroidJavaObject packageMngr = context.Call<AndroidJavaObject>("getPackageManager");
|
||||
string packageName = context.Call<string>("getPackageName");
|
||||
AndroidJavaObject packageInfo =
|
||||
packageMngr.Call<AndroidJavaObject>("getPackageInfo", packageName, 0);
|
||||
return packageInfo.Get<int>("versionCode");
|
||||
}
|
||||
public static T[] FromJsonArray<T>(string json)
|
||||
{
|
||||
string wrappedJson = $"{{\"items\":{json}}}";
|
||||
|
@ -33,6 +43,12 @@ namespace WZ
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void AddIfNotExists<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
|
||||
{
|
||||
if (!dictionary.ContainsKey(key))
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,86 @@ namespace WZ
|
|||
{
|
||||
public static class EncryptionUtils
|
||||
{
|
||||
public static string BytesToHexString(byte[] bytes, bool isUpperCase)
|
||||
{
|
||||
if (bytes == null)
|
||||
return "";
|
||||
|
||||
char[] hexDigits = isUpperCase ? HEX_DIGITS_UPPER : HEX_DIGITS_LOWER;
|
||||
int len = bytes.Length;
|
||||
if (len == 0)
|
||||
return "";
|
||||
|
||||
char[] ret = new char[len << 1];
|
||||
for (int i = 0, j = 0; i < len; i++)
|
||||
{
|
||||
ret[j++] = hexDigits[bytes[i] >> 4 & 0x0f];
|
||||
ret[j++] = hexDigits[bytes[i] & 0x0f];
|
||||
}
|
||||
|
||||
return new string(ret);
|
||||
}
|
||||
|
||||
public static byte[] HexStringToBytes(string hexString)
|
||||
{
|
||||
if (IsSpace(hexString))
|
||||
return new byte[0];
|
||||
|
||||
int len = hexString.Length;
|
||||
if (len % 2 != 0)
|
||||
{
|
||||
hexString = "0" + hexString;
|
||||
len = len + 1;
|
||||
}
|
||||
|
||||
char[] hexBytes = hexString.ToUpper().ToCharArray();
|
||||
byte[] ret = new byte[len >> 1];
|
||||
for (int i = 0; i < len; i += 2)
|
||||
{
|
||||
ret[i >> 1] = (byte)(HexToDec(hexBytes[i]) << 4 | HexToDec(hexBytes[i + 1]));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool IsSpace(string s)
|
||||
{
|
||||
if (s == null)
|
||||
return true;
|
||||
|
||||
for (int i = 0, len = s.Length; i < len; ++i)
|
||||
{
|
||||
if (!char.IsWhiteSpace(s[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int HexToDec(char hexChar)
|
||||
{
|
||||
if (hexChar >= '0' && hexChar <= '9')
|
||||
{
|
||||
return hexChar - '0';
|
||||
}
|
||||
else if (hexChar >= 'A' && hexChar <= 'F')
|
||||
{
|
||||
return hexChar - 'A' + 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly char[] HEX_DIGITS_UPPER =
|
||||
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
|
||||
private static readonly char[] HEX_DIGITS_LOWER =
|
||||
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
private static string GetKey(string packageName)
|
||||
{
|
||||
var keyBytes = Encoding.UTF8.GetBytes(packageName);
|
||||
|
|
|
@ -5,7 +5,10 @@ namespace WZ
|
|||
public class TimeUtils
|
||||
{
|
||||
#region 本地时间
|
||||
|
||||
public static long CurrentTimestamp()
|
||||
{
|
||||
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
}
|
||||
// 获取当前本地时间的毫秒级时间戳
|
||||
public static long GetLocalTimestamp()
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue