using System; using ShiroginSDK.Runtime.Core.SDK; using ShiroginSDK.Runtime.Modules.Data.Scripts; using ShiroginSDK.Runtime.Modules.Events; using ShiroginSDK.Runtime.Modules.IAP.Scripts.SO; using ShiroginSDK.Runtime.Services.Base; using ShiroginSDK.Runtime.Services.Interfaces; using UnityEngine; using UnityEngine.Purchasing; namespace ShiroginSDK.Runtime.Services.Implementations.IAP { /// /// Handles Unity IAP initialization, purchases, and rewards through StoreRepository. /// public class IAPService : ServiceBase, IIAPService, IStoreListener { private ShiroginConfig _config; private IDataService _dataService; private IEventService _eventService; private IStoreController _storeController; private IExtensionProvider _storeExtensions; public bool IsInitialized => _storeController != null; public StoreRepository StoreRepository => _config?.storeRepository; // ------------------------------------------------------- // Public API // ------------------------------------------------------- public void Buy(StoreItem item, Action onSuccess = null) { if (item == null) { Debug.LogError("[IAPService] ❌ Cannot buy. StoreItem is null."); return; } Buy(item.productId, success => { if (success) { Debug.Log($"[IAPService] ✅ Purchase successful for '{item.productId}'"); onSuccess?.Invoke(item); } else { Debug.LogWarning($"[IAPService] ⚠️ Purchase failed or cancelled for '{item.productId}'"); } }); } public void Buy(string productId) { Buy(productId, success => { if (success) Debug.Log($"[IAPService] ✅ Purchase successful for '{productId}'"); else Debug.LogWarning($"[IAPService] ⚠️ Purchase failed or cancelled for '{productId}'"); }); } public void RestorePurchases() { if (!IsInitialized) { Debug.LogWarning("[IAPService] ⚠️ Cannot restore — not initialized."); return; } #if UNITY_IOS var appleExt = _storeExtensions.GetExtension(); appleExt.RestoreTransactions(result => Debug.Log("[IAPService] 🔄 Restore completed: " + result)); #elif UNITY_ANDROID foreach (var product in _storeController.products.all) if (product.hasReceipt && product.definition.type == ProductType.NonConsumable) { var item = GetStoreItem(product.definition.id); item?.GiveReward(); Debug.Log($"[IAPService] 🔄 Restored: {product.definition.id}"); } #else Debug.Log("[IAPService] Restore not supported on this platform."); #endif } public string GetLocalizedPriceString(string productId) { if (!IsInitialized || string.IsNullOrEmpty(productId)) return string.Empty; var p = _storeController.products.WithID(productId); return p != null ? p.metadata.localizedPriceString : string.Empty; } // ------------------------------------------------------- // IStoreListener // ------------------------------------------------------- public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { _storeController = controller; _storeExtensions = extensions; Debug.Log("[IAPService] ✅ Initialized successfully."); } public void OnInitializeFailed(InitializationFailureReason error) { Debug.LogError($"[IAPService] ❌ Initialization failed: {error}"); } public void OnInitializeFailed(InitializationFailureReason error, string message) { Debug.LogError($"[IAPService] ❌ Initialization failed: {error} — {message}"); } public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) { var product = args.purchasedProduct; var item = GetStoreItem(product.definition.id); if (item != null) { item.GiveReward(); var storeData = _dataService?.Get(); storeData?.MarkPurchased(product.definition.id, 1, item.category); _eventService?.Invoke(new ShiroginEvents.Shop.Shop_UIStoreItem_Rewarded(item.GetRewards())); _eventService?.Invoke(new ShiroginEvents.IAP.PurchaseCompletedEvent(product)); Debug.Log($"[IAPService] 🎁 Reward granted for {product.definition.id}"); } else { Debug.LogWarning($"[IAPService] ⚠️ No StoreItem mapped for {product.definition.id}"); } return PurchaseProcessingResult.Complete; } public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) { Debug.LogError($"[IAPService] ❌ Purchase failed: {product.definition.id} - {failureReason}"); } // ------------------------------------------------------- // Initialization // ------------------------------------------------------- protected override void OnInitialize() { _config = ShiroginConfig.Load(); _eventService = ServiceLocator.Get(); _dataService = ServiceLocator.Get(); if (_config == null || _config.storeRepository == null) { Debug.LogError("[IAPService] ❌ Initialization failed — Missing SDKConfig or StoreRepository."); return; } #if UNITY_EDITOR var module = StandardPurchasingModule.Instance(AppStore.NotSpecified); module.useFakeStoreAlways = true; module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser; Debug.Log("[IAPService] 🧩 FakeStore enabled in Editor."); #else var module = StandardPurchasingModule.Instance(); #endif var builder = ConfigurationBuilder.Instance(module); var allItems = _config.storeRepository.GetAll(); if (allItems == null || allItems.Count == 0) { Debug.LogWarning("[IAPService] ⚠️ No products found in StoreRepository."); return; } foreach (var item in allItems) { if (string.IsNullOrEmpty(item.productId)) { Debug.LogWarning("[IAPService] Skipped item with empty productId."); continue; } builder.AddProduct(item.productId, item.productType); } UnityPurchasing.Initialize(this, builder); } protected override void OnDispose() { _storeController = null; _storeExtensions = null; _eventService = null; _dataService = null; } private void Buy(string productId, Action callback) { if (!IsInitialized) { Debug.LogError("[IAPService] ❌ Not initialized yet."); callback?.Invoke(false); return; } var product = _storeController.products.WithID(productId); if (product != null && product.availableToPurchase) { Debug.Log($"[IAPService] 🛒 Purchasing: {productId}"); _storeController.InitiatePurchase(product); callback?.Invoke(true); } else { Debug.LogWarning($"[IAPService] ⚠️ Product not available: {productId}"); callback?.Invoke(false); } } // ------------------------------------------------------- // Helpers // ------------------------------------------------------- private StoreItem GetStoreItem(string productId) { if (StoreRepository == null) return null; return StoreRepository.GetByProductId(productId); } } }