Add PurchaseLogic support for paywall presentation when PurchasesAreCompletedBy.MyApp#842
Merged
Conversation
Replace the PaywallTrampolineActivity (which launched a separate PaywallActivity on top of Unity) with a PaywallView hosted in a hardware-accelerated Dialog window. This presents the paywall on top of the Unity UI without leaving the current Activity. Key changes: - New PaywallViewPresenter that creates a fullscreen Dialog with FLAG_HARDWARE_ACCELERATED to avoid software rendering crashes - PaywallBackPressedOwner provides OnBackPressedDispatcherOwner for Compose's BackHandler (Unity doesn't use ComponentActivity) - PaywallListener tracks purchase/restore/error events and maps them to the same result strings (PURCHASED, RESTORED, etc.) - presentPaywallIfNeeded checks entitlements via Purchases SDK - Removed PaywallTrampolineActivity and PaywallUnityOptions - C# public API and iOS remain unchanged Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Explicitly set window layout to MATCH_PARENT for both dimensions to guarantee fullscreen on tablets and foldables. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds support for custom purchase and restore handlers when presenting paywalls in PurchasesAreCompletedBy.MyApp mode. This allows customers to handle purchases and restores themselves through the paywall UI, matching the existing functionality in React Native and Capacitor SDKs. New public API: - PurchaseLogic class with PerformPurchaseHandler and PerformRestoreHandler delegates - PurchaseLogicResult enum (Success, Cancellation, Error) - Optional purchaseLogic parameter on PaywallOptions constructors Implementation details: - Uses HybridPurchaseLogicBridge from purchases-hybrid-common on both iOS and Android - Android: Dialog uses FLAG_NOT_FOCUSABLE to prevent Unity's message processing from breaking after billing Activity lifecycle transitions - Android: AndroidJavaProxy.Invoke override handles method dispatch since Unity's proxy matching doesn't always resolve the direct C# methods - Android: SynchronizationContext captured at setup time and used to Post async work, ensuring continuations run on the main thread - iOS: Uses PaywallProxy.presentPaywall(options:purchaseLogicBridge:paywallResultHandler:) - Subtester: Adds a "Paywall w/ PurchaseLogic" button (disabled in RevenueCat mode) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9 tasks
PurchasesAreCompletedBy.MyApp
tonidero
commented
Mar 4, 2026
The paywall Dialog used FLAG_NOT_FOCUSABLE permanently, which prevented all back navigation (both KEYCODE_BACK and gesture back on API 33+) from reaching the Dialog. Now FLAG_NOT_FOCUSABLE is only toggled on during active PurchaseLogic operations (where it's needed for correct threading) and off otherwise. Back navigation is handled via: - Dialog.onBackPressed() override for pre-API 33 / button navigation - OnBackInvokedCallback on the Dialog's window for API 33+ gesture nav Both paths dispatch through the OnBackPressedDispatcher for Compose's BackHandler, with a fallback to direct dismissal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make PerformPurchaseHandler accept a PurchaseLogicPurchaseParams object instead of a bare Package, so additional purchase parameters can be added in the future without breaking the API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add currentDialog != null check in the PaywallView dismiss handler to prevent sendPaywallResult from being called twice if the onDismissListener fires before the dismiss handler. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vegaro
reviewed
Mar 9, 2026
… accessed on the UI thread Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…as soon as possible
…wall Resolve conflicts in PaywallViewPresenter.java by adopting the refactored structure from feat/paywall-view-android (extracted methods, edge-to-edge support, cleaner back press routing) while preserving PurchaseLogic additions (HybridPurchaseLogicBridge, setDialogNotFocusable, onPurchaseLogicResult). Conflict resolutions: - Imports: merged both sets (DialogInterface/KeyEvent from origin + Handler/Looper from HEAD) - onError: adopted origin's behavior (RESULT_NOT_PRESENTED) per commit c574d13 - Dialog creation: use origin's createDialog() with HEAD's FLAG_NOT_FOCUSABLE comment - PaywallListener: use origin's extracted createPaywallListener() with HEAD's setDialogNotFocusable calls - Back press: use origin's setupBackPressRouting (OnKeyListener/OnBackInvokedCallback) - dismissDialog: use origin's simpler version + HEAD's currentPurchaseLogicBridge cleanup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vegaro
approved these changes
Mar 11, 2026
|
|
||
| private static async void OnPerformPurchaseAsync(string requestId, string packageJson) | ||
| { | ||
| if (s_currentPurchaseLogic?.PerformPurchase == null) |
Member
There was a problem hiding this comment.
super minor. since you're checking this is not null earlier (in the constructor) I believe this is dead code
Contributor
Author
There was a problem hiding this comment.
I think it could still be relevant in case we haven't set the s_currentPurchaseLogic property before this is called. Which normally it shouldn't... but I guess there is no harm on keeping this?
| internal PurchaseLogicPurchaseParams(Purchases.Package packageToPurchase) | ||
| { | ||
| PackageToPurchase = packageToPurchase; | ||
| } |
Member
There was a problem hiding this comment.
we added replacement mode and subscription option in Android as well. We can add that in another PR
tonidero
added a commit
that referenced
this pull request
Mar 11, 2026
## Summary - Replaces the `PaywallTrampolineActivity` approach (which launched a separate `PaywallActivity` on top of Unity) with a `PaywallView` hosted inside a hardware-accelerated `Dialog` window - The paywall is now presented on top of the Unity UI without leaving the current Activity - The C# public API (`PaywallsPresenter.Present()` / `PresentIfNeeded()`) and iOS implementation remain unchanged - The final objective in a follow-up PR is to add PurchaseLogic functionality to this API: #842 ## Changes ### New: `PaywallViewPresenter.java` - Creates a fullscreen `Dialog` with `FLAG_HARDWARE_ACCELERATED` to ensure Compose + Coil can use hardware bitmaps (Unity's main window uses software rendering) - `PaywallBackPressedOwner` provides `OnBackPressedDispatcherOwner` for Compose's `BackHandler` (Unity's `Activity` doesn't extend `ComponentActivity`) - `PaywallListener` tracks purchase/restore/error events and maps them to the existing result strings (`PURCHASED`, `RESTORED`, `CANCELLED`, `ERROR`, `NOT_PRESENTED`) - `presentPaywallIfNeeded` checks entitlements via `Purchases.getSharedInstance().getCustomerInfo()` before showing the paywall ### Modified - `RevenueCatUI.java` — delegates to `PaywallViewPresenter` instead of `PaywallTrampolineActivity` - `AndroidManifest.xml` — removed `PaywallTrampolineActivity` registration - `build.gradle` — added `compileOnly` for `androidx.compose.ui:ui-android` (needed to resolve `PaywallView`'s class hierarchy at compile time) ### Removed - `PaywallTrampolineActivity.java` — no longer needed - `PaywallUnityOptions.java` — `Parcelable` was only needed for Intent extras ## Test plan - [ ] Present paywall unconditionally — verify it appears overlaid on Unity content - [ ] Present paywall if needed (user has entitlement) — verify `NOT_PRESENTED` result - [ ] Present paywall if needed (user lacks entitlement) — verify paywall appears - [ ] Dismiss via close button — verify `CANCELLED` result - [ ] Dismiss via back button — verify `CANCELLED` result - [ ] Complete a purchase — verify `PURCHASED` result - [ ] Complete a restore — verify `RESTORED` result - [ ] Trigger a purchase error then dismiss — verify `ERROR` result - [ ] Verify iOS paywall still works unchanged 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve conflicts after squash-merge of feat/paywall-view-android into main. All conflicts were PurchaseLogic additions (unique to this branch) vs main (which doesn't have them): kept HEAD's PurchaseLogic code in both files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ajpallares
pushed a commit
that referenced
this pull request
Mar 11, 2026
**This is an automatic release.** ## RevenueCat SDK ### ✨ New Features * Add PurchaseLogic support for paywall presentation when `PurchasesAreCompletedBy.MyApp` (#842) via Toni Rico (@tonidero) ### 📦 Dependency Updates * [AUTOMATIC BUMP] Updates purchases-hybrid-common to 17.46.1 (#854) via RevenueCat Git Bot (@RCGitBot) * [Android 9.23.1](https://github.com/RevenueCat/purchases-android/releases/tag/9.23.1) * [Android 9.23.0](https://github.com/RevenueCat/purchases-android/releases/tag/9.23.0) * [iOS 5.61.0](https://github.com/RevenueCat/purchases-ios/releases/tag/5.61.0) * [AUTOMATIC BUMP] Updates purchases-hybrid-common to 17.46.0 (#853) via RevenueCat Git Bot (@RCGitBot) * [Android 9.23.1](https://github.com/RevenueCat/purchases-android/releases/tag/9.23.1) * [Android 9.23.0](https://github.com/RevenueCat/purchases-android/releases/tag/9.23.0) * [iOS 5.61.0](https://github.com/RevenueCat/purchases-ios/releases/tag/5.61.0) * [AUTOMATIC BUMP] Updates purchases-hybrid-common to 17.44.0 (#850) via RevenueCat Git Bot (@RCGitBot) * [Android 9.23.1](https://github.com/RevenueCat/purchases-android/releases/tag/9.23.1) * [Android 9.23.0](https://github.com/RevenueCat/purchases-android/releases/tag/9.23.0) * [iOS 5.61.0](https://github.com/RevenueCat/purchases-ios/releases/tag/5.61.0) ## RevenueCatUI SDK ### ✨ New Features * Add full screen paywall presentation support (#839) via Cesar de la Vega (@vegaro) ### 🔄 Other Changes * Redo Subtester app and fix Android emulators support (#855) via Cesar de la Vega (@vegaro) * Support PaywallActivity screenOrientation manifest override (#844) via Toni Rico (@tonidero) * refactor: Use PaywallView instead of PaywallActivity on Android (#841) via Toni Rico (@tonidero) * Add API tests for Paywalls and CustomerCenter presenters (#735) via Facundo Menzella (@facumenzella) * Bump fastlane-plugin-revenuecat_internal from `f5c099b` to `e146447` (#852) via dependabot[bot] (@dependabot[bot]) * Bump fastlane-plugin-revenuecat_internal from `8cd957f` to `f5c099b` (#848) via dependabot[bot] (@dependabot[bot])
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.
Summary
PurchaseLogicparameter to paywall presentation, allowing customers inPurchasesAreCompletedBy.MyAppmode to handle purchases and restores themselves through the paywall UIPurchaseLogic(withPerformPurchaseHandlerandPerformRestoreHandlerdelegates) andPurchaseLogicResultenumpurchaseLogicparameter added to allPaywallOptionsconstructorsHybridPurchaseLogicBridgefrom purchases-hybrid-common on both platformsPurchasePackagewithin the handlerAndroid-specific
FLAG_NOT_FOCUSABLEto prevent Unity's message processing from breaking afterProxyBillingActivity(Google Play billing) lifecycle transitionsAndroidJavaProxy.Invokeoverride handles method dispatch as Unity's proxy matching doesn't always resolve direct C# methodsSynchronizationContextcaptured at setup time to ensure async continuations run on the main threadiOS-specific
PaywallProxy.presentPaywall(options:purchaseLogicBridge:paywallResultHandler:)nilbridgeRelated PRs
Test plan
PurchaseLogicon Android — purchase, cancel, and error flowsPurchaseLogicon iOS — purchase, cancel, and error flowsPurchaseLogic— verify no regression on both platforms🤖 Generated with Claude Code