【问题标题】:IOS In App Purchasing Unavailable ProductIOS In App 购买不可用产品
【发布时间】:2020-05-02 23:20:03
【问题描述】:

我一直试图通过 TestFlight 让 Unity IAP 与 IOS 一起工作一段时间,但无济于事。 这是我的错误,我怀疑可能是这种情况。我向 Apple 提交了我的应用程序,但我有一些选择错误,所以他们拒绝了它。现在我的 IAP 处于“需要开发人员操作”状态。似乎没有什么可以让他们摆脱困境。

App Store Connect IAP Products

这可能是问题所在,但我在几个地方读到 TestFlight 不需要“已批准”的 IAP 产品才能正常工作,但“需要开发人员操作”可能有所不同。我什至不确定 IAP 为何处于这种状态。他们都是完整的。我认为苹果在他们批准您的应用程序之前不会将它们标记为“已批准”?我从“功能”选项卡中单独提交了一些 IAP 产品,它们现在也是“需要开发人员操作”。有人能解释一下吗?

我通过我的 iPhone 调试到 Xcode 运行游戏,每个产品都出现此错误:

(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

Unavailable product com.BlueFlamingoGames.Aeroplane._10_tickets -com.BlueFlamingoGames.Aeroplane._10_tickets
UnityEngine.Purchasing.PurchasingManager:CheckForInitialization()
UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List`1)
UnityEngine.Purchasing.AppleStoreImpl:OnProductsRetrieved(String)
System.Action:Invoke()
UnityEngine.Purchasing.Extension.UnityUtil:Update()

也许这可以通过“需要开发人员操作”来解释,或者这就是为什么我的 IAP 被标记为“需要开发人员操作”的原因?我真的不知道。

我确实设法让这一切都适用于 Google Play。这是完全相同的代码,除了初始化 Play Store 而不是 App Store,以及 Store ID 的不同。所以,我相信代码很好。无论如何,如果有用的话,我会附在下面。这就是我所要做的一切,所以欢迎任何想法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine.Purchasing;
using UnityEngine;
using System;

public class Purchaser : MonoBehaviour, IStoreListener
{

    public static Purchaser Instance;

    private void Awake()
    {
        if (Instance != null)
            Destroy(gameObject);
        else
        {
            Instance = this;
            DontDestroyOnLoad(this.gameObject);
        }
    }

    private static IStoreController m_StoreController;          // The Unity Purchasing system.
    private static IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.

    // Product identifiers for all products capable of being purchased: 
    // "convenience" general identifiers for use with Purchasing, and their store-specific identifier 
    // counterparts for use with and outside of Unity Purchasing. Define store-specific identifiers 
    // also on each platform's publisher dashboard (iTunes Connect, Google Play Developer Console, etc.)

    // General product identifiers for the consumable, non-consumable, and subscription products.
    // Use these handles in the code to reference which product to purchase. Also use these values 
    // when defining the Product Identifiers on the store. Except, for illustration purposes, the 
    // kProductIDSubscription - it has custom Apple and Google identifiers. We declare their store-
    // specific mapping to Unity Purchasing's AddProduct, below.
    public static string _3_tickets = "com.BlueFlamingoGames.Aeroplane._3_tickets";
    public static string _5_tickets = "com.BlueFlamingoGames.Aeroplane._6_tickets";
    public static string _10_tickets = "com.BlueFlamingoGames.Aeroplane._10_tickets";
    public static string remove_ads = "com.BlueFlamingoGames.Aeroplane.removeAds";

    public static string kProductIDNonConsumable = "nonconsumable";
    public static string kProductIDConsumable = "consumable";
    public static string kProductIDSubscription = "subscription";

    // Apple App Store-specific product identifier for the subscription product.
    private static string kProductNameAppleSubscription = "com.unity3d.subscription.new";

    // Google Play Store-specific product identifier subscription product.
    private static string kProductNameGooglePlaySubscription = "com.unity3d.subscription.original";

    void Start()
    {
        // If we haven't set up the Unity Purchasing reference
        if (m_StoreController == null)
        {
            Debug.Log("SETUP");
            // Begin to configure our connection to Purchasing
            InitializePurchasing();
        }
    }

    public void InitializePurchasing()
    {
        // If we have already connected to Purchasing ...
        if (IsInitialized())
        {
            // ... we are done here.
            return;
        }

        // Create a builder, first passing in a suite of Unity provided stores.
        var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
        Debug.Log("Initialize");
        builder.AddProduct(_3_tickets, ProductType.Consumable, new IDs(){
                { _3_tickets, AppleAppStore.Name },
                { _3_tickets, GooglePlay.Name },
            });
        builder.AddProduct(_5_tickets, ProductType.Consumable, new IDs(){
                { _5_tickets, AppleAppStore.Name },
                { _5_tickets, GooglePlay.Name },
            });
        builder.AddProduct(_10_tickets, ProductType.Consumable, new IDs(){
                { _10_tickets, AppleAppStore.Name },
                { _10_tickets, GooglePlay.Name },
            });
        builder.AddProduct(remove_ads, ProductType.NonConsumable, new IDs(){
                { remove_ads, AppleAppStore.Name },
                { remove_ads, GooglePlay.Name },
            });

        foreach (string ID in SaveLoadManager.planeIDs)
        {
            string id = "com.BlueFlamingoGames.Aeroplane." + ID;
            builder.AddProduct(id, ProductType.NonConsumable, new IDs(){
                { id, AppleAppStore.Name },
                { id, GooglePlay.Name },
            });
        }
        foreach (string ID in SaveLoadManager.characterIDs)
        {
            string id = "com.BlueFlamingoGames.Aeroplane." + ID;
            builder.AddProduct(id, ProductType.NonConsumable, new IDs(){
                { id, AppleAppStore.Name },
                { id, GooglePlay.Name },
            });
        }
        //builder.AddProduct(DHC2Beaver, ProductType.Consumable);
        // Add a product to sell / restore by way of its identifier, associating the general identifier
        // with its store-specific identifiers.
        // Continue adding the non-consumable product.
        builder.AddProduct(kProductIDNonConsumable, ProductType.NonConsumable);
        // And finish adding the subscription product. Notice this uses store-specific IDs, illustrating
        // if the Product ID was configured differently between Apple and Google stores. Also note that
        // one uses the general kProductIDSubscription handle inside the game - the store-specific IDs 
        // must only be referenced here. 
        builder.AddProduct(kProductIDSubscription, ProductType.Subscription, new IDs(){
                { kProductNameAppleSubscription, AppleAppStore.Name },
                { kProductNameGooglePlaySubscription, GooglePlay.Name },
            });

        // Kick off the remainder of the set-up with an asynchrounous call, passing the configuration 
        // and this class' instance. Expect a response either in OnInitialized or OnInitializeFailed.
        UnityPurchasing.Initialize(this, builder);
    }


    private bool IsInitialized()
    {
        // Only say we are initialized if both the Purchasing references are set.
        return m_StoreController != null && m_StoreExtensionProvider != null;
    }


    public void BuyConsumable(string productID)
    {
        // Buy the consumable product using its general identifier. Expect a response either 
        // through ProcessPurchase or OnPurchaseFailed asynchronously.
        BuyProductID(productID);
    }


    public void BuyNonConsumable(string productID)
    {
        // Buy the non-consumable product using its general identifier. Expect a response either 
        // through ProcessPurchase or OnPurchaseFailed asynchronously.
        BuyProductID(productID);
    }


    public void BuySubscription()
    {
        // Buy the subscription product using its the general identifier. Expect a response either 
        // through ProcessPurchase or OnPurchaseFailed asynchronously.
        // Notice how we use the general product identifier in spite of this ID being mapped to
        // custom store-specific identifiers above.
        BuyProductID(kProductIDSubscription);
    }


    void BuyProductID(string productId)
    {
        // If Purchasing has been initialized ...
        if (IsInitialized())
        {
            // ... look up the Product reference with the general product identifier and the Purchasing 
            // system's products collection.
            Product product = m_StoreController.products.WithID(productId);

            // If the look up found a product for this device's store and that product is ready to be sold ... 
            if (product != null && product.availableToPurchase)
            {
                Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));
                // ... buy the product. Expect a response either through ProcessPurchase or OnPurchaseFailed 
                // asynchronously.
                m_StoreController.InitiatePurchase(product);
            }
            // Otherwise ...
            else
            {
                // ... report the product look-up failure situation  
                Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
            }
        }
        // Otherwise ...
        else
        {
            // ... report the fact Purchasing has not succeeded initializing yet. Consider waiting longer or 
            // retrying initiailization.
            Debug.Log("BuyProductID FAIL. Not initialized.");
        }
    }


    // Restore purchases previously made by this customer. Some platforms automatically restore purchases, like Google. 
    // Apple currently requires explicit purchase restoration for IAP, conditionally displaying a password prompt.
    public void RestorePurchases()
    {
        // If Purchasing has not yet been set up ...
        if (!IsInitialized())
        {
            // ... report the situation and stop restoring. Consider either waiting longer, or retrying initialization.
            Debug.Log("RestorePurchases FAIL. Not initialized.");
            return;
        }

        // If we are running on an Apple device ... 
        if (Application.platform == RuntimePlatform.IPhonePlayer ||
            Application.platform == RuntimePlatform.OSXPlayer)
        {
            // ... begin restoring purchases
            Debug.Log("RestorePurchases started ...");

            // Fetch the Apple store-specific subsystem.
            var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
            // Begin the asynchronous process of restoring purchases. Expect a confirmation response in 
            // the Action<bool> below, and ProcessPurchase if there are previously purchased products to restore.
            apple.RestoreTransactions((result) => {
                // The first phase of restoration. If no more responses are received on ProcessPurchase then 
                // no purchases are available to be restored.
                Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
            });
        }
        // Otherwise ...
        else
        {
            // We are not running on an Apple device. No work is necessary to restore purchases.
            Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
        }
    }


    //  
    // --- IStoreListener
    //

    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
        // Purchasing has succeeded initializing. Collect our Purchasing references.
        Debug.Log("OnInitialized: PASS");

        // Overall Purchasing system, configured with products for this application.
        m_StoreController = controller;
        // Store specific subsystem, for accessing device-specific store features.
        m_StoreExtensionProvider = extensions;
    }


    public void OnInitializeFailed(InitializationFailureReason error)
    {
        // Purchasing set-up has not succeeded. Check error for reason. Consider sharing this reason with the user.
        Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    }


    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    {
        // A consumable product has been purchased by this user.
        foreach(string ID in SaveLoadManager.planeIDs)
        {
            String app_id = "com.BlueFlamingoGames.Aeroplane." + ID;
            if (String.Equals(args.purchasedProduct.definition.id, app_id, StringComparison.Ordinal))
            {
                Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
                SaveLoadPlayerData.Instance.earn_characterOrPlane(ID);
                return PurchaseProcessingResult.Complete;
            }
        }
        foreach (string ID in SaveLoadManager.characterIDs)
        {
            String app_id = "com.BlueFlamingoGames.Aeroplane." + ID;
            if (String.Equals(args.purchasedProduct.definition.id, app_id, StringComparison.Ordinal))
            {
                Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
                SaveLoadPlayerData.Instance.earn_characterOrPlane(ID);
                return PurchaseProcessingResult.Complete;
            }
        }

        if (String.Equals(args.purchasedProduct.definition.id, _3_tickets, StringComparison.Ordinal))
        {
            Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
            int tickets = SaveLoadPlayerData.Instance.get_tickets();
            SaveLoadPlayerData.Instance.set_tickets(tickets + 3);
            return PurchaseProcessingResult.Complete;
        }

        if (String.Equals(args.purchasedProduct.definition.id, _5_tickets, StringComparison.Ordinal))
        {
            Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
            int tickets = SaveLoadPlayerData.Instance.get_tickets();
            SaveLoadPlayerData.Instance.set_tickets(tickets + 6);
            return PurchaseProcessingResult.Complete;
        }

        if (String.Equals(args.purchasedProduct.definition.id, _10_tickets, StringComparison.Ordinal))
        {
            Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
            int tickets = SaveLoadPlayerData.Instance.get_tickets();
            SaveLoadPlayerData.Instance.set_tickets(tickets + 10);
            return PurchaseProcessingResult.Complete;
        }

        if (String.Equals(args.purchasedProduct.definition.id, remove_ads, StringComparison.Ordinal))
        {
            Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
            SaveLoadPlayerData.Instance.remove_ads();
            return PurchaseProcessingResult.Complete;
        }

【问题讨论】:

    标签: ios unity3d in-app-purchase


    【解决方案1】:

    我遇到了和你一样的问题 当我点击购买时,弹出窗口显示服务不可用 我设备的日志输出和你的完全一样 我的代码可以在googleplay上正常使用 我猜我的问题的原因是因为我在中国,我开发的产品是全球性的 我仍在试图找出原因 希望得到解决方案

    【讨论】:

      【解决方案2】:

      原来我没有填写一些银行标准。确保填写所有必要信息。

      【讨论】:

      • 请使用您问题上的编辑链接添加其他信息。 Post Answer 按钮应仅用于问题的完整答案。 - From Review
      猜你喜欢
      • 2011-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-14
      • 1970-01-01
      • 1970-01-01
      • 2012-09-10
      • 1970-01-01
      相关资源
      最近更新 更多