From eecf54c6a494b240ca995f7a064c512b70e88ae7 Mon Sep 17 00:00:00 2001 From: "Liubing\\LB" Date: Thu, 14 May 2026 11:53:38 +0800 Subject: [PATCH] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0tysdk=20=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tysdk/Plugins/Android/ConfigManager.java | 1 + .../tysdk/Plugins/Android/SDKManager.java | 14 ++ .../Packages/tysdk/Runtime/PaymentVerifier.cs | 215 ++++++++++++------ .../Packages/tysdk/Runtime/TYSdkFacade.cs | 13 ++ .../Packages/tysdk/Runtime/TYSdkFacade_Pay.cs | 21 +- .../Packages/tysdk/Runtime/TYSdkModel.cs | 6 + 6 files changed, 199 insertions(+), 71 deletions(-) diff --git a/sdk-intergration/Packages/tysdk/Plugins/Android/ConfigManager.java b/sdk-intergration/Packages/tysdk/Plugins/Android/ConfigManager.java index 4d30160..359a4d8 100644 --- a/sdk-intergration/Packages/tysdk/Plugins/Android/ConfigManager.java +++ b/sdk-intergration/Packages/tysdk/Plugins/Android/ConfigManager.java @@ -25,4 +25,5 @@ public class ConfigManager { public static final String PAY_TYPE_CALLBACK_FUNCTION_NAME = "SKUListResult"; public static final String FCM_NOTICE_FUNCTION_NAME = "FCMCallback"; public static final String FCM_REALNAME_CALLBACK_FUNCTION_NAME = "RealNameCallback"; + public static final String INIT_CALLBACK_FUNCTION_NAME = "InitResult"; } diff --git a/sdk-intergration/Packages/tysdk/Plugins/Android/SDKManager.java b/sdk-intergration/Packages/tysdk/Plugins/Android/SDKManager.java index 2680bd7..25e5cbe 100644 --- a/sdk-intergration/Packages/tysdk/Plugins/Android/SDKManager.java +++ b/sdk-intergration/Packages/tysdk/Plugins/Android/SDKManager.java @@ -126,12 +126,26 @@ public class SDKManager { TYUnityActivity.setSdkInited(); SDKAPI.getIns().onActivityStart(activity); SDKAPI.getIns().onActivityResume(activity); + notifyInitResult(true, "ok"); } catch (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) // ------------------------------------------------------------------------- diff --git a/sdk-intergration/Packages/tysdk/Runtime/PaymentVerifier.cs b/sdk-intergration/Packages/tysdk/Runtime/PaymentVerifier.cs index c496ef2..b23466d 100644 --- a/sdk-intergration/Packages/tysdk/Runtime/PaymentVerifier.cs +++ b/sdk-intergration/Packages/tysdk/Runtime/PaymentVerifier.cs @@ -2,6 +2,7 @@ using System; using System.Text; using System.Threading; using System.Threading.Tasks; +using asap.core; using Newtonsoft.Json; using UnityEngine; using UnityEngine.Networking; @@ -10,122 +11,181 @@ namespace tysdk { public interface IPaymentVerifier { - Task Verify(PaymentInfo info); - void Consume(string token); + Task VerifyAsync(PaymentInfo info); } - public class VerifyResult + public interface IPurchaseConsumer + { + Task 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 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()), - "Dummy" => new RetryablePaymentVerifier(new DummyPaymentVerifier()), + "Flexion" => new PaymentProcessor + { + verifier = new RetryingPaymentVerifier(new FlexionPaymentVerifier()), + consumer = new FlexionPurchaseConsumer() + }, + "Dummy" => new PaymentProcessor + { + verifier = new RetryingPaymentVerifier(new FakePaymentVerifier()), + consumer = new FakePurchaseConsumer() + }, _ => null }; + _cachedChannel = channel; + return _processor; } } - public class RetryablePaymentVerifier : IPaymentVerifier + public class RetryingPaymentVerifier : IPaymentVerifier { private readonly IPaymentVerifier _inner; private const int MaxRetries = 2; private static readonly TimeSpan RetryInterval = TimeSpan.FromSeconds(2); - public RetryablePaymentVerifier(IPaymentVerifier inner) + public RetryingPaymentVerifier(IPaymentVerifier inner) { _inner = inner; } - public async Task Verify(PaymentInfo info) + public async Task VerifyAsync(PaymentInfo info) { - VerifyResult lastResult = null; + PaymentVerifyResult lastResult = null; for (int attempt = 0; attempt <= MaxRetries; attempt++) { 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); } - lastResult = await _inner.Verify(info); + + lastResult = await _inner.VerifyAsync(info); 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; } - - public void Consume(string token) => _inner.Consume(token); } public class FlexionPaymentVerifier : IPaymentVerifier { - // TODO: 替换为实际游戏服务端验签 URL - private const string VERIFY_URL = "https://YOUR_GAME_SERVER/api/pay/verify"; + private const string DEFAULT_PAY_VERIFY_URL = "http://192.168.9.91:7036/api/FlexionCallback"; + + private readonly string _verifyUrl; 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(); + _verifyUrl = config.Get("PAY_VERIFY_URL", DEFAULT_PAY_VERIFY_URL); + } #if UNITY_EDITOR - public async Task Verify(PaymentInfo info) + public async Task VerifyAsync(PaymentInfo info) { Debug.LogWarning("[FlexionPaymentVerifier] Editor placeholder: auto success"); await Task.Yield(); - return new VerifyResult { success = true, msg = "editor_placeholder" }; + return new PaymentVerifyResult { success = true, msg = "editor_placeholder" }; } #else - public async Task Verify(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 SendVerifyRequest(PaymentInfo info) + public async Task VerifyAsync(PaymentInfo info) { var cts = new CancellationTokenSource(Timeout); 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); - 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.downloadHandler = new DownloadHandlerBuffer(); req.SetRequestHeader("Content-Type", "application/json"); - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - cts.Token.Register(() => tcs.TrySetResult(new VerifyResult { success = false, msg = "timeout" })); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + cts.Token.Register(() => tcs.TrySetResult(new PaymentVerifyResult { success = false, msg = "timeout" })); req.SendWebRequest().completed += _ => { if (req.result == UnityWebRequest.Result.Success) { - Debug.Log("[FlexionPaymentVerifier] Response: " + req.downloadHandler.text); - // TODO: 按实际服务端响应格式解析 - tcs.TrySetResult(new VerifyResult { success = true, msg = "ok" }); + var respText = req.downloadHandler.text; + Debug.Log("[FlexionPaymentVerifier] Response: " + respText); + var orderResult = JsonConvert.DeserializeObject(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 { 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) { Debug.LogError("[FlexionPaymentVerifier] Exception: " + e.Message); - return new VerifyResult { success = false, msg = e.Message }; + return new PaymentVerifyResult { success = false, msg = e.Message }; } finally { @@ -142,30 +202,57 @@ namespace tysdk } } #endif + } + public class FlexionPurchaseConsumer : IPurchaseConsumer + { #if UNITY_EDITOR - public void Consume(string token) { } -#elif UNITY_ANDROID - public void Consume(string token) + public async Task ConsumeAsync(string token) { - if (string.IsNullOrEmpty(token)) return; + await Task.Yield(); + return new PurchaseConsumeResult { success = true, msg = "editor_placeholder" }; + } +#elif UNITY_ANDROID + public async Task ConsumeAsync(string token) + { + if (string.IsNullOrEmpty(token)) + return new PurchaseConsumeResult { success = false, msg = "empty_token" }; + UnityBridgeFunc.ConsumePurchase(token); + await Task.Yield(); + return new PurchaseConsumeResult { success = true, msg = "consume_requested" }; + } +#else + public async Task ConsumeAsync(string token) + { + await Task.Yield(); + return new PurchaseConsumeResult { success = false, msg = "unsupported_platform" }; } #endif } - public class DummyPaymentVerifier : IPaymentVerifier + public class FakePaymentVerifier : IPaymentVerifier { - public async Task Verify(PaymentInfo info) + public async Task VerifyAsync(PaymentInfo info) { - Debug.Log("[DummyPaymentVerifier] Verifying purchase: " + info.orderId); + Debug.Log("[FakePaymentVerifier] Verifying purchase: " + info.orderId); await Task.Delay(500); - return new VerifyResult { success = true, msg = "dummy_ok" }; - } - - public void Consume(string token) - { - Debug.Log("[DummyPaymentVerifier] Consume: " + token); + return new PaymentVerifyResult { success = true, msg = "fake_ok" }; } } + + public class FakePurchaseConsumer : IPurchaseConsumer + { + public async Task 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; + } } diff --git a/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade.cs b/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade.cs index 3ce0e53..bed082e 100644 --- a/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade.cs +++ b/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade.cs @@ -130,6 +130,9 @@ namespace tysdk private int LoginTimeoutSeconds => ChannelConfig.LoginTimeoutSeconds; + public static bool IsSdkInited { get; private set; } + public static event System.Action OnInitComplete; + // ================== Init ================== public void Init() { @@ -138,6 +141,16 @@ namespace tysdk #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 public void SetChannelFromNative(string ch) { diff --git a/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade_Pay.cs b/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade_Pay.cs index 2ee3379..ad07fb6 100644 --- a/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade_Pay.cs +++ b/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade_Pay.cs @@ -113,6 +113,7 @@ namespace tysdk result.purchaseJson = jresult["purchaseJson"]?.ToString(); result.signature = jresult["signature"]?.ToString(); result.purchaseToken = jresult["purchaseToken"]?.ToString(); + result.userId = _accountInfo?.strUserId; if (result.code == "-1") { if (!_payFirstNegativeHandled) @@ -127,10 +128,10 @@ namespace tysdk } return; } - var verifier = PaymentVerifierFactory.GetVerifier(); - if (verifier != null && !string.IsNullOrEmpty(result.purchaseJson)) + var processor = PaymentProcessorFactory.GetProcessor(); + if (processor?.verifier != null && !string.IsNullOrEmpty(result.purchaseJson)) { - _VerifyPurchase(result, verifier); + _VerifyPurchase(result, processor); } 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(TimeSpan.FromSeconds(60)); try { - var verifyResult = await verifier.Verify(result); + var verifyResult = await processor.verifier.VerifyAsync(result); if (verifyResult.success) { 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); } diff --git a/sdk-intergration/Packages/tysdk/Runtime/TYSdkModel.cs b/sdk-intergration/Packages/tysdk/Runtime/TYSdkModel.cs index b5b5c6a..ec4f157 100644 --- a/sdk-intergration/Packages/tysdk/Runtime/TYSdkModel.cs +++ b/sdk-intergration/Packages/tysdk/Runtime/TYSdkModel.cs @@ -26,6 +26,12 @@ namespace tysdk 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) { confirmed = orderId == this.orderId && productId == this.productId;