#if UNITY_ANDROID || UNITY_IPHONE || UNITY_STANDALONE_OSX || UNITY_TVOS
// You must obfuscate your secrets using Window > Unity IAP > Receipt Validation Obfuscator
// before receipt validation will compile in this sample.
// #define RECEIPT_VALIDATION
#endif
//#define DELAY_CONFIRMATION // Returns PurchaseProcessingResult.Pending from ProcessPurchase, then calls ConfirmPendingPurchase after a delay
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Purchasing;
using UnityEngine.UI;
#if RECEIPT_VALIDATION
using UnityEngine.Purchasing.Security;
#endif
///
/// An example of basic Unity IAP functionality.
/// To use with your account, configure the product ids (AddProduct)
/// and Google Play key (SetPublicKey).
///
[AddComponentMenu("Unity IAP/Demo")]
public class IAPDemo : MonoBehaviour, IStoreListener
{
// Unity IAP objects
private IStoreController m_Controller;
private IAppleExtensions m_AppleExtensions;
private IMoolahExtension m_MoolahExtensions;
private ISamsungAppsExtensions m_SamsungExtensions;
private IMicrosoftExtensions m_MicrosoftExtensions;
#pragma warning disable 0414
private bool m_IsGooglePlayStoreSelected;
#pragma warning restore 0414
private bool m_IsSamsungAppsStoreSelected;
private bool m_IsCloudMoolahStoreSelected;
private string m_LastTransationID;
private string m_LastReceipt;
private string m_CloudMoolahUserName;
private bool m_IsLoggedIn = false;
private int m_SelectedItemIndex = -1; // -1 == no product
private bool m_PurchaseInProgress;
private Selectable m_InteractableSelectable; // Optimization used for UI state management
#if RECEIPT_VALIDATION
private CrossPlatformValidator validator;
#endif
///
/// This will be called when Unity IAP has finished initialising.
///
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
m_Controller = controller;
m_AppleExtensions = extensions.GetExtension ();
m_SamsungExtensions = extensions.GetExtension ();
m_MoolahExtensions = extensions.GetExtension ();
m_MicrosoftExtensions = extensions.GetExtension ();
InitUI(controller.products.all);
// On Apple platforms we need to handle deferred purchases caused by Apple's Ask to Buy feature.
// On non-Apple platforms this will have no effect; OnDeferred will never be called.
m_AppleExtensions.RegisterPurchaseDeferredListener(OnDeferred);
Debug.Log("Available items:");
foreach (var item in controller.products.all)
{
if (item.availableToPurchase)
{
Debug.Log(string.Join(" - ",
new[]
{
item.metadata.localizedTitle,
item.metadata.localizedDescription,
item.metadata.isoCurrencyCode,
item.metadata.localizedPrice.ToString(),
item.metadata.localizedPriceString,
item.transactionID,
item.receipt
}));
}
}
// Prepare model for purchasing
if (m_Controller.products.all.Length > 0)
{
m_SelectedItemIndex = 0;
}
// Populate the product menu now that we have Products
for (int t = 0; t < m_Controller.products.all.Length; t++)
{
var item = m_Controller.products.all[t];
var description = string.Format("{0} | {1} => {2}", item.metadata.localizedTitle, item.metadata.localizedPriceString, item.metadata.localizedPrice);
// NOTE: my options list is created in InitUI
GetDropdown().options[t] = new Dropdown.OptionData(description);
}
// Ensure I render the selected list element
GetDropdown().RefreshShownValue();
// Now that I have real products, begin showing product purchase history
UpdateHistoryUI();
LogProductDefinitions();
}
///
/// This will be called when a purchase completes.
///
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
Debug.Log("Purchase OK: " + e.purchasedProduct.definition.id);
Debug.Log("Receipt: " + e.purchasedProduct.receipt);
m_LastTransationID = e.purchasedProduct.transactionID;
m_LastReceipt = e.purchasedProduct.receipt;
m_PurchaseInProgress = false;
#if RECEIPT_VALIDATION
// Local validation is available for GooglePlay and Apple stores
if (m_IsGooglePlayStoreSelected ||
Application.platform == RuntimePlatform.IPhonePlayer ||
Application.platform == RuntimePlatform.OSXPlayer ||
Application.platform == RuntimePlatform.tvOS) {
try {
var result = validator.Validate(e.purchasedProduct.receipt);
Debug.Log("Receipt is valid. Contents:");
foreach (IPurchaseReceipt productReceipt in result) {
Debug.Log(productReceipt.productID);
Debug.Log(productReceipt.purchaseDate);
Debug.Log(productReceipt.transactionID);
GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
if (null != google) {
Debug.Log(google.purchaseState);
Debug.Log(google.purchaseToken);
}
AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
if (null != apple) {
Debug.Log(apple.originalTransactionIdentifier);
Debug.Log(apple.subscriptionExpirationDate);
Debug.Log(apple.cancellationDate);
Debug.Log(apple.quantity);
}
}
} catch (IAPSecurityException) {
Debug.Log("Invalid receipt, not unlocking content");
return PurchaseProcessingResult.Complete;
}
}
#endif
// CloudMoolah purchase completion / finishing currently requires using the API
// extension IMoolahExtension.RequestPayout to finish a transaction.
if (m_IsCloudMoolahStoreSelected)
{
// Finish transaction with CloudMoolah server
m_MoolahExtensions.RequestPayOut(e.purchasedProduct.transactionID,
(string transactionID, RequestPayOutState state, string message) => {
if (state == RequestPayOutState.RequestPayOutSucceed) {
// Finally, finish transaction with Unity IAP's local
// transaction log, recording the transaction id there
m_Controller.ConfirmPendingPurchase(e.purchasedProduct);
// Unlock content here.
} else {
Debug.Log("RequestPayOut: failed. transactionID: " + transactionID +
", state: " + state + ", message: " + message);
// Finishing failed. Retry later.
}
});
}
// You should unlock the content here.
// Indicate if we have handled this purchase.
// PurchaseProcessingResult.Complete: ProcessPurchase will not be called
// with this product again, until next purchase.
// PurchaseProcessingResult.Pending: ProcessPurchase will be called
// again with this product at next app launch. Later, call
// m_Controller.ConfirmPendingPurchase(Product) to complete handling
// this purchase. Use to transactionally save purchases to a cloud
// game service.
#if DELAY_CONFIRMATION
StartCoroutine(ConfirmPendingPurchaseAfterDelay(e.purchasedProduct));
return PurchaseProcessingResult.Pending;
#else
UpdateHistoryUI();
return PurchaseProcessingResult.Complete;
#endif
}
#if DELAY_CONFIRMATION
private HashSet m_PendingProducts = new HashSet();
private IEnumerator ConfirmPendingPurchaseAfterDelay(Product p)
{
m_PendingProducts.Add(p.definition.id);
Debug.Log("Delaying confirmation of " + p.definition.id + " for 5 seconds.");
UpdateHistoryUI();
yield return new WaitForSeconds(5f);
Debug.Log("Confirming purchase of " + p.definition.id);
m_Controller.ConfirmPendingPurchase(p);
m_PendingProducts.Remove(p.definition.id);
UpdateHistoryUI();
}
#endif
///
/// This will be called is an attempted purchase fails.
///
public void OnPurchaseFailed(Product item, PurchaseFailureReason r)
{
Debug.Log("Purchase failed: " + item.definition.id);
Debug.Log(r);
m_PurchaseInProgress = false;
}
public void OnInitializeFailed(InitializationFailureReason error)
{
Debug.Log("Billing failed to initialize!");
switch (error)
{
case InitializationFailureReason.AppNotKnown:
Debug.LogError("Is your App correctly uploaded on the relevant publisher console?");
break;
case InitializationFailureReason.PurchasingUnavailable:
// Ask the user if billing is disabled in device settings.
Debug.Log("Billing disabled!");
break;
case InitializationFailureReason.NoProductsAvailable:
// Developer configuration error; check product metadata.
Debug.Log("No products available for purchase!");
break;
}
}
public void Awake()
{
var module = StandardPurchasingModule.Instance();
// The FakeStore supports: no-ui (always succeeding), basic ui (purchase pass/fail), and
// developer ui (initialization, purchase, failure code setting). These correspond to
// the FakeStoreUIMode Enum values passed into StandardPurchasingModule.useFakeStoreUIMode.
module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
var builder = ConfigurationBuilder.Instance(module);
// This enables the Microsoft IAP simulator for local testing.
// You would remove this before building your release package.
builder.Configure().useMockBillingSystem = true;
builder.Configure().SetPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2O/9/H7jYjOsLFT/uSy3ZEk5KaNg1xx60RN7yWJaoQZ7qMeLy4hsVB3IpgMXgiYFiKELkBaUEkObiPDlCxcHnWVlhnzJBvTfeCPrYNVOOSJFZrXdotp5L0iS2NVHjnllM+HA1M0W2eSNjdYzdLmZl1bxTpXa4th+dVli9lZu7B7C2ly79i/hGTmvaClzPBNyX+Rtj7Bmo336zh2lYbRdpD5glozUq+10u91PMDPH+jqhx10eyZpiapr8dFqXl5diMiobknw9CgcjxqMTVBQHK6hS0qYKPmUDONquJn280fBs1PTeA6NMG03gb9FLESKFclcuEZtvM8ZwMMRxSLA9GwIDAQAB");
m_IsGooglePlayStoreSelected = Application.platform == RuntimePlatform.Android && module.androidStore == AndroidStore.GooglePlay;
// CloudMoolah Configuration setings
// All games must set the configuration. the configuration need to apply on the CloudMoolah Portal.
// CloudMoolah APP Key
builder.Configure().appKey = "d93f4564c41d463ed3d3cd207594ee1b";
// CloudMoolah Hash Key
builder.Configure().hashKey = "cc";
// This enables the CloudMoolah test mode for local testing.
// You would remove this, or set to CloudMoolahMode.Production, before building your release package.
builder.Configure().SetMode(CloudMoolahMode.AlwaysSucceed);
// This records whether we are using Cloud Moolah IAP.
// Cloud Moolah requires logging in to access your Digital Wallet, so:
// A) IAPDemo (this) displays the Cloud Moolah GUI button for Cloud Moolah
m_IsCloudMoolahStoreSelected = Application.platform == RuntimePlatform.Android && module.androidStore == AndroidStore.CloudMoolah;
// Define our products.
// In this case our products have the same identifier across all the App stores,
// except on the Mac App store where product IDs cannot be reused across both Mac and
// iOS stores.
// So on the Mac App store our products have different identifiers,
// and we tell Unity IAP this by using the IDs class.
builder.AddProduct("100.gold.coins", ProductType.Consumable, new IDs
{
{"100.gold.coins.mac", MacAppStore.Name},
{"000000596586", TizenStore.Name},
{"com.ff", MoolahAppStore.Name},
});
builder.AddProduct("500.gold.coins", ProductType.Consumable, new IDs
{
{"500.gold.coins.mac", MacAppStore.Name},
{"000000596581", TizenStore.Name},
{"com.ee", MoolahAppStore.Name},
});
builder.AddProduct("sword", ProductType.NonConsumable, new IDs
{
{"sword.mac", MacAppStore.Name},
{"000000596583", TizenStore.Name},
});
builder.AddProduct("subscription", ProductType.Subscription, new IDs
{
{"subscription.mac", MacAppStore.Name}
});
// Write Amazon's JSON description of our products to storage when using Amazon's local sandbox.
// This should be removed from a production build.
builder.Configure().WriteSandboxJSON(builder.products);
// This enables simulated purchase success for Samsung IAP.
// You would remove this, or set to SamsungAppsMode.Production, before building your release package.
builder.Configure().SetMode(SamsungAppsMode.AlwaysSucceed);
// This records whether we are using Samsung IAP. Currently ISamsungAppsExtensions.RestoreTransactions
// displays a blocking Android Activity, so:
// A) Unity IAP does not automatically restore purchases on Samsung Galaxy Apps
// B) IAPDemo (this) displays the "Restore" GUI button for Samsung Galaxy Apps
m_IsSamsungAppsStoreSelected = Application.platform == RuntimePlatform.Android && module.androidStore == AndroidStore.SamsungApps;
// This selects the GroupId that was created in the Tizen Store for this set of products
// An empty or non-matching GroupId here will result in no products available for purchase
builder.Configure().SetGroupId("100000085616");
#if RECEIPT_VALIDATION
string appIdentifier;
#if UNITY_5_6_OR_NEWER
appIdentifier = Application.identifier;
#else
appIdentifier = Application.bundleIdentifier;
#endif
validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), appIdentifier);
#endif
// Now we're ready to initialize Unity IAP.
UnityPurchasing.Initialize(this, builder);
}
///
/// This will be called after a call to IAppleExtensions.RestoreTransactions().
///
private void OnTransactionsRestored(bool success)
{
Debug.Log("Transactions restored.");
}
///
/// iOS Specific.
/// This is called as part of Apple's 'Ask to buy' functionality,
/// when a purchase is requested by a minor and referred to a parent
/// for approval.
///
/// When the purchase is approved or rejected, the normal purchase events
/// will fire.
///
/// Item.
private void OnDeferred(Product item)
{
Debug.Log("Purchase deferred: " + item.definition.id);
}
private void InitUI(IEnumerable items)
{
// Disable the UI while IAP is initializing
// See also UpdateInteractable()
m_InteractableSelectable = GetDropdown(); // References any one of the disabled components
// Show Restore button on supported platforms
if (! (Application.platform == RuntimePlatform.IPhonePlayer ||
Application.platform == RuntimePlatform.OSXPlayer ||
Application.platform == RuntimePlatform.tvOS ||
Application.platform == RuntimePlatform.WSAPlayerX86 ||
Application.platform == RuntimePlatform.WSAPlayerX64 ||
Application.platform == RuntimePlatform.WSAPlayerARM ||
m_IsSamsungAppsStoreSelected || m_IsCloudMoolahStoreSelected) )
{
GetRestoreButton().gameObject.SetActive(false);
}
// Show Register, Login, and Validate buttons on CloudMoolah platform
GetRegisterButton().gameObject.SetActive(m_IsCloudMoolahStoreSelected);
GetLoginButton().gameObject.SetActive(m_IsCloudMoolahStoreSelected);
GetValidateButton().gameObject.SetActive(m_IsCloudMoolahStoreSelected);
foreach (var item in items)
{
// Add initial pre-IAP-initialization content. Update later in OnInitialized.
var description = string.Format("{0} - {1}", item.definition.id, item.definition.type);
GetDropdown().options.Add(new Dropdown.OptionData(description));
}
// Ensure I render the selected list element
GetDropdown().RefreshShownValue();
GetDropdown().onValueChanged.AddListener((int selectedItem) => {
Debug.Log("OnClickDropdown item " + selectedItem);
m_SelectedItemIndex = selectedItem;
});
// Initialize my button event handling
GetBuyButton().onClick.AddListener(() => {
if (m_PurchaseInProgress == true) {
Debug.Log("Please wait, purchasing ...");
return;
}
// For CloudMoolah, games utilizing a connected backend game server may wish to login.
// Standalone games may not need to login.
if (m_IsCloudMoolahStoreSelected && m_IsLoggedIn == false)
{
Debug.LogWarning("CloudMoolah purchase notifications will not be forwarded server-to-server. Login incomplete.");
}
// Don't need to draw our UI whilst a purchase is in progress.
// This is not a requirement for IAP Applications but makes the demo
// scene tidier whilst the fake purchase dialog is showing.
m_PurchaseInProgress = true;
m_Controller.InitiatePurchase(m_Controller.products.all[m_SelectedItemIndex], "aDemoDeveloperPayload");
});
if (GetRestoreButton() != null)
{
GetRestoreButton().onClick.AddListener(() => {
if (m_IsCloudMoolahStoreSelected)
{
if (m_IsLoggedIn == false)
{
Debug.LogError("CloudMoolah purchase restoration aborted. Login incomplete.");
}
else
{
// Restore abnornal transaction identifer, if Client don't receive transaction identifer.
m_MoolahExtensions.RestoreTransactionID((RestoreTransactionIDState restoreTransactionIDState) => {
Debug.Log("restoreTransactionIDState = " + restoreTransactionIDState.ToString());
bool success =
restoreTransactionIDState != RestoreTransactionIDState.RestoreFailed &&
restoreTransactionIDState != RestoreTransactionIDState.NotKnown;
OnTransactionsRestored(success);
});
}
}
else if (m_IsSamsungAppsStoreSelected)
{
m_SamsungExtensions.RestoreTransactions(OnTransactionsRestored);
}
else if (Application.platform == RuntimePlatform.WSAPlayerX86 ||
Application.platform == RuntimePlatform.WSAPlayerX64 ||
Application.platform == RuntimePlatform.WSAPlayerARM)
{
m_MicrosoftExtensions.RestoreTransactions();
}
else
{
m_AppleExtensions.RestoreTransactions(OnTransactionsRestored);
}
});
}
// CloudMoolah requires user registration and supports login to manage the user's
// digital wallet. The CM store also supports remote receipt validation.
// CloudMoolah user registration extension, to establish digital wallet
// This is a "fast" registration, requiring only a password. Users may provide
// more detail including an email address during the purchase flow, a "slow" registration, if desired.
if (GetRegisterButton() != null)
{
GetRegisterButton().onClick.AddListener (() => {
// Provide a unique password to establish the user's account.
// Typically, connected games (with backend game servers), may already
// have available a user-token, which could be supplied here.
m_MoolahExtensions.FastRegister("CMPassword", RegisterSucceeded, RegisterFailed);
});
}
// CloudMoolah user login extension, to access existing digital wallet
if (GetLoginButton() != null)
{
GetLoginButton().onClick.AddListener (() => {
m_MoolahExtensions.Login(m_CloudMoolahUserName, "CMPassword", LoginResult);
});
}
// CloudMoolah remote purchase receipt validation, to determine if the purchase is fraudulent
// NOTE: Remote validation only available for CloudMoolah currently. For local validation,
// see ProcessPurchase.
if (GetValidateButton() != null)
{
GetValidateButton ().onClick.AddListener (() => {
// Remotely validate the last transaction and receipt.
m_MoolahExtensions.ValidateReceipt(m_LastTransationID, m_LastReceipt,
(string transactionID, ValidateReceiptState state, string message) => {
Debug.Log("ValidtateReceipt transactionID:" + transactionID
+ ", state:" + state.ToString() + ", message:" + message);
});
});
}
}
public void LoginResult (LoginResultState state, string errorMsg)
{
if(state == LoginResultState.LoginSucceed)
{
m_IsLoggedIn = true;
}
else
{
m_IsLoggedIn = false;
}
Debug.Log ("LoginResult: state: " + state.ToString () + " errorMsg: " + errorMsg);
}
public void RegisterSucceeded(string cmUserName)
{
Debug.Log ("RegisterSucceeded: cmUserName = " + cmUserName);
m_CloudMoolahUserName = cmUserName;
}
public void RegisterFailed (FastRegisterError error, string errorMessage)
{
Debug.Log ("RegisterFailed: error = " + error.ToString() + ", errorMessage = " + errorMessage);
}
public void UpdateHistoryUI()
{
if (m_Controller == null)
{
return;
}
var itemText = "Item\n\n";
var countText = "Purchased\n\n";
foreach (var item in m_Controller.products.all) {
// Collect history status report
itemText += "\n\n" + item.definition.id;
countText += "\n\n";
#if DELAY_CONFIRMATION
if (m_PendingProducts.Contains(item.definition.id))
countText += "(Pending) ";
#endif
countText += item.hasReceipt.ToString();
}
// Show history
GetText(false).text = itemText;
GetText(true).text = countText;
}
protected void UpdateInteractable()
{
if (m_InteractableSelectable == null)
{
return;
}
bool interactable = m_Controller != null;
if (interactable != m_InteractableSelectable.interactable)
{
if (GetRestoreButton() != null)
{
GetRestoreButton().interactable = interactable;
}
GetBuyButton().interactable = interactable;
GetDropdown().interactable = interactable;
GetRegisterButton().interactable = interactable;
GetLoginButton().interactable = interactable;
}
}
public void Update()
{
UpdateInteractable();
}
private Dropdown GetDropdown()
{
return GameObject.Find("Dropdown").GetComponent();
}
private Button GetBuyButton()
{
return GameObject.Find("Buy").GetComponent