update:更新tysdk 默认修改

This commit is contained in:
2026-05-14 11:53:38 +08:00
parent 49d80ee008
commit eecf54c6a4
6 changed files with 199 additions and 71 deletions

View File

@@ -25,4 +25,5 @@ public class ConfigManager {
public static final String PAY_TYPE_CALLBACK_FUNCTION_NAME = "SKUListResult"; public static final String PAY_TYPE_CALLBACK_FUNCTION_NAME = "SKUListResult";
public static final String FCM_NOTICE_FUNCTION_NAME = "FCMCallback"; public static final String FCM_NOTICE_FUNCTION_NAME = "FCMCallback";
public static final String FCM_REALNAME_CALLBACK_FUNCTION_NAME = "RealNameCallback"; public static final String FCM_REALNAME_CALLBACK_FUNCTION_NAME = "RealNameCallback";
public static final String INIT_CALLBACK_FUNCTION_NAME = "InitResult";
} }

View File

@@ -126,12 +126,26 @@ public class SDKManager {
TYUnityActivity.setSdkInited(); TYUnityActivity.setSdkInited();
SDKAPI.getIns().onActivityStart(activity); SDKAPI.getIns().onActivityStart(activity);
SDKAPI.getIns().onActivityResume(activity); SDKAPI.getIns().onActivityResume(activity);
notifyInitResult(true, "ok");
} catch (Exception e) { } catch (Exception e) {
System.out.println("initSDK Exception " + e); System.out.println("initSDK Exception " + e);
notifyInitResult(false, e.getMessage());
} }
}); });
} }
static void notifyInitResult(boolean success, String msg) {
try {
JSONObject result = new JSONObject();
result.put("code", success ? 0 : 1);
result.put("msg", msg);
UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME,
ConfigManager.INIT_CALLBACK_FUNCTION_NAME, result.toString());
} catch (JSONException e) {
Log.e(TAG, "notifyInitResult error: " + e);
}
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Login / Callbacks (shared) // Login / Callbacks (shared)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------

View File

