Skip to content

Open the performance-optimized paywall URLs when the flag is on - #9757

Open
Kyriakos-Georgiopoulos wants to merge 3 commits into
developfrom
feature/kyriakos/performance-optimized-paywalls/url-resolver
Open

Kyriakos-Georgiopoulos wants to merge 3 commits into
developfrom
feature/kyriakos/performance-optimized-paywalls/url-resolver

Conversation

@Kyriakos-Georgiopoulos

@Kyriakos-Georgiopoulos Kyriakos-Georgiopoulos commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Task/Issue URL: https://app.asana.com/1/137249556945/project/1217319487385906/task/1217750626830941?focus=true
Tech Design URL (if applicable):
API Proposals URL(s) (if applicable): None

Description

Opens the performance-optimized paywall page when the performanceOptimizedPaywalls flag is on, so the front end can test the faster entry points on Android.

  • The paths come from the flag settings, so they can change in remote config without an app release Add performanceOptimizedPaywalls feature flag (off by default) privacy-configuration#5809
  • The URL carries trial=true or trial=false from free trial eligibility, and pir=false when PIR is not part of the offer, so the page renders without asking the app what the user is being offered.
  • Params the app already sets, such as origin, are carried onto the new path unchanged.
  • With the flag off, the old URL is loaded and nothing changes.

Steps to test this PR

VPN paywall

  • Open from Settings > Privacy Pro. URL should be /subscriptions/new/mobile/vpn.

Duck.ai paywall

  • Open from a Duck.ai entry point. URL should be /subscriptions/new/mobile/duckai, with no featurePage in the query.

Query params

  • Trial eligible account: trial=true. Account that already used a trial: trial=false.
  • Region without PIR: pir=false present. Region with PIR: no pir param.
  • Open from app settings: origin=funnel_appsettings_android and environment=staging both survive onto the new path.

Flag off

  • Turn performanceOptimizedPaywalls off, open the paywall. URL should be /subscriptions with no trial or pir, and the old page loads.

Fallback

  • Force stop the app, reopen, go to the paywall before Google Play has loaded products. Old URL loads, no error page, no crash.

UX changes

Before After
existing_paywall.mp4
optimized_paywall.mp4

Note

Medium Risk
Changes which URL loads at paywall open and depends on Play offer/trial state; fallbacks keep the legacy URL, but wrong rewrite could affect purchase funnel and analytics origins.

Overview
When performanceOptimizedPaywalls is enabled, the subscriptions webview no longer loads the generic buy URL directly. A new PaywallUrlResolver rewrites matching paywall URLs to remote-config entry paths (VPN vs Duck.ai from featurePage), injects trial from free-trial eligibility and pir=false when PIR isn’t on the offer, and preserves other query params like origin. If the flag is off, offers aren’t ready, or rewrite fails, the original URL is used unchanged.

SubscriptionWebViewViewModel resolves that URL once and exposes it via initialUrl; SubscriptionsWebViewActivity waits for the first resolved URL before loadUrl. Shared constants for feature pages and query keys replace a local Duck.ai string in JS messaging. Unit tests cover resolver behavior and view-model URL resolution.

Reviewed by Cursor Bugbot for commit 6efc658. Bugbot is set up for automated code reviews on this repo. Configure here.

@nalcalag nalcalag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works as expected. Main thing I'd revisit is how the resolved URL reaches the WebView , now that resolving isn't instant, a fire-and-forget event leaves a window where it can go missing and the user gets an empty screen. Holding it as state the activity can read at any point (a MutableStateFlow rather than a Command) would be safer. Something like:

private val _initialUrl = MutableStateFlow<String?>(null)
val initialUrl = _initialUrl.asStateFlow()

Resolves the paywall URL where the webview loads it, so every entry point is covered. Non-owned params such as origin are carried onto the new path.

https://app.asana.com/1/137249556945/task/1217750626830941
@Kyriakos-Georgiopoulos
Kyriakos-Georgiopoulos force-pushed the feature/kyriakos/performance-optimized-paywalls/url-resolver branch from 646e4bc to 9aac626 Compare September 11, 2026 08:27
@Kyriakos-Georgiopoulos
Kyriakos-Georgiopoulos force-pushed the feature/kyriakos/performance-optimized-paywalls/url-resolver branch from c08ad22 to 6efc658 Compare September 11, 2026 09:52

@nalcalag nalcalag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants