Add offering_id to custom paywall impression event#6456
Conversation
| struct Data { | ||
|
|
||
| var paywallId: String? | ||
| var offeringId: String? |
There was a problem hiding this comment.
[Not related to this line]
Similar to in Android, I wonder if we want to allow overriding the offering used on the developer side... But yeah we can go with this for now and add this later if needed.
There was a problem hiding this comment.
Added it here as well, like in Android
| case appSessionID = "appSessionId" | ||
| case timestamp | ||
| case paywallId | ||
| case offeringId = "offering_id" |
There was a problem hiding this comment.
Hmm curious... why are we adding the offering_id here but not paywall_id?
There was a problem hiding this comment.
Good point, this actually resulted in the same thing, since we use convertToSnakeCase as the keyEncodingStrategy, so this was redundant and I have removed it.
I've also updated the tests to use the same JSON encoder that the actual implementation uses, because we were actually asserting against different keys in the tests than the ones we expect in the backend, due to the difference in JSON encoding settings.
Pass the current cached offering identifier when tracking custom paywall impressions. The field is encoded as `offering_id` in the JSON payload sent to the backend.
- Add offeringId to customPaywallEventMap() for hybrid SDK consumption - Remove redundant explicit CodingKey mappings (convertToSnakeCase handles it) - Update tests to use JSONEncoder.default to verify actual wire format
d99143a to
f07287d
Compare
Add offeringId parameter to CustomPaywallImpressionParams, matching the Android SDK API. When provided, the explicit value is used; otherwise falls back to the current cached offering identifier.
## Summary - Adds `offering_id` field to the custom paywall impression event, populated from the current cached offering identifier - Exposes `cachedCurrentOfferingIdentifier` through `OfferingsManager` and `PurchasesOrchestrator` - Field is omitted from the JSON payload when no offering is cached ## Related PRs - iOS: RevenueCat/purchases-ios#6456 ## Test plan - [x] Unit tests for event creation with and without offering ID - [x] Unit tests for backend event conversion with offering ID - [x] Unit tests for JSON serialization (key present when set, omitted when null) - [x] Unit tests for JSON roundtrip with offering ID <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: this is an additive analytics/event-schema change that threads an optional `offering_id` through existing custom paywall impression tracking, with unit tests covering serialization and null handling. > > **Overview** > Custom paywall impression tracking now includes an optional **`offering_id`** field in the stored/serialized backend event payload. > > `CustomPaywallImpressionParams` gains an `offeringId` parameter, and `Purchases.trackCustomPaywallImpression` will default it from the cached current offering identifier when not explicitly provided (plumbed via new `cachedCurrentOfferingIdentifier` accessors on `OfferingsManager`/`PurchasesOrchestrator`). API tester fixtures and unit tests are updated to cover creation, backend conversion, and JSON behavior (key present when set, omitted when null). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit a2a1f96. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
@_spi(Experimental) APIs are not visible to ObjC consumers. The ObjC API tester will be re-added when the experimental annotation is removed.
| @@ -2163,10 +2163,11 @@ extension Purchases { | |||
| @_spi(Experimental) | |||
| @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | |||
| @objc public func trackCustomPaywallImpression(_ params: CustomPaywallImpressionParams) { | |||
There was a problem hiding this comment.
I see the ObjC API tests were removed. Do we need to remove the @objc marker here as well?
There was a problem hiding this comment.
It was actually failing because of the new property that was added (offeringId). Updated with the right initializers for Objc now as well :)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: ObjC API tester deleted instead of updated
- I restored the deleted ObjC API tester files and re-registered them in
main.mto keep compile-time coverage forRCCustomPaywallImpressionParams, includingofferingIdusage.
- I restored the deleted ObjC API tester files and re-registered them in
Or push these changes by commenting:
@cursor push 9487e792cb
Preview (9487e792cb)
diff --git a/Tests/APITesters/AllAPITests/ObjcAPITester/RCCustomPaywallImpressionAPI.h b/Tests/APITesters/AllAPITests/ObjcAPITester/RCCustomPaywallImpressionAPI.h
new file mode 100644
--- /dev/null
+++ b/Tests/APITesters/AllAPITests/ObjcAPITester/RCCustomPaywallImpressionAPI.h
@@ -1,0 +1,16 @@
+//
+// RCCustomPaywallImpressionAPI.h
+// ObjCAPITester
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RCCustomPaywallImpressionAPI : NSObject
+
++ (void)checkAPI;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Tests/APITesters/AllAPITests/ObjcAPITester/RCCustomPaywallImpressionAPI.m b/Tests/APITesters/AllAPITests/ObjcAPITester/RCCustomPaywallImpressionAPI.m
new file mode 100644
--- /dev/null
+++ b/Tests/APITesters/AllAPITests/ObjcAPITester/RCCustomPaywallImpressionAPI.m
@@ -1,0 +1,33 @@
+//
+// RCCustomPaywallImpressionAPI.m
+// ObjCAPITester
+//
+
+#import "RCCustomPaywallImpressionAPI.h"
+
+@import RevenueCat;
+
+@implementation RCCustomPaywallImpressionAPI
+
++ (void)checkAPI {
+ if (@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)) {
+ // CustomPaywallImpressionParams API
+ RCCustomPaywallImpressionParams *paramsDefault __unused = [[RCCustomPaywallImpressionParams alloc] initWithPaywallId:nil];
+ RCCustomPaywallImpressionParams *paramsWithId __unused = [[RCCustomPaywallImpressionParams alloc] initWithPaywallId:@"my-paywall"];
+ RCCustomPaywallImpressionParams *paramsWithNil __unused = [[RCCustomPaywallImpressionParams alloc] initWithPaywallId:nil];
+ RCCustomPaywallImpressionParams *paramsWithOffering __unused = [[RCCustomPaywallImpressionParams alloc] initWithPaywallId:@"my-paywall" offeringId:@"my-offering"];
+ RCCustomPaywallImpressionParams *paramsOfferingOnly __unused = [[RCCustomPaywallImpressionParams alloc] initWithPaywallId:nil offeringId:@"my-offering"];
+
+ // CustomPaywallImpressionParams properties
+ NSString *paywallId __unused = paramsWithId.paywallId;
+ NSString *offeringId __unused = paramsWithOffering.offeringId;
+
+ // trackCustomPaywallImpression API
+ RCPurchases *purchases = RCPurchases.sharedPurchases;
+ [purchases trackCustomPaywallImpression:paramsDefault];
+ [purchases trackCustomPaywallImpression:paramsWithId];
+ [purchases trackCustomPaywallImpression];
+ }
+}
+
+@end
diff --git a/Tests/APITesters/AllAPITests/ObjcAPITester/main.m b/Tests/APITesters/AllAPITests/ObjcAPITester/main.m
--- a/Tests/APITesters/AllAPITests/ObjcAPITester/main.m
+++ b/Tests/APITesters/AllAPITests/ObjcAPITester/main.m
@@ -30,6 +30,7 @@
#import "RCSubscriptionPeriodAPI.h"
#import "RCTransactionAPI.h"
#import "RCVerificationResultAPI.h"
+#import "RCCustomPaywallImpressionAPI.h"
#import "RCPaywallViewControllerAPI.h"
@@ -91,6 +92,7 @@
[RCVerificationResultAPI checkAPI];
if (@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)) {
+ [RCCustomPaywallImpressionAPI checkAPI];
[RCPaywallViewControllerAPI checkAPI];
}
}This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
ajpallares
left a comment
There was a problem hiding this comment.
I think it makes sense! I only have one more suggestion, to avoid two inits clashing in Swift
| /// - paywallId: An optional identifier for the custom paywall being shown. | ||
| /// - offeringId: An optional identifier for the offering associated with the custom paywall. | ||
| /// If not provided, the SDK will use the current offering identifier from the cache. | ||
| @objc public init(paywallId: String? = nil, offeringId: String? = nil) { |
There was a problem hiding this comment.
| @objc public init(paywallId: String? = nil, offeringId: String? = nil) { | |
| @objc public init(paywallId: String? = nil, offeringId: String?) { |
Probably not a big deal, but in Swift this could clash with the new convenience init below. So I'd say it's better to remove the default value to nil.
Then, to be strict we would need to update the text
If not provided, the SDK will use the current offering identifier from the cache
to say
If
nil, the SDK will use the current offering identifier from the cache
(I'd say to update the doc for the parameter only in the init's documentation, not on the offeringId property itself)
There was a problem hiding this comment.
That's a good point, addressed that and added some more API tests to handle some more variations
## Summary - Removes `@ExperimentalPreviewRevenueCatPurchasesAPI` from `CustomPaywallImpressionParams` and `trackCustomPaywallImpression` Related iOS PR: RevenueCat/purchases-ios#6456 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: this only changes public API annotations/signatures for `trackCustomPaywallImpression`/`CustomPaywallImpressionParams` without altering runtime behavior, but it does affect compile-time opt-in requirements for consumers. > > **Overview** > Promotes custom paywall impression tracking to a stable API by removing `@ExperimentalPreviewRevenueCatPurchasesAPI` from `Purchases.trackCustomPaywallImpression(...)` and `CustomPaywallImpressionParams`. > > Updates the generated API signature files so the methods/classes no longer appear as experimental in the published surface. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ba6bf0e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->


Summary
offering_idfield to the custom paywall impression event, populated from the current cached offering identifierRelated PRs
Test plan
offering_idappearing in encoded JSONoffering_idomitted when nilNote
Medium Risk
Adds a new optional field to the custom paywall impression event payload and changes how impressions are populated (pulling
offeringIdfrom cached offerings when not provided), which could affect backend analytics ingestion and client expectations.Overview
Custom paywall impression tracking now carries an optional
offering_idend-to-end:CustomPaywallEvent/FeatureEventsRequest.CustomPaywallEventinclude and encode the field (andFeatureEvent.toMap()exposes it for hybrid SDK consumers), omitting it whennil.Purchases.trackCustomPaywallImpression(_:)now populatesofferingIdfromCustomPaywallImpressionParamsor falls back to the current cached offering identifier. Public API surface is extended to acceptofferingId(Swift + ObjC), with unit/API tests updated to validate JSON encoding and fallback/override behavior.Written by Cursor Bugbot for commit d8a3392. This will update automatically on new commits. Configure here.