Skip to content

Conversation

@ajpallares
Copy link
Contributor

@ajpallares ajpallares commented Feb 6, 2026

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-android and hybrids

Motivation

The SK2 transaction listener delegate (storeKit2TransactionListener(_:updatedTransaction:)) was not including presentedOfferingContext or presentedPaywall in the PurchasedTransactionData sent to POST /receipt. This meant paywall attribution data was lost for:

  • Purchases made in SK2 observer mode (.purchasesAreCompletedBy: .myApp)
  • Purchases made through StoreView (using this API) / SubscriptionStoreView (using this API), which go through Transaction.updates, not the SDK's purchase flow

Note that the presentedOfferingContext was already being cached for StoreView purchases via onInAppPurchaseStart in PaywallExtensions.swift, but was never retrieved by the SK2 transaction listener — so these purchases were missing offering attribution even in non-observer mode.

Description

  • The SK2 transaction listener now retrieves cached presentedOfferingContext and presentedPaywall before posting the receipt, matching the behavior of the SK1 flow.
  • Introduced a new TransactionReason enum (.purchase, .renewal) that mirrors StoreKit 2's Transaction.Reason (iOS 17+). When the reason is not available (SK2 on iOS < 17), it is nil. This is used to skip attribution for known renewals, avoiding false positives.

Note on duplicate receipt posts in observer mode

In observer mode (i.e. when purchasesAreCompletedBy: .myApp), a purchase can potentially be made through the SDK's purchase method. Although this is considered a faulty integration of the SDK, the SDK does not prevent this in any way. In this case, the POST /receipt after a purchase will be called twice for the same transaction:

  1. From the SK2 transaction listener (fires first via Transaction.updates): contains the attribution data (presentedOfferingContext, presentedPaywall) but with sdk_originated = false.
  2. From the purchase method itself: contains no attribution data (already consumed by the listener) but with sdk_originated = true.

This is expected and confirmed with the backend team — the backend will use the first POST because it contains the attribution data.

@ajpallares
Copy link
Contributor Author

@RCGitBot please test

@ajpallares ajpallares marked this pull request as ready for review February 6, 2026 18:09
@ajpallares ajpallares requested review from a team as code owners February 6, 2026 18:09
@claude
Copy link

claude bot commented Feb 6, 2026

Code review

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

Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great as always 💪 !

@ajpallares ajpallares merged commit b086906 into main Feb 9, 2026
48 checks passed
@ajpallares ajpallares deleted the pallares/send-paywall-data-in-sk2-observer-mode branch February 9, 2026 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants