-
Notifications
You must be signed in to change notification settings - Fork 319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support fetching eligible win-back offers for a product #4431
Support fetching eligible win-back offers for a product #4431
Conversation
*/ | ||
internal func eligibleWinBackOffers( | ||
forProduct product: StoreProduct, | ||
completion: @escaping @Sendable (Result<[WinBackOffer], PublicError>) -> Void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
completion is marked as @Sendable
to avoid warnings below when we dispatch the completion handler on the main actor
@@ -44,7 +44,13 @@ enum AvailabilityChecks { | |||
} | |||
|
|||
static func iOS17APIAvailableOrSkipTest() throws { | |||
guard #available(iOS 17.0, tvOS 17.0, macOS 14.0, watchOS 10.0, *) else { | |||
guard #available(iOS 17.0, tvOS 17.0, macOS 14.0, watchOS 10.0, visionOS 1.0, *) else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added visionOS check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking really really good!! Great work!
Sources/Purchasing/StoreKit2/Win-Back Offers/WinBackOfferEligibilityCalculator.swift
Outdated
Show resolved
Hide resolved
return false | ||
case .verified(let transaction): | ||
// Intentionally exclude transactions acquired through family sharing | ||
return transaction.ownershipType == .purchased |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, you can only be subscribed to a product once, so there must be at most 1 renewalInfo instance that is NOT a family shared one. So maybe we could simplify the logic of the rest of the function by just finding the first status transaction with transaction.ownershipType == .purchased
, like Apple does in their sample code:
let purchasedStatus = statuses.first {
$0.transaction.unsafePayloadValue.ownershipType == .purchased
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is where I thought I might be able to do a bit better than Apple's example in the event where there were multiple renewalInfos.
Is there any chance that there would be more than one renewalInfo for a single product ID if a user subscribes, churns, and then re-subscribed? I'm not seeing many details about this in the docs.
I've implemented this change here and will merge it in to this PR if we think we're safe in the case I mentioned above. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation does not explicitly state that, but it says:
The array can have more than one subscription status if your subscription supports Family Sharing.
Which I'd argue highly suggest the array can normally only have one status (the purchased one) and potentially more only if it supports family sharing.
Given that, plus Apple's sample code makes me very confident there will always be just one status with ownershipType == .purchased
https://developer.apple.com/documentation/storekit/product/subscriptioninfo/3822296-status
return eligibleWinBackOfferIDsPerRenewalInfo | ||
.flatMap { $0 } | ||
.filter { seen.insert($0).inserted } | ||
}() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By using just one status above, we can get rid of all the deduplication logic here.
Co-authored-by: Mark Villacampa <MarkVillacampa@users.noreply.github.com>
Co-authored-by: Mark Villacampa <MarkVillacampa@users.noreply.github.com>
Co-authored-by: Mark Villacampa <MarkVillacampa@users.noreply.github.com>
Description
This PR adds the ability for developers to fetch the win-back offers that a subscriber is eligible for on a given product.
New APIs
All API additions in this PR are marked as
internal
for now. They will be made public once we introduce the ability to redeem a win-back offer in a future PR.The PR introduces a new WinBackOffer type to represent a win-back offer:
Additionally, the PR introduces two new functions on the
Purchases
singleton for fetching the win-back offers for a given product that a subscriber is eligible for. These functions are not available when theENABLE_CUSTOM_ENTITLEMENT_COMPUTATION
compiler flag is enabled.Note
Since we're keeping this functionality as internal for now, this PR is marked with the
pr:other
tag so that it doesn't get marked as a new feature in the auto-generated changelogs.