Conversation
6ebd871 to
be06ed6
Compare
- Add `getWorkflows` and `getWorkflow` endpoint paths - Add `WorkflowsListResponse` and `PublishedWorkflow` response models - Add `WorkflowDetailProcessor` to handle `inline`/`use_cdn` response actions - Add `WorkflowCdnFetcher` protocol and `DirectWorkflowCdnFetcher` implementation - Add `GetWorkflowsOperation` and `GetWorkflowOperation` cacheable network operations - Add `WorkflowsAPI` facade and wire into `Backend` - Add unit tests for all new components iOS equivalent of RevenueCat/purchases-android#3300 Made-with: Cursor
Made-with: Cursor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ 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 37aaf43. Configure here.
purchases/src/main/kotlin/com/revenuecat/purchases/common/workflows/WorkflowCdnFetcher.kt
Outdated
Show resolved
Hide resolved
- 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
| import com.revenuecat.purchases.InternalRevenueCatAPI | ||
| import kotlinx.serialization.json.Json | ||
|
|
||
| internal object WorkflowJsonParser { |
There was a problem hiding this comment.
This feels like an unnecessary abstraction to me. Or what am I missing? Can't we reuse an existing parser?
| val keyIterator = keys() | ||
| while (keyIterator.hasNext()) { | ||
| val key = keyIterator.next() | ||
| map[key] = optString(key) |
There was a problem hiding this comment.
optString will convert anything to string? How does that work? Can we add tests for non-string keys?
facumenzella
left a comment
There was a problem hiding this comment.
Just a couple of questions. Looks good
tonidero
left a comment
There was a problem hiding this comment.
Not a lot of context and just some thoughts. But happy to iterate on this in future PRs since it's still not used
| private val eventsDispatcher: Dispatcher, | ||
| private val httpClient: HTTPClient, | ||
| private val backendHelper: BackendHelper, | ||
| workflowCdnFetcher: WorkflowCdnFetcher = FileCachedWorkflowCdnFetcher(fileRepository = null), |
There was a problem hiding this comment.
Hmm why default here to a different one since we're creating the Backend instance in the PurchasesFactory with a value? If this is for tests, I wonder if we should just make this explicit there... Also, it would make fileRespository not nullable, which feels better?
| @Serializable | ||
| internal data class WorkflowsListResponse( | ||
| val workflows: List<WorkflowSummary> = emptyList(), | ||
| @SerialName("ui_config") val uiConfig: UiConfig, |
There was a problem hiding this comment.
Just curious... why is the uiConfig needed here if we also have it in the individual workflow's model?
| val screens: Map<String, WorkflowScreen>, | ||
| @SerialName("ui_config") val uiConfig: UiConfig, | ||
| @SerialName("content_max_width") val contentMaxWidth: Int? = null, | ||
| val metadata: JsonObject? = null, |
There was a problem hiding this comment.
Hmm I guess this could have nested data? Otherwise, we could make this a Map<String, Any> for ease of use.
| ?: throw JSONException("Unknown workflow response action: $rawAction") | ||
| val normalizedPayload = when (action) { | ||
| WorkflowResponseAction.INLINE -> root.getJSONObject("data").toString() | ||
| WorkflowResponseAction.USE_CDN -> workflowCdnFetcher.fetchCompiledWorkflowJson(root.getString("url")) |
There was a problem hiding this comment.
I wonder if we should just stick to plain Json serialization here, and avoid the custom parsing we are doing here. Then, do the fetching of the compiled workflow as a higher level later on top of the network call? Feels the network call and the logic are a bit mixed right now...

Motivation
This is the first milestone toward multipage paywalls on Android. The backend exposes two endpoints for workflows: one to list available workflows for a subscriber and one to fetch the full published workflow document. The detail endpoint uses an envelope pattern where the server decides whether to return the workflow payload inline or redirect to a CDN URL. This PR adds the full networking layer so the SDK can fetch and parse workflows end-to-end.
Description
Endpoints
Two new
Endpointsubtypes:GetWorkflows(list) andGetWorkflow(detail). The list endpoint participates in signature verification (same as offerings); the detail endpoint does not.Models
All workflow types live in
WorkflowModels.kt:WorkflowSummary,WorkflowsListResponse,PublishedWorkflow(the full document with steps, screens, trigger actions), andWorkflowFetchResultwhich bundles the parsed workflow with anyenrolled_variantsfrom the response envelope.WorkflowResponseActionis an enum (INLINE,USE_CDN) so the envelope action is never compared as a raw string.Response envelope processing
WorkflowDetailHttpProcessorhandles the detail endpoint's envelope outside ofBackend:inline: unwraps thedataobject as the workflow payload.use_cdn: delegates to an injectedWorkflowCdnFetcherto download the compiled JSON from the CDN URL.enrolled_variantsfrom the envelope for experiment tracking.WorkflowDetailHttpProcessoris its own classBackend.call()thin (just network + delegate) and makes envelope logic independently testable.CDN fetching
WorkflowCdnFetcheris afun interfaceso tests can substitute a lambda. The production implementation,FileCachedWorkflowCdnFetcher, usesFileRepositoryfor disk caching when available and falls back to a direct URL read otherwise. Injected intoBackendviaPurchasesFactory.Note
Medium Risk
Adds new backend requests and response processing for workflow documents, including optional CDN download and disk caching, which introduces new network/file I/O paths and envelope parsing that could fail at runtime. Changes are additive but touch
Backendrequest flow and endpoint signature-verification configuration.Overview
Adds a new workflows network layer:
Endpoint.GetWorkflows(list, participates in signature verification) andEndpoint.GetWorkflow(detail, no signature verification), with correspondingBackend.getWorkflows/getWorkflowmethods and callback de-duping.Introduces workflow parsing and envelope normalization: new serializable models (
WorkflowsListResponse,PublishedWorkflow, etc.),WorkflowJsonParser, andWorkflowDetailHttpProcessorthat unwrapsinlinepayloads or fetches compiled JSON via CDN (use_cdn) while preservingenrolled_variants.Wires production CDN fetching with disk caching by injecting
FileCachedWorkflowCdnFetcher(DefaultFileRepository)fromPurchasesFactory, and adds unit tests covering endpoint paths/flags, backend workflow fetching, and envelope/CDN handling.Reviewed by Cursor Bugbot for commit 895becd. Bugbot is set up for automated code reviews on this repo. Configure here.