Skip to content

Create paywall from workflow response. Add USE_WORKFLOWS_ENDPOINT BuildConfig#3350

Merged
vegaro merged 51 commits into
mainfrom
cesar/wfl-46-android-workflowview
Apr 22, 2026
Merged

Create paywall from workflow response. Add USE_WORKFLOWS_ENDPOINT BuildConfig#3350
vegaro merged 51 commits into
mainfrom
cesar/wfl-46-android-workflowview

Conversation

@vegaro

@vegaro vegaro commented Apr 16, 2026

Copy link
Copy Markdown
Member

Wires the workflows endpoint into PaywallViewModel so that when USE_WORKFLOWS_ENDPOINT is enabled, paywalls are resolved through the workflow response rather than directly from offerings.


Note

Medium Risk
Touches paywall state construction and introduces a new (toggle-gated) network path that could affect paywall rendering and error handling when enabled.

Overview
Adds an opt-in workflow-based paywall resolution path in RevenueCatUI. When BuildConfig.USE_WORKFLOWS_ENDPOINT is enabled (via -Prevenuecat.useWorkflowsEndpoint=true), PaywallViewModel fetches GET /workflows/{id} and the offerings list in parallel, then constructs the displayed paywall from the workflow’s initial screen and its offeringIdentifier, instead of relying solely on the offerings cache.

To support this, the SDK introduces an internal suspend API Purchases.awaitGetWorkflow, plumbs workflow fetching through PurchasesOrchestrator/WorkflowManager, and renames WorkflowFetchResult to WorkflowDataResult (plus updates workflow JSON model to use offering_identifier). A new WorkflowScreenMapper maps WorkflowScreen into Offering.PaywallComponents, with accompanying unit tests and mock interface updates.

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

@vegaro vegaro force-pushed the cesar/wfl-46-android-workflowview branch 2 times, most recently from b7b89f3 to e3802b2 Compare April 16, 2026 15:50
vegaro and others added 21 commits April 17, 2026 11:17
- Throw JSONException instead of IllegalArgumentException for unknown
  workflow response actions so AsyncCall.run() catches it gracefully
- Use imported InternalRevenueCatAPI in @file:OptIn instead of FQN

Made-with: Cursor
Each individual workflow already carries its own ui_config, so the
top-level field in the list response is redundant and unused. Also
removes a leftover unused import in Backend.kt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Exposes metadata as Map<String, Any> for easier future consumption.
Uses a private JsonObject backing field for deserialization and a
computed property that converts via a JsonObject.toStringAnyMap()
extension. Also adds the missing WorkflowJsonParser.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neither field is needed for multipage paywalls. Also removes the
JsonObjectToMapSerializer helper that was introduced for metadata.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add @serializable WorkflowDetailResponse with action, data, url, hash,
  and enrolled_variants fields
- Make WorkflowResponseAction serializable with @SerialName annotations
- Add hash field to PublishedWorkflow for CDN content integrity
- Add parseWorkflowDetailResponse to WorkflowJsonParser

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move CDN fetching and envelope parsing out of Backend into a higher-level
resolver. Backend now only handles the HTTP call and deserializes the
WorkflowDetailResponse envelope.

- Remove workflowCdnFetcher from Backend constructor
- Delete WorkflowDetailHttpProcessor (and its tests)
- Remove CDN fetcher from PurchasesFactory Backend construction
- Update BackendWorkflowsTest to verify envelope deserialization
- Remove workflowCdnFetcher from all other Backend test constructors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves WorkflowDetailResponse envelope into WorkflowFetchResult:
- Inline: unwraps data directly
- CDN: fetches from URL, parses, and optionally verifies SHA-256 hash
  (only when SignatureVerificationMode.shouldVerify is true)

Hash verification mirrors the backend's compute_workflow_json_hash using
canonical JSON (sorted keys, compact separators, excluding hash field).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Callers use WorkflowManager.getWorkflow() to get a fully resolved
WorkflowFetchResult without knowing about the envelope/CDN details.

- Wire WorkflowManager in PurchasesFactory with resolver and CDN fetcher
- Inject into PurchasesOrchestrator for future use

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds public methods to fetch workflows (list and detail) and updates
workflow models/parser to support the full workflow structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Purchases public API (getWorkflowsWith, getWorkflowWith) and
coroutine extensions belong on the WorkflowView branch, not here.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vegaro vegaro force-pushed the feat/multipage-paywalls-milestone-1-go8 branch from 4dc08fa to cddfcaf Compare April 17, 2026 09:17
@vegaro vegaro force-pushed the cesar/wfl-46-android-workflowview branch from e3802b2 to 366f6f7 Compare April 17, 2026 09:21
@vegaro vegaro force-pushed the cesar/wfl-46-android-workflowview branch from 366f6f7 to 3cdea00 Compare April 17, 2026 12:15
@emerge-tools

emerge-tools Bot commented Apr 17, 2026

Copy link
Copy Markdown

📸 Snapshot Test

588 unchanged

Name Added Removed Modified Renamed Unchanged Errored Approval
TestPurchasesUIAndroidCompatibility
com.revenuecat.testpurchasesuiandroidcompatibility
0 0 0 0 331 0 N/A
TestPurchasesUIAndroidCompatibility Paparazzi
com.revenuecat.testpurchasesuiandroidcompatibility.paparazzi
0 0 0 0 257 0 N/A

🛸 Powered by Emerge Tools

@vegaro vegaro force-pushed the cesar/wfl-46-android-workflowview branch from cee3982 to 229753f Compare April 17, 2026 13:17
@vegaro vegaro marked this pull request as ready for review April 22, 2026 10:19
@vegaro vegaro requested a review from a team as a code owner April 22, 2026 10:19
@codecov

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 79.17%. Comparing base (82eeba4) to head (4948192).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
...uecat/purchases/common/workflows/WorkflowModels.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3350   +/-   ##
=======================================
  Coverage   79.17%   79.17%           
