From 92268996292ce882028d69e5a1fd1fd7b983f689 Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Mon, 6 Oct 2025 16:30:14 +0200
Subject: [PATCH 01/11] Proper PaywallsBehaviour
---
RevenueCatUI/Scripts/PaywallsBehaviour.cs | 177 ++++++++++++++++++++--
1 file changed, 163 insertions(+), 14 deletions(-)
diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
index f2e1d173..4e631f04 100644
--- a/RevenueCatUI/Scripts/PaywallsBehaviour.cs
+++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
@@ -1,42 +1,191 @@
-using System.Threading.Tasks;
+using System;
using UnityEngine;
+using UnityEngine.Events;
namespace RevenueCatUI
{
///
- /// MonoBehaviour helper that forwards to the static PaywallsPresenter API so paywalls can be driven from scenes.
+ /// MonoBehaviour component for presenting RevenueCat paywalls from the Unity Editor.
+ /// Provides an alternative to PaywallsPresenter for developers who prefer configuring
+ /// paywalls through Unity's Inspector interface.
///
+ [AddComponentMenu("RevenueCat/Paywalls Behaviour")]
public class PaywallsBehaviour : MonoBehaviour
{
+ [Header("Paywall Options")]
+ [Tooltip("The identifier of the offering to present. Leave empty to use the current offering.")]
+ [SerializeField] private string offeringIdentifier;
+
+ [Tooltip("Whether to display a close button on the paywall (only for original template paywalls).")]
+ [SerializeField] private bool displayCloseButton = false;
+
+ [Header("Conditional Presentation")]
+ [Tooltip("If set, the paywall will only be presented if the user doesn't have this entitlement.")]
+ [SerializeField] private string requiredEntitlementIdentifier;
+
+ [Header("Auto Presentation")]
+ [Tooltip("Automatically present the paywall when this component starts.")]
+ [SerializeField] private bool presentOnStart = false;
+
+ [Header("Events")]
+ [Tooltip("Invoked when the paywall presentation is complete with the result.")]
+ public PaywallResultEvent OnPaywallResult = new PaywallResultEvent();
+
+ private bool isPresenting = false;
+
+ public string OfferingIdentifier
+ {
+ get => offeringIdentifier;
+ set => offeringIdentifier = value;
+ }
+
+ public bool DisplayCloseButton
+ {
+ get => displayCloseButton;
+ set => displayCloseButton = value;
+ }
+
+ public string RequiredEntitlementIdentifier
+ {
+ get => requiredEntitlementIdentifier;
+ set => requiredEntitlementIdentifier = value;
+ }
+
+ private void Start()
+ {
+ if (presentOnStart)
+ {
+ PresentPaywall();
+ }
+ }
+
///
- /// Presents a paywall configured in the RevenueCat dashboard.
+ /// Presents the paywall with the configured options.
+ /// Can be called from Unity UI buttons or programmatically.
///
- /// Options for presenting the paywall.
- /// A describing the outcome.
- public async Task PresentPaywall(PaywallOptions options = null)
+ public async void PresentPaywall()
{
- return await PaywallsPresenter.Present(options);
+ if (isPresenting)
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall is already being presented.");
+ return;
+ }
+
+ if (!PaywallsPresenter.IsSupported())
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall UI is not supported on this platform.");
+ HandleResult(PaywallResult.Error);
+ return;
+ }
+
+ isPresenting = true;
+
+ try
+ {
+ var options = CreateOptions();
+ PaywallResult result;
+
+ if (!string.IsNullOrEmpty(requiredEntitlementIdentifier))
+ {
+ result = await PaywallsPresenter.PresentIfNeeded(requiredEntitlementIdentifier, options);
+ }
+ else
+ {
+ result = await PaywallsPresenter.Present(options);
+ }
+
+ HandleResult(result);
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"[RevenueCatUI] Exception in PaywallsBehaviour: {e.Message}");
+ HandleResult(PaywallResult.Error);
+ }
+ finally
+ {
+ isPresenting = false;
+ }
}
///
/// Presents a paywall only if the user does not have the specified entitlement.
///
- /// Entitlement identifier to check before presenting.
- /// Options for presenting the paywall.
- /// A describing the outcome.
- public async Task PresentPaywallIfNeeded(string requiredEntitlementIdentifier, PaywallOptions options = null)
+ /// Entitlement identifier to check before presenting
+ public async void PresentPaywallIfNeeded(string entitlementIdentifier)
{
- return await PaywallsPresenter.PresentIfNeeded(requiredEntitlementIdentifier, options);
+ if (string.IsNullOrEmpty(entitlementIdentifier))
+ {
+ Debug.LogError("[RevenueCatUI] Entitlement identifier cannot be null or empty.");
+ HandleResult(PaywallResult.Error);
+ return;
+ }
+
+ if (isPresenting)
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall is already being presented.");
+ return;
+ }
+
+ if (!PaywallsPresenter.IsSupported())
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall UI is not supported on this platform.");
+ HandleResult(PaywallResult.Error);
+ return;
+ }
+
+ isPresenting = true;
+
+ try
+ {
+ var options = CreateOptions();
+ var result = await PaywallsPresenter.PresentIfNeeded(entitlementIdentifier, options);
+ HandleResult(result);
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"[RevenueCatUI] Exception in PaywallsBehaviour: {e.Message}");
+ HandleResult(PaywallResult.Error);
+ }
+ finally
+ {
+ isPresenting = false;
+ }
}
///
/// Checks if the Paywall UI is available on the current platform.
- /// Returns true on iOS/Android device builds when paywall is supported; otherwise false.
+ /// Returns true on iOS/Android device builds when paywall is supported;
+ /// returns false on other platforms (Editor, Windows, macOS, WebGL, etc.).
///
/// True if UI is supported on this platform, otherwise false.
- public bool IsSupported()
+ public bool IsPaywallSupported()
{
return PaywallsPresenter.IsSupported();
}
+
+ private PaywallOptions CreateOptions()
+ {
+ return new PaywallOptions
+ {
+ OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier,
+ DisplayCloseButton = displayCloseButton
+ };
+ }
+
+ private void HandleResult(PaywallResult result)
+ {
+ if (result == null)
+ {
+ Debug.LogError("[RevenueCatUI] Received null PaywallResult.");
+ OnPaywallResult?.Invoke(new PaywallResult(PaywallResultType.Error));
+ return;
+ }
+
+ OnPaywallResult?.Invoke(result);
+ }
+
+ [Serializable]
+ public class PaywallResultEvent : UnityEvent { }
}
}
+
From 4f8e021c91b6ae5639c883dfafd05f718122a6c1 Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Mon, 6 Oct 2025 20:35:49 +0200
Subject: [PATCH 02/11] better implementation of behavior
---
RevenueCatUI/Scripts/PaywallsBehaviour.cs | 184 ++++++++++++++++++++--
1 file changed, 172 insertions(+), 12 deletions(-)
diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
index 08896c79..ce10589e 100644
--- a/RevenueCatUI/Scripts/PaywallsBehaviour.cs
+++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
@@ -1,32 +1,192 @@
-using System.Threading.Tasks;
+using System;
using UnityEngine;
+using UnityEngine.Events;
namespace RevenueCatUI
{
///
- /// MonoBehaviour helper that forwards to the static PaywallsPresenter API so paywalls can be driven from scenes.
+ /// MonoBehaviour component for presenting RevenueCat paywalls from the Unity Editor.
+ /// Provides an alternative to PaywallsPresenter for developers who prefer configuring
+ /// paywalls through Unity's Inspector interface.
///
+ [AddComponentMenu("RevenueCat/Paywalls Behaviour")]
public class PaywallsBehaviour : MonoBehaviour
{
+ [Header("Paywall Options")]
+ [Tooltip("The identifier of the offering to present. Leave empty to use the current offering.")]
+ [SerializeField] private string offeringIdentifier;
+
+ [Tooltip("Whether to display a close button on the paywall (only for original template paywalls).")]
+ [SerializeField] private bool displayCloseButton = false;
+
+ [Header("Conditional Presentation")]
+ [Tooltip("If set, the paywall will only be presented if the user doesn't have this entitlement.")]
+ [SerializeField] private string requiredEntitlementIdentifier;
+
+ [Header("Auto Presentation")]
+ [Tooltip("Automatically present the paywall when this component starts.")]
+ [SerializeField] private bool presentOnStart = false;
+
+ [Header("Events")]
+ [Tooltip("Invoked when the user completes a purchase.")]
+ public UnityEvent OnPurchased = new UnityEvent();
+
+ [Tooltip("Invoked when the user restores purchases.")]
+ public UnityEvent OnRestored = new UnityEvent();
+
+ [Tooltip("Invoked when the user cancels the paywall.")]
+ public UnityEvent OnCancelled = new UnityEvent();
+
+ [Tooltip("Invoked when the paywall was not presented (user already has entitlement).")]
+ public UnityEvent OnNotPresented = new UnityEvent();
+
+ [Tooltip("Invoked when an error occurs.")]
+ public UnityEvent OnError = new UnityEvent();
+
+ private bool isPresenting = false;
+
+ public string OfferingIdentifier
+ {
+ get => offeringIdentifier;
+ set => offeringIdentifier = value;
+ }
+
+ public bool DisplayCloseButton
+ {
+ get => displayCloseButton;
+ set => displayCloseButton = value;
+ }
+
+ public string RequiredEntitlementIdentifier
+ {
+ get => requiredEntitlementIdentifier;
+ set => requiredEntitlementIdentifier = value;
+ }
+
+ private void Start()
+ {
+ if (presentOnStart)
+ {
+ PresentPaywall();
+ }
+ }
+
///
- /// Presents a paywall configured in the RevenueCat dashboard.
+ /// Presents the paywall with the configured options.
+ /// Can be called from Unity UI buttons or programmatically.
///
- /// Options for presenting the paywall.
- /// A describing the outcome.
- public async Task PresentPaywall(PaywallOptions options = null)
+ public async void PresentPaywall()
{
- return await PaywallsPresenter.Present(options);
+ if (isPresenting)
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall is already being presented.");
+ return;
+ }
+
+ isPresenting = true;
+
+ try
+ {
+ var options = CreateOptions();
+ PaywallResult result;
+
+ if (!string.IsNullOrEmpty(requiredEntitlementIdentifier))
+ {
+ result = await PaywallsPresenter.PresentIfNeeded(requiredEntitlementIdentifier, options);
+ }
+ else
+ {
+ result = await PaywallsPresenter.Present(options);
+ }
+
+ HandleResult(result);
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"[RevenueCatUI] Exception in PaywallsBehaviour: {e.Message}");
+ HandleResult(PaywallResult.Error);
+ }
+ finally
+ {
+ isPresenting = false;
+ }
}
///
/// Presents a paywall only if the user does not have the specified entitlement.
///
- /// Entitlement identifier to check before presenting.
- /// Options for presenting the paywall.
- /// A describing the outcome.
- public async Task PresentPaywallIfNeeded(string requiredEntitlementIdentifier, PaywallOptions options = null)
+ /// Entitlement identifier to check before presenting
+ public async void PresentPaywallIfNeeded(string entitlementIdentifier)
+ {
+ if (string.IsNullOrEmpty(entitlementIdentifier))
+ {
+ Debug.LogError("[RevenueCatUI] Entitlement identifier cannot be null or empty.");
+ HandleResult(PaywallResult.Error);
+ return;
+ }
+
+ if (isPresenting)
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall is already being presented.");
+ return;
+ }
+
+ isPresenting = true;
+
+ try
+ {
+ var options = CreateOptions();
+ var result = await PaywallsPresenter.PresentIfNeeded(entitlementIdentifier, options);
+ HandleResult(result);
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"[RevenueCatUI] Exception in PaywallsBehaviour: {e.Message}");
+ HandleResult(PaywallResult.Error);
+ }
+ finally
+ {
+ isPresenting = false;
+ }
+ }
+
+ private PaywallOptions CreateOptions()
+ {
+ return new PaywallOptions
+ {
+ OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier,
+ DisplayCloseButton = displayCloseButton
+ };
+ }
+
+ private void HandleResult(PaywallResult result)
{
- return await PaywallsPresenter.PresentIfNeeded(requiredEntitlementIdentifier, options);
+ if (result == null)
+ {
+ Debug.LogError("[RevenueCatUI] Received null PaywallResult.");
+ OnError?.Invoke();
+ return;
+ }
+
+ switch (result.Result)
+ {
+ case PaywallResultType.Purchased:
+ OnPurchased?.Invoke();
+ break;
+ case PaywallResultType.Restored:
+ OnRestored?.Invoke();
+ break;
+ case PaywallResultType.Cancelled:
+ OnCancelled?.Invoke();
+ break;
+ case PaywallResultType.NotPresented:
+ OnNotPresented?.Invoke();
+ break;
+ case PaywallResultType.Error:
+ OnError?.Invoke();
+ break;
+ }
}
}
}
+
From 60406d573cf5153f055d29236eb115db79e59312 Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Tue, 7 Oct 2025 10:05:03 +0200
Subject: [PATCH 03/11] remove presentOnStart
---
RevenueCatUI/Scripts/PaywallsBehaviour.cs | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
index ce10589e..465c63c3 100644
--- a/RevenueCatUI/Scripts/PaywallsBehaviour.cs
+++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
@@ -23,10 +23,6 @@ public class PaywallsBehaviour : MonoBehaviour
[Tooltip("If set, the paywall will only be presented if the user doesn't have this entitlement.")]
[SerializeField] private string requiredEntitlementIdentifier;
- [Header("Auto Presentation")]
- [Tooltip("Automatically present the paywall when this component starts.")]
- [SerializeField] private bool presentOnStart = false;
-
[Header("Events")]
[Tooltip("Invoked when the user completes a purchase.")]
public UnityEvent OnPurchased = new UnityEvent();
@@ -63,14 +59,6 @@ public string RequiredEntitlementIdentifier
set => requiredEntitlementIdentifier = value;
}
- private void Start()
- {
- if (presentOnStart)
- {
- PresentPaywall();
- }
- }
-
///
/// Presents the paywall with the configured options.
/// Can be called from Unity UI buttons or programmatically.
From e9189fe22487d28371dfb94fb7b13115e27b6092 Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Tue, 7 Oct 2025 10:06:01 +0200
Subject: [PATCH 04/11] add PaywallResultHandler
---
.../Assets/Scripts/PaywallResultHandler.cs | 53 +++++++++++++++++++
.../Scripts/PaywallResultHandler.cs.meta | 2 +
2 files changed, 55 insertions(+)
create mode 100644 Subtester/Assets/Scripts/PaywallResultHandler.cs
create mode 100644 Subtester/Assets/Scripts/PaywallResultHandler.cs.meta
diff --git a/Subtester/Assets/Scripts/PaywallResultHandler.cs b/Subtester/Assets/Scripts/PaywallResultHandler.cs
new file mode 100644
index 00000000..7e9449b6
--- /dev/null
+++ b/Subtester/Assets/Scripts/PaywallResultHandler.cs
@@ -0,0 +1,53 @@
+using UnityEngine;
+using UnityEngine.UI;
+using RevenueCatUI;
+
+public class PaywallResultHandler : MonoBehaviour
+{
+ [SerializeField] private Text infoLabel;
+
+ public void OnPaywallPurchased()
+ {
+ Debug.Log("User purchased!");
+ if (infoLabel != null)
+ {
+ infoLabel.text = "PURCHASED - User completed a purchase";
+ }
+ }
+
+ public void OnPaywallRestored()
+ {
+ Debug.Log("User restored purchases");
+ if (infoLabel != null)
+ {
+ infoLabel.text = "RESTORED - User restored previous purchases";
+ }
+ }
+
+ public void OnPaywallCancelled()
+ {
+ Debug.Log("User cancelled the paywall");
+ if (infoLabel != null)
+ {
+ infoLabel.text = "CANCELLED - User dismissed the paywall";
+ }
+ }
+
+ public void OnPaywallNotPresented()
+ {
+ Debug.Log("Paywall not needed - user already has access");
+ if (infoLabel != null)
+ {
+ infoLabel.text = "NOT PRESENTED - User already has entitlement";
+ }
+ }
+
+ public void OnPaywallError()
+ {
+ Debug.LogError("Error presenting paywall");
+ if (infoLabel != null)
+ {
+ infoLabel.text = "ERROR - An error occurred during paywall";
+ }
+ }
+}
diff --git a/Subtester/Assets/Scripts/PaywallResultHandler.cs.meta b/Subtester/Assets/Scripts/PaywallResultHandler.cs.meta
new file mode 100644
index 00000000..8a8ea79e
--- /dev/null
+++ b/Subtester/Assets/Scripts/PaywallResultHandler.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 7b6da05e14f2d4ed2bf16f2d1fa80ed4
\ No newline at end of file
From 5f1250a507e3659fff559be46c3ade9df5a27e27 Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:17:08 +0200
Subject: [PATCH 05/11] add logs and make PresentPaywallIfNeeded private
---
RevenueCatUI/Scripts/PaywallsBehaviour.cs | 26 ++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
index 465c63c3..0de2a06e 100644
--- a/RevenueCatUI/Scripts/PaywallsBehaviour.cs
+++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
@@ -100,11 +100,7 @@ public async void PresentPaywall()
}
}
- ///
- /// Presents a paywall only if the user does not have the specified entitlement.
- ///
- /// Entitlement identifier to check before presenting
- public async void PresentPaywallIfNeeded(string entitlementIdentifier)
+ private async void PresentPaywallIfNeeded(string entitlementIdentifier)
{
if (string.IsNullOrEmpty(entitlementIdentifier))
{
@@ -159,18 +155,38 @@ private void HandleResult(PaywallResult result)
switch (result.Result)
{
case PaywallResultType.Purchased:
+ if (OnPurchased.GetPersistentEventCount() == 0)
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall purchase completed but OnPurchased event has no listeners.");
+ }
OnPurchased?.Invoke();
break;
case PaywallResultType.Restored:
+ if (OnRestored.GetPersistentEventCount() == 0)
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall restore completed but OnRestored event has no listeners.");
+ }
OnRestored?.Invoke();
break;
case PaywallResultType.Cancelled:
+ if (OnCancelled.GetPersistentEventCount() == 0)
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall cancelled but OnCancelled event has no listeners.");
+ }
OnCancelled?.Invoke();
break;
case PaywallResultType.NotPresented:
+ if (OnNotPresented.GetPersistentEventCount() == 0)
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall not presented but OnNotPresented event has no listeners.");
+ }
OnNotPresented?.Invoke();
break;
case PaywallResultType.Error:
+ if (OnError.GetPersistentEventCount() == 0)
+ {
+ Debug.LogWarning("[RevenueCatUI] Paywall error occurred but OnError event has no listeners.");
+ }
OnError?.Invoke();
break;
}
From 1a5b6fd805b84840a996626109aed2c9d930115d Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:28:16 +0200
Subject: [PATCH 06/11] better docs
---
RevenueCatUI/Scripts/PaywallsBehaviour.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
index 0de2a06e..56cb497f 100644
--- a/RevenueCatUI/Scripts/PaywallsBehaviour.cs
+++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
@@ -24,16 +24,16 @@ public class PaywallsBehaviour : MonoBehaviour
[SerializeField] private string requiredEntitlementIdentifier;
[Header("Events")]
- [Tooltip("Invoked when the user completes a purchase.")]
+ [Tooltip("Invoked when the user completes a purchase and the paywall is dismissed.")]
public UnityEvent OnPurchased = new UnityEvent();
- [Tooltip("Invoked when the user restores purchases.")]
+ [Tooltip("Invoked when the user restores purchases and the paywall is dismissed.")]
public UnityEvent OnRestored = new UnityEvent();
- [Tooltip("Invoked when the user cancels the paywall.")]
+ [Tooltip("Invoked when the user cancels the paywall and the paywall is dismissed.")]
public UnityEvent OnCancelled = new UnityEvent();
- [Tooltip("Invoked when the paywall was not presented (user already has entitlement).")]
+ [Tooltip("Invoked when the paywall was not presented, for example when the user already has the required entitlement).")]
public UnityEvent OnNotPresented = new UnityEvent();
[Tooltip("Invoked when an error occurs.")]
From 57436614995262314016ded4d68e817f1f997486 Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:37:01 +0200
Subject: [PATCH 07/11] add logs
---
RevenueCatUI/Scripts/PaywallsBehaviour.cs | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
index 56cb497f..7466d34f 100644
--- a/RevenueCatUI/Scripts/PaywallsBehaviour.cs
+++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
@@ -147,7 +147,7 @@ private void HandleResult(PaywallResult result)
{
if (result == null)
{
- Debug.LogError("[RevenueCatUI] Received null PaywallResult.");
+ Debug.Log("[RevenueCatUI] Received null PaywallResult.");
OnError?.Invoke();
return;
}
@@ -157,35 +157,35 @@ private void HandleResult(PaywallResult result)
case PaywallResultType.Purchased:
if (OnPurchased.GetPersistentEventCount() == 0)
{
- Debug.LogWarning("[RevenueCatUI] Paywall purchase completed but OnPurchased event has no listeners.");
+ Debug.Log("[RevenueCatUI] Paywall purchase completed but OnPurchased event has no listeners.");
}
OnPurchased?.Invoke();
break;
case PaywallResultType.Restored:
if (OnRestored.GetPersistentEventCount() == 0)
{
- Debug.LogWarning("[RevenueCatUI] Paywall restore completed but OnRestored event has no listeners.");
+ Debug.Log("[RevenueCatUI] Paywall restore completed but OnRestored event has no listeners.");
}
OnRestored?.Invoke();
break;
case PaywallResultType.Cancelled:
if (OnCancelled.GetPersistentEventCount() == 0)
{
- Debug.LogWarning("[RevenueCatUI] Paywall cancelled but OnCancelled event has no listeners.");
+ Debug.Log("[RevenueCatUI] Paywall cancelled but OnCancelled event has no listeners.");
}
OnCancelled?.Invoke();
break;
case PaywallResultType.NotPresented:
if (OnNotPresented.GetPersistentEventCount() == 0)
{
- Debug.LogWarning("[RevenueCatUI] Paywall not presented but OnNotPresented event has no listeners.");
+ Debug.Log("[RevenueCatUI] Paywall not presented but OnNotPresented event has no listeners.");
}
OnNotPresented?.Invoke();
break;
case PaywallResultType.Error:
if (OnError.GetPersistentEventCount() == 0)
{
- Debug.LogWarning("[RevenueCatUI] Paywall error occurred but OnError event has no listeners.");
+ Debug.Log("[RevenueCatUI] Paywall error occurred but OnError event has no listeners.");
}
OnError?.Invoke();
break;
From 7c31085067e1232cecb7913568fe9c8035b5ebdf Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:43:04 +0200
Subject: [PATCH 08/11] capitalize Paywalls
---
RevenueCatUI/Scripts/PaywallsBehaviour.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
index 7466d34f..cc999ac9 100644
--- a/RevenueCatUI/Scripts/PaywallsBehaviour.cs
+++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
@@ -5,9 +5,9 @@
namespace RevenueCatUI
{
///
- /// MonoBehaviour component for presenting RevenueCat paywalls from the Unity Editor.
+ /// MonoBehaviour component for presenting RevenueCat Paywalls from the Unity Editor.
/// Provides an alternative to PaywallsPresenter for developers who prefer configuring
- /// paywalls through Unity's Inspector interface.
+ /// Paywalls through Unity's Inspector interface.
///
[AddComponentMenu("RevenueCat/Paywalls Behaviour")]
public class PaywallsBehaviour : MonoBehaviour
@@ -16,7 +16,7 @@ public class PaywallsBehaviour : MonoBehaviour
[Tooltip("The identifier of the offering to present. Leave empty to use the current offering.")]
[SerializeField] private string offeringIdentifier;
- [Tooltip("Whether to display a close button on the paywall (only for original template paywalls).")]
+ [Tooltip("Whether to display a close button on the paywall (only for original template RevenueCat Paywalls).")]
[SerializeField] private bool displayCloseButton = false;
[Header("Conditional Presentation")]
From 627f78b41ae1be111a8102bbc5b58ce071dbf043 Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Thu, 9 Oct 2025 13:04:36 +0200
Subject: [PATCH 09/11] PaywallOptions.Offering adjustment
---
RevenueCatUI/Scripts/PaywallOptions.cs | 2 -
RevenueCatUI/Scripts/PaywallsBehaviour.cs | 50 ++++++++++++++++---
.../Android/AndroidPaywallPresenter.cs | 14 +++---
3 files changed, 51 insertions(+), 15 deletions(-)
diff --git a/RevenueCatUI/Scripts/PaywallOptions.cs b/RevenueCatUI/Scripts/PaywallOptions.cs
index 2bd68362..dd2372bd 100644
--- a/RevenueCatUI/Scripts/PaywallOptions.cs
+++ b/RevenueCatUI/Scripts/PaywallOptions.cs
@@ -20,8 +20,6 @@ public class PaywallOptions
///
public bool DisplayCloseButton { get; set; } = false;
- internal string OfferingIdentifier => Offering?.Identifier;
-
internal Purchases.PresentedOfferingContext PresentedOfferingContext =>
Offering?.AvailablePackages != null && Offering.AvailablePackages.Count > 0
? Offering.AvailablePackages[0].PresentedOfferingContext
diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
index cc999ac9..6e35c2ed 100644
--- a/RevenueCatUI/Scripts/PaywallsBehaviour.cs
+++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
@@ -75,7 +76,7 @@ public async void PresentPaywall()
try
{
- var options = CreateOptions();
+ var options = await CreateOptionsAsync();
PaywallResult result;
if (!string.IsNullOrEmpty(requiredEntitlementIdentifier))
@@ -119,7 +120,7 @@ private async void PresentPaywallIfNeeded(string entitlementIdentifier)
try
{
- var options = CreateOptions();
+ var options = await CreateOptionsAsync();
var result = await PaywallsPresenter.PresentIfNeeded(entitlementIdentifier, options);
HandleResult(result);
}
@@ -134,13 +135,48 @@ private async void PresentPaywallIfNeeded(string entitlementIdentifier)
}
}
- private PaywallOptions CreateOptions()
+ private async Task GetOfferingsAsync()
{
- return new PaywallOptions
+ var purchases = GetComponent();
+ if (purchases == null)
{
- OfferingIdentifier = string.IsNullOrEmpty(offeringIdentifier) ? null : offeringIdentifier,
- DisplayCloseButton = displayCloseButton
- };
+ Debug.LogWarning($"[RevenueCatUI] Purchases component not found.");
+ return null;
+ }
+
+ var tcs = new TaskCompletionSource();
+
+ purchases.GetOfferings((offerings, error) =>
+ {
+ if (error != null)
+ {
+ Debug.LogWarning($"[RevenueCatUI] Error getting offerings: {error}");
+ tcs.SetResult(null);
+ }
+ else
+ {
+ tcs.SetResult(offerings);
+ }
+ });
+
+ return await tcs.Task;
+ }
+
+ private async Task CreateOptionsAsync()
+ {
+ if (!string.IsNullOrEmpty(offeringIdentifier))
+ {
+ var offerings = await GetOfferingsAsync();
+
+ if (offerings?.All != null && offerings.All.ContainsKey(offeringIdentifier))
+ {
+ return new PaywallOptions(offerings.All[offeringIdentifier], displayCloseButton);
+ }
+
+ Debug.LogWarning($"[RevenueCatUI] Offering '{offeringIdentifier}' not found. Using offering identifier only.");
+ return new PaywallOptions(displayCloseButton);
+ }
+ return new PaywallOptions(displayCloseButton);
}
private void HandleResult(PaywallResult result)
diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs
index 232fc121..baf087d4 100644
--- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs
+++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs
@@ -49,13 +49,14 @@ public Task PresentPaywallAsync(PaywallOptions options)
_current = new TaskCompletionSource();
try
{
- var offering = options?.OfferingIdentifier;
+ var offeringIdentifier = options?.Offering.Identifier;
var displayCloseButton = options?.DisplayCloseButton ?? false;
var presentedOfferingContextJson = options?.PresentedOfferingContext?.ToJsonString();
- Debug.Log($"[RevenueCatUI][Android] presentPaywall offering='{offering ?? ""}', displayCloseButton={displayCloseButton}");
+ Debug.Log($"[RevenueCatUI][Android] presentPaywall offering='{offeringIdentifier ?? ""}', " +
+ $"displayCloseButton={displayCloseButton}");
var currentActivity = AndroidApplication.currentActivity;
- _plugin.CallStatic("presentPaywall", new object[] { currentActivity, offering, presentedOfferingContextJson, displayCloseButton });
+ _plugin.CallStatic("presentPaywall", new object[] { currentActivity, offeringIdentifier, presentedOfferingContextJson, displayCloseButton });
}
catch (Exception e)
{
@@ -83,12 +84,13 @@ public Task PresentPaywallIfNeededAsync(string requiredEntitlemen
_current = new TaskCompletionSource();
try
{
- var offering = options?.OfferingIdentifier;
+ var offeringIdentifier = options?.Offering.Identifier;
var displayCloseButton = options?.DisplayCloseButton ?? true;
var presentedOfferingContextJson = options?.PresentedOfferingContext?.ToJsonString();
- Debug.Log($"[RevenueCatUI][Android] presentPaywallIfNeeded entitlement='{requiredEntitlementIdentifier}', offering='{offering ?? ""}', displayCloseButton={displayCloseButton}");
+ Debug.Log($"[RevenueCatUI][Android] presentPaywallIfNeeded entitlement='{requiredEntitlementIdentifier}', '" +
+ $"offering='{offeringIdentifier ?? ""}', displayCloseButton={displayCloseButton}");
var currentActivity = AndroidApplication.currentActivity;
- _plugin.CallStatic("presentPaywallIfNeeded", new object[] { currentActivity, requiredEntitlementIdentifier, offering, presentedOfferingContextJson, displayCloseButton });
+ _plugin.CallStatic("presentPaywallIfNeeded", new object[] { currentActivity, requiredEntitlementIdentifier, offeringIdentifier, presentedOfferingContextJson, displayCloseButton });
}
catch (Exception e)
{
From 2c0e358a35db40365d78512070b0182049938cb5 Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Thu, 9 Oct 2025 15:21:52 +0200
Subject: [PATCH 10/11] OfferingSelection
---
.../Scripts/PresentedOfferingContext.cs | 7 ++
RevenueCatUI/Scripts/PaywallOptions.cs | 68 ++++++++++++++-----
RevenueCatUI/Scripts/PaywallsBehaviour.cs | 46 ++-----------
.../Android/AndroidPaywallPresenter.cs | 4 +-
4 files changed, 67 insertions(+), 58 deletions(-)
diff --git a/RevenueCat/Scripts/PresentedOfferingContext.cs b/RevenueCat/Scripts/PresentedOfferingContext.cs
index 578580a9..55d14f6d 100644
--- a/RevenueCat/Scripts/PresentedOfferingContext.cs
+++ b/RevenueCat/Scripts/PresentedOfferingContext.cs
@@ -24,6 +24,13 @@ public PresentedOfferingContext(JSONNode response)
}
}
+ public PresentedOfferingContext(string offeringIdentifier)
+ {
+ OfferingIdentifier = offeringIdentifier;
+ PlacementIdentifier = null;
+ TargetingContext = null;
+ }
+
public override string ToString()
{
return $"{nameof(OfferingIdentifier)}: {OfferingIdentifier}\n" +
diff --git a/RevenueCatUI/Scripts/PaywallOptions.cs b/RevenueCatUI/Scripts/PaywallOptions.cs
index dd2372bd..cc61dc82 100644
--- a/RevenueCatUI/Scripts/PaywallOptions.cs
+++ b/RevenueCatUI/Scripts/PaywallOptions.cs
@@ -2,28 +2,57 @@
namespace RevenueCatUI
{
+ internal abstract class OfferingSelection
+ {
+ internal sealed class OfferingType : OfferingSelection
+ {
+ public Purchases.Offering Offering { get; }
+
+ public OfferingType(Purchases.Offering offering)
+ {
+ Offering = offering;
+ }
+
+ internal override Purchases.Offering GetOffering() => Offering;
+ internal override string GetOfferingIdentifier() => Offering.Identifier;
+ internal override Purchases.PresentedOfferingContext GetPresentedOfferingContext() =>
+ Offering.AvailablePackages != null && Offering.AvailablePackages.Count > 0
+ ? Offering.AvailablePackages[0].PresentedOfferingContext
+ : null;
+ }
+
+ internal sealed class IdOnly : OfferingSelection
+ {
+ public string OfferingId { get; }
+ private Purchases.PresentedOfferingContext _presentedOfferingContext;
+
+ public IdOnly(string offeringId)
+ {
+ OfferingId = offeringId;
+ _presentedOfferingContext = new Purchases.PresentedOfferingContext(offeringId);
+ }
+
+ internal override Purchases.Offering GetOffering() => null;
+ internal override string GetOfferingIdentifier() => OfferingId;
+ internal override Purchases.PresentedOfferingContext GetPresentedOfferingContext() => _presentedOfferingContext;
+ }
+
+ internal abstract Purchases.Offering GetOffering();
+ internal abstract string GetOfferingIdentifier();
+ internal abstract Purchases.PresentedOfferingContext GetPresentedOfferingContext();
+ }
+
///
/// Options for configuring paywall presentation.
///
[Serializable]
public class PaywallOptions
{
- ///
- /// The offering to present.
- /// If not provided, the current offering will be used.
- ///
- public Purchases.Offering Offering { get; set; }
+ internal readonly OfferingSelection _offeringSelection;
- ///
- /// Whether to display a close button on the paywall.
- /// Only applicable for original template paywalls, ignored for V2 Paywalls.
- ///
- public bool DisplayCloseButton { get; set; } = false;
-
- internal Purchases.PresentedOfferingContext PresentedOfferingContext =>
- Offering?.AvailablePackages != null && Offering.AvailablePackages.Count > 0
- ? Offering.AvailablePackages[0].PresentedOfferingContext
- : null;
+ internal bool DisplayCloseButton { get; }
+ internal string OfferingIdentifier => _offeringSelection?.GetOfferingIdentifier();
+ internal Purchases.PresentedOfferingContext PresentedOfferingContext => _offeringSelection?.GetPresentedOfferingContext();
///
/// Creates a new PaywallOptions instance.
@@ -32,6 +61,7 @@ public class PaywallOptions
/// Whether to display a close button. Only applicable for original template paywalls, ignored for V2 Paywalls.
public PaywallOptions(bool displayCloseButton = false)
{
+ _offeringSelection = null;
DisplayCloseButton = displayCloseButton;
}
@@ -42,7 +72,13 @@ public PaywallOptions(bool displayCloseButton = false)
/// Whether to display a close button. Only applicable for original template paywalls, ignored for V2 Paywalls.
public PaywallOptions(Purchases.Offering offering, bool displayCloseButton = false)
{
- Offering = offering;
+ _offeringSelection = offering != null ? new OfferingSelection.OfferingType(offering) : null;
+ DisplayCloseButton = displayCloseButton;
+ }
+
+ internal PaywallOptions(string offeringIdentifier, bool displayCloseButton = false)
+ {
+ _offeringSelection = !string.IsNullOrEmpty(offeringIdentifier) ? new OfferingSelection.IdOnly(offeringIdentifier) : null;
DisplayCloseButton = displayCloseButton;
}
}
diff --git a/RevenueCatUI/Scripts/PaywallsBehaviour.cs b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
index 6e35c2ed..651e2684 100644
--- a/RevenueCatUI/Scripts/PaywallsBehaviour.cs
+++ b/RevenueCatUI/Scripts/PaywallsBehaviour.cs
@@ -76,7 +76,7 @@ public async void PresentPaywall()
try
{
- var options = await CreateOptionsAsync();
+ var options = CreateOptions();
PaywallResult result;
if (!string.IsNullOrEmpty(requiredEntitlementIdentifier))
@@ -120,7 +120,7 @@ private async void PresentPaywallIfNeeded(string entitlementIdentifier)
try
{
- var options = await CreateOptionsAsync();
+ var options = CreateOptions();
var result = await PaywallsPresenter.PresentIfNeeded(entitlementIdentifier, options);
HandleResult(result);
}
@@ -135,48 +135,14 @@ private async void PresentPaywallIfNeeded(string entitlementIdentifier)
}
}
- private async Task GetOfferingsAsync()
+ private PaywallOptions CreateOptions()
{
- var purchases = GetComponent();
- if (purchases == null)
+ if (string.IsNullOrEmpty(offeringIdentifier))
{
- Debug.LogWarning($"[RevenueCatUI] Purchases component not found.");
- return null;
- }
-
- var tcs = new TaskCompletionSource();
-
- purchases.GetOfferings((offerings, error) =>
- {
- if (error != null)
- {
- Debug.LogWarning($"[RevenueCatUI] Error getting offerings: {error}");
- tcs.SetResult(null);
- }
- else
- {
- tcs.SetResult(offerings);
- }
- });
-
- return await tcs.Task;
- }
-
- private async Task CreateOptionsAsync()
- {
- if (!string.IsNullOrEmpty(offeringIdentifier))
- {
- var offerings = await GetOfferingsAsync();
-
- if (offerings?.All != null && offerings.All.ContainsKey(offeringIdentifier))
- {
- return new PaywallOptions(offerings.All[offeringIdentifier], displayCloseButton);
- }
-
- Debug.LogWarning($"[RevenueCatUI] Offering '{offeringIdentifier}' not found. Using offering identifier only.");
return new PaywallOptions(displayCloseButton);
}
- return new PaywallOptions(displayCloseButton);
+
+ return new PaywallOptions(offeringIdentifier, displayCloseButton);
}
private void HandleResult(PaywallResult result)
diff --git a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs
index baf087d4..f2a7e298 100644
--- a/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs
+++ b/RevenueCatUI/Scripts/Platforms/Android/AndroidPaywallPresenter.cs
@@ -49,7 +49,7 @@ public Task PresentPaywallAsync(PaywallOptions options)
_current = new TaskCompletionSource();
try
{
- var offeringIdentifier = options?.Offering.Identifier;
+ var offeringIdentifier = options?.OfferingIdentifier;
var displayCloseButton = options?.DisplayCloseButton ?? false;
var presentedOfferingContextJson = options?.PresentedOfferingContext?.ToJsonString();
@@ -84,7 +84,7 @@ public Task PresentPaywallIfNeededAsync(string requiredEntitlemen
_current = new TaskCompletionSource();
try
{
- var offeringIdentifier = options?.Offering.Identifier;
+ var offeringIdentifier = options?.OfferingIdentifier;
var displayCloseButton = options?.DisplayCloseButton ?? true;
var presentedOfferingContextJson = options?.PresentedOfferingContext?.ToJsonString();
Debug.Log($"[RevenueCatUI][Android] presentPaywallIfNeeded entitlement='{requiredEntitlementIdentifier}', '" +
From efb0e0957f4133a24c452d830a2318d35d2e75ae Mon Sep 17 00:00:00 2001
From: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Date: Thu, 9 Oct 2025 16:19:23 +0200
Subject: [PATCH 11/11] fix compilation of PurchasesListener
---
Subtester/Assets/Scripts/PurchasesListener.cs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/Subtester/Assets/Scripts/PurchasesListener.cs b/Subtester/Assets/Scripts/PurchasesListener.cs
index 1f927698..b2c26747 100644
--- a/Subtester/Assets/Scripts/PurchasesListener.cs
+++ b/Subtester/Assets/Scripts/PurchasesListener.cs
@@ -263,10 +263,7 @@ private System.Collections.IEnumerator PresentPaywallCoroutine()
private System.Collections.IEnumerator PresentPaywallWithOptionsCoroutine()
{
- var options = new RevenueCatUI.PaywallOptions
- {
- DisplayCloseButton = false
- };
+ var options = new RevenueCatUI.PaywallOptions(displayCloseButton: false);
var task = RevenueCatUI.PaywallsPresenter.Present(options);
while (!task.IsCompleted) { yield return null; }