From d66667d98e833d63ea0ab4d3ffec837b74b12ae3 Mon Sep 17 00:00:00 2001 From: Will Taylor Date: Wed, 21 Oct 2026 11:29:25 -0500 Subject: [PATCH] feat: parse OwnershipType.assigned (#8064) * parse OwnershipType.assigned * Update PurchaseOwnershipTypeTests.swift * lint * Update baseline swiftinterface files (#8065) * remove unnecessary availability requirements * add parsing test --------- Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- .../PurchaseOwnershipType+Extensions.swift | 1 + Sources/Identity/SubscriptionInfo.swift | 2 ++ .../CustomerInfoDimensionProvider.swift | 1 + .../PurchasedSK2Product.swift | 16 +------------ .../Purchasing/PurchaseOwnershipType.swift | 24 +++++++++++++++++++ .../PurchasesAPI.swift | 1 + .../ObjcAPITester/RCPurchasesAPI.m | 1 + .../SwiftAPITester/PurchasesAPI.swift | 1 + .../Purchasing/EntitlementInfosTests.swift | 18 ++++++++++++++ .../PurchaseOwnershipTypeTests.swift | 17 +++++++++++++ ...evenuecat-api-ios-simulator.swiftinterface | 1 + api/revenuecat-api-ios.swiftinterface | 1 + api/revenuecat-api-macos.swiftinterface | 1 + ...venuecat-api-tvos-simulator.swiftinterface | 1 + api/revenuecat-api-tvos.swiftinterface | 1 + ...ecat-api-visionos-simulator.swiftinterface | 1 + api/revenuecat-api-visionos.swiftinterface | 1 + ...uecat-api-watchos-simulator.swiftinterface | 1 + api/revenuecat-api-watchos.swiftinterface | 1 + 19 files changed, 76 insertions(+), 15 deletions(-) diff --git a/Sources/CodableExtensions/PurchaseOwnershipType+Extensions.swift b/Sources/CodableExtensions/PurchaseOwnershipType+Extensions.swift index 157e9604c4..d664c89ca1 100644 --- a/Sources/CodableExtensions/PurchaseOwnershipType+Extensions.swift +++ b/Sources/CodableExtensions/PurchaseOwnershipType+Extensions.swift @@ -61,6 +61,7 @@ private extension PurchaseOwnershipType { switch self { case .purchased: return "PURCHASED" case .familyShared: return "FAMILY_SHARED" + case .assigned: return "ASSIGNED" case .unknown: return nil } } diff --git a/Sources/Identity/SubscriptionInfo.swift b/Sources/Identity/SubscriptionInfo.swift index a2d829d071..a7ed7e5bb4 100644 --- a/Sources/Identity/SubscriptionInfo.swift +++ b/Sources/Identity/SubscriptionInfo.swift @@ -56,6 +56,8 @@ import Foundation /// How the Customer received access to this subscription: /// - ``PurchaseOwnershipType/purchased``: The customer bought the subscription. /// - ``PurchaseOwnershipType/familyShared``: The Customer has access to the product via their family. + /// - ``PurchaseOwnershipType/assigned``: The Customer has been assigned access to this product + /// through an organization. @objc public let ownershipType: PurchaseOwnershipType /// Type of the current subscription period: diff --git a/Sources/LocalRules/CustomerInfoDimensionProvider.swift b/Sources/LocalRules/CustomerInfoDimensionProvider.swift index 936f441df6..3dbb03e0e4 100644 --- a/Sources/LocalRules/CustomerInfoDimensionProvider.swift +++ b/Sources/LocalRules/CustomerInfoDimensionProvider.swift @@ -252,6 +252,7 @@ private extension PurchaseOwnershipType { switch self { case .purchased: return "PURCHASED" case .familyShared: return "FAMILY_SHARED" + case .assigned: return "ASSIGNED" case .unknown: return nil } } diff --git a/Sources/OfflineEntitlements/PurchasedSK2Product.swift b/Sources/OfflineEntitlements/PurchasedSK2Product.swift index dfa66a0359..2e85478986 100644 --- a/Sources/OfflineEntitlements/PurchasedSK2Product.swift +++ b/Sources/OfflineEntitlements/PurchasedSK2Product.swift @@ -62,7 +62,7 @@ extension PurchasedSK2Product { expiresDate: transaction.expirationDate, store: .appStore, isSandbox: sandboxEnvironmentDetector.isSandbox, - ownershipType: transaction.ownershipType.type, + ownershipType: PurchaseOwnershipType.from(storeKitOwnershipType: transaction.ownershipType), productPlanIdentifier: self.productPlanIdentifier ) self.entitlement = .init( @@ -96,20 +96,6 @@ private extension StoreKit.Transaction.OfferType { } -@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) -private extension StoreKit.Transaction.OwnershipType { - - var type: PurchaseOwnershipType { - switch self { - case .familyShared: - return .familyShared - default: - return .purchased - } - } - -} - @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) private extension PurchasedSK2Product { static func productPlanIdentifier(from transaction: StoreKit.Transaction) -> String? { diff --git a/Sources/Purchasing/PurchaseOwnershipType.swift b/Sources/Purchasing/PurchaseOwnershipType.swift index 21a841b3a0..ec337f880c 100644 --- a/Sources/Purchasing/PurchaseOwnershipType.swift +++ b/Sources/Purchasing/PurchaseOwnershipType.swift @@ -13,6 +13,7 @@ // import Foundation +import StoreKit /// The types used to describe whether a transaction was purchased by the user, /// or is available to them through Family Sharing. @@ -32,6 +33,11 @@ import Foundation */ case unknown = 2 + /** + The user has access to the purchase through an organization. + */ + case assigned = 3 + } extension PurchaseOwnershipType: CaseIterable {} @@ -42,3 +48,21 @@ extension PurchaseOwnershipType: DefaultValueProvider { static let defaultValue: Self = .purchased } + +internal extension PurchaseOwnershipType { + + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + static func from(storeKitOwnershipType: StoreKit.Transaction.OwnershipType) -> PurchaseOwnershipType { + switch storeKitOwnershipType { + case .familyShared: + return .familyShared + #if compiler(>=6.4) + case .assigned: + return .assigned + #endif + default: + return .purchased + } + } + +} diff --git a/Tests/APITesters/AllAPITests/CustomEntitlementComputationSwiftAPITester/PurchasesAPI.swift b/Tests/APITesters/AllAPITests/CustomEntitlementComputationSwiftAPITester/PurchasesAPI.swift index 93b135663b..9ce5c0d9cd 100644 --- a/Tests/APITesters/AllAPITests/CustomEntitlementComputationSwiftAPITester/PurchasesAPI.swift +++ b/Tests/APITesters/AllAPITests/CustomEntitlementComputationSwiftAPITester/PurchasesAPI.swift @@ -56,6 +56,7 @@ func checkPurchasesEnums() { switch oType! { case .purchased, .familyShared, + .assigned, .unknown: print(oType!) @unknown default: diff --git a/Tests/APITesters/AllAPITests/ObjcAPITester/RCPurchasesAPI.m b/Tests/APITesters/AllAPITests/ObjcAPITester/RCPurchasesAPI.m index 44722ff51f..639b75b12e 100644 --- a/Tests/APITesters/AllAPITests/ObjcAPITester/RCPurchasesAPI.m +++ b/Tests/APITesters/AllAPITests/ObjcAPITester/RCPurchasesAPI.m @@ -284,6 +284,7 @@ NSURL *url; switch(o) { case RCPurchaseOwnershipTypePurchased: case RCPurchaseOwnershipTypeFamilyShared: + case RCPurchaseOwnershipTypeAssigned: case RCPurchaseOwnershipTypeUnknown: NSLog(@"%ld", (long)o); } diff --git a/Tests/APITesters/AllAPITests/SwiftAPITester/PurchasesAPI.swift b/Tests/APITesters/AllAPITests/SwiftAPITester/PurchasesAPI.swift index 88f658ac5b..66d80c1468 100644 --- a/Tests/APITesters/AllAPITests/SwiftAPITester/PurchasesAPI.swift +++ b/Tests/APITesters/AllAPITests/SwiftAPITester/PurchasesAPI.swift @@ -65,6 +65,7 @@ func checkPurchasesEnums() { switch oType! { case .purchased, .familyShared, + .assigned, .unknown: print(oType!) @unknown default: diff --git a/Tests/UnitTests/Purchasing/EntitlementInfosTests.swift b/Tests/UnitTests/Purchasing/EntitlementInfosTests.swift index 06b1465b2e..d0fa65536b 100644 --- a/Tests/UnitTests/Purchasing/EntitlementInfosTests.swift +++ b/Tests/UnitTests/Purchasing/EntitlementInfosTests.swift @@ -647,6 +647,24 @@ class EntitlementInfosTests: TestCase { expect(entitlement.ownershipType) == .unknown } + func testParseAssignedOwnershipTypeInEntitlementInfoAndSubscriptionInfo() throws { + stubResponse(entitlements: [ + "pro_cat": [ + "expires_date": "2200-07-26T23:50:40Z", + "product_identifier": "monthly_freetrial", + "purchase_date": "2019-07-26T23:45:40Z" + ] + ], + subscriptions: mockSubscriptions(ownershipType: "ASSIGNED")) + + let customerInfo = try CustomerInfo(data: response) + let entitlement = try XCTUnwrap(customerInfo.entitlements.active["pro_cat"]) + let subscription = try XCTUnwrap(customerInfo.subscriptionsByProductIdentifier["monthly_freetrial"]) + + expect(entitlement.ownershipType) == .assigned + expect(subscription.ownershipType) == .assigned + } + func testParseOwnershipTypeDefaultsToPurchasedIfMissing() throws { let mockEntitlements = [ "pro_cat": [ diff --git a/Tests/UnitTests/Purchasing/PurchaseOwnershipTypeTests.swift b/Tests/UnitTests/Purchasing/PurchaseOwnershipTypeTests.swift index 831268a766..15ea1c9129 100644 --- a/Tests/UnitTests/Purchasing/PurchaseOwnershipTypeTests.swift +++ b/Tests/UnitTests/Purchasing/PurchaseOwnershipTypeTests.swift @@ -13,6 +13,7 @@ import Nimble @testable import RevenueCat +import StoreKit import XCTest class PurchaseOwnershipTypeTests: TestCase { @@ -32,3 +33,19 @@ class PurchaseOwnershipTypeTests: TestCase { } } + +// MARK: - From StoreKit OwnershipType +extension PurchaseOwnershipTypeTests { + + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + func testFromStoreKitOwnershipType() throws { + try AvailabilityChecks.iOS15APIAvailableOrSkipTest() + + expect(PurchaseOwnershipType.from(storeKitOwnershipType: .purchased)) == .purchased + expect(PurchaseOwnershipType.from(storeKitOwnershipType: .familyShared)) == .familyShared + #if compiler(>=6.4) + expect(PurchaseOwnershipType.from(storeKitOwnershipType: .assigned)) == .assigned + #endif + } + +} diff --git a/api/revenuecat-api-ios-simulator.swiftinterface b/api/revenuecat-api-ios-simulator.swiftinterface index 0b0d35af23..0e14e6f8dd 100644 --- a/api/revenuecat-api-ios-simulator.swiftinterface +++ b/api/revenuecat-api-ios-simulator.swiftinterface @@ -2265,6 +2265,7 @@ extension RevenueCat.PackageType : Swift.Codable { case purchased = 0 case familyShared = 1 case unknown = 2 + case assigned = 3 public init?(rawValue: Swift.Int) public typealias RawValue = Swift.Int public var rawValue: Swift.Int { diff --git a/api/revenuecat-api-ios.swiftinterface b/api/revenuecat-api-ios.swiftinterface index 690bca6c29..1e9799ea0c 100644 --- a/api/revenuecat-api-ios.swiftinterface +++ b/api/revenuecat-api-ios.swiftinterface @@ -2265,6 +2265,7 @@ extension RevenueCat.PackageType : Swift.Codable { case purchased = 0 case familyShared = 1 case unknown = 2 + case assigned = 3 public init?(rawValue: Swift.Int) public typealias RawValue = Swift.Int public var rawValue: Swift.Int { diff --git a/api/revenuecat-api-macos.swiftinterface b/api/revenuecat-api-macos.swiftinterface index 88a2472560..dcd761136e 100644 --- a/api/revenuecat-api-macos.swiftinterface +++ b/api/revenuecat-api-macos.swiftinterface @@ -2232,6 +2232,7 @@ extension RevenueCat.PackageType : Swift.Codable { case purchased = 0 case familyShared = 1 case unknown = 2 + case assigned = 3 public init?(rawValue: Swift.Int) public typealias RawValue = Swift.Int public var rawValue: Swift.Int { diff --git a/api/revenuecat-api-tvos-simulator.swiftinterface b/api/revenuecat-api-tvos-simulator.swiftinterface index 7a454d948b..2f0843bd05 100644 --- a/api/revenuecat-api-tvos-simulator.swiftinterface +++ b/api/revenuecat-api-tvos-simulator.swiftinterface @@ -2232,6 +2232,7 @@ extension RevenueCat.PackageType : Swift.Codable { case purchased = 0 case familyShared = 1 case unknown = 2 + case assigned = 3 public init?(rawValue: Swift.Int) public typealias RawValue = Swift.Int public var rawValue: Swift.Int { diff --git a/api/revenuecat-api-tvos.swiftinterface b/api/revenuecat-api-tvos.swiftinterface index 7709695ff6..b8c2c7fa09 100644 --- a/api/revenuecat-api-tvos.swiftinterface +++ b/api/revenuecat-api-tvos.swiftinterface @@ -2232,6 +2232,7 @@ extension RevenueCat.PackageType : Swift.Codable { case purchased = 0 case familyShared = 1 case unknown = 2 + case assigned = 3 public init?(rawValue: Swift.Int) public typealias RawValue = Swift.Int public var rawValue: Swift.Int { diff --git a/api/revenuecat-api-visionos-simulator.swiftinterface b/api/revenuecat-api-visionos-simulator.swiftinterface index 0210da1dbf..74d386e296 100644 --- a/api/revenuecat-api-visionos-simulator.swiftinterface +++ b/api/revenuecat-api-visionos-simulator.swiftinterface @@ -2265,6 +2265,7 @@ extension RevenueCat.PackageType : Swift.Codable { case purchased = 0 case familyShared = 1 case unknown = 2 + case assigned = 3 public init?(rawValue: Swift.Int) public typealias RawValue = Swift.Int public var rawValue: Swift.Int { diff --git a/api/revenuecat-api-visionos.swiftinterface b/api/revenuecat-api-visionos.swiftinterface index e5fbd10a7c..3e83162ba0 100644 --- a/api/revenuecat-api-visionos.swiftinterface +++ b/api/revenuecat-api-visionos.swiftinterface @@ -2265,6 +2265,7 @@ extension RevenueCat.PackageType : Swift.Codable { case purchased = 0 case familyShared = 1 case unknown = 2 + case assigned = 3 public init?(rawValue: Swift.Int) public typealias RawValue = Swift.Int public var rawValue: Swift.Int { diff --git a/api/revenuecat-api-watchos-simulator.swiftinterface b/api/revenuecat-api-watchos-simulator.swiftinterface index cb7ab471fc..60643ad2fc 100644 --- a/api/revenuecat-api-watchos-simulator.swiftinterface +++ b/api/revenuecat-api-watchos-simulator.swiftinterface @@ -2233,6 +2233,7 @@ extension RevenueCat.PackageType : Swift.Codable { case purchased = 0 case familyShared = 1 case unknown = 2 + case assigned = 3 public init?(rawValue: Swift.Int) public typealias RawValue = Swift.Int public var rawValue: Swift.Int { diff --git a/api/revenuecat-api-watchos.swiftinterface b/api/revenuecat-api-watchos.swiftinterface index 969c691fa2..26079f29f8 100644 --- a/api/revenuecat-api-watchos.swiftinterface +++ b/api/revenuecat-api-watchos.swiftinterface @@ -2233,6 +2233,7 @@ extension RevenueCat.PackageType : Swift.Codable { case purchased = 0 case familyShared = 1 case unknown = 2 + case assigned = 3 public init?(rawValue: Swift.Int) public typealias RawValue = Swift.Int public var rawValue: Swift.Int {