-
-
Save RimuruDev/3a81bfba6c78ec572baa1e272235b644 to your computer and use it in GitHub Desktop.
A wrapper script for Unity IAP (In-App Purchases) that can be used for common IAP tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEngine.Purchasing; | |
using UnityEngine.Purchasing.Security; | |
public class IAPManager : IStoreListener | |
{ | |
public enum State { PendingInitialize, Initializing, SuccessfullyInitialized, FailedToInitialize }; | |
private static IAPManager m_instance = null; | |
public static IAPManager Instance | |
{ | |
get | |
{ | |
if( m_instance == null ) | |
m_instance = new IAPManager(); | |
return m_instance; | |
} | |
} | |
private State m_initializationState = State.PendingInitialize; | |
public State InitializationState { get { return m_initializationState; } } | |
public bool IsInitialized { get { return m_initializationState == State.SuccessfullyInitialized; } } | |
public delegate void InitializationCallback( bool success ); | |
private InitializationCallback m_onInitialized; | |
public event InitializationCallback OnInitialized | |
{ | |
add | |
{ | |
if( m_initializationState == State.SuccessfullyInitialized || m_initializationState == State.FailedToInitialize ) | |
value?.Invoke( m_initializationState == State.SuccessfullyInitialized ); | |
else | |
m_onInitialized += value; | |
} | |
remove { m_onInitialized -= value; } | |
} | |
public delegate void CompletedPurchaseCallback( Product product ); | |
public CompletedPurchaseCallback OnPurchaseCompleted; | |
public delegate void FailedPurchaseCallback( Product product, PurchaseFailureReason failureReason ); | |
public FailedPurchaseCallback OnPurchaseFailed; | |
public delegate void NativeIAPWindowClosedCallback(); | |
private NativeIAPWindowClosedCallback onIAPWindowClosed; | |
public delegate void NativeRestoreWindowClosedCallback( bool success ); | |
private NativeRestoreWindowClosedCallback onRestoreWindowClosed; | |
private IStoreController storeController; | |
private IExtensionProvider storeExtensions; | |
#pragma warning disable IDE0044 | |
private CrossPlatformValidator purchaseValidator; | |
#pragma warning restore IDE0044 | |
public void Initialize() | |
{ | |
Initialize( null, true ); | |
} | |
public void Initialize( params ProductDefinition[] products ) | |
{ | |
Initialize( products, false ); | |
} | |
public void Initialize( IEnumerable<ProductDefinition> products ) | |
{ | |
Initialize( products, false ); | |
} | |
private void Initialize( IEnumerable<ProductDefinition> products, bool initializeWithIAPCatalog ) | |
{ | |
if( m_initializationState != State.PendingInitialize ) | |
{ | |
Debug.LogWarning( "IAP is already initializing!" ); | |
return; | |
} | |
#if UNITY_EDITOR | |
// Allows simulating failed IAP transactions in the Editor | |
StandardPurchasingModule.Instance().useFakeStoreUIMode = FakeStoreUIMode.StandardUser; | |
#endif | |
ConfigurationBuilder builder = ConfigurationBuilder.Instance( StandardPurchasingModule.Instance() ); | |
if( initializeWithIAPCatalog ) | |
IAPConfigurationHelper.PopulateConfigurationBuilder( ref builder, ProductCatalog.LoadDefaultCatalog() ); | |
else if( products != null ) | |
builder.AddProducts( products ); | |
if( StandardPurchasingModule.Instance().appStore == AppStore.GooglePlay ) | |
builder.Configure<IGooglePlayConfiguration>().SetDeferredPurchaseListener( OnDeferredPurchase ); | |
m_initializationState = State.Initializing; | |
UnityPurchasing.Initialize( this, builder ); | |
} | |
public void Purchase( string productID, NativeIAPWindowClosedCallback onIAPWindowClosed = null ) | |
{ | |
if( !IsInitialized ) | |
{ | |
Debug.LogWarning( "IAP isn't initialized yet, can't purchased items!" ); | |
onIAPWindowClosed?.Invoke(); | |
return; | |
} | |
this.onIAPWindowClosed = onIAPWindowClosed; | |
storeController.InitiatePurchase( productID ); | |
} | |
public void RestorePurchases( NativeRestoreWindowClosedCallback onRestoreWindowClosed = null ) | |
{ | |
if( !IsInitialized ) | |
{ | |
Debug.LogWarning( "IAP isn't initialized yet, can't restore purchases!" ); | |
onRestoreWindowClosed?.Invoke( false ); | |
return; | |
} | |
this.onRestoreWindowClosed = onRestoreWindowClosed; | |
switch( StandardPurchasingModule.Instance().appStore ) | |
{ | |
case AppStore.AppleAppStore: storeExtensions.GetExtension<IAppleExtensions>().RestoreTransactions( ( success ) => OnNativeRestoreWindowClosed( success ) ); break; | |
case AppStore.GooglePlay: storeExtensions.GetExtension<IGooglePlayStoreExtensions>().RestoreTransactions( ( success ) => OnNativeRestoreWindowClosed( success ) ); break; | |
} | |
} | |
public bool IsNonConsumablePurchased( string productID ) | |
{ | |
if( !IsInitialized ) | |
{ | |
Debug.LogWarning( "IAP isn't initialized yet, can't check previous purchases!" ); | |
return false; | |
} | |
if( string.IsNullOrEmpty( productID ) ) | |
{ | |
Debug.LogWarning( "Empty productID is passed!" ); | |
return false; | |
} | |
Product product = storeController.products.WithID( productID ); | |
if( product == null ) | |
{ | |
Debug.LogWarning( "IAP Product not found: " + productID ); | |
return false; | |
} | |
return product.hasReceipt && IsPurchaseValid( product ); | |
} | |
void IStoreListener.OnInitialized( IStoreController storeController, IExtensionProvider storeExtensions ) | |
{ | |
this.storeController = storeController; | |
this.storeExtensions = storeExtensions; | |
if( StandardPurchasingModule.Instance().appStore == AppStore.AppleAppStore ) | |
storeExtensions.GetExtension<IAppleExtensions>().RegisterPurchaseDeferredListener( OnDeferredPurchase ); | |
// The CrossPlatform validator only supports Google Play and Apple App Store | |
switch( StandardPurchasingModule.Instance().appStore ) | |
{ | |
case AppStore.GooglePlay: | |
case AppStore.AppleAppStore: | |
case AppStore.MacAppStore: | |
{ | |
#if !UNITY_EDITOR | |
//byte[] appleTangleData = AppleStoreKitTestTangle.Data(); // While testing with StoreKit Testing | |
byte[] appleTangleData = AppleTangle.Data(); | |
purchaseValidator = new CrossPlatformValidator( GooglePlayTangle.Data(), appleTangleData, Application.identifier ); | |
#endif | |
break; | |
} | |
} | |
m_initializationState = State.SuccessfullyInitialized; | |
m_onInitialized?.Invoke( true ); | |
} | |
void IStoreListener.OnInitializeFailed( InitializationFailureReason error ) | |
{ | |
Debug.LogWarning( "IAP initialization failed: " + error ); | |
m_initializationState = State.FailedToInitialize; | |
m_onInitialized?.Invoke( false ); | |
} | |
PurchaseProcessingResult IStoreListener.ProcessPurchase( PurchaseEventArgs purchaseEvent ) | |
{ | |
try | |
{ | |
Product product = purchaseEvent.purchasedProduct; | |
if( IsPurchaseValid( product ) ) | |
{ | |
if( StandardPurchasingModule.Instance().appStore == AppStore.GooglePlay && storeExtensions.GetExtension<IGooglePlayStoreExtensions>().IsPurchasedProductDeferred( product ) ) | |
{ | |
// The purchase is deferred; therefore, we do not unlock the content or complete the transaction. | |
// ProcessPurchase will be called again once the purchase is completed | |
return PurchaseProcessingResult.Pending; | |
} | |
OnPurchaseCompleted?.Invoke( product ); | |
} | |
return PurchaseProcessingResult.Complete; | |
} | |
finally | |
{ | |
OnNativeIAPWindowClosed(); | |
} | |
} | |
void IStoreListener.OnPurchaseFailed( Product product, PurchaseFailureReason failureReason ) | |
{ | |
Debug.LogWarning( $"IAP purchase failed for '{product.definition.id}': {failureReason}" ); | |
OnPurchaseFailed?.Invoke( product, failureReason ); | |
OnNativeIAPWindowClosed(); | |
} | |
private void OnDeferredPurchase( Product product ) | |
{ | |
Debug.Log( $"IAP purchase of {product.definition.id} is deferred" ); | |
OnNativeIAPWindowClosed(); | |
} | |
private bool IsPurchaseValid( Product product ) | |
{ | |
if( purchaseValidator != null ) | |
{ | |
try | |
{ | |
purchaseValidator.Validate( product.receipt ); | |
} | |
catch( IAPSecurityException reason ) | |
{ | |
Debug.LogWarning( "Invalid IAP receipt: " + reason ); | |
return false; | |
} | |
} | |
return true; | |
} | |
private void OnNativeIAPWindowClosed() | |
{ | |
try | |
{ | |
onIAPWindowClosed?.Invoke(); | |
onIAPWindowClosed = null; | |
} | |
catch( Exception e ) | |
{ | |
Debug.LogException( e ); | |
} | |
} | |
private void OnNativeRestoreWindowClosed( bool success ) | |
{ | |
Debug.Log( "IAP purchases restored: " + success ); | |
try | |
{ | |
onRestoreWindowClosed?.Invoke( success ); | |
onRestoreWindowClosed = null; | |
} | |
catch( Exception e ) | |
{ | |
Debug.LogException( e ); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment