You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity无代码IAP购买数据恢复及历史保存问题咨询

关于In-App Purchase购买恢复的问题解答

嘿,我来帮你理清楚这个IAP恢复的问题,先直接给你明确答案,再一步步说怎么实现你要的功能:

先回答你的核心疑问

  • 消耗品(金币类):默认情况下,苹果和谷歌的IAP系统都不会自动恢复消耗品的购买记录——因为这类商品设计就是"买一次用一次",系统不会帮你留存这类消费记录。所以玩家删了应用或者换设备重装后,之前买的100金币这类消耗品,现在的代码逻辑下是找不回来的。
  • 非消耗品(去广告):非消耗品是支持通过官方IAP接口恢复的,但你当前的代码完全没处理恢复逻辑,所以现在重装后也不会自动恢复,得手动加恢复流程才行。

实现购买历史恢复的具体方案

1. 非消耗品(去广告)的恢复实现

你需要在游戏启动时(比如进入主界面的时机)添加恢复购买的逻辑,调用Unity IAP的RestoreTransactions方法,然后在购买回调里处理去广告的状态。

给你一段示例代码,你可以整合到你的IAP管理类里:

using UnityEngine.Purchasing;

public class IAPManager : IStoreListener
{
    private IStoreController m_StoreController;
    private IExtensionProvider m_ExtensionProvider;

    // 初始化IAP的代码(你应该已经有类似的了)
    public void InitializePurchasing()
    {
        // ...你的初始化逻辑,获取m_StoreController和m_ExtensionProvider
    }

    // 触发恢复购买的方法,可以在游戏启动时调用,或者加个"恢复购买"按钮让玩家手动点
    public void RestorePurchases()
    {
        if (!m_StoreController.IsInitialized())
        {
            Debug.LogError("IAP还没初始化,没法恢复购买");
            return;
        }

        // 苹果平台要求必须有明确的恢复入口,谷歌可以自动处理但最好也支持手动恢复
        if (Application.platform == RuntimePlatform.IPhonePlayer || 
            Application.platform == RuntimePlatform.OSXPlayer)
        {
            Debug.Log("开始恢复已购项目...");
            m_StoreController.RestoreTransactions((success) => {
                if (success)
                {
                    Debug.Log("恢复成功,已购项目会自动同步");
                }
                else
                {
                    Debug.Log("恢复失败,可能没有已购项目或者网络有问题");
                }
            });
        }
        else
        {
            Debug.Log("安卓平台自动检查已购非消耗品");
            CheckExistingNonConsumables();
        }
    }

    // 处理购买/恢复购买的核心回调
    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    {
        string productId = args.purchasedProduct.definition.id;
        string transactionId = args.purchasedProduct.transactionID;

        switch (productId)
        {
            case "removeads":
                // 把去广告状态存在本地,启动时就检查这个值
                PlayerPrefs.SetInt("HasRemovedAds", 1);
                PlayerPrefs.Save();
                // 这里调用你的去广告逻辑,比如隐藏广告组件
                ApplyRemoveAds();
                break;
            
            // 处理金币类消耗品
            case "Gold.50":
            case "Gold.100":
            case "Gold.250":
            case "Gold.500":
            case "Gold.1000":
                // 关键:用订单ID标记是否已经处理过这个购买,避免重复加金币
                if (!PlayerPrefs.HasKey("ProcessedTxn_" + transactionId))
                {
                    int addGold = int.Parse(productId.Split('.')[1]);
                    int currentGold = PlayerPrefs.GetInt("Gold", 0);
                    PlayerPrefs.SetInt("Gold", currentGold + addGold);
                    GoldText.text = PlayerPrefs.GetInt("Gold").ToString();
                    // 标记这个订单已经处理过
                    PlayerPrefs.SetInt("ProcessedTxn_" + transactionId, 1);
                    PlayerPrefs.Save();
                }
                break;
            
            default:
                Debug.LogWarning("未知的商品ID:" + productId);
                break;
        }
        return PurchaseProcessingResult.Complete;
    }

    // 安卓平台主动检查已购非消耗品
    private void CheckExistingNonConsumables()
    {
        foreach (var product in m_StoreController.products.all)
        {
            if (product.definition.type == ProductType.NonConsumable && product.hasReceipt)
            {
                if (product.definition.id == "removeads")
                {
                    PlayerPrefs.SetInt("HasRemovedAds", 1);
                    PlayerPrefs.Save();
                    ApplyRemoveAds();
                }
            }
        }
    }

    // 封装去广告的逻辑,方便调用
    private void ApplyRemoveAds()
    {
        // 这里写你的实际去广告代码,比如:
        // FindObjectOfType<AdBanner>().gameObject.SetActive(false);
        Debug.Log("已应用去广告");
    }

    // 其他IAP接口实现(比如OnInitializeFailed、OnPurchaseFailed)
    public void OnInitializeFailed(InitializationFailureReason error) {}
    public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) {}
}

2. 消耗品(金币)的跨设备恢复方案

刚才说了,官方IAP不支持消耗品的自动恢复,所以要让玩家换设备/重装后能找回金币,必须用云端存储:

  • 可以用Unity官方的Cloud Save,或者Firebase Realtime Database、Firestore这类第三方服务。
  • 每次玩家购买消耗品后,把购买记录(订单ID、购买的金币数量、玩家唯一标识)上传到云端。
  • 游戏启动时,从云端拉取玩家的所有未重复处理的购买记录,同步金币数量。

如果暂时不想搞云端,用PlayerPrefs存订单ID只能解决同一设备上的重复购买问题,换设备还是会丢失,所以这只是临时方案,长期来看一定要上云端。

3. 对你当前脚本的优化提醒

你现在的代码直接在购买成功后就加金币,没有处理订单重复的问题,也没有恢复逻辑,所以:

  • 把所有购买处理逻辑移到ProcessPurchase回调里,这样恢复购买时也能触发相同的逻辑。
  • 必须用订单ID来标记每个消耗品购买是否已经处理,不然恢复时会重复加金币。
  • 非消耗品一定要把状态存在本地(比如PlayerPrefs),游戏启动时先检查这个状态,直接应用去广告。

最后总结一下

  • 非消耗品:通过调用RestoreTransactions就能恢复,只要在代码里监听回调并设置状态就行。
  • 消耗品:官方不自动恢复,必须自己做云端存储购买记录,才能跨设备/重装后找回。
  • 不管是哪种商品,都要处理订单ID的重复校验,避免玩家重复获得福利。

内容的提问来源于stack exchange,提问作者duhaonur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:28:00