Add presented offering context to custom paywall events#3424
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 c1701e85a67ca633a8ccaba1716ee33d5d79a007. Configure here.
tonidero
left a comment
There was a problem hiding this comment.
Just some suggestions in the public API but in general makes sense. Thank you for adding this!
| * identifier from the cache. | ||
| * @property offering An optional [Offering] associated with the custom paywall. When provided, the | ||
| * SDK will derive both the offering identifier and the presented offering context (placement and | ||
| * targeting information) from this offering. | ||
| */ | ||
| @Poko | ||
| public class CustomPaywallImpressionParams @JvmOverloads constructor( | ||
| public val paywallId: String? = null, | ||
| public val offeringId: String? = null, |
There was a problem hiding this comment.
I think we should deprecate the constructor with the offeringId and just leave one with the optional offering as the preferred way to do this, so there are less likely chances of folks passing us the offering id directly
Also, doesn't make sense to have this constructor with both the offeringId + offering. Instead, I would say to make the main constructor private or internal, and make it store just the paywallId + offeringId + presentedOfferingContext.
There was a problem hiding this comment.
I would also add a @InternalRevenueCatAPI constructor that received both an optional offeringId + presentedOfferingContext. This will be meant to be used by hybrids
There was a problem hiding this comment.
Thanks @tonidero, great callouts. Refactored it a bit and implemented both suggestions :)
d588c0d to
92a1214
Compare
tonidero
left a comment
There was a problem hiding this comment.
I think this makes a lot of sense! Thank you!!
| resolvedPresentedOfferingContext = resolvedOffering | ||
| ?.availablePackages | ||
| ?.firstOrNull() | ||
| ?.presentedOfferingContext |
There was a problem hiding this comment.
Not related to this PR but I'm seeing we have this code (getting the presented offering context from the first package of an offering) in a ton of places. This could/should be an internal function on the Offering object and uses that instead... But I think that should be on a separate PR.
There was a problem hiding this comment.
Agree! Was thinking about that as well, will do so in a follow-up
| @@ -139,10 +142,21 @@ public void onError(@NonNull PurchasesError error) {} | |||
| VirtualCurrencies cachedVirtualCurrencies = purchases.getCachedVirtualCurrencies(); | |||
|
|
|||
| // trackCustomPaywallImpression API | |||
| Offering offering = new Offering( | |||
There was a problem hiding this comment.
Hmm we could potentially just have this as a parameter to avoid having to create a new instance here, same for the kotlin API tests... NABD though.
There was a problem hiding this comment.
Good idea, just updated it :)
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3424 +/- ##
==========================================
+ Coverage 79.96% 80.03% +0.07%
==========================================
Files 370 370
Lines 15014 15048 +34
Branches 2071 2074 +3
==========================================
+ Hits 12006 12044 +38
+ Misses 2167 2161 -6
- Partials 841 843 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
9f46297 to
a4ed790
Compare
a4ed790 to
7564901
Compare
…ession events (#1665) Builds on native PRs RevenueCat/purchases-ios#6707 and RevenueCat/purchases-android#3424 Adds the `presentedOfferingContext` (optional) parameter support to the custom paywall impression event API. When present the value will be read from the passed map as `[string: any]` and parses it using existing helpers like in other places.
…y across SDK (RevenueCat#3513) Added a small helper function for getting the `PresentedOfferingContext` from an Offering object as a follow up on RevenueCat#3424 (comment) Replacing all occurrences of ``` offering.availablePackages.firstOrNull()?.presentedOfferingContext ``` with the use of the new helper ``` offering.presentedOfferingContext ``` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Mechanical refactor with equivalent semantics for offerings with packages; empty offerings still yield null context where callers pass it through directly. > > **Overview** > Adds an **`@InternalRevenueCatAPI`** `Offering.presentedOfferingContext` property (placement/targeting from the first available package, or **null** when there are no packages) and updates the public API stub files accordingly. > > Call sites across **purchases** and **RevenueCat UI** that previously inlined `availablePackages.firstOrNull()?.presentedOfferingContext` now use `offering.presentedOfferingContext`, including custom paywall impressions, paywall activity launch options, and embedded paywall views. **`PaywallViewModel`** keeps a local `presentedOfferingContextOrDefault` extension that falls back to `PresentedOfferingContext(identifier)` for paywall analytics when the offering has no packages. > > Unit tests cover the new property and the empty-offering case for `CustomPaywallImpressionParams`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 184c137. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
**This is an automatic release.** ## RevenueCat SDK ### ✨ New Features * Add presented offering context to custom paywall events (RevenueCat#3424) via Rick (@rickvdl) * Add Workflows list endpoint (RevenueCat#3509) via Cesar de la Vega (@vegaro) ## RevenueCatUI SDK ### Paywalls_v2 #### 🐞 Bugfixes * Fix 1px seam between sliding multipage paywall pages (RevenueCat#3526) via Cesar de la Vega (@vegaro) ### 🔄 Other Changes * refactor: extract Offering.presentedOfferingContext() helper and apply across SDK (RevenueCat#3513) via Rick (@rickvdl) * Add JSON Logic string + array operators (RevenueCat#3485) via Antonio Pallares (@ajpallares) * Add ForbiddenPublicSealedClass detekt rule (RevenueCat#3503) via Toni Rico (@tonidero) * Update baseline profiles (RevenueCat#3519) via RevenueCat Git Bot (@RCGitBot) * build(deps): bump fastlane-plugin-revenuecat_internal from `af7bb5c` to `ce6a7ef` (RevenueCat#3515) via dependabot[bot] (@dependabot[bot]) * Add JSON Logic comparison operators (<, <=, >, >=) (RevenueCat#3484) via Antonio Pallares (@ajpallares) * Add JSON Logic arithmetic operators (+, -, *, /, %) (RevenueCat#3483) via Antonio Pallares (@ajpallares) * Add WorkflowEvent model and backend serialization (RevenueCat#3486) via Cesar de la Vega (@vegaro) * RulesEngine: add JSON Logic predicate evaluator (RevenueCat#3482) via Antonio Pallares (@ajpallares) * Add :rules-engine-internal skeleton module (RevenueCat#3478) via Antonio Pallares (@ajpallares) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Version bump and changelog/docs/CI path updates only; no application logic changes in the diff. > > **Overview** > This **automatic release** finalizes **Android SDK 10.8.0** by replacing **`10.8.0-SNAPSHOT`** with **`10.8.0`** across versioning (`gradle.properties`, `.version`, `Config.frameworkVersion`), sample apps, and changelog files. > > Release notes for **10.8.0** are recorded in **`CHANGELOG.md`** / **`CHANGELOG.latest.md`** (workflows list API, paywall offering context on custom events, multipage paywall seam fix, rules-engine/JSON Logic work, etc.). **Docs publishing** now targets **`10.8.0`** on S3, and **`docs/index.html`** redirects to the new doc URL. > > There are **no functional code changes** in this diff beyond version strings and release metadata. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit c3048b8. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Description
Adds
placement_identifier,targeting_revision, andtargeting_rule_idto the custom paywall impression event.Mirrors the event payload work in iOS PR RevenueCat/purchases-ios#6707 for Android. The internal paywall events already received this context via #3253; this PR brings the same placement/targeting attribution to custom paywall events.
What's new
CustomPaywallImpressionParamsgains anOffering-based constructor. Passing anOfferinglets the SDK derive the offering identifier andPresentedOfferingContextfrom the offering's first available package.The params object stores
paywallId,offeringId, andpresentedOfferingContext; it does not store or expose theOfferingobject. This keeps the public params surface focused on the values needed for event tracking.The string
offeringIdconstructor is deprecated in favor of passing anOffering, because an offering identifier alone cannot reliably carry placement and targeting context.An
@InternalRevenueCatAPIconstructor acceptspaywallId,offeringId, andpresentedOfferingContextdirectly, so hybrid SDKs can pass the context through PHC to native without exposingOfferingon the public params API.Purchases.trackCustomPaywallImpression(params)resolves event data with this fallback chain:offering_idsentpaywallId)cachedOfferings.currentofferingIdmatching cacheofferingIdnot in cacheOfferingobjectpresentedOfferingContextofferingIdCustomPaywallEvent.Impression.Datacarries flatplacementIdentifier/targetingRevision/targetingRuleIdfields, mirroring the existing internalPaywallEventAndroid shape.The wire payload nests these under
presented_offering_context. The custom paywall event has its ownBackendEvent.CustomPaywallPresentedOfferingContextDatarather than reusing the internal paywall event's struct, matching the iOS approach where each event owns its own.Note
Medium Risk
Public API surface changes (new constructors, deprecation) and analytics event schema updates; behavior is localized to custom paywall tracking rather than purchases or auth.
Overview
Custom paywall impression tracking now sends placement and targeting context (placement identifier, targeting revision, rule id) on the backend event, nested as
presented_offering_context, aligning custom paywalls with internal paywall analytics.CustomPaywallImpressionParamsadds constructors that take anOffering(or offering-only) so the SDK can setofferingIdandpresentedOfferingContextfrom the offering’s first package. TheofferingIdstring constructor is deprecated in favor ofOffering. Params exposepresentedOfferingContextbut not the offering object itself.Purchases.trackCustomPaywallImpressionresolves what gets tracked: explicit context from params, lookup from cached offerings when only an offering id or nothing is passed, or values taken directly from a passedOffering.Wire and storage paths extend
CustomPaywallEvent.Impression.DataandBackendEvent.CustomPaywallwith the new fields;cachedOfferingsis exposed on the orchestrator/offerings manager to support resolution.Reviewed by Cursor Bugbot for commit ea3369c. Bugbot is set up for automated code reviews on this repo. Configure here.