FYI, other games’s IAP works perfectly fine. The only one that has the problem is my game.
This is the code that I use to do the IAP. It’s based on the IAP tutorial so it should work.right?
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine.Purchasing;
public enum CurrencyType
{
RUBY,
GOLD
}
public class CurrencyManager : MonoBehaviour, IStoreListener {
#region Static Delegates
public static Action OnRubyValueChanged = delegate {};
public static Action OnGoldValueChanged = delegate {};
public static Action<bool, int, int> OnRetrieveCurrencyData = delegate {};
public static Action<bool> OnBuyGold = delegate { };
#endregion
#region Constants
public const string RUBY_100_PACK_PRODUCT_ID = "rubies_1"; //Real id in the Google Store / App Store
public const string RUBY_250_PACK_PRODUCT_ID = "rubies_2"; //Real id in the Google Store / App Store
public const string RUBY_1500_PACK_PRODUCT_ID = "rubies_3"; //Real id in the Google Store / App Store
public const string RUBY_10_PACK_PRODUCT_ID = "rubies_4"; //Real id in the Google Store / App Store
public const string RUBY_100_PACK_ITEM_ID = "rubies_100";
public const string RUBY_250_PACK_ITEM_ID = "rubies_250";
public const string RUBY_1500_PACK_ITEM_ID = "rubies_1500";
public const string RUBY_10_PACK_ITEM_ID = "rubies_10";
public const string GOLD_1000_PACK_ITEM_ID = "gold_1000";
public const string GOLD_2750_PACK_ITEM_ID = "gold_2750";
public const string GOLD_17500_PACK_ITEM_ID = "gold_17500";
//Test values for editor because in the editor we don't use In App and just use the TEST API.
public const int RUBY_100_VALUE = 100; //Real value
public const int RUBY_250_VALUE = 250; //Real value
public const int RUBY_1500_VALUE = 1500; //Real value
public const int RUBY_10_VALUE = 10; //Real value
public const int GOLD_1000_VALUE = 1000; //Real value
public const int GOLD_2750_VALUE = 2750; //Real value
public const int GOLD_17500_VALUE = 17500; //Real value
#endregion
#region Variables
private static CurrencyManager _instance = null;
private static int _currentCharId = 0;
private static IStoreController m_StoreController; // Reference to the Purchasing system.
private static IExtensionProvider m_StoreExtensionProvider; // Reference to store-specific Purchasing subsystems.
private IPurchaseVerifier _verifier = null;
private int _rubies = 0;
private int _gold = 0;
private Dictionary<string, int> rubyRealValues = new Dictionary<string, int>();
private Dictionary<string, int> goldRealValues = new Dictionary<string, int>();
#endregion
#region Properties
public static int Ruby
{
get{ return _instance._rubies; }
private set
{
_instance._rubies = value;
OnRubyValueChanged();
}
}
public static int Gold
{
get{ return _instance._gold; }
//Setter should only be used once per play to get the current data form the server.
//Use VirtualCurrency.Give() / VirtualCurrency.Take() to do it afterwards.
private set
{
_instance._gold = value;
OnGoldValueChanged();
}
}
#endregion
#region Monobehaviour Methods
void Awake ()
{
if (_instance == null)
{
_instance = this;
DontDestroyOnLoad(this.gameObject);
UILobbyController.Instance.dontDestroyObjects.Add(this.gameObject);
#if UNITY_ANDROID
_verifier = new AndroidPurchaseVerifier();
#elif UNITY_IOS
_verifier = new iOSPurchaseVerifier();
#else
#endif
SetRubiesRealValues();
SetGoldRealValues();
}
else if (_instance != this)
{
DestroyImmediate(this.gameObject);
return;
}
}
/// <summary>
/// Start this instance.
/// </summary>
void Start()
{
// If we haven't set up the Unity Purchasing reference
if (m_StoreController == null)
{
// Begin to configure our connection to Purchasing
InitializePurchasing();
}
}
void OnDestroy()
{
if (_instance == this)
{
_instance = null;
//EraDB.OnAuth -= OnUserAuth;
}
}
#endregion
#region IStoreListener Methods
private bool IsInitialized()
{
// Only say we are initialized if both the Purchasing references are set.
return m_StoreController != null && m_StoreExtensionProvider != null;
}
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)
{
_verifier.Set(args);
_verifier.Verify();
//CurrencyManager.VerifyInApp(extra["productId"], extra["token"]);
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.
Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
}
#endregion
#region Methods
public void SetRubiesRealValues()
{
rubyRealValues.Add(RUBY_100_PACK_ITEM_ID, RUBY_100_VALUE);
rubyRealValues.Add(RUBY_250_PACK_ITEM_ID, RUBY_250_VALUE);
rubyRealValues.Add(RUBY_1500_PACK_ITEM_ID, RUBY_1500_VALUE);
rubyRealValues.Add(RUBY_10_PACK_ITEM_ID, RUBY_10_VALUE);
}
public void SetGoldRealValues()
{
goldRealValues.Add(GOLD_1000_PACK_ITEM_ID, GOLD_1000_VALUE);
goldRealValues.Add(GOLD_2750_PACK_ITEM_ID, GOLD_2750_VALUE);
goldRealValues.Add(GOLD_17500_PACK_ITEM_ID, GOLD_17500_VALUE);
}
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());
// Add a product to sell / restore by way of its identifier, associating the general identifier with its store-specific identifiers.
builder.AddProduct(RUBY_100_PACK_ITEM_ID, ProductType.Consumable, new IDs() { { RUBY_100_PACK_PRODUCT_ID, AppleAppStore.Name }, { RUBY_100_PACK_PRODUCT_ID, GooglePlay.Name }, });// Continue adding the consumable product.
builder.AddProduct(RUBY_250_PACK_ITEM_ID, ProductType.Consumable, new IDs() { { RUBY_250_PACK_PRODUCT_ID, AppleAppStore.Name }, { RUBY_250_PACK_PRODUCT_ID, GooglePlay.Name }, });// Continue adding the consumable product.
builder.AddProduct(RUBY_1500_PACK_ITEM_ID, ProductType.Consumable, new IDs() { { RUBY_1500_PACK_PRODUCT_ID, AppleAppStore.Name }, { RUBY_1500_PACK_PRODUCT_ID, GooglePlay.Name }, });// Continue adding the consumable product.
builder.AddProduct(RUBY_10_PACK_ITEM_ID, ProductType.Consumable, new IDs() { { RUBY_10_PACK_PRODUCT_ID, AppleAppStore.Name }, { RUBY_10_PACK_PRODUCT_ID, GooglePlay.Name }, });// Continue adding the consumable product.
UnityPurchasing.Initialize(this, builder);
}
public void OnUserAuth()
{
RetrieveCurrencyData();
}
void BuyProductID(string productId)
{
// If the stores throw an unexpected exception, use try..catch to protect my logic here.
try
{
// 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.");
}
}
// Complete the unexpected exception handling ...
catch (Exception e)
{
// ... by reporting any unexpected exception for later diagnosis.
Debug.Log("BuyProductID: FAIL. Exception during purchase. " + e);
}
}
// Restore purchases previously made by this customer. Some platforms automatically restore purchases. Apple currently requires explicit purchase restoration for IAP.
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);
}
}
public string GetLocalizedPrice(string productID)
{
// ... 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 product is found
if (product != null )
{
return product.metadata.localizedPriceString;
}
else
{
Debug.Log("Cannot found product with ID = " + productID );
}
return "";
}
public void OnInAppConfirmation(string productID, string purchaseToken)
{
//GameUI.LoadScreen.Instance.On("Verifying In App...");
string request = JsonConvert.SerializeObject(new StoreVerifyInAppRequest( productID, purchaseToken ));
Debug.Log("Request = " + request);
string address = EraDB.StoreVerifyInAppAddress;
Debug.Log("Address = " + address);
EraDB.GameDatabase.Post(address, request, resp =>
{
if (resp.exception == null)
{
if (resp.response.message == "OK")
{
string statusMsg = "Buy Rubies Successful! Getting the new data from the server! " + resp.response.Text;
Debug.Log(statusMsg);
CurrencyManager.RetrieveCurrencyDataFromServer();
Mixpanel.SendEvent("BuyRubies", new Dictionary<string, object> {
{"ID", productID}
});
}
else
{
ErrorResponse errorResp = JsonConvert.DeserializeObject<ErrorResponse>(resp.response.Text);
string statusMsg = "Buy Rubies Failed! " + errorResp.errorMessage;
Debug.Log(statusMsg);
UINotification.Add(statusMsg, Color.red);
CurrencyManager.RetrieveCurrencyDataFromServer();
}
}
else
{
string statusMsg = "Buy Rubies Failed! " + resp.exception.Message;
Debug.Log(statusMsg);
UINotification.Add(statusMsg, Color.red);
//_shopPanel.statusLabel.PopStatus(Color.red, statusMsg);
CurrencyManager.RetrieveCurrencyDataFromServer();
//GameUI.LoadScreen.Instance.Off();
}
}, true);
}
public void RetrieveCurrencyData()
{
//Debug.Log("Char ID = " + PlayerPrefs.GetInt("char_id", 0).ToString());
//string address = EraDB.GameDatabase.StoreCurrencyGetAddress + "/" + PlayerPrefs.GetInt("char_id", 0);
string address = EraDB.StoreCurrencyGetAddress + "/" + HeroData.Instance.charId;
EraDB.GameDatabase.Get(address, resp =>
//StartCoroutine(EraDB.GameDatabase.hGetRequest(EraDB.GameDatabase.UserCurrencyGetAddress, request, resp =>
//StartCoroutine(EraDB.GameDatabase.hPostRequest(EraDB.GameDatabase.UserCurrencyGetAddress, request, resp =>
{
if (resp.exception == null)
{
if (resp.response.message == "OK")
{
Debug.Log("Get Currency Successful! " + resp.response.Text);
CurrencyGetResponse currency = JsonConvert.DeserializeObject<CurrencyGetResponse>(resp.response.Text);
Ruby = currency.rubies;
Gold = currency.gold;
_currentCharId = HeroData.Instance.charId;
OnRetrieveCurrencyData(true, currency.rubies, currency.gold);
}
else
{
ErrorResponse errorResp = JsonConvert.DeserializeObject<ErrorResponse>(resp.response.Text);
if (!string.IsNullOrEmpty(errorResp.errorMessage))
Debug.Log("Get Currency Failed! " + errorResp.errorMessage);
OnRetrieveCurrencyData(false, Ruby, Gold);
}
}
else
{
Debug.Log("Get Currency Failed! " + resp.exception.Message);
OnRetrieveCurrencyData(false, Ruby, Gold);
}
}, false);
}
public void BuyGold(int goldValue)
{
//string request = JsonConvert.SerializeObject(new StoreGoldBuyRequest( PlayerPrefs.GetInt("char_id", 0), _goldValues[merchandizeEnum] ));
string request = JsonConvert.SerializeObject(new StoreGoldBuyRequest(HeroData.Instance.charId, goldValue));
Debug.Log("Request = " + request);
string address = EraDB.StoreGoldBuyAddress;
Debug.Log("Address = " + address);
EraDB.GameDatabase.Post(address, request, resp =>
{
if (resp.exception == null)
{
if (resp.response.message == "OK")
{
string statusMsg = "Buy " + goldValue.ToString() + " Gold Successful! " + resp.response.Text;
Debug.Log(statusMsg);
//_shopPanel.statusLabel.PopStatus(Color.green, statusMsg);
string respText = resp.response.Text;
Mixpanel.SendEvent("BuyGold", new Dictionary<string, object> {
{"Amount", goldValue.ToString()}
});
CurrencyManager.RetrieveCurrencyDataFromServer();
OnBuyGold(true);
}
else
{
ErrorResponse errorResp = JsonConvert.DeserializeObject<ErrorResponse>(resp.response.Text);
string statusMsg = "Buy " + goldValue.ToString() + " Gold Failed! " + errorResp.errorMessage;
//_shopPanel.statusLabel.PopStatus(Color.red, statusMsg);
UINotification.Add(statusMsg, Color.red);
CurrencyManager.RetrieveCurrencyDataFromServer();
OnBuyGold(false);
}
}
else
{
string statusMsg = "Buy " + goldValue.ToString() + " Gold Failed! " + resp.exception.Message;
Debug.Log(statusMsg);
OnBuyGold(false);
//_shopPanel.statusLabel.PopStatus(Color.red, statusMsg);
UINotification.Add(statusMsg, Color.red);
}
});
}
#if UNITY_EDITOR
void AddGoldFromEditor(int amount)
{
GameUI.LoadScreen.Instance.On("Set gold from editor. Not using the production API but using the test API...");
//int charID = PlayerPrefs.GetInt("char_id", 0);
int charID = HeroData.Instance.charId;
string request = JsonConvert.SerializeObject(new TestSetGoldRequest(charID, Gold + amount));
EraDB.GameDatabase.Post(EraDB.TestSetGoldAddress, request, resp =>
{
if (resp.exception == null)
{
if (resp.response.message == "OK")
{
string statusMsg = "Set gold from editor successful! " + resp.response.Text;
Debug.Log(statusMsg);
CurrencyManager.RetrieveCurrencyDataFromServer();
OnBuyGold(true);
GameUI.LoadScreen.Instance.Off();
//_statusLabel.PopStatus(Color.green, statusMsg);
}
else
{
string statusMsg = "Set gold from editor failed! " + resp.response.Text;
Debug.Log(statusMsg);
CurrencyManager.RetrieveCurrencyDataFromServer();
OnBuyGold(false);
GameUI.LoadScreen.Instance.Off();
}
}
else
{
GameUI.LoadScreen.Instance.Off();
string statusMsg = "Set gold from editor failed! " + resp.exception.Message;
OnBuyGold(false);
Debug.Log(statusMsg);
}
});
}
void AddRubyFromEditor(int amount)
{
GameUI.LoadScreen.Instance.On("Set rubies from editor. Not using the production API but using the test API...");
//int charID = PlayerPrefs.GetInt("char_id", 0);
int charID = HeroData.Instance.charId;
string request = JsonConvert.SerializeObject(new TestSetRubyRequest(Ruby + amount));
EraDB.GameDatabase.Post(EraDB.TestSetRubyAddress, request, resp =>
{
if (resp.exception == null)
{
if (resp.response.message == "OK")
{
string statusMsg = "Set rubies from editor successful! " + resp.response.Text;
Debug.Log(statusMsg);
CurrencyManager.RetrieveCurrencyDataFromServer();
GameUI.LoadScreen.Instance.Off();
//_statusLabel.PopStatus(Color.green, statusMsg);
}
else
{
string statusMsg = "Set rubies from editor failed! " + resp.response.Text;
Debug.Log(statusMsg);
CurrencyManager.RetrieveCurrencyDataFromServer();
GameUI.LoadScreen.Instance.Off();
}
}
else
{
GameUI.LoadScreen.Instance.Off();
string statusMsg = "Set rubies from editor failed! " + resp.exception.Message;
Debug.Log(statusMsg);
}
});
}
#endif
//-------------------------------------------------------------------------------------
//Static methods
public static bool Exists()
{
return !(_instance == null && FindObjectOfType<CurrencyManager>() == null);
}
private static CurrencyManager Instance()
{
if (!Exists())
{
return Instantiate();
}
return _instance;
}
private static CurrencyManager Instantiate()
{
GameObject go = new GameObject("CurrencyManager");
CurrencyManager currencyManagerComp = go.AddComponent<CurrencyManager>();
currencyManagerComp.Awake();
return currencyManagerComp;
}
public static string GetPrice(string productId)
{
return Instance().GetLocalizedPrice(productId);
}
public static void RetrieveCurrencyDataFromServer()
{
Instance().RetrieveCurrencyData();
}
public static void ReduceCurrency(CurrencyType type, int amount, bool changeAPI)
{
if (type == CurrencyType.RUBY)
Ruby -= amount;
else if (type == CurrencyType.GOLD)
Gold -= amount;
//TODO: API CALL
if (changeAPI)
{
}
}
public static void AddCurrency(CurrencyType type, int amount, bool changeAPI)
{
if (type == CurrencyType.RUBY)
Ruby += amount;
else if (type == CurrencyType.GOLD)
Gold += amount;
//TODO: API CALL
if (changeAPI)
{
}
}
public static void VerifyInApp(string productID, string purchaseToken)
{
Instance().OnInAppConfirmation(productID, purchaseToken);
}
public static void BuyRubies(string rubiesId)
{
#if UNITY_EDITOR
Instance().AddRubyFromEditor(Instance().rubyRealValues[rubiesId]);
#else
Instance().BuyProductID(rubiesId);
#endif
}
public static void BuyGold(string goldId)
{
#if UNITY_EDITOR
Instance().AddGoldFromEditor(Instance().goldRealValues[goldId]);
#else
Instance().BuyGold(Instance().goldRealValues[goldId]);
#endif
}
#endregion
}