@@ -2,6 +2,7 @@ using System;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using asap.core;
using Newtonsoft.Json; using Newtonsoft.Json;
using UnityEngine; using UnityEngine;
using UnityEngine.Networking; using UnityEngine.Networking;
@@ -10,122 +11,181 @@ namespace tysdk
{ {
public interface IPaymentVerifier public interface IPaymentVerifier
{ {
Task<VerifyResult> Verify(PaymentInfo info); Task<PaymentVerifyResult> VerifyAsync(PaymentInfo info);
void Consume(string token);
} }
public class VerifyResult public interface IPurchaseConsumer
{
Task<PurchaseConsumeResult> ConsumeAsync(string token);
}
public class PaymentVerifyResult
{
public bool success;
public string msg;
public string orderId;
}
public class FlexionOrderResult
{
public int code { get; set; }
public string info { get; set; }
public string orderId { get; set; }
}
public class PurchaseConsumeResult
{ {
public bool success; public bool success;
public string msg; public string msg;
} }
public static class PaymentVerifierFactory public class PaymentProcessor
{ {
public static IPaymentVerifier GetVerifier() public IPaymentVerifier verifier;
public IPurchaseConsumer consumer;
}
public static class PaymentProcessorFactory
{
private static PaymentProcessor _processor;
private static string _cachedChannel;
public static PaymentProcessor GetProcessor()
{ {
return ChannelConfig.Channel switch var channel = ChannelConfig.Channel;
if (_processor != null && _cachedChannel == channel)
return _processor;
_processor = channel switch
{ {
"Flexion" => new RetryablePaymentVerifier(new FlexionPaymentVerifier()), "Flexion" => new PaymentProcessor
"Dummy" => new RetryablePaymentVerifier(new DummyPaymentVerifier()), {
verifier = new RetryingPaymentVerifier(new FlexionPaymentVerifier()),
consumer = new FlexionPurchaseConsumer()
},
"Dummy" => new PaymentProcessor
{
verifier = new RetryingPaymentVerifier(new FakePaymentVerifier()),
consumer = new FakePurchaseConsumer()
},
_ => null _ => null
}; };
_cachedChannel = channel;
return _processor;
} }
} }
public class RetryablePaymentVerifier : IPaymentVerifier public class RetryingPaymentVerifier : IPaymentVerifier
{ {
private readonly IPaymentVerifier _inner; private readonly IPaymentVerifier _inner;
private const int MaxRetries = 2; private const int MaxRetries = 2;
private static readonly TimeSpan RetryInterval = TimeSpan.FromSeconds(2); private static readonly TimeSpan RetryInterval = TimeSpan.FromSeconds(2);
public RetryablePaymentVerifier(IPaymentVerifier inner) public RetryingPaymentVerifier(IPaymentVerifier inner)
{ {
_inner = inner; _inner = inner;
} }
public async Task<VerifyResult> Verify(PaymentInfo info) public async Task<PaymentVerifyResult> VerifyAsync(PaymentInfo info)
{ {
VerifyResult lastResult = null; PaymentVerifyResult lastResult = null;
for (int attempt = 0; attempt <= MaxRetries; attempt++) for (int attempt = 0; attempt <= MaxRetries; attempt++)
{ {
if (attempt > 0) if (attempt > 0)
{ {
Debug.Log($"[RetryableVerifier] Flow retry {attempt}/{MaxRetries}, waiting {RetryInterval.TotalSeconds}s"); Debug.Log($"[RetryingPaymentVerifier] Retry {attempt}/{MaxRetries}, waiting {RetryInterval.TotalSeconds}s");
await Task.Delay(RetryInterval); await Task.Delay(RetryInterval);
} }
lastResult = await _inner.Verify(info);
lastResult = await _inner.VerifyAsync(info);
if (lastResult.success) return lastResult; if (lastResult.success) return lastResult;
Debug.LogWarning($"[RetryableVerifier] Flow attempt {attempt + 1} failed: {lastResult.msg}");
Debug.LogWarning($"[RetryingPaymentVerifier] Attempt {attempt + 1} failed: {lastResult.msg}");
} }
return lastResult; return lastResult;
} }
public void Consume(string token) => _inner.Consume(token);
} }
public class FlexionPaymentVerifier : IPaymentVerifier public class FlexionPaymentVerifier : IPaymentVerifier
{ {
// TODO: 替换为实际游戏服务端验签 URL private const string DEFAULT_PAY_VERIFY_URL = "http://192.168.9.91:7036/api/FlexionCallback";
private const string VERIFY_URL = "https://YOUR_GAME_SERVER/api/pay/verify";
private readonly string _verifyUrl;
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30); private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
private const int MaxRetries = 2;
private static readonly TimeSpan RetryBackoff = TimeSpan.FromSeconds(2); public FlexionPaymentVerifier()
{
var config = GContext.container.Resolve<IConfig>();
_verifyUrl = config.Get<string>("PAY_VERIFY_URL", DEFAULT_PAY_VERIFY_URL);
}
#if UNITY_EDITOR #if UNITY_EDITOR
public async Task<VerifyResult> Verify(PaymentInfo info) public async Task<PaymentVerifyResult> VerifyAsync(PaymentInfo info)
{ {
Debug.LogWarning("[FlexionPaymentVerifier] Editor placeholder: auto success"); Debug.LogWarning("[FlexionPaymentVerifier] Editor placeholder: auto success");
await Task.Yield(); await Task.Yield();
return new VerifyResult { success = true, msg = "editor_placeholder" }; return new PaymentVerifyResult { success = true, msg = "editor_placeholder" };
} }
#else #else
public async Task<VerifyResult> Verify(PaymentInfo info) public async Task<PaymentVerifyResult> VerifyAsync(PaymentInfo info)
{
VerifyResult lastResult = null;
for (int attempt = 0; attempt <= MaxRetries; attempt++)
{
if (attempt > 0)
{
var delay = TimeSpan.FromSeconds(RetryBackoff.TotalSeconds * attempt);
Debug.Log($"[FlexionPaymentVerifier] HTTP retry {attempt}/{MaxRetries}, waiting {delay.TotalSeconds}s");
await Task.Delay(delay);
}
lastResult = await SendVerifyRequest(info);
if (lastResult.success) return lastResult;
Debug.LogWarning($"[FlexionPaymentVerifier] HTTP attempt {attempt + 1} failed: {lastResult.msg}");
}
return lastResult;
}
private async Task<VerifyResult> SendVerifyRequest(PaymentInfo info)
{ {
var cts = new CancellationTokenSource(Timeout); var cts = new CancellationTokenSource(Timeout);
try try
{ {
Debug.Log("[FlexionPaymentVerifier] Sending verification request"); Debug.Log($"[FlexionPaymentVerifier] VerifyAsync start: url={_verifyUrl}");
Debug.Log($"[FlexionPaymentVerifier] Verify fields: orderId={info.orderId} productId={info.productId}" +
$" hasPurchaseJson={!string.IsNullOrEmpty(info.purchaseJson)} hasSignature={!string.IsNullOrEmpty(info.signature)}" +
$" purchaseToken={info.purchaseToken}");
var body = JsonConvert.SerializeObject(info); var body = JsonConvert.SerializeObject(info);
using var req = new UnityWebRequest(VERIFY_URL, "POST"); Debug.Log("[FlexionPaymentVerifier] Request body: " + body);
using var req = new UnityWebRequest(_verifyUrl, "POST");
req.certificateHandler = new BypassCertificateHandler();
req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(body)); req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(body));
req.downloadHandler = new DownloadHandlerBuffer(); req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("Content-Type", "application/json"); req.SetRequestHeader("Content-Type", "application/json");
var tcs = new TaskCompletionSource<VerifyResult>(TaskCreationOptions.RunContinuationsAsynchronously); var tcs = new TaskCompletionSource<PaymentVerifyResult>(TaskCreationOptions.RunContinuationsAsynchronously);
cts.Token.Register(() => tcs.TrySetResult(new VerifyResult { success = false, msg = "timeout" })); cts.Token.Register(() => tcs.TrySetResult(new PaymentVerifyResult { success = false, msg = "timeout" }));
req.SendWebRequest().completed += _ => req.SendWebRequest().completed += _ =>
{ {
if (req.result == UnityWebRequest.Result.Success) if (req.result == UnityWebRequest.Result.Success)
{ {
Debug.Log("[FlexionPaymentVerifier] Response: " + req.downloadHandler.text); var respText = req.downloadHandler.text;
// TODO: 按实际服务端响应格式解析 Debug.Log("[FlexionPaymentVerifier] Response: " + respText);
tcs.TrySetResult(new VerifyResult { success = true, msg = "ok" }); var orderResult = JsonConvert.DeserializeObject<FlexionOrderResult>(respText);
if (orderResult == null)
{
tcs.TrySetResult(new PaymentVerifyResult { success = false, msg = "parse_error" });
}
else if (orderResult.code == 0)
{
tcs.TrySetResult(new PaymentVerifyResult
{ success = true, msg = orderResult.info, orderId = orderResult.orderId });
}
else if (orderResult.code == 1)
{
tcs.TrySetResult(new PaymentVerifyResult
{ success = true, msg = orderResult.info, orderId = orderResult.orderId });
}
else if (orderResult.code == 2)
{
tcs.TrySetResult(new PaymentVerifyResult
{ success = true, msg = orderResult.info, orderId = orderResult.orderId });
}
else
{
// -1: 公钥/签名/玩家验证失败, -2: 写入失败, -3: 数据解析错误
tcs.TrySetResult(new PaymentVerifyResult
{ success = false, msg = orderResult.info ?? $"code={orderResult.code}" });
}
} }
else else
{ {
Debug.LogWarning("[FlexionPaymentVerifier] Failed: " + req.error); Debug.LogWarning("[FlexionPaymentVerifier] Failed: " + req.error);
tcs.TrySetResult(new VerifyResult { success = false, msg = req.error }); tcs.TrySetResult(new PaymentVerifyResult { success = false, msg = req.error });
} }
}; };
@@ -134,7 +194,7 @@ namespace tysdk
catch (Exception e) catch (Exception e)
{ {
Debug.LogError("[FlexionPaymentVerifier] Exception: " + e.Message); Debug.LogError("[FlexionPaymentVerifier] Exception: " + e.Message);
return new VerifyResult { success = false, msg = e.Message }; return new PaymentVerifyResult { success = false, msg = e.Message };
} }
finally finally
{ {
@@ -142,30 +202,57 @@ namespace tysdk
} }
} }
#endif #endif
}
public class FlexionPurchaseConsumer : IPurchaseConsumer
{
#if UNITY_EDITOR #if UNITY_EDITOR
public void Consume(string token) { } public async Task<PurchaseConsumeResult> ConsumeAsync(string token)
#elif UNITY_ANDROID
public void Consume(string token)
{ {
if (string.IsNullOrEmpty(token)) return; await Task.Yield();
return new PurchaseConsumeResult { success = true, msg = "editor_placeholder" };
}
#elif UNITY_ANDROID
public async Task<PurchaseConsumeResult> ConsumeAsync(string token)
{
if (string.IsNullOrEmpty(token))
return new PurchaseConsumeResult { success = false, msg = "empty_token" };
UnityBridgeFunc.ConsumePurchase(token); UnityBridgeFunc.ConsumePurchase(token);
await Task.Yield();
return new PurchaseConsumeResult { success = true, msg = "consume_requested" };
}
#else
public async Task<PurchaseConsumeResult> ConsumeAsync(string token)
{
await Task.Yield();
return new PurchaseConsumeResult { success = false, msg = "unsupported_platform" };
} }
#endif #endif
} }
public class DummyPaymentVerifier : IPaymentVerifier public class FakePaymentVerifier : IPaymentVerifier
{ {
public async Task<VerifyResult> Verify(PaymentInfo info) public async Task<PaymentVerifyResult> VerifyAsync(PaymentInfo info)
{ {
Debug.Log("[DummyPaymentVerifier] Verifying purchase: " + info.orderId); Debug.Log("[FakePaymentVerifier] Verifying purchase: " + info.orderId);
await Task.Delay(500); await Task.Delay(500);
return new VerifyResult { success = true, msg = "dummy_ok" }; return new PaymentVerifyResult { success = true, msg = "fake_ok" };
}
public void Consume(string token)
{
Debug.Log("[DummyPaymentVerifier] Consume: " + token);
} }
} }
public class FakePurchaseConsumer : IPurchaseConsumer
{
public async Task<PurchaseConsumeResult> ConsumeAsync(string token)
{
Debug.Log("[FakePurchaseConsumer] Consume: " + token);
await Task.Yield();
return new PurchaseConsumeResult { success = true, msg = "fake_ok" };
}
}
internal class BypassCertificateHandler : CertificateHandler
{
protected override bool ValidateCertificate(byte[] certificateData) => true;
}
} }

