Skip to content

Comments

Share PaywallPromoOfferCache between main and exit offer paywalls#6180

Open
facumenzella wants to merge 5 commits intomainfrom
fix/share-promo-offer-cache-exit-paywall
Open

Share PaywallPromoOfferCache between main and exit offer paywalls#6180
facumenzella wants to merge 5 commits intomainfrom
fix/share-promo-offer-cache-exit-paywall

Conversation

@facumenzella
Copy link
Member

@facumenzella facumenzella commented Jan 31, 2026

Summary

  • Fixes a race condition where promotional offers were not applied when purchasing quickly from exit offer paywalls
  • The exit offer paywall was creating a new PaywallPromoOfferCache instance, causing eligibility to be computed asynchronously
  • If a user purchased before the computation completed, the promotional offer ID wouldn't be included in the purchase

Changes

  • Add promoOfferCache property to PaywallViewConfiguration
  • Update PaywallsV2View to accept an optional external cache (creates new one if not provided)
  • Share cache through SwiftUI modifiers (PresentingPaywallModifier, PresentingPaywallBindingModifier)
  • Share cache through UIKit path (PaywallViewController for hybrid SDKs)
  • Use internal init(content:) for exit offer creation to pass the shared cache

How It Works

  1. Main paywall creates PaywallPromoOfferCache and computes eligibility
  2. When exit offer is shown, it receives the same cache instance
  3. Exit offer's .task still calls computeEligibility() (for any new products)
  4. For already-computed products, get() returns immediately with the cached promotional offer

Test plan

  • Verify promotional offers work on main paywall (no regression)
  • Verify promotional offers work on exit offer paywall when purchasing quickly
  • Test SwiftUI path with presentPaywallIfNeeded
  • Test UIKit path with PaywallViewController
  • Test hybrid SDKs (React Native, Flutter) if applicable

🤖 Generated with Claude Code

…t offer paywalls

The exit offer paywall was creating a new PaywallPromoOfferCache instance,
causing eligibility to be computed asynchronously. If a user purchased before
the computation completed, the promotional offer ID wouldn't be included.

This fix shares the same cache instance between the main paywall and exit offer:
- Add promoOfferCache property to PaywallViewConfiguration
- Update PaywallsV2View to accept an optional external cache
- Pass shared cache through SwiftUI modifiers (PresentingPaywallModifier,
  PresentingPaywallBindingModifier)
- Pass shared cache in UIKit path (PaywallViewController for hybrids)

Now when the exit offer appears, it reuses the pre-populated cache from the
main paywall, and promotional offers are immediately available for purchase.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@facumenzella facumenzella requested a review from a team as a code owner January 31, 2026 07:28
@facumenzella facumenzella changed the title Fix race condition: share PaywallPromoOfferCache between main and exit offer paywalls Share PaywallPromoOfferCache between main and exit offer paywalls Jan 31, 2026
@claude
Copy link

claude bot commented Jan 31, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@facumenzella facumenzella requested a review from vegaro February 13, 2026 16:43
@emerge-tools
Copy link

emerge-tools bot commented Feb 13, 2026

📸 Snapshot Test

242 unchanged

Name Added Removed Modified Renamed Unchanged Errored Approval
RevenueCat
com.revenuecat.PaywallsTester
0 0 0 0 242 0 N/A

🛸 Powered by Emerge Tools

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