=======================================
  Files         360      360           
  Lines       14429    14429           
  Branches     1979     1979           
=======================================
  Hits        11424    11424           
  Misses       2194     2194           
  Partials      811      811           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@facumenzella facumenzella left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

let's go

@vegaro vegaro added pr:other and removed pr:fix A bug fix labels Apr 22, 2026
purchases.storefrontCountryCode,
options.mode,
)
}

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.

Workflow enrolled variants data silently dropped

Medium Severity

updateStateFromWorkflow receives a WorkflowDataResult containing enrolledVariants (experiment variant assignments from the server) but completely ignores it. The enrolledVariants map is fetched from the workflow endpoint, threaded through WorkflowDetailResolver and WorkflowManager, but silently dropped when the paywall Offering is constructed. This data is likely needed for experiment tracking or analytics parity with iOS (which uses both fields from the fetch result).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7778578. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is future work, so we can ignore. Still unclear how we'll use enrolledVariants. Probably passing it to PresentedOfferingContext?

Comment thread purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4948192. Configure here.

onSuccess = continuation::resume,
onError = { continuation.resumeWithException(PurchasesException(it)) },
)
}

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.

awaitGetWorkflow inconsistently placed as member function

Low Severity

Every other await* suspend wrapper in the codebase (awaitCreateSupportTicket, awaitCustomerCenterConfigData, awaitOfferings, awaitSyncPurchases, etc.) is defined as an extension function in coroutinesExtensions.kt. The new awaitGetWorkflow breaks this convention by being a member function directly inside the Purchases class. This makes the method harder to discover alongside its peers and introduces an inconsistency in how internal coroutine-friendly APIs are organized.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4948192. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Leaving as is

@vegaro vegaro enabled auto-merge April 22, 2026 12:25
@vegaro vegaro added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit 6611d80 Apr 22, 2026
40 checks passed
@vegaro vegaro deleted the cesar/wfl-46-android-workflowview branch April 22, 2026 12:56
matteinn pushed a commit to matteinn/purchases-android that referenced this pull request Apr 28, 2026
**This is an automatic release.**

## RevenueCat SDK
### 🐞 Bugfixes
* fix: move Google BillingClient connection off the main thread (RevenueCat#3369)
via Toni Rico (@tonidero)
* [EXTERNAL] fix(google): guard showInAppMessages against BillingClient
runtime crashes (RevenueCat#3367) by @matteinn (RevenueCat#3368) via Monika Mateska
(@MonikaMateska)

## RevenueCatUI SDK
### Paywallv2
#### 🐞 Bugfixes
* Add Workflows network layer (RevenueCat#3300) via Cesar de la Vega (@vegaro)

### 🔄 Other Changes
* Fix `revenuecat.useWorkflowsEndpoint` compiler flag (RevenueCat#3374) via Cesar
de la Vega (@vegaro)
* Create paywall from workflow response. Add `USE_WORKFLOWS_ENDPOINT`
BuildConfig (RevenueCat#3350) via Cesar de la Vega (@vegaro)
* Refactor: Remove unnecessary lint suppressions (RevenueCat#3373) via cursor[bot]
(@cursor[bot])
* Bump fastlane-plugin-revenuecat_internal from `a1eed48` to `b822f01`
(RevenueCat#3371) via dependabot[bot] (@dependabot[bot])
* Bump fastlane from 2.232.2 to 2.233.0 (RevenueCat#3370) via dependabot[bot]
(@dependabot[bot])
* Attempt to fix `AssertionError` "ms is denormalized" in
`QueryPurchasesUseCaseTest` (RevenueCat#3361) via Cesar de la Vega (@vegaro)
* Update baseline profiles (RevenueCat#3296) via Jaewoong Eum (@skydoves)
* fix: reduce precision for flaky HeaderDirectHeroImage snapshot (RevenueCat#3362)
via Cesar de la Vega (@vegaro)
* Fix test failures reported twice (RevenueCat#3360) via Cesar de la Vega
(@vegaro)
* refactor: extract `updateStateFromOffering` in `PaywallViewModel`
(RevenueCat#3359) via Cesar de la Vega (@vegaro)
* [Fix] Include parent tabs component_name in tab-control switch
interaction events (RevenueCat#3358) via Monika Mateska (@MonikaMateska)
* Refactor: Remove unnecessary lint suppressions (RevenueCat#3348) via cursor[bot]
(@cursor[bot])
* fix: always upload CI test results even when tests fail (RevenueCat#3357) via
Cesar de la Vega (@vegaro)
* refactor: extract `RevenueCatDialogScaffold` (RevenueCat#3355) via Cesar de la
Vega (@vegaro)
* Fix Slack notifications for nightly integration tests (RevenueCat#3354) via Toni
Rico (@tonidero)
* UI events for paywall component interactions (RevenueCat#3287) via Monika
Mateska (@MonikaMateska)
* Bump fastlane-plugin-revenuecat_internal from `20911d1` to `a1eed48`
(RevenueCat#3351) via dependabot[bot] (@dependabot[bot])

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Primarily a version bump and release automation updates (docs
deploy/redirect and changelog); no functional library code changes
beyond updating embedded version constants.
> 
> **Overview**
> Cuts the `10.2.1` release by updating version references across the
repo (Gradle `VERSION_NAME`, internal `frameworkVersion`, sample/test
app dependency pins, and `.version`).
> 
> Updates the docs release pipeline and website redirect to publish and
point at `10.2.1`, and refreshes `CHANGELOG.md`/`CHANGELOG.latest.md`
with the 10.2.1 release notes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a0a325b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants