-
Notifications
You must be signed in to change notification settings - Fork 316
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
Offline Entitlements
: don't compute offline CustomerInfo
when purchasing a consumable products
#2522
Offline Entitlements
: don't compute offline CustomerInfo
when purchasing a consumable products
#2522
Changes from all commits
4861237
5254eb2
929aac7
a92f9e2
0c5ba14
a020a38
e33bc8b
3184abf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,7 +42,7 @@ class PurchasedProductsFetcher: PurchasedProductsFetcherType { | |
func fetchPurchasedProducts() async throws -> [PurchasedSK2Product] { | ||
var result: [PurchasedSK2Product] = [] | ||
|
||
for transaction in await self.transactions { | ||
for transaction in try await self.transactions { | ||
switch transaction { | ||
case let .unverified(transaction, verificationError): | ||
Logger.appleWarning( | ||
|
@@ -67,26 +67,30 @@ class PurchasedProductsFetcher: PurchasedProductsFetcherType { | |
private static let cacheDuration: DispatchTimeInterval = .minutes(5) | ||
|
||
private var transactions: Transactions { | ||
get async { | ||
get async throws { | ||
if !self.cache.isCacheStale(durationInSeconds: Self.cacheDuration.seconds), | ||
let cache = self.cache.cachedInstance, !cache.isEmpty { | ||
Logger.debug(Strings.offlineEntitlements.purchased_products_returning_cache(count: cache.count)) | ||
return cache | ||
} | ||
|
||
let result = await TimingUtil.measureAndLogIfTooSlow( | ||
let result = try await TimingUtil.measureAndLogIfTooSlow( | ||
threshold: .purchasedProducts, | ||
message: Strings.offlineEntitlements.purchased_products_fetching_too_slow | ||
) { | ||
return await Self.fetchTransactions() | ||
return try await Self.fetchTransactions() | ||
} | ||
|
||
self.cache.cache(instance: result) | ||
return result | ||
} | ||
} | ||
|
||
private static func fetchTransactions() async -> Transactions { | ||
private static func fetchTransactions() async throws -> Transactions { | ||
guard await !Self.hasPendingConsumablePurchase else { | ||
throw Error.foundConsumablePurchase | ||
} | ||
|
||
var result: Transactions = [] | ||
|
||
Logger.debug(Strings.offlineEntitlements.purchased_products_fetching) | ||
|
@@ -97,4 +101,48 @@ class PurchasedProductsFetcher: PurchasedProductsFetcherType { | |
return result | ||
} | ||
|
||
private static var hasPendingConsumablePurchase: Bool { | ||
get async { | ||
return await StoreKit.Transaction.unfinished.contains { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to check, but I'm guessing SK2 will cache the transactions internally right? So this shouldn't take too long. Also, will this be executed in a background thread? I'm guessing since the API is using async/await that might be handled internally by SK2 though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The short answer is: it doesn't matter. Concurrency in Swift is different from what you'd expect. Even if this takes 1 hour, it's not stopping the thread, it creates a continuation. |
||
$0.productType.productCategory == .nonSubscription | ||
} | ||
} | ||
} | ||
|
||
} | ||
// MARK: - Error | ||
|
||
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) | ||
extension PurchasedProductsFetcher { | ||
|
||
enum Error: Swift.Error, CustomNSError { | ||
|
||
case foundConsumablePurchase | ||
|
||
var errorUserInfo: [String: Any] { | ||
return [ | ||
NSLocalizedDescriptionKey: Strings.offlineEntitlements | ||
.computing_offline_customer_info_for_consumable_product.description | ||
] | ||
} | ||
|
||
} | ||
|
||
} | ||
// MARK: - Extensions | ||
|
||
@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) | ||
private extension StoreKit.VerificationResult where SignedType == StoreKit.Transaction { | ||
|
||
var productType: StoreProduct.ProductType { | ||
return .init(self.underlyingTransaction.productType) | ||
} | ||
|
||
private var underlyingTransaction: StoreKit.Transaction { | ||
switch self { | ||
case let .unverified(transaction, _): return transaction | ||
case let .verified(transaction): return transaction | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"headers" : { | ||
"Authorization" : "Bearer asharedsecret" | ||
}, | ||
"request" : { | ||
"body" : { | ||
"app_user_id" : "user", | ||
"attributes" : { | ||
"$attConsentStatus" : { | ||
"updated_at_ms" : 1678307200000, | ||
"value" : "authorized" | ||
} | ||
}, | ||
"currency" : "UYU", | ||
"fetch_token" : "YW4gYXdlc29tZSByZWNlaXB0", | ||
"initiation_source" : "purchase", | ||
"is_restore" : false, | ||
"observer_mode" : false, | ||
"price" : "15.99", | ||
"product_id" : "product_id", | ||
"store_country" : "ESP" | ||
}, | ||
"method" : "POST", | ||
"url" : "https://api.revenuecat.com/v1/receipts" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"headers" : { | ||
"Authorization" : "Bearer asharedsecret" | ||
}, | ||
"request" : { | ||
"body" : { | ||
"app_user_id" : "user", | ||
"attributes" : { | ||
"$attConsentStatus" : { | ||
"updated_at_ms" : 1678307200000, | ||
"value" : "authorized" | ||
} | ||
}, | ||
"fetch_token" : "YW4gYXdlc29tZSByZWNlaXB0", | ||
"initiation_source" : "purchase", | ||
"is_restore" : false, | ||
"observer_mode" : false | ||
}, | ||
"method" : "POST", | ||
"url" : "https://api.revenuecat.com/v1/receipts" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"headers" : { | ||
"Authorization" : "Bearer asharedsecret" | ||
}, | ||
"request" : { | ||
"body" : { | ||
"app_user_id" : "user", | ||
"attributes" : { | ||
"$attConsentStatus" : { | ||
"updated_at_ms" : 1678307200000, | ||
"value" : "authorized" | ||
} | ||
}, | ||
"currency" : "UYU", | ||
"fetch_token" : "YW4gYXdlc29tZSByZWNlaXB0", | ||
"initiation_source" : "purchase", | ||
"is_restore" : false, | ||
"observer_mode" : false, | ||
"price" : "15.99", | ||
"product_id" : "product_id", | ||
"store_country" : "ESP" | ||
}, | ||
"method" : "POST", | ||
"url" : "https://api.revenuecat.com/v1/receipts" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"headers" : { | ||
"Authorization" : "Bearer asharedsecret" | ||
}, | ||
"request" : { | ||
"body" : { | ||
"app_user_id" : "user", | ||
"attributes" : { | ||
"$attConsentStatus" : { | ||
"updated_at_ms" : 1678307200000, | ||
"value" : "authorized" | ||
} | ||
}, | ||
"fetch_token" : "YW4gYXdlc29tZSByZWNlaXB0", | ||
"initiation_source" : "purchase", | ||
"is_restore" : false, | ||
"observer_mode" : false | ||
}, | ||
"method" : "POST", | ||
"url" : "https://api.revenuecat.com/v1/receipts" | ||
} | ||
} |
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.
Is it possible to unit test these? I guess we would have to mock
StoreKit
. We do have integration tests so it's probably okish, but I think it's good to test things at different granularities to try to have as much coverage as possible.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.
It's indirectly tested, but I agree it would be useful to test independently 👍🏻 which we can do with
SKTestSession
.I added a type in #2533 to do something similar, so I'm going to combine both things and test them.
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.
Extracted and tested in #2539 like you suggested.