Playvoi/Assets/ShiroginSDK/Runtime/Services/Implementations/IAP/IAPService.cs
2025-10-30 22:48:16 +03:00

236 lines
No EOL
8.4 KiB
C#

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
{
/// <summary>
/// Handles Unity IAP initialization, purchases, and rewards through StoreRepository.
/// </summary>
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<StoreItem> 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<IAppleExtensions>();
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>();
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<IEventService>();
_dataService = ServiceLocator.Get<IDataService>();
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<bool> 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);
}
}
}