IAPButton.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. #if UNITY_PURCHASING
  2. using UnityEngine.Events;
  3. using UnityEngine.UI;
  4. using System.IO;
  5. using System.Collections.Generic;
  6. namespace UnityEngine.Purchasing
  7. {
  8. [RequireComponent (typeof (Button))]
  9. [AddComponentMenu("Unity IAP/IAP Button")]
  10. [HelpURL("https://docs.unity3d.com/Manual/UnityIAP.html")]
  11. public class IAPButton : MonoBehaviour
  12. {
  13. public enum ButtonType
  14. {
  15. Purchase,
  16. Restore
  17. }
  18. [System.Serializable]
  19. public class OnPurchaseCompletedEvent : UnityEvent<Product> {};
  20. [System.Serializable]
  21. public class OnPurchaseFailedEvent : UnityEvent<Product, PurchaseFailureReason> {};
  22. [HideInInspector]
  23. public string productId;
  24. [Tooltip("The type of this button, can be either a purchase or a restore button")]
  25. public ButtonType buttonType = ButtonType.Purchase;
  26. [Tooltip("Consume the product immediately after a successful purchase")]
  27. public bool consumePurchase = true;
  28. [Tooltip("Event fired after a successful purchase of this product")]
  29. public OnPurchaseCompletedEvent onPurchaseComplete;
  30. [Tooltip("Event fired after a failed purchase of this product")]
  31. public OnPurchaseFailedEvent onPurchaseFailed;
  32. [Tooltip("[Optional] Displays the localized title from the app store")]
  33. public Text titleText;
  34. [Tooltip("[Optional] Displays the localized description from the app store")]
  35. public Text descriptionText;
  36. [Tooltip("[Optional] Displays the localized price from the app store")]
  37. public Text priceText;
  38. void Start ()
  39. {
  40. Button button = GetComponent<Button>();
  41. if (buttonType == ButtonType.Purchase) {
  42. if (button) {
  43. button.onClick.AddListener(PurchaseProduct);
  44. }
  45. if (string.IsNullOrEmpty(productId)) {
  46. Debug.LogError("IAPButton productId is empty");
  47. }
  48. if (!IAPButtonStoreManager.Instance.HasProductInCatalog(productId)) {
  49. Debug.LogWarning("The product catalog has no product with the ID \"" + productId + "\"");
  50. }
  51. } else if (buttonType == ButtonType.Restore) {
  52. if (button) {
  53. button.onClick.AddListener(Restore);
  54. }
  55. }
  56. }
  57. void OnEnable()
  58. {
  59. if (buttonType == ButtonType.Purchase) {
  60. IAPButtonStoreManager.Instance.AddButton(this);
  61. UpdateText();
  62. }
  63. }
  64. void OnDisable()
  65. {
  66. if (buttonType == ButtonType.Purchase) {
  67. IAPButtonStoreManager.Instance.RemoveButton(this);
  68. }
  69. }
  70. void PurchaseProduct()
  71. {
  72. if (buttonType == ButtonType.Purchase) {
  73. Debug.Log("IAPButton.PurchaseProduct() with product ID: " + productId);
  74. IAPButtonStoreManager.Instance.InitiatePurchase(productId);
  75. }
  76. }
  77. void Restore()
  78. {
  79. if (buttonType == ButtonType.Restore) {
  80. if (Application.platform == RuntimePlatform.WSAPlayerX86 || Application.platform == RuntimePlatform.WSAPlayerX64 || Application.platform == RuntimePlatform.WSAPlayerARM) {
  81. IAPButtonStoreManager.Instance.ExtensionProvider.GetExtension<IMicrosoftExtensions>().RestoreTransactions();
  82. } else if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.tvOS) {
  83. IAPButtonStoreManager.Instance.ExtensionProvider.GetExtension<IAppleExtensions>().RestoreTransactions(OnTransactionsRestored);
  84. } else if (Application.platform == RuntimePlatform.Android && StandardPurchasingModule.Instance().appStore == AppStore.SamsungApps) {
  85. IAPButtonStoreManager.Instance.ExtensionProvider.GetExtension<ISamsungAppsExtensions>().RestoreTransactions(OnTransactionsRestored);
  86. } else if (Application.platform == RuntimePlatform.Android && StandardPurchasingModule.Instance().appStore == AppStore.CloudMoolah) {
  87. IAPButtonStoreManager.Instance.ExtensionProvider.GetExtension<IMoolahExtension>().RestoreTransactionID((restoreTransactionIDState) => {
  88. OnTransactionsRestored(restoreTransactionIDState != RestoreTransactionIDState.RestoreFailed && restoreTransactionIDState != RestoreTransactionIDState.NotKnown);
  89. });
  90. } else {
  91. Debug.LogWarning(Application.platform.ToString() + " is not a supported platform for the Codeless IAP restore button");
  92. }
  93. }
  94. }
  95. void OnTransactionsRestored(bool success)
  96. {
  97. Debug.Log("Transactions restored: " + success);
  98. }
  99. /**
  100. * Invoked to process a purchase of the product associated with this button
  101. */
  102. public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
  103. {
  104. Debug.Log(string.Format("IAPButton.ProcessPurchase(PurchaseEventArgs {0} - {1})", e, e.purchasedProduct.definition.id));
  105. onPurchaseComplete.Invoke(e.purchasedProduct);
  106. return (consumePurchase) ? PurchaseProcessingResult.Complete : PurchaseProcessingResult.Pending;
  107. }
  108. /**
  109. * Invoked on a failed purchase of the product associated with this button
  110. */
  111. public void OnPurchaseFailed (Product product, PurchaseFailureReason reason)
  112. {
  113. Debug.Log(string.Format("IAPButton.OnPurchaseFailed(Product {0}, PurchaseFailureReason {1})", product, reason));
  114. onPurchaseFailed.Invoke(product, reason);
  115. }
  116. private void UpdateText()
  117. {
  118. var product = IAPButtonStoreManager.Instance.GetProduct(productId);
  119. if (product != null) {
  120. if (titleText != null) {
  121. titleText.text = product.metadata.localizedTitle;
  122. }
  123. if (descriptionText != null) {
  124. descriptionText.text = product.metadata.localizedDescription;
  125. }
  126. if (priceText != null) {
  127. priceText.text = product.metadata.localizedPriceString;
  128. }
  129. }
  130. }
  131. public class IAPButtonStoreManager : IStoreListener
  132. {
  133. private static IAPButtonStoreManager instance = new IAPButtonStoreManager();
  134. private ProductCatalog catalog;
  135. private List<IAPButton> activeButtons = new List<IAPButton>();
  136. private IAPListener m_Listener;
  137. protected IStoreController controller;
  138. protected IExtensionProvider extensions;
  139. private IAPButtonStoreManager()
  140. {
  141. catalog = ProductCatalog.LoadDefaultCatalog();
  142. StandardPurchasingModule module = StandardPurchasingModule.Instance();
  143. module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
  144. ConfigurationBuilder builder = ConfigurationBuilder.Instance(module);
  145. IAPConfigurationHelper.PopulateConfigurationBuilder(ref builder, catalog);
  146. UnityPurchasing.Initialize (this, builder);
  147. }
  148. public static IAPButtonStoreManager Instance {
  149. get {
  150. return instance;
  151. }
  152. }
  153. public IStoreController StoreController {
  154. get {
  155. return controller;
  156. }
  157. }
  158. public IExtensionProvider ExtensionProvider {
  159. get {
  160. return extensions;
  161. }
  162. }
  163. public bool HasProductInCatalog(string productID)
  164. {
  165. foreach (var product in catalog.allProducts) {
  166. if (product.id == productID) {
  167. return true;
  168. }
  169. }
  170. return false;
  171. }
  172. public Product GetProduct(string productID)
  173. {
  174. if (controller != null && controller.products != null && !string.IsNullOrEmpty(productID)) {
  175. return controller.products.WithID(productID);
  176. }
  177. return null;
  178. }
  179. public void AddButton(IAPButton button)
  180. {
  181. activeButtons.Add(button);
  182. }
  183. public void RemoveButton(IAPButton button)
  184. {
  185. activeButtons.Remove(button);
  186. }
  187. public void AddListener(IAPListener listener)
  188. {
  189. if (m_Listener != null)
  190. Debug.LogWarning("There is more than one active IAPListener. Only the most recent IAPListener will receive purchase events.");
  191. m_Listener = listener;
  192. }
  193. public void RemoveListener(IAPListener listener)
  194. {
  195. if (m_Listener == listener)
  196. m_Listener = null;
  197. }
  198. public void InitiatePurchase(string productID)
  199. {
  200. if (controller == null) {
  201. Debug.LogError("Purchase failed because Purchasing was not initialized correctly");
  202. foreach (var button in activeButtons) {
  203. if (button.productId == productID) {
  204. button.OnPurchaseFailed(null, Purchasing.PurchaseFailureReason.PurchasingUnavailable);
  205. }
  206. }
  207. return;
  208. }
  209. controller.InitiatePurchase(productID);
  210. }
  211. public void OnInitialized (IStoreController controller, IExtensionProvider extensions)
  212. {
  213. this.controller = controller;
  214. this.extensions = extensions;
  215. foreach (var button in activeButtons) {
  216. button.UpdateText();
  217. }
  218. }
  219. public void OnInitializeFailed (InitializationFailureReason error)
  220. {
  221. Debug.LogError(string.Format("Purchasing failed to initialize. Reason: {0}", error.ToString()));
  222. }
  223. public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
  224. {
  225. foreach (var button in activeButtons) {
  226. if (button.productId == e.purchasedProduct.definition.id) {
  227. return button.ProcessPurchase(e);
  228. }
  229. }
  230. if (m_Listener != null) {
  231. return m_Listener.ProcessPurchase(e);
  232. } else {
  233. Debug.LogWarning("Purchase not correctly processed for product \"" + e.purchasedProduct.definition.id + "\". Add an active IAPButton to process this purchase, or add an IAPListener to receive any unhandled purchase events.");
  234. return PurchaseProcessingResult.Pending;
  235. }
  236. }
  237. public void OnPurchaseFailed (Product product, PurchaseFailureReason reason)
  238. {
  239. foreach (var button in activeButtons) {
  240. if (button.productId == product.definition.id) {
  241. button.OnPurchaseFailed(product, reason);
  242. return;
  243. }
  244. }
  245. if (m_Listener != null) {
  246. m_Listener.OnPurchaseFailed(product, reason);
  247. return;
  248. } else {
  249. Debug.LogWarning("Failed purchase not correctly handled for product \"" + product.definition.id + "\". Add an active IAPButton to handle this failure, or add an IAPListener to receive any unhandled purchase failures.");
  250. }
  251. }
  252. }
  253. }
  254. }
  255. #endif