Files
tysdk-intergration/sdk-intergration/Packages/tysdk/Runtime/PaymentVerifier.cs

259 lines
9.5 KiB
C#

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<PaymentVerifyResult> VerifyAsync(PaymentInfo info);
}
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 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<PaymentVerifyResult> 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<IConfig>();
_verifyUrl = config.Get<string>("PAY_VERIFY_URL", DEFAULT_PAY_VERIFY_URL);
}
#if UNITY_EDITOR
public async Task<PaymentVerifyResult> 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<PaymentVerifyResult> 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<PaymentVerifyResult>(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<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
{
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<PurchaseConsumeResult> ConsumeAsync(string token)
{
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);
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
}
public class FakePaymentVerifier : IPaymentVerifier
{
public async Task<PaymentVerifyResult> 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<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;
}
}