Created
June 27, 2018 05:17
-
-
Save estebanfeldman/1897c342fa1c3c1fa6da4a08bb16118f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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