feat:1、添加项目
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af9d868830da4e39ac9dacbd87918ec1
|
||||
timeCreated: 1756198041
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 752ae72dcb564aed925a1e54f34c9751
|
||||
timeCreated: 1756198065
|
||||
@@ -0,0 +1,59 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SGModule.GooglePay
|
||||
{
|
||||
public class GooglePayData
|
||||
{
|
||||
[JsonProperty("innerOrderId")] public string innerOrderId;
|
||||
[JsonProperty("amount")] public int amount;
|
||||
[JsonProperty("sku")] public string sku;
|
||||
[JsonProperty("currency")] public string currency = "USD";
|
||||
[JsonProperty("shopName")] public string shopName;
|
||||
[JsonProperty("type")] public string type;
|
||||
[JsonProperty("status")] public int status;
|
||||
[JsonProperty("orderId")] public string orderId;
|
||||
[JsonProperty("packageName")] public string packageName;
|
||||
[JsonProperty("productId")] public string productId;
|
||||
[JsonProperty("purchaseToken")] public string purchaseToken;
|
||||
[JsonProperty("purchaseState")] public int purchaseState;
|
||||
[JsonProperty("test")] public bool test;
|
||||
[JsonProperty("isCompleted")] public bool isCompleted;
|
||||
}
|
||||
|
||||
public class GoogleCheckData
|
||||
{
|
||||
[JsonProperty("signedPayload")] public string signedPayload;
|
||||
[JsonProperty("innerOrderId")] public string innerOrderId;
|
||||
}
|
||||
|
||||
public class GooglePayCancelInfo
|
||||
{
|
||||
[JsonProperty("innerOrderId")] public string innerOrderId;
|
||||
[JsonProperty("reason")] public string reason;
|
||||
[JsonProperty("message")] public string message;
|
||||
[JsonProperty("productId")] public string productId;
|
||||
}
|
||||
|
||||
|
||||
public class GoogleSubscribeData
|
||||
{
|
||||
[JsonProperty("orderId")] public string orderId;
|
||||
[JsonProperty("packageName")] public string packageName;
|
||||
[JsonProperty("productId")] public string productId;
|
||||
[JsonProperty("purchaseState")] public int purchaseState;
|
||||
[JsonProperty("purchaseToken")] public string purchaseToken;
|
||||
[JsonProperty("sku")] public string sku;
|
||||
[JsonProperty("amount")] public int amount;
|
||||
[JsonProperty("currency")] public string currency = "USD";
|
||||
[JsonProperty("expires_time")] public long expires_time;
|
||||
}
|
||||
|
||||
|
||||
public class GoogleSubscribeDataHistory
|
||||
{
|
||||
[JsonProperty("orig_tx_id")] public string orig_tx_id;
|
||||
[JsonProperty("sku")] public string sku;
|
||||
[JsonProperty("status")] public int status;
|
||||
[JsonProperty("renew_time")] public long renew_time;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be118fdd590a4ce7825e12a2d432f7a4
|
||||
timeCreated: 1756198178
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eea901421b8f43738bb0222236ed670b
|
||||
timeCreated: 1756198077
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace SGModule.GooglePay
|
||||
{
|
||||
public enum GooglePayBackType
|
||||
{
|
||||
/// <summary>
|
||||
///创建支付订单
|
||||
/// </summary>
|
||||
Create,
|
||||
/// <summary>
|
||||
///验证支付订单
|
||||
/// </summary>
|
||||
Check,
|
||||
/// <summary>
|
||||
///取消支付订单
|
||||
/// </summary>
|
||||
Cancel,
|
||||
/// <summary>
|
||||
///支付超时(可能多个订单,或者未联网时,拉起支付时支付数据需要1分钟后重置,需要进行提示处理)
|
||||
/// </summary>
|
||||
TimeOut,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15fef0f3bf56425e85298d8e4c41921f
|
||||
timeCreated: 1756199124
|
||||
@@ -0,0 +1,957 @@
|
||||
using SGModule.Common.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SGModule.Common.Helper;
|
||||
using SGModule.DataStorage;
|
||||
using SGModule.NetKit;
|
||||
|
||||
#if UNITY_IAP
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using DG.Tweening;
|
||||
using SGModule.Common.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using SGModule.Net;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Purchasing;
|
||||
using UnityEngine.Purchasing.Extension;
|
||||
|
||||
namespace SGModule.GooglePay
|
||||
{
|
||||
public class GooglePayManager : SingletonMonoBehaviour<GooglePayManager>, IDetailedStoreListener
|
||||
{
|
||||
private static readonly string SubscriptionExpiresTime = "SubscriptionExpiresTime";
|
||||
private IStoreController _storeController;
|
||||
private static IExtensionProvider _extensionProvider;
|
||||
private static IGooglePlayStoreExtensions _googleExtension;
|
||||
private static IAppleExtensions _appleExtension;
|
||||
|
||||
private Action<string> _failedCallback;
|
||||
|
||||
private Action<GooglePayBackType, GoogleResponseData> _successCallback;
|
||||
|
||||
private Dictionary<string, (string id, string type)> _products = new();
|
||||
|
||||
private GooglePayData _payData;
|
||||
|
||||
private string _packageName;
|
||||
|
||||
private float _lastPayAttemptTime;
|
||||
|
||||
|
||||
public void SendDebugToServer(string error, string stackTrace = "")
|
||||
{
|
||||
// ErrorLogKit.Send("debug", error, stackTrace);
|
||||
}
|
||||
|
||||
private Dictionary<string, PayProductConfig> _productConfigs = new();
|
||||
|
||||
private Dictionary<string, string> _innerData = new();
|
||||
|
||||
/// <summary>
|
||||
/// 通过 ProductConfig 数组初始化商品
|
||||
/// </summary>
|
||||
public void InitProduct(List<PayProductConfig> configs, string packageName,
|
||||
Action<GooglePayBackType, GoogleResponseData> successCallback)
|
||||
{
|
||||
var module = StandardPurchasingModule.Instance();
|
||||
ConfigurationBuilder builder = ConfigurationBuilder.Instance(module);
|
||||
|
||||
_successCallback = successCallback;
|
||||
_packageName = packageName;
|
||||
SendDebugToServer("[Google pay] InitProduct 开始------");
|
||||
try
|
||||
{
|
||||
foreach (var config in configs)
|
||||
{
|
||||
Debug.Log($"[Google pay] InitProduct ------{config.sku} , {config.type} ");
|
||||
if (Enum.TryParse<ProductType>(config.type, true, out var productType))
|
||||
{
|
||||
_productConfigs.Add(config.sku, config);
|
||||
|
||||
builder.AddProduct(config.sku, productType);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendDebugToServer($"无法解析 ProductType: {config.type}");
|
||||
Debug.LogError($"无法解析 ProductType: {config.type}");
|
||||
// 可根据需要处理默认类型或跳过该商品
|
||||
}
|
||||
}
|
||||
|
||||
SendDebugToServer("[Google pay] InitProduct 结束------");
|
||||
UnityPurchasing.Initialize(this, builder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SendDebugToServer($" 初始化" +
|
||||
$"商品 失败 InitProduct: {e.Message}");
|
||||
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
|
||||
Debug.Log($"[Google pay] InitProduct -----2 ");
|
||||
}
|
||||
|
||||
|
||||
private void SetPayData(string sku)
|
||||
{
|
||||
var payData = GetPayData(sku);
|
||||
_payData = payData;
|
||||
}
|
||||
|
||||
private GooglePayData GetPayData(string sku)
|
||||
{
|
||||
if (_productConfigs.TryGetValue(sku, out var data))
|
||||
{
|
||||
Debug.Log($"[Google pay] InitProduct ------{data.sku} , {data.type} ");
|
||||
return new GooglePayData
|
||||
{
|
||||
sku = sku,
|
||||
currency = "USD",
|
||||
amount = (int)Math.Round(data.price * 100),
|
||||
};
|
||||
}
|
||||
|
||||
Debug.LogError($" set _payData error------_payData is null ");
|
||||
return null;
|
||||
}
|
||||
|
||||
private Coroutine _payDataCheckCoroutine;
|
||||
|
||||
/// <summary>
|
||||
/// 启动_payData超时检测协程
|
||||
/// </summary>
|
||||
private void StartPayDataTimeoutCheck()
|
||||
{
|
||||
if (_payDataCheckCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_payDataCheckCoroutine);
|
||||
}
|
||||
|
||||
_payDataCheckCoroutine = StartCoroutine(PayDataTimeoutCheck());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测_payData是否超时的协程
|
||||
/// </summary>
|
||||
private IEnumerator PayDataTimeoutCheck()
|
||||
{
|
||||
float startTime = Time.time;
|
||||
float timeout = 30f; // 1分钟超时
|
||||
|
||||
if (_payData.innerOrderId == null)
|
||||
{
|
||||
timeout = 10f;
|
||||
}
|
||||
|
||||
while (Time.time - startTime < timeout)
|
||||
{
|
||||
// 如果_payData已经为null,停止检测
|
||||
if (_payData == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// 1分钟后检查,如果_payData仍然不为null,则设为null
|
||||
if (_payData != null)
|
||||
{
|
||||
_innerData = null;
|
||||
_payData = null;
|
||||
Debug.Log("[Apple Pay] _payData超时,已自动设为null");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 购买接口
|
||||
/// </summary>
|
||||
/// <param name="payData">购买需要的字段存放</param>
|
||||
/// <param name="successCallback">成功回调(ApplePayBackType为订单返回的类型:具体看 ApplePayBackType 枚举),作用:用来打点</param>
|
||||
/// <param name="failedCallback">失败回调 (string为失败原因)</param>
|
||||
public void Purchase(string sku, Action<GooglePayBackType, GoogleResponseData> successCallback,
|
||||
Action<string> failedCallback)
|
||||
{
|
||||
Debug.Log($"[Google pay] Purchase-0--------- {JsonConvert.SerializeObject(_payData)}");
|
||||
|
||||
SendDebugToServer("[Google pay] 购买 Purchase ------");
|
||||
|
||||
if (Time.time - _lastPayAttemptTime < 5)
|
||||
{
|
||||
failedCallback?.Invoke("Clicks are too frequent");
|
||||
return;
|
||||
}
|
||||
|
||||
_lastPayAttemptTime = Time.time;
|
||||
Debug.Log($"[Google pay] Purchase-1---------{JsonConvert.SerializeObject(_payData)}");
|
||||
if (_payData != null)
|
||||
{
|
||||
//启动_payData超时检测协程
|
||||
StartPayDataTimeoutCheck();
|
||||
_successCallback?.Invoke(GooglePayBackType.TimeOut, null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[Google pay] Purchase-2---------");
|
||||
|
||||
if (!IsInitialized())
|
||||
{
|
||||
failedCallback?.Invoke("Not Initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[Google pay] Purchase-3---------{sku}");
|
||||
|
||||
var product = _storeController.products.WithID(sku);
|
||||
if (product == null)
|
||||
{
|
||||
failedCallback?.Invoke("product is not found ");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!product.availableToPurchase)
|
||||
{
|
||||
failedCallback?.Invoke("product is not available for purchase");
|
||||
return;
|
||||
}
|
||||
|
||||
_successCallback = successCallback;
|
||||
_failedCallback = failedCallback;
|
||||
|
||||
SetPayData(sku);
|
||||
|
||||
|
||||
Debug.Log($"[Google pay] Purchase-4---------");
|
||||
|
||||
if (_payData != null)
|
||||
{
|
||||
var payData = new GooglePayData
|
||||
{
|
||||
sku = sku,
|
||||
currency = "USD",
|
||||
amount = _payData.amount,
|
||||
};
|
||||
|
||||
if (_productConfigs.TryGetValue(sku, out var data))
|
||||
{
|
||||
if (Enum.TryParse<ProductType>(data.type, true, out var productType))
|
||||
{
|
||||
Debug.Log($"[google pay] 是否是订阅类型=== {productType}");
|
||||
if (productType == ProductType.Subscription)
|
||||
{
|
||||
_successCallback(GooglePayBackType.Create, null);
|
||||
_storeController.InitiatePurchase(product);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GooglePayNet.GooglePayCreate<GooglePayData>(payData, (response) =>
|
||||
{
|
||||
Debug.Log($"[Google pay] Purchase-5---------{response.IsSuccess}");
|
||||
|
||||
if (response.IsSuccess)
|
||||
{
|
||||
if (_innerData == null) _innerData = new();
|
||||
|
||||
_successCallback?.Invoke(GooglePayBackType.Create, null);
|
||||
Debug.Log($"[Google pay] Purchase--5-innerOrderId-------{response.Data.innerOrderId}");
|
||||
if (_payData != null && response.Data.innerOrderId != null)
|
||||
{
|
||||
Debug.Log($"[Google pay] Purchase-555555-innerOrderId");
|
||||
_payData.innerOrderId = response.Data.innerOrderId;
|
||||
_innerData[response.Data.innerOrderId] = sku;
|
||||
SaveInnerIdData(_innerData);
|
||||
}
|
||||
|
||||
_storeController.InitiatePurchase(product);
|
||||
}
|
||||
|
||||
SendDebugToServer($"[Google pay] Purchase-6-------创建内部ID innerOrderId--{_payData.innerOrderId}");
|
||||
|
||||
Debug.Log($"[Google pay] Purchase-7-------innerOrderId--{_payData.innerOrderId}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IOS恢复内购
|
||||
/// 会在删除应用后,第一次安装是自动恢复
|
||||
/// </summary>
|
||||
/// <param name="restoreCallback">恢复回调</param>
|
||||
public void AppleRestore(Action<bool, string> restoreCallback)
|
||||
{
|
||||
if (!IsInitialized())
|
||||
{
|
||||
Debug.LogWarning("[IAP] IAppleExtensions 未初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("[IAP] 用户手动恢复购买");
|
||||
_appleExtension.RestoreTransactions((success, error) =>
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
Debug.Log("[Google pay] Restore Transactions 成功");
|
||||
// 这里会触发 ProcessPurchase 回调
|
||||
restoreCallback(true, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[Google pay] Restore Transactions 失败: {error}");
|
||||
// 可以提示用户重试
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#region 购买成功回调
|
||||
|
||||
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
|
||||
{
|
||||
var product = purchaseEvent.purchasedProduct;
|
||||
var productType = product.definition.type;
|
||||
var sku = product.definition.id;
|
||||
|
||||
|
||||
Debug.Log($" ProcessPurchase 1 Purchase: {JsonConvert.SerializeObject(purchaseEvent.purchasedProduct)}");
|
||||
|
||||
if (purchaseEvent is not { purchasedProduct: not null })
|
||||
{
|
||||
Debug.LogError("[Google pay] ProcessPurchase 2 : purchaseEvent 或 purchasedProduct 为 null");
|
||||
return PurchaseProcessingResult.Complete;
|
||||
}
|
||||
|
||||
Debug.Log($"购买商品类型: {productType}, SKU: {sku}");
|
||||
if (productType == ProductType.Subscription)
|
||||
{
|
||||
SendDebugToServer($"[Google pay] 购买商品类型 Purchase ---{productType}-{sku}--");
|
||||
|
||||
// 服务器验证
|
||||
UploadReceiptForValidation(purchaseEvent, sku, isSuccess =>
|
||||
{
|
||||
// if (isSuccess)
|
||||
// {
|
||||
// // 验证成功后的逻辑
|
||||
// // 验证成功,完成购买
|
||||
// _storeController.ConfirmPendingPurchase(product);
|
||||
// }
|
||||
|
||||
_storeController.ConfirmPendingPurchase(product);
|
||||
});
|
||||
|
||||
return PurchaseProcessingResult.Pending;
|
||||
}
|
||||
|
||||
if (productType == ProductType.NonConsumable)
|
||||
{
|
||||
Debug.Log($" 识别到非消耗性商品 ----{DataMgr.ApplePayTransactionID.Value.Contains(product.transactionID)}");
|
||||
if (DataMgr.ApplePayTransactionID.Value.Contains(product.transactionID))
|
||||
{
|
||||
return PurchaseProcessingResult.Complete;
|
||||
}
|
||||
|
||||
DataMgr.ApplePayTransactionID.Value.Add(product.transactionID);
|
||||
}
|
||||
|
||||
// 普通商品直接处理
|
||||
HandlePurchaseSuccess(purchaseEvent, isSuccess =>
|
||||
{
|
||||
Debug.Log($"普通商品直接处理 isSuccess==========={isSuccess}");
|
||||
// if (isSuccess)
|
||||
// {
|
||||
// // 验证成功,完成购买
|
||||
// _storeController.ConfirmPendingPurchase(product);
|
||||
// }
|
||||
_storeController.ConfirmPendingPurchase(product);
|
||||
});
|
||||
|
||||
return PurchaseProcessingResult.Pending;
|
||||
}
|
||||
|
||||
|
||||
//服务器验证
|
||||
private void UploadReceiptForValidation(PurchaseEventArgs purchaseEvent, string sku,
|
||||
Action<bool> onValidationComplete)
|
||||
{
|
||||
Debug.Log("识别到订阅商品,准备进行订阅验证");
|
||||
SendDebugToServer($"[Google pay] 识别到订阅商品,准备进行订阅验证");
|
||||
|
||||
var productConfig = GetPayData(sku);
|
||||
if (productConfig != null)
|
||||
{
|
||||
var receiptObj = JObject.Parse(purchaseEvent.purchasedProduct.receipt);
|
||||
var payload = JObject.Parse((string)receiptObj["Payload"]);
|
||||
var jsonData = JObject.Parse((string)payload["json"]);
|
||||
var transactionID = purchaseEvent.purchasedProduct.transactionID;
|
||||
Debug.Log($"订阅商品ID: {sku} transactionID: {transactionID} _payData.amount: {productConfig.amount}");
|
||||
|
||||
SendDebugToServer(
|
||||
$"订阅商品ID: {sku} transactionID: {transactionID} _payData.amount: {productConfig.amount}");
|
||||
|
||||
GoogleSubscribeData requestData = new()
|
||||
{
|
||||
orderId = (string)jsonData["orderId"],
|
||||
packageName = _packageName,
|
||||
productId = (string)jsonData["productId"],
|
||||
purchaseState = (int)jsonData["purchaseState"],
|
||||
purchaseToken = (string)jsonData["purchaseToken"],
|
||||
sku = sku,
|
||||
amount = GetPayData(sku).amount,
|
||||
currency = "USD"
|
||||
};
|
||||
GooglePayNet.GoogleSubscribeCheck<GoogleSubscribeData>(
|
||||
requestData,
|
||||
response =>
|
||||
{
|
||||
bool isSuccess = response.IsSuccess;
|
||||
|
||||
// 在这里返回验证结果
|
||||
onValidationComplete?.Invoke(isSuccess);
|
||||
|
||||
_payData = null;
|
||||
|
||||
if (isSuccess)
|
||||
{
|
||||
Debug.Log("订阅验证成功");
|
||||
SendDebugToServer($"订阅验证成功");
|
||||
|
||||
SaveSubscriptionExpireTime(response.Data.expires_time);
|
||||
_successCallback?.Invoke(GooglePayBackType.Check, new GoogleResponseData()
|
||||
{
|
||||
expires_time = response.Data.expires_time,
|
||||
sku = sku
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[Google pay] 订阅验证失败");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支付成功后的本地处理(订阅和普通商品共用)
|
||||
/// </summary>
|
||||
private void HandlePurchaseSuccess(PurchaseEventArgs purchaseEvent, Action<bool> onValidationComplete,
|
||||
bool isTest = false)
|
||||
{
|
||||
var payDataJson = GetApplePayData();
|
||||
Debug.Log($" HandlePurchaseSuccess payDataJson: {JsonConvert.SerializeObject(payDataJson)}");
|
||||
|
||||
var statusDictionary = JsonConvert.DeserializeObject<Dictionary<string, GooglePayData>>(payDataJson)
|
||||
?? new Dictionary<string, GooglePayData>();
|
||||
|
||||
string transactionID = purchaseEvent.purchasedProduct.transactionID;
|
||||
SendDebugToServer($"普通商品 HandlePurchaseSuccess: {transactionID}");
|
||||
|
||||
Debug.Log($"[google pay] 购买成功: transactionID: {transactionID}");
|
||||
var receiptObj = JObject.Parse(purchaseEvent.purchasedProduct.receipt);
|
||||
|
||||
var payload = JObject.Parse((string)receiptObj["Payload"]);
|
||||
var jsonData = JObject.Parse((string)payload["json"]);
|
||||
|
||||
Debug.Log($"[google pay] 购买成功: orderId: {(string)jsonData["orderId"]}");
|
||||
//GPA.3320-7000-4171-67632
|
||||
//efbjipchlddmgamgdjkmidcn.AO-J1Oy9pfnlyIpj7qvbisgYb55XEGtV-GDr1Q-HoR96N1jZp3YRvgJRTajlxFaws3CxF-Qu6mXY2WHIPqjO7Ul3eSNHOC5PaD9LaF4Mv0ZNq7e5IcIaMSs
|
||||
if (!statusDictionary.ContainsKey(transactionID))
|
||||
{
|
||||
Debug.Log($"记录新交易 transactionID: {transactionID}");
|
||||
Debug.Log($"-----_payData: {JsonConvert.SerializeObject(_payData)}");
|
||||
if (_payData == null || string.IsNullOrWhiteSpace(_payData.innerOrderId))
|
||||
{
|
||||
Debug.Log("[Google pay] _payData 为空或 innerOrderId 无效");
|
||||
var innerdata = GetInnerIdData();
|
||||
var isFind = false;
|
||||
Debug.Log($" HandlePurchaseSuccess innerdata: {JsonConvert.SerializeObject(innerdata)}");
|
||||
if (innerdata != null)
|
||||
{
|
||||
foreach (var item in innerdata)
|
||||
{
|
||||
Debug.Log(
|
||||
$" HandlePurchaseSuccess item: {item.Key} {item.Value} jsonData productId=={(string)jsonData["productId"]}");
|
||||
if (item.Value == (string)jsonData["productId"])
|
||||
{
|
||||
_payData = new()
|
||||
{
|
||||
innerOrderId = item.Key
|
||||
};
|
||||
isFind = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isFind)
|
||||
{
|
||||
Debug.LogWarning("[Google pay] 内部订单ID 无效");
|
||||
onValidationComplete?.Invoke(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_payData.orderId = (string)jsonData["orderId"];
|
||||
_payData.packageName = (string)jsonData["packageName"];
|
||||
_payData.productId = (string)jsonData["productId"];
|
||||
_payData.purchaseToken = (string)jsonData["purchaseToken"];
|
||||
_payData.purchaseState = (int)jsonData["purchaseState"];
|
||||
|
||||
statusDictionary.Add(transactionID, _payData);
|
||||
SaveApplePayData(statusDictionary);
|
||||
|
||||
_innerData = null;
|
||||
_payData = null;
|
||||
}
|
||||
|
||||
if (statusDictionary.TryGetValue(transactionID, out var cValue))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cValue.innerOrderId) || isTest)
|
||||
{
|
||||
ApplePaySuccess(transactionID, onValidationComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 请求订阅信息重试
|
||||
|
||||
private int _retryCount = 0;
|
||||
private const int MaxRetryCount = 2;
|
||||
private const float RetryDelay = 60f;
|
||||
|
||||
public void CheckSubscription(Action<bool> onFinish)
|
||||
{
|
||||
GooglePayNet.GoogleSubscribeHistory<List<GoogleSubscribeDataHistory>>(null, (response) =>
|
||||
{
|
||||
if (response.IsSuccess)
|
||||
{
|
||||
var responseData = response.Data;
|
||||
Debug.Log($"[Google pay] 订阅检查成功: {JsonConvert.SerializeObject(responseData)}");
|
||||
|
||||
if (responseData == null || responseData.Count == 0)
|
||||
{
|
||||
// Debug.Log($"[Google pay] 订阅检查成功: {JsonConvert.SerializeObject(responseData)}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
GoogleSubscribeDataHistory latestSubscription = null;
|
||||
foreach (var sub in responseData)
|
||||
{
|
||||
if (latestSubscription == null || sub.renew_time > latestSubscription.renew_time)
|
||||
{
|
||||
latestSubscription = sub;
|
||||
}
|
||||
}
|
||||
|
||||
//服务器时间
|
||||
long currentTime = ServerClock.GetCurrentServerTime();
|
||||
Debug.Log($"[Google pay] latestSubscription: {JsonConvert.SerializeObject(latestSubscription)}");
|
||||
|
||||
|
||||
Debug.Log($"[Google pay] 订阅检查失效时间: currentTime: {currentTime} renew_time: {latestSubscription.renew_time}");
|
||||
|
||||
if (currentTime < latestSubscription.renew_time)
|
||||
{
|
||||
Debug.Log($"[Google pay] 订阅检查成功:");
|
||||
_successCallback(GooglePayBackType.Check, new GoogleResponseData()
|
||||
{
|
||||
sku = latestSubscription.sku,
|
||||
expires_time = latestSubscription.renew_time
|
||||
});
|
||||
|
||||
_retryCount = 0;
|
||||
onFinish?.Invoke(true); // ✅ 成功 → 解锁
|
||||
}
|
||||
else
|
||||
{
|
||||
Retry(onFinish);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Retry(onFinish);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void Retry(Action<bool> onFinish)
|
||||
{
|
||||
if (_retryCount < MaxRetryCount)
|
||||
{
|
||||
_retryCount++;
|
||||
Debug.Log($"[Google pay] latestSubscription == null,{RetryDelay} 秒后重试,第 {_retryCount} 次...");
|
||||
StartCoroutine(RetryCoroutine(onFinish));
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[Google pay] 订阅检查失败,已达到最大重试次数!");
|
||||
_retryCount = 0;
|
||||
onFinish?.Invoke(false); // ✅ 失败 → 解锁
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator RetryCoroutine(Action<bool> onFinish)
|
||||
{
|
||||
yield return new WaitForSeconds(RetryDelay);
|
||||
CheckSubscription(onFinish);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public static DataStorage<long> VipExpirationTime = new(SubscriptionExpiresTime);
|
||||
|
||||
/// <summary>
|
||||
/// 保存订阅失效时间
|
||||
/// </summary>
|
||||
/// <param name="expires_time">过期时间戳</param>
|
||||
private void SaveSubscriptionExpireTime(long expires_time)
|
||||
{
|
||||
var expireTime = expires_time;
|
||||
if (expireTime > 0)
|
||||
{
|
||||
VipExpirationTime.Value = expireTime;
|
||||
Debug.Log($"[Google pay] 保存订阅失效时间: {expireTime}");
|
||||
VipExpirationTime.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveApplePayData(Dictionary<string, GooglePayData> payData)
|
||||
{
|
||||
// 保存更新后的数据
|
||||
string json = JsonConvert.SerializeObject(payData);
|
||||
PlayerPrefs.SetString("SGModule_apple_pay_data", json);
|
||||
}
|
||||
|
||||
private string GetApplePayData()
|
||||
{
|
||||
string json = PlayerPrefs.GetString("SGModule_apple_pay_data", "");
|
||||
// GetString if (json == "")
|
||||
// {
|
||||
// json = DataWrapper.ApplePayData;
|
||||
// }
|
||||
return json;
|
||||
}
|
||||
|
||||
private void SaveInnerIdData(Dictionary<string, string> innerData)
|
||||
{
|
||||
// 保存更新后的数据
|
||||
string json = JsonConvert.SerializeObject(innerData);
|
||||
Debug.Log($"[Inner save Data] ==: {json}");
|
||||
PlayerPrefs.SetString("SGModule_inner_order_id", json);
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetInnerIdData()
|
||||
{
|
||||
string json = PlayerPrefs.GetString("SGModule_inner_order_id", "");
|
||||
Debug.Log($"[Inner get Data] ==: {json}");
|
||||
return JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
|
||||
}
|
||||
|
||||
|
||||
private void ApplePaySuccess(string transactionID, Action<bool> onValidationComplete)
|
||||
{
|
||||
Debug.Log("apple 支付 StartCoroutine------");
|
||||
StartCoroutine(ProcessPayData(transactionID, onValidationComplete));
|
||||
}
|
||||
|
||||
private IEnumerator ProcessPayData(string orderId, Action<bool> onValidationComplete)
|
||||
{
|
||||
// 发起请求
|
||||
ApplePayRequest(orderId, onValidationComplete);
|
||||
|
||||
yield return null; // 等待本次请求完成
|
||||
}
|
||||
|
||||
private void ApplePayRequest(string transactionID, Action<bool> onValidationComplete)
|
||||
{
|
||||
var payDataJson = GetApplePayData();
|
||||
Debug.Log($"[IAP] ApplePayRequest 1 payDataJson: {JsonConvert.SerializeObject(payDataJson)}");
|
||||
SendDebugToServer($"验单 普通商品: {transactionID}");
|
||||
|
||||
var statusDictionary = JsonConvert.DeserializeObject<Dictionary<string, GooglePayData>>(payDataJson);
|
||||
|
||||
if (statusDictionary.ContainsKey(transactionID))
|
||||
{
|
||||
Debug.Log($"[IAP] ApplePayRequest 2 transactionID: {transactionID}");
|
||||
|
||||
var data = statusDictionary[transactionID];
|
||||
|
||||
GooglePayCheckInfo reqData = new()
|
||||
{
|
||||
innerOrderId = data.innerOrderId,
|
||||
orderId = data.orderId,
|
||||
packageName = data.packageName,
|
||||
productId = data.productId,
|
||||
purchaseToken = data.purchaseToken,
|
||||
purchaseState = data.purchaseState,
|
||||
test = false,
|
||||
};
|
||||
|
||||
GooglePayNet.GooglePayCheck<GooglePayData>(reqData, _packageName, (response) =>
|
||||
{
|
||||
Debug.Log(
|
||||
$"[IAP] ApplePayRequest 3 response.IsSuccess: {JsonConvert.SerializeObject(response)}");
|
||||
|
||||
onValidationComplete?.Invoke(response.IsSuccess);
|
||||
|
||||
if (response.IsSuccess)
|
||||
{
|
||||
_successCallback(GooglePayBackType.Check, new GoogleResponseData()
|
||||
{
|
||||
sku = data.productId
|
||||
});
|
||||
|
||||
var innerdata = GetInnerIdData();
|
||||
innerdata.Remove(data.innerOrderId);
|
||||
SaveInnerIdData(innerdata);
|
||||
|
||||
statusDictionary.Remove(transactionID);
|
||||
// 保存更新后的数据
|
||||
SaveApplePayData(statusDictionary);
|
||||
|
||||
_innerData = null;
|
||||
_payData = null;
|
||||
}
|
||||
|
||||
if (!new List<int>() { 1021, 1026, 1027, 1028 }.Contains(response.Code))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
statusDictionary.Remove(transactionID);
|
||||
// 保存更新后的数据
|
||||
SaveApplePayData(statusDictionary);
|
||||
|
||||
var innerdata = GetInnerIdData();
|
||||
innerdata.Remove(reqData.innerOrderId);
|
||||
SaveInnerIdData(innerdata);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 初始化
|
||||
|
||||
private bool IsInitialized()
|
||||
{
|
||||
Debug.Log("[IAP] check IsInitialized======:");
|
||||
return _storeController != null && _extensionProvider != null;
|
||||
}
|
||||
|
||||
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
|
||||
{
|
||||
Debug.Log("[IAP] OnInitialized success======:");
|
||||
SendDebugToServer($" OnInitialized 初始化成功 :");
|
||||
|
||||
_storeController = controller;
|
||||
_extensionProvider = extensions;
|
||||
_appleExtension = extensions.GetExtension<IAppleExtensions>();
|
||||
_googleExtension = extensions.GetExtension<IGooglePlayStoreExtensions>();
|
||||
|
||||
}
|
||||
|
||||
public void OnInitializeFailed(InitializationFailureReason error)
|
||||
{
|
||||
Debug.Log("[IAP] OnInitializeFailed1 Reason:" + error);
|
||||
SendDebugToServer($" OnInitializeFailed 初始化失败 :");
|
||||
// throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void OnInitializeFailed(InitializationFailureReason error, string message)
|
||||
{
|
||||
Debug.Log("[IAP] OnInitializeFailed2 Reason:" + error);
|
||||
Debug.Log("[IAP] OnInitializeFailed2 message:" + message);
|
||||
|
||||
SendDebugToServer($" OnInitializeFailed 初始化失败 {message}");
|
||||
|
||||
// throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 购买失败回调
|
||||
|
||||
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
|
||||
{
|
||||
Debug.Log("[google fail] OnPurchaseFailed===1==:");
|
||||
HandleOnPurchaseFail(product, failureReason);
|
||||
}
|
||||
|
||||
|
||||
public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
|
||||
{
|
||||
Debug.Log("[google fail] OnPurchaseFailed===2==:");
|
||||
|
||||
if (failureDescription != null)
|
||||
{
|
||||
HandleOnPurchaseFail(product, failureDescription.reason, failureDescription);
|
||||
}
|
||||
|
||||
|
||||
// if (failureDescription.reason.ToString() == "DuplicateTransaction")
|
||||
// {
|
||||
// Debug.Log("DuplicateTransaction detected, restoring transactions...");
|
||||
//
|
||||
// var googlePlay = _extensionProvider.GetExtension<IGooglePlayStoreExtensions>();
|
||||
// googlePlay.RestoreTransactions((success, msg) =>
|
||||
// {
|
||||
// Debug.Log($"RestoreTransactions result: {success}, message: {msg}");
|
||||
// // 发奖励逻辑不要写这里,ProcessPurchase 会处理未完成订单
|
||||
//
|
||||
// if (success)
|
||||
// {
|
||||
// _storeController.ConfirmPendingPurchase(product);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
private void HandleOnPurchaseFail(Product product, PurchaseFailureReason failureReason,
|
||||
PurchaseFailureDescription failureDescription = null)
|
||||
{
|
||||
if (failureDescription != null && failureDescription.reason == PurchaseFailureReason.DuplicateTransaction)
|
||||
{
|
||||
// ✅ 只处理消耗型商品
|
||||
if (product.definition.type == ProductType.Consumable)
|
||||
{
|
||||
Debug.Log("DuplicateTransaction: 消耗型商品未消费,尝试补偿...");
|
||||
|
||||
var googlePlay = _extensionProvider.GetExtension<IGooglePlayStoreExtensions>();
|
||||
googlePlay.RestoreTransactions((success, msg) =>
|
||||
{
|
||||
Debug.Log($"RestoreTransactions result: {success}, message: {msg}");
|
||||
// 发奖励逻辑不要写这里,ProcessPurchase 会处理未完成订单
|
||||
|
||||
if (success)
|
||||
{
|
||||
_storeController.ConfirmPendingPurchase(product);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[google fail] HandleOnPurchaseFail 1 ");
|
||||
|
||||
if (_payData?.innerOrderId == null && !_payData.sku.Contains(".sub")) return;
|
||||
|
||||
Debug.Log($"[google fail] HandleOnPurchaseFail 2 {_payData?.innerOrderId} ");
|
||||
|
||||
GooglePayCancelInfo reqData = new();
|
||||
if (failureDescription != null)
|
||||
{
|
||||
var receiptObj = JObject.Parse(failureDescription.item.Product.receipt);
|
||||
|
||||
var payload = JObject.Parse((string)receiptObj["Payload"]);
|
||||
var jsonData = JObject.Parse((string)payload["json"]);
|
||||
|
||||
reqData.innerOrderId = _payData.innerOrderId;
|
||||
reqData.reason = failureDescription.reason.ToString();
|
||||
reqData.message = failureDescription.message;
|
||||
reqData.productId = (string)jsonData["productId"];
|
||||
}
|
||||
else
|
||||
{
|
||||
reqData.innerOrderId = _payData.innerOrderId;
|
||||
|
||||
reqData.reason = failureReason.ToString();
|
||||
}
|
||||
|
||||
GooglePayNet.GooglePayCancel<GooglePayCancelInfo>(reqData, (response) =>
|
||||
{
|
||||
string msg = ToFriendlyString(failureReason);
|
||||
Debug.Log("[Google pay] HandleOnPurchaseFail---:" + response.IsSuccess + " reason: " + msg);
|
||||
|
||||
if (!_payData.sku.Contains(".sub"))
|
||||
{
|
||||
var innerdata = GetInnerIdData();
|
||||
Debug.Log(
|
||||
$"[Google pay] HandleOnPurchaseFail----- _payData.innerOrderId: {_payData.innerOrderId}");
|
||||
|
||||
innerdata.Remove(_payData.innerOrderId);
|
||||
SaveInnerIdData(innerdata);
|
||||
}
|
||||
|
||||
_failedCallback?.Invoke(msg);
|
||||
|
||||
_innerData = null;
|
||||
_payData = null;
|
||||
_successCallback?.Invoke(GooglePayBackType.Cancel, null);
|
||||
});
|
||||
}
|
||||
|
||||
private static string ToFriendlyString(PurchaseFailureReason reason)
|
||||
{
|
||||
Debug.Log($"ToFriendlyString 1 reason==={reason}");
|
||||
|
||||
return reason switch
|
||||
{
|
||||
PurchaseFailureReason.ProductUnavailable => "商品不可用",
|
||||
PurchaseFailureReason.PurchasingUnavailable => "购买功能不可用",
|
||||
PurchaseFailureReason.ExistingPurchasePending => "已有未完成的购买",
|
||||
PurchaseFailureReason.SignatureInvalid => "签名验证失败",
|
||||
PurchaseFailureReason.UserCancelled => "用户取消",
|
||||
PurchaseFailureReason.PaymentDeclined => "支付被拒绝",
|
||||
PurchaseFailureReason.DuplicateTransaction => "重复的交易ID",
|
||||
PurchaseFailureReason.Unknown => "未知错误",
|
||||
_ => $"未识别的错误: {(int)reason}"
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#else
|
||||
namespace SGModule.GooglePay {
|
||||
public class GooglePayManager: SingletonMonoBehaviour<GooglePayManager>{
|
||||
public void StartPay() {
|
||||
}
|
||||
|
||||
public string GetApplePayName(string key)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
public void Purchase(GooglePayData payData, Action<GooglePayBackType> successCallback,
|
||||
Action<string> failedCallback) {
|
||||
var msg = "Apple Pay: Purchase Failed, No plugin is installed.";
|
||||
Log.Info("[Apple IOS]",msg);
|
||||
failedCallback?.Invoke(msg);
|
||||
}
|
||||
|
||||
public void GoogleRestore(Action<bool, string> restoreCallback)
|
||||
{
|
||||
|
||||
}
|
||||
public void InitProduct(List<ProductConfig>configs, string packageName)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
public class GoogleResponseData
|
||||
{
|
||||
[JsonProperty("expires_time")] public long expires_time;
|
||||
[JsonProperty("sku")] public string sku;
|
||||
}
|
||||
|
||||
public class GooglePayCheckInfo
|
||||
{
|
||||
public string innerOrderId;
|
||||
public string orderId;
|
||||
public string packageName;
|
||||
public string productId;
|
||||
public string purchaseToken;
|
||||
public int purchaseState;
|
||||
public bool test;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6606e06edba94040a92bdc90b4ee6b1d
|
||||
timeCreated: 1756198314
|
||||
@@ -0,0 +1,80 @@
|
||||
using SGModule.Common.Helper;
|
||||
using SGModule.Net;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace SGModule.GooglePay
|
||||
{
|
||||
public static class GooglePayNet
|
||||
{
|
||||
#region GooglePay
|
||||
/// <summary>
|
||||
/// ios支付创建订单
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="onCompleted"></param>
|
||||
public static void GooglePayCreate<T>(GooglePayData data, UnityAction<ResponseData<T>> onCompleted = null)
|
||||
{
|
||||
NetKit.NetKit.Instance.Post<T>("shop/googlePayCreate", data,
|
||||
response => { onCompleted?.Invoke(response); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ios支付取消订单
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="onCompleted"></param>
|
||||
public static void GooglePayCancel<T>(GooglePayCancelInfo data, UnityAction<ResponseData<T>> onCompleted = null)
|
||||
{
|
||||
NetKit.NetKit.Instance.Post<T>("shop/googlePayCancel", data,
|
||||
response => { onCompleted?.Invoke(response); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ios支付验证订单
|
||||
/// </summary>
|
||||
/// <param name="transactionID">apple支付返回的订单id</param>
|
||||
/// <param name="innerOrderId">内部订单id,创建订单时返回的</param>
|
||||
/// <param name="infoData"></param>
|
||||
/// <param name="key">包名</param>
|
||||
/// <param name="onCompleted">回调</param>
|
||||
public static void GooglePayCheck<T>(GooglePayCheckInfo infoData, string key, UnityAction<ResponseData<T>> onCompleted = null)
|
||||
{
|
||||
NetKit.NetKit.Instance.Post<T>("shop/googlePayCheck", infoData, response => { onCompleted?.Invoke(response); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ios支付订阅
|
||||
/// </summary>
|
||||
/// <param name="requestData">订阅请求数据</param>
|
||||
/// <param name="onCompleted">回调</param>
|
||||
public static void GoogleSubscribeCheck<T>(T requestData, UnityAction<ResponseData<T>> onCompleted = null)
|
||||
{
|
||||
|
||||
Log.Info("[Google pay]",$" GoogleSubscribeCheck 开始...{requestData}");
|
||||
|
||||
NetKit.NetKit.Instance.Post<T>(
|
||||
"shop/googleSubscribe",
|
||||
requestData,
|
||||
response =>
|
||||
{
|
||||
Log.Info("[Google pay]",$"GoogleSubscribeCheck 结束...{response.IsSuccess}");
|
||||
onCompleted?.Invoke(response);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void GoogleSubscribeHistory<T>(T requestData, UnityAction<ResponseData<T>> onCompleted = null)
|
||||
{
|
||||
NetKit.NetKit.Instance.Post<T>(
|
||||
"shop/subscribeHistory",
|
||||
requestData,
|
||||
response =>
|
||||
{
|
||||
onCompleted?.Invoke(response);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5151b63a9adc43ef9ab5e8d014c7140f
|
||||
timeCreated: 1756199078
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54d17c7a3e243184ba8890a66b001267
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
#if UNITY_IAP
|
||||
using Newtonsoft.Json;
|
||||
using SGModule.ConfigLoader;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Purchasing;
|
||||
|
||||
namespace SGModule.GooglePay
|
||||
{
|
||||
[ConfigKey("applePay2")]
|
||||
public class PayProductConfig
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string name;
|
||||
[JsonProperty("sku")]
|
||||
public string sku;
|
||||
[JsonProperty("price")]
|
||||
public float price;
|
||||
[JsonProperty("type")]
|
||||
public string type;
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4b89b14416e451caafb0cc8a3a9c6fb
|
||||
timeCreated: 1756199166
|
||||
Reference in New Issue
Block a user