273 lines
No EOL
9.2 KiB
C#
273 lines
No EOL
9.2 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;
|
|
#if SHIROGIN_IAP
|
|
using UnityEngine.Purchasing;
|
|
#endif
|
|
|
|
namespace ShiroginSDK.Runtime.Services.Implementations.IAP
|
|
{
|
|
#if SHIROGIN_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);
|
|
}
|
|
}
|
|
#else
|
|
public class IAPService : ServiceBase, IIAPService
|
|
{
|
|
public bool IsInitialized => false;
|
|
public StoreRepository StoreRepository => null;
|
|
|
|
protected override void OnInitialize()
|
|
{
|
|
Debug.Log("[IAPService] Unity IAP Package missing.");
|
|
}
|
|
|
|
protected override void OnDispose()
|
|
{
|
|
}
|
|
|
|
public void Buy(StoreItem item, Action<StoreItem> onSuccess = null)
|
|
{
|
|
Debug.LogWarning("IAP missing");
|
|
}
|
|
|
|
public void Buy(string productId)
|
|
{
|
|
}
|
|
|
|
public void RestorePurchases()
|
|
{
|
|
}
|
|
|
|
public string GetLocalizedPriceString(string productId)
|
|
{
|
|
return "-";
|
|
}
|
|
}
|
|
#endif
|
|
} |