448 lines
16 KiB
C#
448 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using asap.core;
|
|
using Newtonsoft.Json.Linq;
|
|
using tysdk;
|
|
using UnityEngine;
|
|
using Newtonsoft.Json;
|
|
using System.Net.Http;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using UnityEngine.Networking;
|
|
using System.Threading;
|
|
|
|
namespace game
|
|
{
|
|
public class AggIAPService : IIAPService
|
|
{
|
|
[Inject] public IUserService userService { get; set; }
|
|
[Inject] public IConfig config { get; set; }
|
|
[Inject] public ICustomServerMgr svr { get; set; }
|
|
|
|
public void Init() { }
|
|
|
|
public void Release() { }
|
|
|
|
|
|
#if UNITY_ANDROID
|
|
private const string payment = "google";
|
|
#else
|
|
private const string payment = "apple";
|
|
#endif
|
|
|
|
class IAPConfig
|
|
{
|
|
public bool Active { get; set; }
|
|
public string OrderSvrUrl { get; set; }
|
|
public string FuncKey { get; set; }
|
|
}
|
|
|
|
|
|
private async Task<IAPConfig> BeforePurch()
|
|
{
|
|
var configRemoteUrl = config.Get<string>(GConstant.K_Static_Res_URL, GConstant.V_Static_Res_URL);
|
|
string iapConfigUrl = null;
|
|
//var encrypted = false;
|
|
|
|
if (configRemoteUrl == GConstant.V_Static_Res_URL)
|
|
{
|
|
iapConfigUrl = String.Format(configRemoteUrl, $"{Application.platform}iapcfg.json");
|
|
//encrypted = true;
|
|
}
|
|
else
|
|
{
|
|
iapConfigUrl = configRemoteUrl.Replace(VersionTool.PackVer, $"{Application.platform}iapcfg.json");
|
|
}
|
|
|
|
var timeoutValues = new float[]{1,2,4};
|
|
var maxRetryTimes = timeoutValues.Length;
|
|
|
|
for (int retryTime = 0; retryTime < maxRetryTimes; retryTime++)
|
|
{
|
|
var iapConfig = await DoGetRawConfig(iapConfigUrl,timeoutValues[retryTime], retryTime, maxRetryTimes);
|
|
if (iapConfig != null )
|
|
{
|
|
Debug.Log($"<color=orange>Load iapConfig success {retryTime + 1}/{maxRetryTimes}</color>");
|
|
return iapConfig;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
private async Task<IAPConfig> DoGetRawConfig(string url, float timeoutInSec, int retryTimes, int maxRetryTimes)
|
|
{
|
|
using(var req = UnityWebRequest.Get(url))
|
|
{
|
|
var stopwatch = new System.Diagnostics.Stopwatch();
|
|
var cts = new CancellationTokenSource();
|
|
cts.CancelAfter(TimeSpan.FromSeconds(timeoutInSec));
|
|
var ct = cts.Token;
|
|
stopwatch.Start();
|
|
req.timeout = (int)timeoutInSec;
|
|
req.SetRequestHeader("Accept", "application/json");
|
|
var op = req.SendWebRequest();
|
|
try
|
|
{
|
|
while (!op.isDone && !ct.IsCancellationRequested)
|
|
{
|
|
await Awaiters.NextFrame;
|
|
}
|
|
ct.ThrowIfCancellationRequested();
|
|
stopwatch.Stop();
|
|
|
|
if (req.result == UnityWebRequest.Result.Success)
|
|
{
|
|
var decryptKey = "tbambooz";
|
|
|
|
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
|
|
des.Key = Encoding.ASCII.GetBytes(decryptKey);
|
|
des.IV = Encoding.ASCII.GetBytes(decryptKey);
|
|
var rawCfg = req.downloadHandler.text;
|
|
|
|
ICryptoTransform decryptor = des.CreateDecryptor(des.Key, des.IV);
|
|
byte[] inputBytes = Convert.FromBase64String(rawCfg);
|
|
byte[] outputBytes = decryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length);
|
|
rawCfg = Encoding.UTF8.GetString(outputBytes);
|
|
var iapConfig = JsonConvert.DeserializeObject<IAPConfig>(rawCfg);
|
|
Debug.Log($"[AggIAPService] DoGetRawConfig Success {url}");
|
|
return iapConfig;
|
|
}
|
|
|
|
#if AGG
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "iap_config");
|
|
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
|
|
e.AddContent("reason", req.result.ToString());
|
|
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
|
|
e.AddContent("error", req.error);
|
|
}
|
|
#endif // AGG
|
|
|
|
Debug.LogWarning($"Load iap config failed {req.result.ToString()} {req.error} {retryTimes + 1}/{maxRetryTimes}");
|
|
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
req.Abort();
|
|
stopwatch.Stop();
|
|
|
|
#if AGG
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "iap_config");
|
|
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
|
|
e.AddContent("reason", "cancel");
|
|
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
|
|
e.AddContent("error", req.error ?? "cancel");
|
|
}
|
|
#endif // AGG
|
|
|
|
Debug.LogWarning($"Load remote config cancel {req.result.ToString()} {req.error} {retryTimes + 1}/{maxRetryTimes}");
|
|
|
|
}
|
|
finally
|
|
{
|
|
cts.Dispose();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
public async Task<IList<SKUDetail>> GetSKUs()
|
|
{
|
|
Debug.Log("[AggIAPService] GetSKUList fetch");
|
|
var prodList = await TYSdkFacade.Instance.GetSKUList();
|
|
if (prodList.code != 0)
|
|
{
|
|
Debug.LogWarning("[AggIAPService] GetSKUList Failed");
|
|
return null;
|
|
}
|
|
Debug.Log($"[AggIAPService] GetSKUList Success prod Num {prodList.products.Count}");
|
|
return prodList.products;
|
|
}
|
|
|
|
public async Task<PaymentInfo> Pay(Action<string> OnSuccessFromSdk, string replenishmentDataStr, SKUDetail sku, int count, float usdPrice, JObject extraPurcheInfo)
|
|
{
|
|
await Awaiters.NextFrame;
|
|
GameCore.UIManager.ShowWaitingBlock("AggIAPService::Pay");
|
|
await Awaiters.NextFrame;
|
|
|
|
//======= check iap config
|
|
var iapCfg = await BeforePurch();
|
|
if (iapCfg == null)
|
|
{
|
|
Debug.LogWarning("Fetch iap cfg fail");
|
|
GameCore.UIManager.HideWaitingBlock("AggIAPService::Pay");
|
|
SendRechargeEvent(sku, usdPrice, "iapcfg_fail", extraPurcheInfo, count);
|
|
return new PaymentInfo() { code = "10002" };
|
|
}
|
|
else if (!iapCfg.Active)
|
|
{
|
|
GameCore.UIManager.HideWaitingBlock("AggIAPService::Pay");
|
|
return new PaymentInfo() { code = "10001" };
|
|
}
|
|
//===== check iap config end======
|
|
|
|
RTServiceBgFgManager.keepAlive = true;
|
|
SendRechargeEvent(sku, usdPrice, "start", extraPurcheInfo, count);
|
|
|
|
var purchaseInfo = new JObject();
|
|
purchaseInfo["ver"] = 3;
|
|
purchaseInfo["pfid"] = userService.UserId;
|
|
purchaseInfo["sum"] = usdPrice * count;
|
|
purchaseInfo["customData"] = replenishmentDataStr;
|
|
|
|
PaymentInfo paymentInfo = await TYSdkFacade.Instance.Pay(sku, count, usdPrice, purchaseInfo);
|
|
|
|
if (!paymentInfo.isSuccessFromSdk)
|
|
{
|
|
SendRechargeEvent(sku, usdPrice, "fail", extraPurcheInfo, count);
|
|
}
|
|
else
|
|
{
|
|
OnSuccessFromSdk(paymentInfo.orderId);
|
|
await WaitForSvrOrder(paymentInfo, sku, usdPrice, count, extraPurcheInfo, iapCfg);
|
|
}
|
|
|
|
if (paymentInfo.isSuccess)
|
|
{
|
|
await ConfirmOrder(paymentInfo, iapCfg);
|
|
}
|
|
|
|
GameCore.UIManager.HideWaitingBlock("AggIAPService::Pay");
|
|
RTServiceBgFgManager.keepAlive = false;
|
|
return paymentInfo;
|
|
}
|
|
|
|
private async Task WaitForSvrOrder(PaymentInfo paymentInfo, SKUDetail sku, float usdPrice, int count, JObject extraPurcheInfo, IAPConfig iapCfg)
|
|
{
|
|
Debug.Log("[AggIAPService] WaitForSvrOrder");
|
|
await Awaiters.Seconds(2);
|
|
|
|
var startTime = ZZTimeHelper.UtcNow();
|
|
|
|
var confirmEvt = new CS_OrderConfirmEvt()
|
|
{
|
|
orderId = paymentInfo.orderId,
|
|
sdkUserId = userService.CustomId
|
|
}.ToJson();
|
|
|
|
while (true)
|
|
{
|
|
var result = await IAPPost("csconfirm", iapCfg, confirmEvt);
|
|
|
|
Debug.Log("[AggIAPService] WaitForSvrOrder request done");
|
|
|
|
if (!string.IsNullOrEmpty(result))
|
|
{
|
|
|
|
Debug.Log($"[AggIAPService] WaitForSvrOrder respon {result}");
|
|
|
|
var sconfirmEvt = JsonConvert.DeserializeObject<SC_OrderConfirmEvt>(result);
|
|
|
|
if (sconfirmEvt.isSuccess)
|
|
{
|
|
if (paymentInfo.Check(sconfirmEvt.orderId, sconfirmEvt.productId))
|
|
{
|
|
SendRevenueEvent(sku, usdPrice, paymentInfo.orderId, extraPurcheInfo, count);
|
|
}
|
|
else
|
|
{
|
|
SendRechargeEvent(sku, usdPrice, "checkfail", extraPurcheInfo, paymentInfo.count);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("[AggIAPService] WaitForSvrOrder request error");
|
|
}
|
|
|
|
await Awaiters.Seconds(2);
|
|
|
|
if (ZZTimeHelper.UtcNow() - startTime > TimeSpan.FromSeconds(50))
|
|
{
|
|
SendRechargeEvent(sku, usdPrice, "timeout", extraPurcheInfo, paymentInfo.count);
|
|
|
|
Debug.Log("[AggIAPService] WaitForSvrOrder timeout");
|
|
break;
|
|
}
|
|
|
|
Debug.Log("[AggIAPService] WaitForSvrOrder retry");
|
|
}
|
|
}
|
|
|
|
private async Task ConfirmOrder(PaymentInfo paymentInfo, IAPConfig iapCfg)
|
|
{
|
|
var confirmEvt = new CS_OrderConfirmEvt()
|
|
{
|
|
orderId = paymentInfo.orderId,
|
|
sdkUserId = userService.CustomId
|
|
}.ToJson();
|
|
int count = 3;
|
|
string result;
|
|
bool re = false;
|
|
while (count > 0)
|
|
{
|
|
count--;
|
|
result = await IAPPost("receive", iapCfg, confirmEvt);
|
|
if (bool.TryParse(result, out re) && re)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if (!re)
|
|
{
|
|
paymentInfo.code = "10000";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取未领奖的订单号列表 时间戳
|
|
/// </summary>
|
|
/// <param name="day">时间戳 最早订单时间 -1m</param>
|
|
/// <returns></returns>
|
|
public async Task<Dictionary<string, string>> GetOrderUnreceived()
|
|
{
|
|
var iapCfg = await BeforePurch();
|
|
|
|
if (iapCfg == null)
|
|
{
|
|
Debug.LogError("Fetch iap cfg fail, on get unreceive order ");
|
|
return new Dictionary<string, string>();
|
|
}
|
|
|
|
var orderUnreceived = new CS_OrderUnreceived()
|
|
{
|
|
sdkUserId = userService.CustomId,
|
|
customData = true
|
|
}.ToJson();
|
|
|
|
string result = await IAPPost("unreceive", iapCfg, orderUnreceived);
|
|
|
|
if (string.IsNullOrEmpty(result) || result == "[]")
|
|
{
|
|
return new Dictionary<string, string>();
|
|
}
|
|
else
|
|
{
|
|
SC_OrderUnreceived response = JsonConvert.DeserializeObject<SC_OrderUnreceived>(result);
|
|
return response?.orders;
|
|
}
|
|
}
|
|
|
|
|
|
public async Task<bool> ConfirmOrders(List<string> orderIDs)
|
|
{
|
|
if (orderIDs == null || orderIDs.Count == 0) return false;
|
|
|
|
var confirmEvt = new CS_OrdersConfirmEvt()
|
|
{
|
|
orderIds = orderIDs,
|
|
sdkUserId = userService.CustomId
|
|
}.ToJson();
|
|
var iapCfg = await BeforePurch();
|
|
if (iapCfg == null)
|
|
{
|
|
return false;
|
|
}
|
|
string result = await IAPPost("receiveorders", iapCfg, confirmEvt);
|
|
if (string.IsNullOrEmpty(result) || result == "[]" || !bool.TryParse(result, out bool re))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void SendRechargeEvent(SKUDetail sku, float usdPrice, string state, JObject extraPurcheInfo, int count = 1)
|
|
{
|
|
using (var e = GEvent.TackEvent("recharge_state"))
|
|
{
|
|
e.AddContent(AFInAppEvents.CONTENT_ID, sku.ProdID)
|
|
.AddContent(AFInAppEvents.PRICE, usdPrice)
|
|
.AddContent(AFInAppEvents.CURRENCY, sku.price_currency_code)
|
|
.AddContent("payment", payment)
|
|
.AddContent("payment_state", state)
|
|
.AddContent(AFInAppEvents.QUANTITY, count);
|
|
if (null != extraPurcheInfo)
|
|
{
|
|
foreach (var item in extraPurcheInfo)
|
|
{
|
|
e.AddContent(item.Key, item.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SendRevenueEvent(SKUDetail sku, float usdPrice, string orderId, JObject extraPurcheInfo, int count = 1)
|
|
{
|
|
using (var e = GEvent.RevenueEvent())
|
|
{
|
|
e.AddContent(AFInAppEvents.CONTENT_ID, sku.ProdID)
|
|
.AddContent(AFInAppEvents.PRICE, usdPrice)
|
|
.AddContent(AFInAppEvents.CURRENCY, sku.price_currency_code)
|
|
.AddContent("payment", payment)
|
|
.AddContent(AFInAppEvents.QUANTITY, 1)
|
|
.AddContent("order_id", orderId)
|
|
.AddContent("restore", false)
|
|
.AddContent("payment_state", "finish")
|
|
.AddContent("af_revenue", sku.ProdPrice * count);
|
|
|
|
if (null != extraPurcheInfo)
|
|
{
|
|
foreach (var item in extraPurcheInfo)
|
|
{
|
|
e.AddContent(item.Key, item.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async Task<string> IAPPost(string route, IAPConfig iapCfg, string bodyString)
|
|
{
|
|
var apiurl = config.Get<string>(GConstant.K_Event_API_URL, iapCfg.OrderSvrUrl);
|
|
if (apiurl == iapCfg.OrderSvrUrl)
|
|
{
|
|
|
|
return await svr.CustomServerPost($"order/{route}", bodyString);
|
|
}
|
|
else
|
|
{
|
|
var httpClient = new HttpClient();
|
|
httpClient.BaseAddress = new System.Uri(iapCfg.OrderSvrUrl);
|
|
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
|
httpClient.DefaultRequestHeaders.Add("x-functions-key", iapCfg.FuncKey);
|
|
|
|
try
|
|
{
|
|
var content = new StringContent(bodyString, Encoding.UTF8, "application/json");
|
|
var response = await httpClient.PostAsync(route, content);
|
|
string result = await response.Content.ReadAsStringAsync();
|
|
if (response.StatusCode == System.Net.HttpStatusCode.OK)
|
|
{
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"iap post fail {response.StatusCode}");
|
|
return null;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
|
|
Debug.LogError($"iap post fail {e.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|