From 161c4beffc1c89ccbd168e7a047776f9c866a953 Mon Sep 17 00:00:00 2001 From: Mark Bridges Date: Fri, 11 Nov 2016 00:06:17 +0000 Subject: [PATCH 1/4] Fixed issue with restored transactions Fixed an issue, discovered on OSX, where when a user attempts to repurchase a non-consumable IAP, the 'purchase' completion block is never called. --- SwiftyStoreKit/InAppProductPurchaseRequest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwiftyStoreKit/InAppProductPurchaseRequest.swift b/SwiftyStoreKit/InAppProductPurchaseRequest.swift index 3bd344c5..3c038ebd 100644 --- a/SwiftyStoreKit/InAppProductPurchaseRequest.swift +++ b/SwiftyStoreKit/InAppProductPurchaseRequest.swift @@ -121,7 +121,7 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver { transactionResults.append(.failed(error: transaction.error ?? altError)) paymentQueue.finishTransaction(transaction) case .restored: - if !isPurchaseRequest { + if isPurchaseRequest { transactionResults.append(.restored(productId: transactionProductIdentifier)) paymentQueue.finishTransaction(transaction) } From 4e93211d3a0ee02e2bba200b4c8a52db492722a0 Mon Sep 17 00:00:00 2001 From: Mark Bridges Date: Fri, 11 Nov 2016 08:08:46 +0000 Subject: [PATCH 2/4] Added TransactionType enum --- .../InAppProductPurchaseRequest.swift | 74 ++++++++++--------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/SwiftyStoreKit/InAppProductPurchaseRequest.swift b/SwiftyStoreKit/InAppProductPurchaseRequest.swift index 3c038ebd..04911d37 100644 --- a/SwiftyStoreKit/InAppProductPurchaseRequest.swift +++ b/SwiftyStoreKit/InAppProductPurchaseRequest.swift @@ -27,12 +27,17 @@ import Foundation class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver { + enum TransactionType { + case purchase(product: SKProduct) + case restore + } + enum TransactionResult { case purchased(productId: String) case restored(productId: String) case failed(error: Error) } - + typealias RequestCallback = ([TransactionResult]) -> () private let callback: RequestCallback private var purchases : [SKPaymentTransactionState: [String]] = [:] @@ -40,32 +45,34 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver { var paymentQueue: SKPaymentQueue { return SKPaymentQueue.default() } - - let product : SKProduct? - + + let transactionType: TransactionType + + deinit { paymentQueue.remove(self) } // Initialiser for product purchase - private init(product: SKProduct?, callback: @escaping RequestCallback) { - - self.product = product + private init(transactionType: TransactionType, callback: @escaping RequestCallback) { + self.transactionType = transactionType self.callback = callback super.init() paymentQueue.add(self) } // MARK: Public methods class func startPayment(_ product: SKProduct, applicationUsername: String = "", callback: @escaping RequestCallback) -> InAppProductPurchaseRequest { - let request = InAppProductPurchaseRequest(product: product, callback: callback) + let transactionType = TransactionType.purchase(product: product) + let request = InAppProductPurchaseRequest(transactionType: transactionType, callback: callback) request.startPayment(product, applicationUsername: applicationUsername) return request } class func restorePurchases(_ callback: @escaping RequestCallback) -> InAppProductPurchaseRequest { - let request = InAppProductPurchaseRequest(product: nil, callback: callback) + let transactionType = TransactionType.restore + let request = InAppProductPurchaseRequest(transactionType: transactionType, callback: callback) request.startRestorePurchases() return request } - + // MARK: Private methods private func startPayment(_ product: SKProduct, applicationUsername: String = "") { guard let _ = product._productIdentifier else { @@ -75,43 +82,42 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver { } let payment = SKMutablePayment(product: product) payment.applicationUsername = applicationUsername - + DispatchQueue.global(qos: .default).async { self.paymentQueue.add(payment) } } private func startRestorePurchases() { - + DispatchQueue.global(qos: .default).async { self.paymentQueue.restoreCompletedTransactions() } } - + // MARK: SKPaymentTransactionObserver func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { - + var transactionResults: [TransactionResult] = [] - + for transaction in transactions { - + let transactionProductIdentifier = transaction.payment.productIdentifier - - var isPurchaseRequest = false - if let productIdentifier = product?._productIdentifier { - if transactionProductIdentifier != productIdentifier { + switch transactionType { + case .purchase(let product): + guard transactionProductIdentifier == product._productIdentifier else { continue } - isPurchaseRequest = true + + default: + break } let transactionState = transaction.transactionState switch transactionState { case .purchased: - if isPurchaseRequest { - transactionResults.append(.purchased(productId: transactionProductIdentifier)) - paymentQueue.finishTransaction(transaction) - } + transactionResults.append(.purchased(productId: transactionProductIdentifier)) + paymentQueue.finishTransaction(transaction) case .failed: // TODO: How to discriminate between purchase and restore? // It appears that in some edge cases transaction.error is nil here. Since returning an associated error is @@ -121,10 +127,8 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver { transactionResults.append(.failed(error: transaction.error ?? altError)) paymentQueue.finishTransaction(transaction) case .restored: - if isPurchaseRequest { - transactionResults.append(.restored(productId: transactionProductIdentifier)) - paymentQueue.finishTransaction(transaction) - } + transactionResults.append(.restored(productId: transactionProductIdentifier)) + paymentQueue.finishTransaction(transaction) case .purchasing: // In progress: do nothing break @@ -145,13 +149,13 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver { } } } - + func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) { - + } - + func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) { - + DispatchQueue.main.async { self.callback([.failed(error: error)]) } @@ -160,12 +164,12 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver { func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { // This method will be called after all purchases have been restored (includes the case of no purchases) guard let restored = purchases[.restored], restored.count > 0 else { - + self.callback([]) return } } - + func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) { } From 5028e4ff997ae0a0d5048cfca896fa6f49f62d1b Mon Sep 17 00:00:00 2001 From: Mark Bridges Date: Fri, 11 Nov 2016 08:13:11 +0000 Subject: [PATCH 3/4] Made processPuchase result accept a restored type --- SwiftyStoreKit/SwiftyStoreKit.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwiftyStoreKit/SwiftyStoreKit.swift b/SwiftyStoreKit/SwiftyStoreKit.swift index f7f378e8..e64ab947 100644 --- a/SwiftyStoreKit/SwiftyStoreKit.swift +++ b/SwiftyStoreKit/SwiftyStoreKit.swift @@ -263,7 +263,7 @@ public class SwiftyStoreKit { case .failed(let error): return .error(error: .failed(error: error)) case .restored(let productId): - return .error(error: .failed(error: storeInternalError(code: InternalErrorCode.restoredPurchaseWhenPurchasing.rawValue, description: "Cannot restore product \(productId) from purchase path"))) + return .success(productId: productId) } } From f6ab17f0ea11ceba27d0eb8cdd07ca4c14e6812e Mon Sep 17 00:00:00 2001 From: Mark Bridges Date: Thu, 17 Nov 2016 19:46:48 +0000 Subject: [PATCH 4/4] Builds --- SwiftyStoreKit/SwiftyStoreKit.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SwiftyStoreKit/SwiftyStoreKit.swift b/SwiftyStoreKit/SwiftyStoreKit.swift index 58b9fab0..1f6232f5 100644 --- a/SwiftyStoreKit/SwiftyStoreKit.swift +++ b/SwiftyStoreKit/SwiftyStoreKit.swift @@ -222,7 +222,7 @@ public class SwiftyStoreKit { return } - inflightPurchases[productIdentifier] = InAppProductPurchaseRequest.startPayment(product: product, atomically: atomically, applicationUsername: applicationUsername) { results in + inflightPurchases[productIdentifier] = InAppProductPurchaseRequest.startPayment(product, atomically: atomically, applicationUsername: applicationUsername) { results in self.inflightPurchases[productIdentifier] = nil @@ -239,8 +239,8 @@ public class SwiftyStoreKit { return .success(product: product) case .failed(let error): return .error(error: .failed(error: error)) - case .restored(let productId): - return .success(productId: productId) + case .restored(let product): + return .success(product: product) } }