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; }