diff --git a/SwiftyStoreKit/InAppProductPurchaseRequest.swift b/SwiftyStoreKit/InAppProductPurchaseRequest.swift index c71c2837..d359b3f8 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(product: Product) case restored(product: Product) case failed(error: Error) } - + typealias RequestCallback = ([TransactionResult]) -> () private let callback: RequestCallback private var purchases : [SKPaymentTransactionState: [String]] = [:] @@ -40,34 +45,38 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver { var paymentQueue: SKPaymentQueue { return SKPaymentQueue.default() } - - let product : SKProduct? + + let transactionType: TransactionType let atomically: Bool deinit { paymentQueue.remove(self) } - // Initialiser for product purchase - private init(product: SKProduct?, atomically: Bool, callback: @escaping RequestCallback) { + // Initialiser for product purchase + private init(transactionType: TransactionType, atomically: Bool, callback: @escaping RequestCallback) { + self.transactionType = transactionType self.atomically = atomically - self.product = product self.callback = callback super.init() paymentQueue.add(self) } + // MARK: Public methods - class func startPayment(product: SKProduct, atomically: Bool, applicationUsername: String = "", callback: @escaping RequestCallback) -> InAppProductPurchaseRequest { - let request = InAppProductPurchaseRequest(product: product, atomically: atomically, callback: callback) + class func startPayment(_ product: SKProduct, atomically: Bool, applicationUsername: String = "", callback: @escaping RequestCallback) -> InAppProductPurchaseRequest { + let transactionType = TransactionType.purchase(product: product) + let request = InAppProductPurchaseRequest(transactionType: transactionType, atomically: atomically, callback: callback) request.startPayment(product, applicationUsername: applicationUsername) return request } + class func restorePurchases(atomically: Bool, callback: @escaping RequestCallback) -> InAppProductPurchaseRequest { - let request = InAppProductPurchaseRequest(product: nil, atomically: atomically, callback: callback) + let transactionType = TransactionType.restore + let request = InAppProductPurchaseRequest(transactionType: transactionType, atomically: atomically, callback: callback) request.startRestorePurchases() return request } - + class func finishTransaction(_ transaction: PaymentTransaction) { guard let skTransaction = transaction as? SKPaymentTransaction else { print("Object is not a SKPaymentTransaction: \(transaction)") @@ -85,45 +94,44 @@ 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 { - let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically) - transactionResults.append(.purchased(product: product)) - if atomically { - paymentQueue.finishTransaction(transaction) - } + let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically) + transactionResults.append(.purchased(product: product)) + if atomically { + paymentQueue.finishTransaction(transaction) } case .failed: // TODO: How to discriminate between purchase and restore? @@ -134,12 +142,10 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver { transactionResults.append(.failed(error: transaction.error ?? altError)) paymentQueue.finishTransaction(transaction) case .restored: - if !isPurchaseRequest { - let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically) - transactionResults.append(.restored(product: product)) - if atomically { - paymentQueue.finishTransaction(transaction) - } + let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically) + transactionResults.append(.restored(product: product)) + if atomically { + paymentQueue.finishTransaction(transaction) } case .purchasing: // In progress: do nothing @@ -161,13 +167,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)]) } @@ -176,12 +182,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]) { } diff --git a/SwiftyStoreKit/SwiftyStoreKit.swift b/SwiftyStoreKit/SwiftyStoreKit.swift index 7742f274..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 @@ -240,7 +240,8 @@ public class SwiftyStoreKit { case .failed(let error): return .error(error: .failed(error: error)) case .restored(let product): - return .error(error: .failed(error: storeInternalError(code: InternalErrorCode.restoredPurchaseWhenPurchasing.rawValue, description: "Cannot restore product \(product.productId) from purchase path"))) + return .success(product: product) + } }