1+ import Combine
12import Foundation
23import Storage
34import 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