Skip to content

Instantly share code, notes, and snippets.

@eka
Created June 27, 2018 05:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eka/1897c342fa1c3c1fa6da4a08bb16118f to your computer and use it in GitHub Desktop.
Save eka/1897c342fa1c3c1fa6da4a08bb16118f to your computer and use it in GitHub Desktop.
using System;
using System.Collections;
using System.Collections.Generic;
using DoozyUI;
using TMPro;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.SceneManagement;
namespace YakDogGames
{
// Placing the Purchaser class in the CompleteProject namespace allows it to interact with ScoreManager,
// one of the existing Survival Shooter scripts.
// Deriving the Purchaser class from IStoreListener enables it to receive messages from Unity Purchasing.
public class IAPShopManager : MonoBehaviour, IStoreListener
{
public UIElement IAPPurchaseErrorElement;
public TMP_Text PurchaseErrorMessageText;
public TMP_Text CoinsCounterText;
public List<IAPProduct> IAPProducts = new List<IAPProduct>();
public Transform ProductButtonWrapperPrefab;
public Transform ProductsButtonsTransform;
private static IStoreController _storeController; // The Unity Purchasing system.
private static IExtensionProvider _storeExtensionProvider; // The store-specific Purchasing subsystems.
private Dictionary<string, UIButton> ProductIdButtonDict = new Dictionary<string, UIButton>();
// 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.
void Start()
{
// If we haven't set up the Unity Purchasing reference
if (_storeController == null)
{
// Begin to configure our connection to Purchasing
InitializePurchasing();
}
CoinsCounterText.text = Wallet.Instance.Coins.ToString();
if (!PersistenceManager.Instance.CanShowAds())
{
// NoAdsButton.Interactable = false;
}
}
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.
AddProductsToUnityPurchasing(builder);
// 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 void AddProductsToUnityPurchasing(ConfigurationBuilder builder)
{
foreach (var product in IAPProducts)
{
var productId = product.ProductId;
Debug.Log(string.Format("Adding Product {0} to Configuration Builder.", productId));
var productType = product.Consumable ? ProductType.Consumable : ProductType.NonConsumable;
Debug.Log("Product Type: " + productType);
builder.AddProduct(productId, productType);
}
}
private bool IsInitialized()
{
// Only say we are initialized if both the Purchasing references are set.
return _storeController != null && _storeExtensionProvider != null;
}
#region PurchaseMethod
private void OnBuyProduct(IAPProduct product, UIButton button)
{
Debug.Log("OnBuyProduct clicked");
Debug.Log("Product: " + product.DebugTitle);
BuyProductID(product.ProductId);
}
#endregion
public void OnOkButtonClicked()
{
SceneManager.LoadScene("Main");
}
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);
throw new NotImplementedException();
}
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 = _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.
_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.
// TODO: do we need restore purchases? Is this implemented in Google Play Services?
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;
}
// 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");
// TODO: we need to check for already purchased products so we don't charge twice for NoAds
// Overall Purchasing system, configured with products for this application.
_storeController = controller;
SetupProductUIButtons(controller);
// Store specific subsystem, for accessing device-specific store features.
_storeExtensionProvider = extensions;
}
private void SetupProductUIButtons(IStoreController controller)
{
// setup product buttons
foreach (var product in IAPProducts)
{
var productId = product.ProductId;
var iapProduct = controller.products.WithStoreSpecificID(productId);
var newProductButton = Instantiate(ProductButtonWrapperPrefab, ProductsButtonsTransform);
var pb = newProductButton.GetComponent<ProductButtonWrapper>();
Debug.Log(iapProduct.metadata.isoCurrencyCode);
Debug.Log(iapProduct.metadata.localizedPrice);
#if UNITY_EDITOR
string title = string.Format(
"+{0} {1}", product.DebugTitle, product.DebugPrice
);
#else
string title = string.Format(
"+{0} {1} {2}",
iapProduct.metadata.localizedTitle,
iapProduct.metadata.localizedPrice,
iapProduct.metadata.isoCurrencyCode
);
#endif
pb.Initialize(product.ProductIcon, title);
// setup the button listener
pb.BuyProductButton.OnClick.AddListener(() => { OnBuyProduct(product, pb.BuyProductButton); });
// associate button to the product ID so we can work on the button itself later
ProductIdButtonDict[productId] = pb.BuyProductButton;
// Disable any consumable button that was previously bought.
if (product.Consumable)
{
UpdateButtonForConsumableProduct(product);
}
}
}
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)
{
var productId = args.purchasedProduct.definition.id;
var iapProduct = IAPProducts.Find((product) => { return String.Equals(productId, product.ProductId); });
if (iapProduct == null)
{
Debug.Log(
string.Format(
"ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id
)
);
}
else
{
Debug.Log("Purchased: " + iapProduct.DebugTitle);
switch (iapProduct.ProductType)
{
case ProductTypes.Coin:
Wallet.Instance.AddCoins(iapProduct.Quantity);
CoinsCounterText.text = Wallet.Instance.Coins.ToString();
break;
case ProductTypes.NoAds:
PersistenceManager.Instance.SaveNoAdsStatus(true);
UpdateButtonForConsumableProduct(iapProduct);
break;
default:
Debug.LogWarning("Unknown product type: " + iapProduct);
break;
}
}
// Return a flag indicating whether this product has completely been received, or if the application needs
// to be reminded of this purchase at next app launch. Use PurchaseProcessingResult.Pending when still
// saving purchased products to the cloud, and when that save is delayed.
return PurchaseProcessingResult.Complete;
}
private void UpdateButtonForConsumableProduct(IAPProduct iapProduct)
{
var productId = iapProduct.ProductId;
if (!ProductIdButtonDict.ContainsKey(productId))
{
Debug.LogWarning("Can't find button for productId: " + productId);
return;
}
var button = ProductIdButtonDict[productId];
var product = _storeController.products.WithStoreSpecificID(productId);
if (product.hasReceipt)
{
button.Interactable = false;
}
}
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)
);
PurchaseErrorMessageText.text = string.Format(
"Product: {0}\n{1}", product.definition.storeSpecificId, failureReason.ToString()
);
IAPPurchaseErrorElement.Show(false);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment