Thanks, I got it working. I am going to post the code I used here in case it helps someone because there were a number of things to work out beyond those few lines.
Firstly, I made a ProductInfo class to store the JSON info in. I included a SubscriptionPeriods dictionary to translate the subscriptionPeriodUnit to a readable string. I tried to use SubscriptionPeriodUnit but it didn’t correspond to the values I was getting (2 = month), is that a bug? It seems like month and week are swapped.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class ProductInfo
{
public string subscriptionNumberOfUnits;
public string subscriptionPeriodUnit = "";
public string localizedPrice;
public string isoCurrencyCode;
public string localizedPriceString;
public string localizedTitle;
public string localizedDescription;
public string introductoryPriceLocale;
public string introductoryPriceNumberOfPeriods;
public string numberOfUnits;
public string unit;
// I have only tested 2 = month, I have not tested other values in this list
public Dictionary<string, string> SubscriptionPeriods = new Dictionary<string, string>()
{
{"0", "day" },
{"1", "week" },
{"2", "month" },
{"3", "year" },
{"4", "not available" }
};
public static ProductInfo CreateFromJSON(string jsonString)
{
return JsonUtility.FromJson<ProductInfo>(jsonString);
}
public string GetSubscriptionPeriod()
{
if(this.subscriptionPeriodUnit == "") { return ""; };
return SubscriptionPeriods[this.subscriptionPeriodUnit];
}
//IAP Product details productJSON {"subscriptionNumberOfUnits":"2","subscriptionPeriodUnit":"2","localizedPrice":"0.99","isoCurrencyCode":"USD","localizedPriceString":"$0.99","localizedTitle":"IAP Title","localizedDescription":"Enjoy the subscription!","introductoryPrice":"","introductoryPriceLocale":"","introductoryPriceNumberOfPeriods":"","numberOfUnits":"","unit":""}
}
Secondly, using the Version 2 example in this thread , I used this code, see OnInitialized for the most relevant bit, this is still a work in progress, but I am 99% sure I am successfully retrieving product info before purchasing:
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Security;
// Deriving the Purchaser class from IStoreListener enables it to receive messages from Unity Purchasing.
public class IAPManager : Singleton<IAPManager>, IStoreListener
{
private static IStoreController m_StoreController; // The Unity Purchasing system.
private static IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.
private IAppleExtensions m_AppleExtensions;
private IGooglePlayStoreExtensions m_GoogleExtensions;
// PRODUCT IDs
public string OWN_FOREVER = "NON_CONSUMABLE_ID";
public string SUBSCRIPTION = "SUBSCRIPTION_ID";
private WithTrialEdition DataManager;
public Dictionary<string, ProductInfo> ProductsInStore = new Dictionary<string, ProductInfo>();
void Start()
{
// Set our data manager
DataManager = GameObject.Find("WithTrialEdition").GetComponent<WithTrialEdition>();
if (DataManager.HasPurchasedApp())
{
// if before we even start the data manager sees that planetscene.OwnsApp is true then this may not be the Trial version.
return;
}
if (m_StoreController == null)
{
InitializePurchasing();
}
}
public void InitializePurchasing()
{
if (IsInitialized())
{
return;
}
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
builder.AddProduct(OWN_FOREVER, ProductType.NonConsumable);
builder.AddProduct(SUBSCRIPTION, ProductType.Subscription);
UnityPurchasing.Initialize(this, builder);
}
public bool IsInitialized()
{
return m_StoreController != null && m_StoreExtensionProvider != null;
}
public void BuyForever()
{
Debug.Log("IAP Buy Forever");
BuyProductID(OWN_FOREVER);
}
public void BuySubscription()
{
Debug.Log("IAP Buy Subscription");
BuyProductID(SUBSCRIPTION);
}
public string GetProductPriceFromStore(string id)
{
if (m_StoreController != null && m_StoreController.products != null)
return m_StoreController.products.WithID(id).metadata.localizedPriceString;
else
return "";
}
public string GetProductSubscriptionDurationUnit(string id)
{
if (ProductsInStore.ContainsKey(id))
{
return ProductsInStore[id].GetSubscriptionPeriod();
}
return "";
}
public int GetProductSubscriptionDurationAmt(string id)
{
if (ProductsInStore.ContainsKey(id))
{
int result;
if (int.TryParse(ProductsInStore[id].subscriptionNumberOfUnits, out result))
{
return result;
}
else
{
return -1;
}
}
return -1;
}
void BuyProductID(string productId)
{
if (IsInitialized())
{
Product product = m_StoreController.products.WithID(productId);
if (product != null && product.availableToPurchase)
{
Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));
m_StoreController.InitiatePurchase(product);
}
else
{
Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
}
}
else
{
Debug.Log("BuyProductID FAIL. Not initialized.");
}
}
public void RestorePurchases()
{
m_StoreExtensionProvider.GetExtension<IAppleExtensions>().RestoreTransactions(result => {
if (result)
{
Debug.Log("Restore purchases succeeded.");
}
else
{
Debug.Log("Restore purchases failed.");
}
});
}
//
// --- IStoreListener
//
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
// Purchasing has succeeded initializing. Collect our Purchasing references.
Debug.Log("IAP 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;
m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();
m_GoogleExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
m_GoogleExtensions?.SetDeferredPurchaseListener(OnPurchaseDeferred);
Dictionary<string, string> dict = m_AppleExtensions.GetIntroductoryPriceDictionary();
Dictionary<string, string> product_details = m_AppleExtensions.GetProductDetails();
bool isSubscribed = false;
bool hasPurchasedForever = false;
foreach (UnityEngine.Purchasing.Product item in controller.products.all)
{
if (product_details != null && product_details.ContainsKey(item.definition.storeSpecificId))
{
var productJSON = product_details[item.definition.storeSpecificId];
ProductInfo productInfo = ProductInfo.CreateFromJSON(productJSON);
Debug.Log("IAP Product details productJSON" + productJSON);
ProductsInStore.Add(item.definition.storeSpecificId, productInfo);
Debug.Log("IAP Product details productInfo.subscriptionPeriodUnit" + productInfo.subscriptionPeriodUnit);
Debug.Log("IAP Product details productInfo.subscriptionNumberOfUnits" + productInfo.subscriptionNumberOfUnits);
Debug.Log("IAP Product details productInfo.GetSubscriptionPeriod()" + productInfo.GetSubscriptionPeriod());
}
if (item.receipt != null)
{
// ITEMS WE HAVE A RECEIPT FOR
string intro_json = (dict == null || !dict.ContainsKey(item.definition.storeSpecificId)) ? null : dict[item.definition.storeSpecificId];
if (item.definition.type == ProductType.Subscription)
{
SubscriptionManager p = new SubscriptionManager(item, intro_json);
SubscriptionInfo info = p.getSubscriptionInfo();
Debug.Log("IAP SubInfo: " + info.getProductId().ToString());
Debug.Log("IAP isSubscribed: " + info.isSubscribed().ToString());
Debug.Log("IAP isFreeTrial: " + info.isFreeTrial().ToString());
Debug.Log("IAP info.getSubscriptionPeriod" + info.getSubscriptionPeriod());
if(info.isSubscribed() == Result.True)
{
isSubscribed = true;
}
} else if (item.definition.type == ProductType.NonConsumable)
{
if(String.Equals(item.definition.id, OWN_FOREVER, StringComparison.Ordinal))
{
hasPurchasedForever = true;
}
}
}
}
Debug.Log("IAP isSubscribed: " + isSubscribed);
Debug.Log("IAP hasPurchasedForever: " + hasPurchasedForever);
}
public void OnPurchaseDeferred(Product product)
{
Debug.Log("Deferred product " + product.definition.id.ToString());
}
public void OnInitializeFailed(InitializationFailureReason error)
{
// Purchasing set-up has not succeeded. Check error for reason. Consider sharing this reason with the user.
Debug.Log("IAP OnInitializeFailed InitializationFailureReason:" + error);
}
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
Debug.Log(string.Format("IAP ProcessPurchase"));
try
{
var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
var result = validator.Validate(args.purchasedProduct.receipt);
Debug.Log("IAP Validate = " + result.ToString());
foreach (IPurchaseReceipt productReceipt in result)
{
Debug.Log("IAP Valid receipt for " + productReceipt.productID.ToString());
if (String.Equals(productReceipt.productID.ToString(), OWN_FOREVER, StringComparison.Ordinal))
{
Debug.Log(string.Format("IAP ProcessPurchase: PASS. Product: '{0}'", productReceipt.productID.ToString()));
DataManager.OnPurchasedForever();
}
else if (String.Equals(productReceipt.productID.ToString(), SUBSCRIPTION, StringComparison.Ordinal))
{
Debug.Log(string.Format("IAP ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
DataManager.OnPurchasedSubscription();
}
}
}
catch (Exception e)
{
Debug.Log("IAP Error is " + e.Message.ToString());
}
Debug.Log(string.Format("IAP ProcessPurchase: " + args.purchasedProduct.definition.id));
return PurchaseProcessingResult.Complete;
}
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
// A product purchase attempt did not succeed. Check failureReason for more detail. Consider sharing
// this reason with the user to guide their troubleshooting actions.
Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
}
}
I hope this helps someone, Unity IAP is not simple to navigate!