-
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
TransactionPoster
: avoid posting transactions multiple times
#2914
Changes from all commits
57d4017
d55d779
42a9891
7abf1a5
b844a4a
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 |
---|---|---|
|
@@ -865,7 +865,7 @@ extension PurchasesOrchestrator: StoreKit2TransactionListenerDelegate { | |
let subscriberAttributes = self.unsyncedAttributes | ||
let adServicesToken = self.attribution.unsyncedAdServicesToken | ||
|
||
let result: Result<CustomerInfo, BackendError> = await Async.call { completed in | ||
let result: TransactionPosterResult = await Async.call { completed in | ||
self.transactionPoster.handlePurchasedTransaction( | ||
StoreTransaction.from(transaction: transaction), | ||
data: .init( | ||
|
@@ -1058,7 +1058,7 @@ private extension PurchasesOrchestrator { | |
source: .init(isRestore: isRestore, initiationSource: initiationSource) | ||
), | ||
observerMode: self.observerMode) { result in | ||
self.handleReceiptPost(result: result, | ||
self.handleReceiptPost(result: .init(result), | ||
subscriberAttributes: unsyncedAttributes, | ||
adServicesToken: adServicesToken, | ||
completion: completion) | ||
|
@@ -1068,7 +1068,7 @@ private extension PurchasesOrchestrator { | |
} | ||
} | ||
|
||
func handleReceiptPost(result: Result<CustomerInfo, BackendError>, | ||
func handleReceiptPost(result: TransactionPosterResult, | ||
subscriberAttributes: SubscriberAttribute.Dictionary, | ||
adServicesToken: String?, | ||
completion: (@Sendable (Result<CustomerInfo, PurchasesError>) -> Void)?) { | ||
|
@@ -1080,21 +1080,33 @@ private extension PurchasesOrchestrator { | |
|
||
if let completion = completion { | ||
self.operationDispatcher.dispatchOnMainThread { | ||
completion(result.mapError { $0.asPurchasesError }) | ||
self.convertToResult(result) { result in | ||
completion(result.mapError { $0.asPurchasesError }) | ||
} | ||
} | ||
} | ||
} | ||
|
||
func handlePostReceiptResult(_ result: Result<CustomerInfo, BackendError>, | ||
func handlePostReceiptResult(_ result: TransactionPosterResult, | ||
subscriberAttributes: SubscriberAttribute.Dictionary, | ||
adServicesToken: String?) { | ||
if let customerInfo = result.value { | ||
switch result { | ||
case .alreadyPosted: | ||
// Nothing to do | ||
break | ||
|
||
case let .success(customerInfo): | ||
self.customerInfoManager.cache(customerInfo: customerInfo, appUserID: self.appUserID) | ||
} | ||
|
||
self.markSyncedIfNeeded(subscriberAttributes: subscriberAttributes, | ||
adServicesToken: adServicesToken, | ||
error: result.error) | ||
self.markSyncedIfNeeded(subscriberAttributes: subscriberAttributes, | ||
adServicesToken: adServicesToken, | ||
error: nil) | ||
|
||
case let .failure(error): | ||
self.markSyncedIfNeeded(subscriberAttributes: subscriberAttributes, | ||
adServicesToken: adServicesToken, | ||
error: error) | ||
} | ||
} | ||
|
||
func handlePurchasedTransaction(_ purchasedTransaction: StoreTransaction, | ||
|
@@ -1104,31 +1116,41 @@ private extension PurchasesOrchestrator { | |
let unsyncedAttributes = self.unsyncedAttributes | ||
let adServicesToken = self.attribution.unsyncedAdServicesToken | ||
|
||
self.transactionPoster.handlePurchasedTransaction( | ||
purchasedTransaction, | ||
data: .init( | ||
appUserID: self.appUserID, | ||
presentedOfferingID: offeringID, | ||
unsyncedAttributes: unsyncedAttributes, | ||
aadAttributionToken: adServicesToken, | ||
storefront: storefront, | ||
source: self.purchaseSource(for: purchasedTransaction.productIdentifier, | ||
transaction: purchasedTransaction, | ||
restored: restored) | ||
) | ||
) { result in | ||
self.handlePostReceiptResult(result, | ||
subscriberAttributes: unsyncedAttributes, | ||
adServicesToken: adServicesToken) | ||
self.operationDispatcher.dispatchOnWorkerThread { | ||
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.
|
||
self.transactionPoster.handlePurchasedTransaction( | ||
purchasedTransaction, | ||
data: .init( | ||
appUserID: self.appUserID, | ||
presentedOfferingID: offeringID, | ||
unsyncedAttributes: unsyncedAttributes, | ||
aadAttributionToken: adServicesToken, | ||
storefront: storefront, | ||
source: self.purchaseSource(for: purchasedTransaction.productIdentifier, | ||
transaction: purchasedTransaction, | ||
restored: restored) | ||
) | ||
) { result in | ||
let completion: (@Sendable(Result<CustomerInfo, PurchasesError>) -> Void)? = | ||
self.getAndRemovePurchaseCompletedCallback(forTransaction: purchasedTransaction) | ||
.map { completion in | ||
return { @Sendable result in | ||
self.operationDispatcher.dispatchOnMainActor { | ||
completion( | ||
purchasedTransaction, | ||
result.value, | ||
result.error?.asPublicError, | ||
result.error?.isCancelledError ?? false | ||
) | ||
} | ||
} | ||
} | ||
|
||
if let completion = self.getAndRemovePurchaseCompletedCallback(forTransaction: purchasedTransaction) { | ||
self.operationDispatcher.dispatchOnMainActor { | ||
completion(purchasedTransaction, | ||
result.value, | ||
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. There's a possibility that this ends up with
Need to write a unit test for this and fix it. 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. Fixed and tested. |
||
result.error?.asPublicError, | ||
result.error?.isCancelledError ?? false | ||
) | ||
} | ||
self.handleReceiptPost( | ||
result: result, | ||
subscriberAttributes: unsyncedAttributes, | ||
adServicesToken: adServicesToken, | ||
completion: completion | ||
) | ||
} | ||
} | ||
} | ||
|
@@ -1200,6 +1222,19 @@ private extension PurchasesOrchestrator { | |
} | ||
} | ||
|
||
func convertToResult( | ||
_ transactionResult: TransactionPosterResult, | ||
completion: @escaping @Sendable (Result<CustomerInfo, BackendError>) -> Void | ||
) { | ||
transactionResult.toResult(completion: completion) { | ||
self.customerInfoManager.customerInfo( | ||
appUserID: self.appUserID, | ||
fetchPolicy: .cachedOrFetched, | ||
completion: $0 | ||
) | ||
} | ||
} | ||
|
||
} | ||
|
||
private extension PurchasesOrchestrator { | ||
|
@@ -1235,7 +1270,7 @@ extension PurchasesOrchestrator { | |
let unsyncedAttributes = self.unsyncedAttributes | ||
let adServicesToken = self.attribution.unsyncedAdServicesToken | ||
|
||
let result = await self.transactionPoster.handlePurchasedTransaction( | ||
let postResult = await self.transactionPoster.handlePurchasedTransaction( | ||
transaction, | ||
data: .init( | ||
appUserID: self.appUserID, | ||
|
@@ -1248,11 +1283,12 @@ extension PurchasesOrchestrator { | |
) | ||
) | ||
|
||
self.handlePostReceiptResult(result, | ||
self.handlePostReceiptResult(postResult, | ||
subscriberAttributes: unsyncedAttributes, | ||
adServicesToken: adServicesToken) | ||
|
||
return try result | ||
return try await self | ||
.convertToResult(postResult) | ||
.mapError(\.asPurchasesError) | ||
.get() | ||
} | ||
|
@@ -1268,4 +1304,14 @@ extension PurchasesOrchestrator { | |
} | ||
} | ||
|
||
/// Converts a `TransactionPosterResult` to a `Result<CustomerInfo, BackendError>` | ||
/// by optionally loading a customer info for `TransactionPosterResult.alreadyPosted`. | ||
private func convertToResult( | ||
_ transactionResult: TransactionPosterResult | ||
) async -> Result<CustomerInfo, BackendError> { | ||
return await Async.call { completion in | ||
self.convertToResult(transactionResult, completion: completion) | ||
} | ||
} | ||
|
||
} |
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 a follow-up to #2954. We look for the first un-posted transaction now.