View File

@@ -130,6 +130,9 @@ namespace tysdk
private int LoginTimeoutSeconds => ChannelConfig.LoginTimeoutSeconds; private int LoginTimeoutSeconds => ChannelConfig.LoginTimeoutSeconds;
public static bool IsSdkInited { get; private set; }
public static event System.Action<bool, string> OnInitComplete;
// ================== Init ================== // ================== Init ==================
public void Init() public void Init()
{ {
@@ -138,6 +141,16 @@ namespace tysdk
#endif #endif
} }
public void InitResult(string json)
{
var data = Newtonsoft.Json.Linq.JObject.Parse(json);
var code = (int)data["code"];
var msg = (string)data["msg"];
IsSdkInited = code == 0;
UnityEngine.Debug.Log($"[TYSdkFacade] InitResult: code={code} msg={msg}");
OnInitComplete?.Invoke(IsSdkInited, msg);
}
// Called from Java SDKManager.InitSDK to sync channel // Called from Java SDKManager.InitSDK to sync channel
public void SetChannelFromNative(string ch) public void SetChannelFromNative(string ch)
{ {

View File

@@ -113,6 +113,7 @@ namespace tysdk
result.purchaseJson = jresult["purchaseJson"]?.ToString(); result.purchaseJson = jresult["purchaseJson"]?.ToString();
result.signature = jresult["signature"]?.ToString(); result.signature = jresult["signature"]?.ToString();
result.purchaseToken = jresult["purchaseToken"]?.ToString(); result.purchaseToken = jresult["purchaseToken"]?.ToString();
result.userId = _accountInfo?.strUserId;
if (result.code == "-1") if (result.code == "-1")
{ {
if (!_payFirstNegativeHandled) if (!_payFirstNegativeHandled)
@@ -127,10 +128,10 @@ namespace tysdk
} }
return; return;
} }
var verifier = PaymentVerifierFactory.GetVerifier(); var processor = PaymentProcessorFactory.GetProcessor();
if (verifier != null && !string.IsNullOrEmpty(result.purchaseJson)) if (processor?.verifier != null && !string.IsNullOrEmpty(result.purchaseJson))
{ {
_VerifyPurchase(result, verifier); _VerifyPurchase(result, processor);
} }
else else
{ {
@@ -138,18 +139,24 @@ namespace tysdk
} }
} }
private async void _VerifyPurchase(PaymentInfo result, IPaymentVerifier verifier) private async void _VerifyPurchase(PaymentInfo result, PaymentProcessor processor)
{ {
TYSDKCallbackManager.Instance.ResetCallbackTimeout<PaymentInfo>(TimeSpan.FromSeconds(60)); TYSDKCallbackManager.Instance.ResetCallbackTimeout<PaymentInfo>(TimeSpan.FromSeconds(60));
try try
{ {
var verifyResult = await verifier.Verify(result); var verifyResult = await processor.verifier.VerifyAsync(result);
if (verifyResult.success) if (verifyResult.success)
{ {
result.verified = true; result.verified = true;
if (!string.IsNullOrEmpty(result.purchaseToken)) if (!string.IsNullOrEmpty(verifyResult.orderId))
result.serverOrderId = verifyResult.orderId;
if (processor.consumer != null && !string.IsNullOrEmpty(result.purchaseToken))
{ {
verifier.Consume(result.purchaseToken); var consumeResult = await processor.consumer.ConsumeAsync(result.purchaseToken);
if (!consumeResult.success)
{
Debug.LogWarning("[TYSdkFacade] Purchase consume failed: " + consumeResult.msg);
}
} }
TYSDKCallbackManager.Instance.TryTriggerCallback(result); TYSDKCallbackManager.Instance.TryTriggerCallback(result);
} }

View File

@@ -26,6 +26,12 @@ namespace tysdk
public bool verified; public bool verified;
// Server-side order ID returned from verification
public string serverOrderId;
// Player ID for server-side verification
public string userId;
public bool Check(string orderId, string productId) public bool Check(string orderId, string productId)
{ {
confirmed = orderId == this.orderId && productId == this.productId; confirmed = orderId == this.orderId && productId == this.productId;