feat(paywalls): Add callbacks to presentPaywall and presentPaywallIfNeeded - #1898
Draft
AlvaroBrey wants to merge 3 commits into
Draft
AlvaroBrey wants to merge 3 commits into
AlvaroBrey wants to merge 3 commits into
Conversation
AlvaroBrey
force-pushed
the
feat/present-paywall-callbacks
branch
from
September 15, 2026 07:02
7d29307 to
cfbc180
Compare
AlvaroBrey
force-pushed
the
feat/present-paywall-callbacks
branch
from
September 15, 2026 08:44
cfbc180 to
997960f
Compare
Member
Author
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 06db779. Configure here.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PaywallViewcallbacks (onPurchaseStarted,onPurchaseCompleted,onPurchaseCancelled,onPurchaseError,onRestoreCompleted,onRestoreError,onWebCheckoutOpened,onUrlOpened,onInteraction) as named optional parameters ofpresentPaywall()andpresentPaywallIfNeeded(), so modally presented paywalls report the same events as the embedded view. NoonDismiss: the returnedFuture<PaywallResult>completes on dismissal.com.revenuecat.purchasesui/PresentedPaywallchannel handled by the existingPaywallViewMethodHandler. The plugin channel'sonRestore*andonDismissmethod names already belong to the customer center handler.hasListener). On Android that listener is a non-serializable arg, so a paywall recreated after process death is dismissed withERROR; presentations without callbacks keep today's behavior.presentCustomerCenter()callbacks make.onPurchasePackageInitiatedis not exposed; the native wrapper auto-resumes, as it does forPaywallView.onPurchaseCompletednever firing on iOS when a purchase completes with no StoreKit transaction: the handler parsed the absent transaction and threw. It affectedPaywallViewand the footer view already, since all four surfaces sharePaywallViewMethodHandler; the callback now receives an emptyStoreTransaction.Checklist
Agent description
Motivation
PaywallViewgainedonInteractionin #1897, but apps that present paywalls modally still only get the finalPaywallResult. They cannot route interactions to analytics or react to purchase events while the paywall is up. Both natives already have the hook unused:PresentPaywallOptions.paywallListeneron Android andPaywallProxy.delegateon iOS.Description
purchases_ui_flutter.dart: both present methods build aPaywallViewMethodHandlerwhen any callback is given, install it on the presented-paywall channel, send'hasListener': true, and clear the handler in afinallyonce the result arrives. Without callbacks the argument map is unchanged.PurchasesUiFlutterPlugin.kt: a secondMethodChanneland aPaywallListenerWrapperpassed aspaywallListenerwhenhasListener. The wrapper bodies moved to a sharedforwardingPaywallListener(channel)thatviews/PaywallView.ktnow uses too, so both surfaces send the same method names.onPurchasePackageInitiatedis left to PHC's default, which resumes the purchase.PurchasesUiFlutterPlugin.swift:PresentedPaywallDelegateForwardermirrors thePurchasesUiPaywallViewdelegate extension on a secondFlutterMethodChannel. It is set aspaywallProxy.delegatebefore presenting and dropped in the result handler. It does not implement the purchase-initiated delegate method, soPaywallProxyresumes automatically. macOS is untouched (same file,#if os(iOS)).api_tester:_checkPresentPaywallWithListenerscovers every callback on both methods.Regression gates
paywall_view_method_handler_test.dart:onPurchaseCompleted fires when there is no store transaction, which throwstype 'Null' is not a subtype of type 'Map<dynamic, dynamic>'without the handler guard.presentPaywall callbacks › delivers callbacks while presented and stops after the result: a call on the presented-paywall channel reaches the callback while the native result is pending, and no longer after it resolves.presentPaywall callbacks › sends hasListener only when a callback is given; the existing exact-match argument tests prove the key is absent otherwise.Rejected:
purchases_ui_flutterchannel with prefixed method names: duplicates the parsing already inPaywallViewMethodHandlerand keeps the customer center switch growing.PaywallListenerobject parameter: diverges from bothpresentCustomerCenter()andPaywallView, which take individual callbacks.Note
Medium Risk
Touches purchase/restore event plumbing across Dart, Android, and iOS; behavior changes only when callbacks are passed, but incorrect listener lifecycle could drop or duplicate events during modal presentation.
Overview
Modal paywalls can now emit the same lifecycle events as embedded
PaywallView.RevenueCatUI.presentPaywall()andpresentPaywallIfNeeded()accept optional callbacks for purchase, restore, web checkout, URL opens, and paywall interactions; dismissal still comes from the returnedFuture<PaywallResult>(noonDismiss).When any callback is provided, Dart registers
PaywallViewMethodHandleron a newcom.revenuecat.purchasesui/PresentedPaywallchannel, passeshasListener: trueto native, and clears the handler when the presentation completes. Android attachesforwardingPaywallListener(extracted from inlinePaywallViewcode) only whenhasListeneris set. iOS addsPresentedPaywallDelegateForwarderon the same channel and tears it down in the paywall result handler.Bugfix:
PaywallViewMethodHandlerno longer crashes ononPurchaseCompletedwhenstoreTransactionis null (iOS purchases without a StoreKit transaction); callers get an emptyStoreTransactioninstead.Reviewed by Cursor Bugbot for commit 06db779. Bugbot is set up for automated code reviews on this repo. Configure here.