using UnityEngine; using UnityEngine.Purchasing; using System; using UnityEngine.Purchasing.Extension; namespace BulletHellTemplate { /// /// Enum to switch between Test Mode and Production Mode. /// In Test Mode, purchases will be simulated without the need for a Google Developer account. /// In Production Mode, real purchases will be processed through the Google Play or Apple App Store. /// public enum PurchaseMode { TestMode, ProductionMode } /// /// Manages the In-App Purchasing system for the game, handling both Test and Production modes. /// Supports purchasing virtual currencies and other in-game items. /// public class IAPManager : MonoBehaviour, IDetailedStoreListener { public static IAPManager Singleton; private IStoreController storeController; private IExtensionProvider storeExtensionProvider; [Header("IAP Configuration")] [Tooltip("List of IAP items that can be purchased.")] public IAPItem[] purchasableItems; // List of IAP items as ScriptableObjects [Tooltip("Set whether the game should run in Test Mode or Production Mode.")] public PurchaseMode purchaseMode = PurchaseMode.ProductionMode; private void Awake() { if (Singleton == null) { Singleton = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } private void Start() { InitializePurchasing(); } /// /// Initializes the purchasing system for IAP, based on the selected PurchaseMode. /// If TestMode is selected, purchases will be simulated. /// public void InitializePurchasing() { if (IsInitialized()) return; // Choose the appropriate module based on the purchase mode var module = (purchaseMode == PurchaseMode.TestMode) ? StandardPurchasingModule.Instance(AppStore.NotSpecified) : StandardPurchasingModule.Instance(); var builder = ConfigurationBuilder.Instance(module); // Add purchasable IAP items to the IAP system using their USD price foreach (var iapItem in purchasableItems) { builder.AddProduct(iapItem.itemId, ProductType.Consumable); } UnityPurchasing.Initialize(this, builder); } /// /// Checks whether the IAP system is initialized. /// /// Returns true if the IAP system is initialized, otherwise false. private bool IsInitialized() { return storeController != null && storeExtensionProvider != null; } /// /// Initiates the purchase of an IAP item, based on the provided itemId. /// If in TestMode, the purchase will be simulated. /// /// The unique ID of the IAP item to purchase. public void BuyCurrency(string itemId) { if (IsInitialized()) { Product product = storeController.products.WithID(itemId); if (product != null && product.availableToPurchase) { Debug.Log($"Attempting to buy product: {product.definition.id}"); storeController.InitiatePurchase(product); } else { Debug.Log("BuyCurrency: Product not found or not available for purchase."); } } else { Debug.Log("BuyCurrency: Not initialized."); } } /// /// Called when the purchase of a product is complete. /// Processes the purchased item and grants it to the player. /// /// The product that was purchased. public void OnPurchaseComplete(Product product) { foreach (var iapItem in purchasableItems) { if (product.definition.id == iapItem.itemId) { int currentAmount = MonetizationManager.GetCurrency(iapItem.associatedCurrency.coinID); int newAmount = currentAmount + iapItem.currencyAmount; // Ensure the purchase is securely processed before updating the currency MonetizationManager.SetCurrency(iapItem.associatedCurrency.coinID, newAmount); Debug.Log($"Purchase complete: {iapItem.itemName}, new amount: {newAmount}"); } } } /// /// Called when a purchase fails, providing the reason for the failure. /// /// The product that failed to purchase. /// The reason for the purchase failure. public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) { Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureReason}"); } /// /// Required by IDetailedStoreListener to handle initialization failure with detailed information. /// /// The type of initialization failure. /// Additional message regarding the failure. public void OnInitializeFailed(InitializationFailureReason error, string message) { Debug.Log($"IAP Initialization Failed: {error}, Message: {message}"); } /// /// Called when a purchase fails, providing detailed information about the failure. /// /// The product that failed to purchase. /// The detailed description of the failure. public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription) { Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureDescription.reason}, message: {failureDescription.message}"); } /// /// Processes the purchase and delivers the purchased item to the player. /// In TestMode, the process is simulated. /// /// The purchase event arguments. /// A PurchaseProcessingResult indicating the result of the processing. public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent) { // Validate the purchased product before processing Product purchasedProduct = purchaseEvent.purchasedProduct; if (purchasedProduct != null && !string.IsNullOrEmpty(purchasedProduct.definition.id)) { Debug.Log($"Processing purchase for {purchasedProduct.definition.id}"); // Ensure the purchased product is one of the defined IAP items foreach (var iapItem in purchasableItems) { if (purchasedProduct.definition.id == iapItem.itemId) { int currentAmount = MonetizationManager.GetCurrency(iapItem.associatedCurrency.coinID); int newAmount = currentAmount + iapItem.currencyAmount; // Apply the purchased currency to the player account MonetizationManager.SetCurrency(iapItem.associatedCurrency.coinID, newAmount); Debug.Log($"Purchase processed: {iapItem.itemName}, new total: {newAmount}"); return PurchaseProcessingResult.Complete; } } } Debug.LogError("Purchase failed: Invalid product or product definition."); return PurchaseProcessingResult.Pending; // Mark as pending if there are issues } /// /// Called when the IAP system is successfully initialized. /// This method sets the internal store controller and extension provider. /// /// The store controller for managing IAP products. /// The store extension provider. public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { storeController = controller; storeExtensionProvider = extensions; } /// /// Called when the IAP initialization fails. /// /// The reason for the initialization failure. public void OnInitializeFailed(InitializationFailureReason error) { Debug.Log("IAP Initialization Failed: " + error); } } }