using System; using System.Text; using System.Threading; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace tysdk { public interface IPaymentVerifier { Task Verify(PaymentInfo info); void Consume(string token); } public class VerifyResult { public bool success; public string msg; } public static class PaymentVerifierFactory { public static IPaymentVerifier GetVerifier() { return ChannelConfig.Channel switch { "Flexion" => new RetryablePaymentVerifier(new FlexionPaymentVerifier()), "Dummy" => new RetryablePaymentVerifier(new DummyPaymentVerifier()), _ => null }; } } public class RetryablePaymentVerifier : IPaymentVerifier { private readonly IPaymentVerifier _inner; private const int MaxRetries = 2; private static readonly TimeSpan RetryInterval = TimeSpan.FromSeconds(2); public RetryablePaymentVerifier(IPaymentVerifier inner) { _inner = inner; } public async Task Verify(PaymentInfo info) { VerifyResult lastResult = null; for (int attempt = 0; attempt <= MaxRetries; attempt++) { if (attempt > 0) { Debug.Log($"[RetryableVerifier] Flow retry {attempt}/{MaxRetries}, waiting {RetryInterval.TotalSeconds}s"); await Task.Delay(RetryInterval); } lastResult = await _inner.Verify(info); if (lastResult.success) return lastResult; Debug.LogWarning($"[RetryableVerifier] Flow 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 static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30); private const int MaxRetries = 2; private static readonly TimeSpan RetryBackoff = TimeSpan.FromSeconds(2); #if UNITY_EDITOR public async Task Verify(PaymentInfo info) { Debug.LogWarning("[FlexionPaymentVerifier] Editor placeholder: auto success"); await Task.Yield(); return new VerifyResult { 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(BuildRequestBody(info)); if (lastResult.success) return lastResult; Debug.LogWarning($"[FlexionPaymentVerifier] HTTP attempt {attempt + 1} failed: {lastResult.msg}"); } return lastResult; } private async Task SendVerifyRequest(string body) { var cts = new CancellationTokenSource(Timeout); try { Debug.Log("[FlexionPaymentVerifier] Sending verification request"); using var req = new UnityWebRequest(VERIFY_URL, "POST"); 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" })); 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" }); } else { Debug.LogWarning("[FlexionPaymentVerifier] Failed: " + req.error); tcs.TrySetResult(new VerifyResult { success = false, msg = req.error }); } }; return await tcs.Task; } catch (Exception e) { Debug.LogError("[FlexionPaymentVerifier] Exception: " + e.Message); return new VerifyResult { success = false, msg = e.Message }; } finally { cts.Dispose(); } } private static string BuildRequestBody(PaymentInfo info) { var body = new StringBuilder(); body.Append('{'); body.Append("\"purchaseJson\":").Append(Escape(info.purchaseJson)).Append(','); body.Append("\"signature\":").Append(Escape(info.signature)).Append(','); body.Append("\"orderId\":").Append(Escape(info.orderId)).Append(','); body.Append("\"productId\":").Append(Escape(info.productId)).Append(','); body.Append("\"purchaseToken\":").Append(Escape(info.purchaseToken)); body.Append('}'); return body.ToString(); } #endif private static string Escape(string s) { if (s == null) return "null"; return '"' + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + '"'; } #if UNITY_EDITOR public void Consume(string token) { } #elif UNITY_ANDROID public void Consume(string token) { if (string.IsNullOrEmpty(token)) return; using var sdkManager = new AndroidJavaClass("com.unity3d.player.SDKManager"); sdkManager.CallStatic("ConsumeFlexionPurchase", token); } #endif } public class DummyPaymentVerifier : IPaymentVerifier { public async Task Verify(PaymentInfo info) { Debug.Log("[DummyPaymentVerifier] 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); } } }