Files
BallCrushBest_GP/Assets/Scripts/IAPPayManager.cs
T

590 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// IAPPayManager.cs
// IAP 接入使用(MonoBehaviour
//
// 将此脚本挂到场景中的任意 GameObject 即可运行示例。
// 请将商品 ID 替换为你在 App Store Connect / Google Play Console 中配置的真实 ID。
// ============================================================
using System;
using System.Collections;
using System.Collections.Generic;
using SDK_IAP;
using UnityEngine;
using UnityEngine.Purchasing;
namespace BallKingdomCrush
{
public class IAPPayManager : MonoBehaviour
{
// ──────────────────────────────────────────────────────────
// 单例实例
// ──────────────────────────────────────────────────────────
private static IAPPayManager _instance;
public static IAPPayManager Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<IAPPayManager>();
}
return _instance;
}
}
// ──────────────────────────────────────────────────────────
// 商品 ID 常量(替换为真实 ID)
// ──────────────────────────────────────────────────────────
public static string PRODUCT_FIRST_GIFT = "comwackyllamagamegift199"; //首充 消耗品
public static string PRODUCT_REMOVE_ADS = "comwackyllamagameremove299"; //移除广告 消耗品
public static string PRODUCT_PASS_BONUS = "comwackyllamagamepass999"; //通行证礼包 消耗品
public static string PRODUCT_SPACE_BONUS = "comwackyllamagamespace2499"; //加一格礼包 非消耗品
public static string PRODUCT_SHOP_1 = "comwackyllamagameshop199"; //商店档位1 消耗品
public static string PRODUCT_SHOP_2 = "comwackyllamagameshop399"; //商店档位2 消耗品
public static string PRODUCT_SHOP_3= "comwackyllamagameshop999"; //商店档位3 消耗品
public static string PRODUCT_SHOP_4 = "comwackyllamagameshop1999"; //商店档位4 消耗品
public static string PRODUCT_SHOP_5 = "comwackyllamagameshop3999"; //商店档位5 消耗品
public static string PRODUCT_THREE_DAY = "comwackyllamagamethreeday399"; //三天礼包 消耗品
public static string PRODUCT_VIP_WEEK = "comwackyllamagamesub999"; // 周订阅 Subscription
public static string PRODUCT_VIP_MONTH = "comwackyllamagamesub2999"; // 月订阅 Subscription
public static string PRODUCT_VIP_YEAR = "comwackyllamagamesub9999"; // 年订阅 Subscription
// ──────────────────────────────────────────────────────────
// 生命周期
// ──────────────────────────────────────────────────────────
private void Awake()
{
// 确保单例唯一性
if (_instance != null && _instance != this)
{
Debug.LogWarning("[IAP Google] 检测到多个 IAPGoogleManager 实例,销毁重复对象");
Destroy(gameObject);
return;
}
_instance = this;
// 订阅发货事件(持续监听,全局处理发货逻辑)
IAPManager.OnDeliver += HandleDeliver;
IAPManager.OnInitialized += HandleInitialized;
}
private void Start()
{
InitIAP();
//StartCoroutine(Test());
}
//private IEnumerator Test()
//{
// int index = 0;
// while (true)
// {
// yield return new WaitForSeconds(1f);
// Analytics.OnTrackEvent?.Invoke($"test{index}", new Dictionary<string, string>());
// index++;
// }
//}
private void OnDestroy()
{
IAPManager.OnDeliver -= HandleDeliver;
IAPManager.OnInitialized -= HandleInitialized;
IAPManager.Dispose();
}
// ──────────────────────────────────────────────────────────
// 初始化
// ──────────────────────────────────────────────────────────
private void InitIAP()
{
var products = new List<ProductDefine>
{
// 消耗品
ProductDefine.Simple(PRODUCT_FIRST_GIFT, ProductType.Consumable),
ProductDefine.Simple(PRODUCT_REMOVE_ADS, ProductType.Consumable),
ProductDefine.Simple(PRODUCT_PASS_BONUS, ProductType.Consumable),
ProductDefine.Simple(PRODUCT_SHOP_1, ProductType.Consumable),
ProductDefine.Simple(PRODUCT_SHOP_2, ProductType.Consumable),
ProductDefine.Simple(PRODUCT_SHOP_3, ProductType.Consumable),
ProductDefine.Simple(PRODUCT_SHOP_4, ProductType.Consumable),
ProductDefine.Simple(PRODUCT_SHOP_5, ProductType.Consumable),
ProductDefine.Simple(PRODUCT_THREE_DAY, ProductType.Consumable),
// 非消耗品
ProductDefine.Simple(PRODUCT_SPACE_BONUS, ProductType.NonConsumable),
// 订阅
ProductDefine.Simple(PRODUCT_VIP_WEEK, ProductType.Subscription),
ProductDefine.Simple(PRODUCT_VIP_MONTH, ProductType.Subscription),
ProductDefine.Simple(PRODUCT_VIP_YEAR, ProductType.Subscription),
};
Debug.Log("[IAP Google] 开始初始化...");
IAPManager.Init(products, OnInitCallback);
}
// Init 完成回调(与 OnInitialized 事件等价,二选一即可)
private void OnInitCallback(bool success)
{
if (success)
{
Debug.Log("[IAP Google] 初始化成功!");
ShowProductPrices();
}
else
{
Debug.LogError("[IAP Google] 初始化失败,请检查网络或 App Store / Google Play 配置。");
}
}
// 也可以通过静态事件监听(适合多个 Manager 分散处理)
private void HandleInitialized(bool success)
{
// 此处可做更新 UI 等操作
}
// ──────────────────────────────────────────────────────────
// 显示商品价格(初始化成功后调用)
// ──────────────────────────────────────────────────────────
public void ShowProductPrices()
{
var products = IAPManager.GetProducts();
foreach (var p in products)
{
Debug.Log($"[IAP Google] 商品: {p.definition.id} | 价格: {p.metadata.localizedPriceString}");
}
}
// ──────────────────────────────────────────────────────────
// 购买
// ──────────────────────────────────────────────────────────
/// <summary>通用购买方法 - 适用于消耗品和非消耗品</summary>
/// <param name="productId">商品ID</param>
/// <param name="productName">商品名称(用于日志)</param>
/// <param name="onSuccess">购买成功后的回调</param>
public void BuyProduct(string productId, string productName, System.Action onSuccess = null)
{
IAPManager.Buy(productId, result =>
{
if (result.success)
{
Debug.Log($"[IAP Google] 购买成功: {productName} ({result.productId}) | tid={result.transactionId}");
onSuccess?.Invoke();
}
else
{
Debug.LogWarning($"[IAP Google] 购买失败: {productName} ({result.productId}) - {result.error}");
PurchasingManager.SendEventClickByName(productId, "open");
}
});
}
/// <summary>通用订阅方法 - 适用于订阅类型商品</summary>
/// <param name="productId">商品ID</param>
/// <param name="subscriptionName">订阅名称(用于日志)</param>
/// <param name="onSuccess">订阅成功后的回调</param>
public void SubscribeProduct(string productId, string subscriptionName, System.Action onSuccess = null)
{
IAPManager.Buy(productId, result =>
{
if (result.success)
{
Debug.Log($"[IAP Google] 订阅成功: {subscriptionName} ({result.productId}) | tid={result.transactionId}");
// ShowSubscriptionInfo();
onSuccess?.Invoke();
}
else
{
Debug.LogWarning($"[IAP Google] 订阅失败: {subscriptionName} ({result.productId}) - {result.error}");
PurchasingManager.SendEventClickByName(productId, "open");
}
});
}
// ──────────────────────────────────────────────────────────
// 恢复购买(iOS 界面上必须提供此按钮)
// ──────────────────────────────────────────────────────────
public void OnRestoreButtonClicked()
{
// UI 控制:仅在需要时显示此按钮
if (!IAPManager.NeedShowRestoreButton()) return;
IAPManager.Restore(result =>
{
if (result.success)
Debug.Log("[IAP Google] 恢复购买流程完成(实际恢复内容通过 OnDeliver 发放)");
else
Debug.LogWarning($"[IAP Google] 恢复购买失败: {result.error}");
});
}
// ──────────────────────────────────────────────────────────
// 权益检查
// ──────────────────────────────────────────────────────────
/// <summary>通用权益检查方法 - 适用于非消耗品和订阅</summary>
/// <param name="productId">商品ID</param>
/// <param name="productName">商品名称(用于日志)</param>
/// <param name="onResult">检查结果回调</param>
public void CheckEntitlement(string productId, string productName, System.Action<EntitlementStatus> onResult = null)
{
IAPManager.CheckEntitlement(productId, status =>
{
Debug.Log($"[IAP Google] {productName} 权益状态: {status}");
onResult?.Invoke(status);
});
}
/// <summary>检查加一格礼包权益</summary>
public void CheckSpaceBonusEntitlement()
{
CheckEntitlement(PRODUCT_SPACE_BONUS, "加一格礼包", state =>
{
if (state == EntitlementStatus.FullyEntitled)
{
// 激活加一格权限
}
});
}
/// <summary>检查VIP订阅权益</summary>
public void CheckVipEntitlement()
{
CheckEntitlement(PRODUCT_VIP_MONTH, "VIP月卡", status =>
{
if (status == EntitlementStatus.FullyEntitled)
{
// 激活VIP权限
}
});
}
// ──────────────────────────────────────────────────────────
// 订阅信息查询
// ──────────────────────────────────────────────────────────
/// <summary>显示指定商品的订阅信息</summary>
/// <param name="productId">商品ID</param>
public void ShowSubscriptionInfo(string productId)
{
var info = IAPManager.GetSubscriptionInfo(productId);
Debug.Log($"[IAP Google] VIP 订阅状态 ({productId}):");
Debug.Log($" isSubscribed = {info.isSubscribed}");
Debug.Log($" isExpired = {info.isExpired}");
Debug.Log($" expireDate = {info.expireDate}");
Debug.Log($" isAutoRenewing= {info.isAutoRenewing}");
if (info.isSubscribed && !info.isExpired)
{
int vipLevel = GetVipLevelByProductId(productId);
if (vipLevel > 0)
{
DataMgr.VipLevel.Value = vipLevel;
Debug.Log($"[IAP Google] 设置 VIP 等级: {vipLevel}");
}
if (info.expireDate.Year > 1970 && info.expireDate.Year < 10000)
{
var expireTimestamp = ((DateTimeOffset)info.expireDate).ToUnixTimeSeconds();
Debug.Log($"Expire timestamp: {expireTimestamp}");
DataMgr.VipExpirationTime.Value = Math.Max(DataMgr.VipExpirationTime.Value, expireTimestamp);
}
else
{
Debug.LogWarning($"[IAP Google] 无效的到期时间: {info.expireDate}");
}
}
else
{
Debug.Log($"[IAP Google] 用户未订阅或订阅已过期");
}
}
/// <summary>根据商品ID获取VIP等级</summary>
/// <param name="productId">商品ID</param>
/// <returns>VIP等级:周订阅=1,月订阅=2,年订阅=3,其他=0</returns>
private int GetVipLevelByProductId(string productId)
{
if (productId == PRODUCT_VIP_WEEK)
return 1;
else if (productId == PRODUCT_VIP_MONTH)
return 2;
else if (productId == PRODUCT_VIP_YEAR)
return 3;
return 0;
}
// ──────────────────────────────────────────────────────────
// 全局发货处理(OnDeliver 事件接收)
// ──────────────────────────────────────────────────────────
/// <summary>
/// 统一发货处理入口。
/// 无论是新购买、补单、还是恢复购买,都会触发此方法。
/// ⚠️ 幂等保护已由 DeliverGuardLite 处理,此处无需再判重。
/// </summary>
private void HandleDeliver(string productId)
{
if (!IsValidProduct(productId))
{
Debug.LogWarning($"[IAP Google] 非法商品ID,拒绝发货: {productId}");
return;
}
if (productId == PRODUCT_VIP_WEEK || productId == PRODUCT_VIP_MONTH || productId == PRODUCT_VIP_YEAR)
{
// 订阅商品:需要先获取有效的订阅信息,成功后才会分发支付成功消息
StartCoroutine(DelayedGetSubscriptionInfo(productId));
}
else
{
UICtrlDispatcher.Instance.Dispatch(UICtrlMsg.PayloadingUI_Close);
// 非订阅商品(消耗品):直接发货
Debug.Log($"[IAP Google] 发货通知: {productId}");
GameDispatcher.Instance.Dispatch(GameMsg.IAP_PAY_SUCCESS, productId);
PurchasingManager.SendEventClickByName(productId, "open");
PurchasingManager.SendEventClickByName(productId, "success");
}
}
private IEnumerator DelayedGetSubscriptionInfo(string productId)
{
Debug.Log($"[IAP Google] 开始获取订阅信息: {productId}");
// 先尝试立即获取一次
var immediateInfo = IAPManager.GetSubscriptionInfo(productId);
// 检查是否是无效数据
bool isInvalid = immediateInfo.expireDate == default(DateTime) ||
(immediateInfo.expireDate.Year == 1 && !immediateInfo.isSubscribed);
if (!isInvalid && immediateInfo.isSubscribed)
{
// 立即获取到了有效数据,直接处理
bool success = ProcessSubscriptionInfo(immediateInfo, productId);
if (success)
{
DispatchPaySuccess(productId);
}
yield break;
}
// 无效数据,开始重试
Debug.Log($"[IAP Google] 订阅信息未就绪,开始重试...");
int maxRetries = 5;
float waitTime = 1.5f;
for (int i = 0; i < maxRetries; i++)
{
yield return new WaitForSeconds(waitTime); // 每次等待0.5秒
var info = IAPManager.GetSubscriptionInfo(productId);
if (info.isSubscribed && !info.isExpired && info.expireDate.Year > 1970)
{
Debug.Log($"[IAP Google] 订阅信息获取成功 (重试 {i + 1} 次)");
bool success = ProcessSubscriptionInfo(info, productId);
if (success)
{
DispatchPaySuccess(productId);
}
yield break;
}
Debug.Log($"[IAP Google] 第 {i + 1} 次重试: isSubscribed={info.isSubscribed}, isExpired={info.isExpired}, expireDate={info.expireDate}");
}
// 所有重试都失败了,使用降级方案
Debug.LogError($"[IAP Google] 无法获取订阅信息,使用降级方案");
bool fallbackSuccess = ProcessFallbackSubscription(productId);
if (fallbackSuccess)
{
DispatchPaySuccess(productId);
}
else
{
// 降级也失败了,上报错误,不分发支付成功
Debug.LogError($"[IAP Google] 降级方案也失败,支付成功消息将不分发,请检查配置");
PurchasingManager.SendEventClickByName(productId, "open");
// 可选:向用户显示错误提示
}
}
/// <summary>
/// 处理订阅信息,设置VIP等级和过期时间
/// </summary>
/// <returns>是否设置成功</returns>
private bool ProcessSubscriptionInfo(SubscriptionInfoLite info, string productId)
{
try
{
Debug.Log($"[IAP Google] VIP 订阅状态 ({productId}):");
Debug.Log($" isSubscribed = {info.isSubscribed}");
Debug.Log($" isExpired = {info.isExpired}");
Debug.Log($" expireDate = {info.expireDate}");
Debug.Log($" isAutoRenewing= {info.isAutoRenewing}");
// 获取VIP等级
int vipLevel = GetVipLevelByProductId(productId);
if (vipLevel <= 0)
{
Debug.LogError($"[IAP Google] 无法获取VIP等级,商品ID: {productId}");
return false;
}
// 获取过期时间戳
long expireTimestamp = 0;
if (info.expireDate.Year > 1970 && info.expireDate.Year < 10000)
{
expireTimestamp = ((DateTimeOffset)info.expireDate).ToUnixTimeSeconds();
}
else
{
Debug.LogWarning($"[IAP Google] 无效的到期时间: {info.expireDate},使用预估时间");
expireTimestamp = GetEstimatedExpireTimestamp(productId);
}
if (expireTimestamp <= 0)
{
Debug.LogError($"[IAP Google] 无法获取有效的过期时间戳");
return false;
}
// 保存数据(使用前后对比,确保设置成功)
int oldVipLevel = DataMgr.VipLevel.Value;
long oldExpireTime = DataMgr.VipExpirationTime.Value;
DataMgr.VipLevel.Value = Math.Max(DataMgr.VipLevel.Value, vipLevel);
DataMgr.VipExpirationTime.Value = Math.Max(DataMgr.VipExpirationTime.Value, expireTimestamp);
// 验证设置是否成功
bool vipLevelSuccess = DataMgr.VipLevel.Value >= vipLevel;
bool expireTimeSuccess = DataMgr.VipExpirationTime.Value >= expireTimestamp;
if (vipLevelSuccess && expireTimeSuccess)
{
Debug.Log($"[IAP Google] VIP设置成功 - 等级: {vipLevel} (原:{oldVipLevel}), 过期时间: {expireTimestamp} (原:{oldExpireTime})");
// 可选:触发VIP状态更新事件
return true;
}
else
{
Debug.LogError($"[IAP Google] VIP设置失败 - 等级设置: {vipLevelSuccess}, 过期时间设置: {expireTimeSuccess}");
return false;
}
}
catch (Exception e)
{
Debug.LogError($"[IAP Google] 处理订阅信息时发生异常: {e.Message}\n{e.StackTrace}");
return false;
}
}
/// <summary>
/// 降级方案:当无法从商店获取订阅信息时,根据商品类型估算VIP信息
/// </summary>
private bool ProcessFallbackSubscription(string productId)
{
try
{
Debug.LogWarning($"[IAP Google] 执行降级方案: {productId}");
int vipLevel = GetVipLevelByProductId(productId);
if (vipLevel <= 0)
{
Debug.LogError($"[IAP Google] 降级方案失败 - 无法获取VIP等级");
return false;
}
long expireTimestamp = GetEstimatedExpireTimestamp(productId);
if (expireTimestamp <= 0)
{
Debug.LogError($"[IAP Google] 降级方案失败 - 无法获取预估过期时间");
return false;
}
// 设置VIP信息
DataMgr.VipLevel.Value = Math.Max(DataMgr.VipLevel.Value, vipLevel);
DataMgr.VipExpirationTime.Value = Math.Max(DataMgr.VipExpirationTime.Value, expireTimestamp);
Debug.Log($"[IAP Google] 降级方案成功 - 等级: {vipLevel}, 过期时间: {expireTimestamp}");
return true;
}
catch (Exception e)
{
Debug.LogError($"[IAP Google] 降级方案异常: {e.Message}");
return false;
}
}
/// <summary>
/// 获取预估的过期时间戳
/// </summary>
private long GetEstimatedExpireTimestamp(string productId)
{
TimeSpan duration;
if (productId == PRODUCT_VIP_WEEK)
{
duration = TimeSpan.FromDays(7);
}
else if (productId == PRODUCT_VIP_MONTH)
{
duration = TimeSpan.FromDays(30);
}
else if (productId == PRODUCT_VIP_YEAR)
{
duration = TimeSpan.FromDays(365);
}
else
{
duration = TimeSpan.FromDays(30);
}
var expireDate = DateTime.UtcNow.Add(duration);
return ((DateTimeOffset)expireDate).ToUnixTimeSeconds();
}
/// <summary>
/// 分发支付成功消息(统一出口)
/// </summary>
private void DispatchPaySuccess(string productId)
{
UICtrlDispatcher.Instance.Dispatch(UICtrlMsg.PayloadingUI_Close);
Debug.Log($"[IAP Google] 支付成功并完成发货: {productId}");
GameDispatcher.Instance.Dispatch(GameMsg.IAP_PAY_SUCCESS, productId);
PurchasingManager.SendEventClickByName(productId, "open");
PurchasingManager.SendEventClickByName(productId, "success");
}
/// <summary>验证商品ID是否为已定义的有效商品</summary>
/// <param name="productId">商品ID</param>
/// <returns>是否为有效商品</returns>
private bool IsValidProduct(string productId)
{
return productId == PRODUCT_FIRST_GIFT ||
productId == PRODUCT_REMOVE_ADS ||
productId == PRODUCT_PASS_BONUS ||
productId == PRODUCT_SPACE_BONUS ||
productId == PRODUCT_SHOP_1 ||
productId == PRODUCT_SHOP_2 ||
productId == PRODUCT_SHOP_3 ||
productId == PRODUCT_SHOP_4 ||
productId == PRODUCT_SHOP_5 ||
productId == PRODUCT_THREE_DAY ||
productId == PRODUCT_VIP_WEEK ||
productId == PRODUCT_VIP_MONTH ||
productId == PRODUCT_VIP_YEAR;
}
}
}