using System; using System.Text; using System.Threading; using System.Threading.Tasks; using asap.core; using Newtonsoft.Json; using UnityEngine; using UnityEngine.Networking; namespace tysdk { public interface IPaymentVerifier { Task VerifyAsync(PaymentInfo info); } 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 class PaymentProcessor { public IPaymentVerifier verifier; public IPurchaseConsumer consumer; } public static class PaymentProcessorFactory { private static PaymentProcessor _processor; private static string _cachedChannel; public static PaymentProcessor GetProcessor() { var channel = ChannelConfig.Channel; if (_processor != null && _cachedChannel == channel) return _processor; _processor = channel switch { "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 RetryingPaymentVerifier : IPaymentVerifier { private readonly IPaymentVerifier _inner; private const int MaxRetries = 2; private static readonly TimeSpan RetryInterval = TimeSpan.FromSeconds(2); public RetryingPaymentVerifier(IPaymentVerifier inner) { _inner = inner; } public async Task VerifyAsync(PaymentInfo info) { PaymentVerifyResult lastResult = null; for (int attempt = 0; attempt <= MaxRetries; attempt++) { if (attempt > 0) { Debug.Log($"[RetryingPaymentVerifier] Retry {attempt}/{MaxRetries}, waiting {RetryInterval.TotalSeconds}s"); await Task.Delay(RetryInterval); } lastResult = await _inner.VerifyAsync(info); if (lastResult.success) return lastResult; Debug.LogWarning($"[RetryingPaymentVerifier] Attempt {attempt + 1} failed: {lastResult.msg}"); } return lastResult; } } public class FlexionPaymentVerifier : IPaymentVerifier { 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); public FlexionPaymentVerifier() { var config = GContext.container.Resolve(); _verifyUrl = config.Get("PAY_VERIFY_URL", DEFAULT_PAY_VERIFY_URL); } #if UNITY_EDITOR public async Task VerifyAsync(PaymentInfo info) { Debug.LogWarning("[FlexionPaymentVerifier] Editor placeholder: auto success"); await Task.Yield(); return new PaymentVerifyResult { success = true, msg = "editor_placeholder" }; } #else public async Task VerifyAsync(PaymentInfo info) { var cts = new CancellationTokenSource(Timeout); try { 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); 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 PaymentVerifyResult { success = false, msg = "timeout" })); req.SendWebRequest().completed += _ => { if (req.result == UnityWebRequest.Result.Success) { 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 PaymentVerifyResult { success = false, msg = req.error }); } }; return await tcs.Task; } catch (Exception e) { Debug.LogError("[FlexionPaymentVerifier] Exception: " + e.Message); return new PaymentVerifyResult { success = false, msg = e.Message }; } finally { cts.Dispose(); } } #endif } public class FlexionPurchaseConsumer : IPurchaseConsumer { #if UNITY_EDITOR public async Task ConsumeAsync(string token) { 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 FakePaymentVerifier : IPaymentVerifier { public async Task VerifyAsync(PaymentInfo info) { Debug.Log("[FakePaymentVerifier] Verifying purchase: " + info.orderId); await Task.Delay(500); 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; } }