[Billing Plans]: Support billing plans in CustomerInfo#6802
Conversation
4 builds increased size
RevenueCat 1.0 (1)
|
| Item | Install Size Change |
|---|---|
| DYLD.String Table | ⬆️ 30.3 kB |
| 📝 RCInstallmentsInfo.Objc Metadata | ⬆️ 8.8 kB |
| RevenueCat.ProductsManager.ProductsManager | ⬆️ 6.6 kB |
| DYLD.Exports | ⬆️ 3.7 kB |
| Code Signature | ⬆️ 3.4 kB |
BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.local-source
⚖️ Compare build
📦 Install build
⏱️ Analyze build performance
Total install size change: ⬆️ 38.4 kB (0.31%)
Total download size change: ⬆️ 17.4 kB (0.42%)
Largest size changes
| Item | Install Size Change |
|---|---|
| RevenueCat.ProductsManager.products(withIdentifiers,completion) | ⬆️ 2.6 kB |
| 📝 RCInstallmentsInfo.Objc Metadata | ⬆️ 2.1 kB |
| DYLD.Exports | ⬆️ 1.8 kB |
| DYLD.String Table | ⬆️ 1.6 kB |
| 📝 RevenueCat.TestStoreProduct.init(localizedTitle,price,currencyCod... | ⬆️ 1.2 kB |
BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.cocoapods
⚖️ Compare build
📦 Install build
⏱️ Analyze build performance
Total install size change: ⬆️ 67.7 kB (0.25%)
Total download size change: ⬆️ 21.2 kB (0.34%)
Largest size changes
| Item | Install Size Change |
|---|---|
| DYLD.String Table | ⬆️ 18.7 kB |
| RevenueCat.ProductsManager.products(withIdentifiers,completion) | ⬆️ 2.6 kB |
| 📝 RCInstallmentsInfo.Objc Metadata | ⬆️ 2.1 kB |
| DYLD.Exports | ⬆️ 1.7 kB |
| Code Signature | ⬆️ 1.7 kB |
BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.spm
⚖️ Compare build
📦 Install build
⏱️ Analyze build performance
Total install size change: ⬆️ 39.0 kB (0.36%)
Total download size change: ⬆️ 18.1 kB (0.43%)
Largest size changes
| Item | Install Size Change |
|---|---|
| RevenueCat.ProductsManager.products(withIdentifiers,completion) | ⬆️ 2.6 kB |
| 📝 RCInstallmentsInfo.Objc Metadata | ⬆️ 2.1 kB |
| DYLD.Exports | ⬆️ 1.8 kB |
| Code Signature | ⬆️ 1.2 kB |
| 📝 RevenueCat.TestStoreProduct.init(localizedTitle,price,currencyCod... | ⬆️ 1.2 kB |
🛸 Powered by Emerge Tools
Comment trigger: Size diff threshold of 100.00kB exceeded
|
Marking ready for review to get cursor reviews |
|
|
||
| for product in products { | ||
| for entitlement in mapping.entitlements(for: product.productIdentifier) { | ||
| for entitlement in mapping.entitlements(for: product.id) { |
There was a problem hiding this comment.
Subscriptions dictionary loses data for same-productIdentifier products
Medium Severity
The subscriptions dictionary is keyed by productIdentifier (base), so when multiple PurchasedSK2Product entries share the same base product identifier but have different billing plans, only the last one survives. Meanwhile, the entitlements lookup on line 83 uses product.id (compound identifier), creating an inconsistency: entitlements are correctly resolved for all products, but subscription data (expiration dates, active subscriptions) is silently dropped for all but the last product with a given productIdentifier. This means products with distinct billing plans that share a base StoreKit product ID won't all appear in activeSubscriptions.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 2122281. Configure here.
There was a problem hiding this comment.
I don't think that this can happen in practice. StoreKit only allows you to be subscribed to one billing plan on a product at a time, so you can't be subscribed to both com.rc.product and com.rc.product:monthly at the same time, so the conflict can't occur.
There was a problem hiding this comment.
But… to store kit, aren't they both actually com.rc.product?
There was a problem hiding this comment.
@JZDesign yes but we are converting those to compound ids for the mapping, see the change right below.
| @@ -0,0 +1,17 @@ | |||
| { | |||
| "product_entitlement_mapping": { | |||
| "com.revenuecat.foo_1:monthly": { | |||
There was a problem hiding this comment.
Should we have some tests that verify what happens if we have multiple entries here, one for the "monthly" plan and one for the "upfront" with different entitlements (weird scenario... not sure if we want to support it?), and see how the offline customer info calculator would handle that?
Also, in case we don't want to support different entitlements for different "base plans", would it make sense to not include it here?
There was a problem hiding this comment.
Great idea!
We do support having different entitlements across the different billing plans on a single StoreKit product. In this case, we'd want to test that com.rc.product (upFront variant) maps to one entitlement, and that com.rc.product:monthly maps to a different one.
I've updated the existing test that uses this fixture to do that here: 57352d5
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ 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 57352d5. Configure here.
| default: | ||
| return rawValue | ||
| } | ||
| } |
There was a problem hiding this comment.
Asymmetric handling of unknown billing plan types breaks offline entitlements
Medium Severity
The static compoundProductIDPlanComponent(from:) returns unknown raw values as-is (creating compound mapping keys like "product:yearly"), while the instance compoundProductIDPlanComponent returns nil for unknown types. Since BillingPlanType.from(storeKitBillingPlanType:) returns nil for unrecognized StoreKit billing plans, the device-side PurchasedSK2Product.id will be just the base productIdentifier. This means the mapping has compound keys for unknown plan types that the device-side code can never produce, causing mapping.entitlements(for: product.id) to fail for future billing plan types that the backend knows about but the SDK doesn't yet recognize.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 57352d5. Configure here.
There was a problem hiding this comment.
I don't think this is a case we can handle gracefully without an SDK update. If Apple introduces a new billing plan type, the raw value we get from StoreKit likely won't match the RevenueCat/backend plan identifier we use in compound product IDs. For example, the current StoreKit raw values are:
- Monthly:
MONTHLY - Up-front:
BILLED_UPFRONT
So we can't safely infer the backend identifier heuristically for future billing plan types.
I also don't think this is a major concern in practice. Supporting a new billing plan type would require an SDK update anyway, and users purchasing that plan would likely be doing so through an SDK version that already knows how to map it for offline entitlements.






Description
Updates CustomerInfo handling (online/offline) to support purchases for products with billing plans.
Testing
Note
Medium Risk
Changes how entitlements are mapped and identified by introducing compound product IDs (e.g.
product:monthly) and propagatingproductPlanIdentifierthrough CustomerInfo, which could affect entitlement unlocking if identifiers don’t match backend payloads.Overview
Adds billing-plan awareness to
CustomerInfoby propagatingproductPlanIdentifierthroughCustomerInfoResponse.SubscriptionandEntitlementInfo, including Apple StoreKit 2 transactions.Offline entitlements now key lookups by a compound product id (
PurchasedSK2Product.id) and the product→entitlement mapping response can includebase_plan_id; conversion toProductEntitlementMappingnow builds compound keys (skippingupFront).Updates tests and fixtures to cover base-plan decoding, compound mapping behavior, and offline entitlement creation for multiple billing plans, plus a small tester-app tweak to use a compound product id.
Reviewed by Cursor Bugbot for commit 2122281. Bugbot is set up for automated code reviews on this repo. Configure here.