Skip to content

Commit 472ec8b

Browse files
authored
Merge pull request #8124 from woocommerce/issue/8121-improve-transaction-listener
[In-app Purchases] Improve transaction handling logic
2 parents 636a273 + d3945c7 commit 472ec8b

File tree

2 files changed

+50
-11
lines changed

2 files changed

+50
-11
lines changed

Networking/Networking/Model/WordPressApiError.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ public enum WordPressApiError: Error, Decodable, Equatable {
88
///
99
case unknown(code: String, message: String)
1010

11+
/// An order already exists for this IAP receipt
12+
///
13+
case productPurchased
14+
1115
/// Decodable Initializer.
1216
///
1317
public init(from decoder: Decoder) throws {
@@ -16,6 +20,8 @@ public enum WordPressApiError: Error, Decodable, Equatable {
1620
let message = try container.decode(String.self, forKey: .message)
1721

1822
switch code {
23+
case Constants.productPurchased:
24+
self = .productPurchased
1925
default:
2026
self = .unknown(code: code, message: message)
2127
}
@@ -25,6 +31,7 @@ public enum WordPressApiError: Error, Decodable, Equatable {
2531
/// Constants for Possible Error Identifiers
2632
///
2733
private enum Constants {
34+
static let productPurchased = "product_purchased"
2835
}
2936

3037
/// Coding Keys
@@ -50,6 +57,10 @@ extension WordPressApiError: CustomStringConvertible {
5057

5158
public var description: String {
5259
switch self {
60+
case .productPurchased:
61+
return NSLocalizedString(
62+
"An order aready exists for this receipt",
63+
comment: "Error message when an order already exists in the backend for a given receipt")
5364
case .unknown(let code, let message):
5465
let messageFormat = NSLocalizedString(
5566
"WordPress API Error: [%1$@] %2$@",
@@ -59,3 +70,11 @@ extension WordPressApiError: CustomStringConvertible {
5970
}
6071
}
6172
}
73+
74+
// MARK: - LocalizedError Conformance
75+
//
76+
extension WordPressApiError: LocalizedError {
77+
public var errorDescription: String? {
78+
description
79+
}
80+
}

Yosemite/Yosemite/Stores/InAppPurchaseStore.swift

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Combine
12
import Foundation
23
import Storage
34
import StoreKit
@@ -9,6 +10,7 @@ public class InAppPurchaseStore: Store {
910
private var listenTask: Task<Void, Error>?
1011
private let remote: InAppPurchasesRemote
1112
private var useBackend = true
13+
private var pauseTransactionListener = CurrentValueSubject<Bool, Never>(false)
1214

1315
public override init(dispatcher: Dispatcher, storageManager: StorageManagerType, network: Network) {
1416
remote = InAppPurchasesRemote(network: network)
@@ -93,6 +95,12 @@ private extension InAppPurchaseStore {
9395

9496

9597
logInfo("Purchasing product \(product.id) for site \(siteID) with options \(purchaseOptions)")
98+
logInfo("Pausing transaction listener")
99+
pauseTransactionListener.send(true)
100+
defer {
101+
logInfo("Resuming transaction listener")
102+
pauseTransactionListener.send(false)
103+
}
96104
let purchaseResult = try await product.purchase(options: purchaseOptions)
97105
switch purchaseResult {
98106
case .success(let result):
@@ -193,15 +201,22 @@ private extension InAppPurchaseStore {
193201
let receiptData = try await getAppReceipt()
194202

195203
logInfo("Sending transaction to API for site \(siteID)")
196-
let orderID = try await remote.createOrder(
197-
for: siteID,
198-
price: priceInCents,
199-
productIdentifier: product.id,
200-
appStoreCountryCode: countryCode,
201-
receiptData: receiptData
202-
)
203-
logInfo("Successfully registered purchase with Order ID \(orderID)")
204-
204+
do {
205+
let orderID = try await remote.createOrder(
206+
for: siteID,
207+
price: priceInCents,
208+
productIdentifier: product.id,
209+
appStoreCountryCode: countryCode,
210+
receiptData: receiptData
211+
)
212+
logInfo("Successfully registered purchase with Order ID \(orderID)")
213+
} catch WordPressApiError.productPurchased {
214+
// Ignore errors for existing purchase
215+
logInfo("Existing order found for transaction \(transaction.id) on site \(siteID), ignoring")
216+
} catch {
217+
// Rethrow any other error
218+
throw error
219+
}
205220
}
206221

207222
func userIsEntitledToProduct(with id: String) async throws -> Bool {
@@ -251,11 +266,16 @@ private extension InAppPurchaseStore {
251266
assert(listenTask == nil, "InAppPurchaseStore.listenForTransactions() called while already listening for transactions")
252267

253268
listenTask = Task.detached { [weak self] in
269+
guard let self else {
270+
return
271+
}
254272
for await result in Transaction.updates {
255273
do {
256-
try await self?.handleCompletedTransaction(result)
274+
// Wait until the purchase finishes
275+
_ = await self.pauseTransactionListener.values.contains(false)
276+
try await self.handleCompletedTransaction(result)
257277
} catch {
258-
self?.logError("Error handling transaction update: \(error)")
278+
self.logError("Error handling transaction update: \(error)")
259279
}
260280
}
261281
}

0 commit comments

Comments
 (0)