Repository navigation
4.1.0: currentEntitlement(for:) returns the transaction behind an entitlement - #15
Merged
Merged
Conversation
hasEntitlement(for:) answers whether the customer may use a product, but some answers need a date. A free trial that App Review requires to be a free non-consumable is the case that prompted this: the app computes the end of the trial from when it was "bought". That date has to come from the App Store, so that it survives a reinstall and is the same on every device of a Family Sharing group. Until now the only way to get it was to import StoreKit next to the kit. currentEntitlement(for:) returns that transaction as a StoreTransaction. It reads Transaction.currentEntitlements(for:), the same source as hasEntitlement(for:), so a non-nil result is exactly the case where that returns true, and it brings the cached flag and the purchasedProducts event into line the same way. The shared bookkeeping moved into syncCachedFlag so the two cannot drift apart. When several transactions entitle the customer, the rule is fixed and documented: revoked ones are skipped, the customer's own purchase wins over a family-shared grant (nobody else can withdraw it, and its date is theirs), then the earliest purchase date, then the lowest id. The answer therefore does not move when a second grant appears later, and it never depends on StoreKit's unspecified order. The rule lives in StoreTransaction.preferredEntitlement(among:) and is tested with constructed values; reading StoreKit itself still needs a host app and stays untested, like every other live StoreKit path. The new requirement has a default that returns nil, so conformers written for 4.0, including the stand-ins apps keep for tests and previews, keep compiling. That makes this 4.1.0 rather than a major. The docs say when a mock should implement it: as soon as it answers hasEntitlement(for:) with true. The purchase date, original purchase date, ownership and revocation date that callers need were already public on StoreTransaction.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
hasEntitlement(for:)says whether the customer may use a product.currentEntitlement(for:)returns thetransaction behind that answer, with its dates, so an app can work out things like when a trial ends without
importing StoreKit:
The case that prompted it: App Review requires the free trial of a one-time purchase to be a free
non-consumable (guideline 3.1.1), and the app has to compute the trial's end from when it was "bought". That
date comes from the App Store, so it survives a reinstall and is the same on every device in a Family Sharing
group.
Behaviour
It reads
Transaction.currentEntitlements(for:), the same source ashasEntitlement(for:), so it returns atransaction exactly when that returns
true. It updates the cached flag and thepurchasedProductseventthe same way; the shared code moved into
syncCachedFlag.When several transactions qualify, the rule is fixed and documented:
purchaseDatewins, then the lowestid.The answer therefore does not depend on StoreKit's order and does not change when a second grant appears
later.
The new requirement has a default implementation that returns
nil, so 4.0 conformers (including the mocksapps keep for tests and previews) still compile. That makes this a minor release, 4.1.0.
README asks for 4.1.0. GettingStarted, MigratingTo4 and the symbol pages cover the new method and say when a
mock should implement it.
Tests
CurrentEntitlementTestschecks the selection rule using transactions built by hand:id;nildefault;any PurchasesProtocol.The
Transaction.currentEntitlements(for:)call itself is not tested, like every other live StoreKit path inthe kit, because it needs a host app.
Verified locally (Xcode 27.2 / Swift 6.4.2)
swift test: 106 tests in 12 suites pass, with 0 warnings.xcodebuild buildsucceeds for iOS, Mac Catalyst, watchOS, tvOS and visionOS with 0 warnings, and the testsbuild for visionOS.
-warnings-as-errors.Examples/PaywallDemobuilds unchanged.