Я уже некоторое время пытаюсь заставить Unity IAP работать с IOS через TestFlight, но безрезультатно. Вот мои ошибки и что я подозреваю, может быть дело. Я отправил свое приложение в Apple, и у меня был неправильный выбор, поэтому они отклонили его. Теперь мои IAP находятся в состоянии «Требуется действие разработчика». Кажется, я ничего не могу сделать, чтобы вытащить их из этого.
App Store Connect IAP Products
Это может быть проблемой, но в нескольких местах я прочитал, что TestFlight не нужны "одобренные" продукты IAP для чтобы они работали, но, возможно, «Требуется действие разработчика» другое. Я даже не уверен, почему IAPs находятся в этом состоянии. Они все завершены. Я думаю, что Apple не пометит их как «Одобренные», пока они не одобрят и ваше приложение? Я представил некоторые продукты 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. Итак, я считаю, что код в порядке. В любом случае, я приложу это ниже, если это могло бы быть полезным. Это все, что у меня есть go, поэтому любые идеи приветствуются.
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;
}