Andrea Bizzotto 8 vuotta sitten
vanhempi
commit
c0749d206c

+ 16 - 8
SwiftyStoreKit.xcodeproj/project.pbxproj

@@ -21,6 +21,9 @@
 		6502F63B1B985CA1004E342D /* InAppProductQueryRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6502F6231B98586A004E342D /* InAppProductQueryRequest.swift */; };
 		6502F63C1B985CA4004E342D /* SwiftyStoreKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6502F6241B98586A004E342D /* SwiftyStoreKit.swift */; };
 		650307F21E3163AA001332A4 /* RestorePurchasesControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650307F11E3163AA001332A4 /* RestorePurchasesControllerTests.swift */; };
+		650307F41E3177EF001332A4 /* RestorePurchasesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650307F31E3177EF001332A4 /* RestorePurchasesController.swift */; };
+		650307F51E3177EF001332A4 /* RestorePurchasesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650307F31E3177EF001332A4 /* RestorePurchasesController.swift */; };
+		650307F61E3177EF001332A4 /* RestorePurchasesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650307F31E3177EF001332A4 /* RestorePurchasesController.swift */; };
 		651A71251CD651AF000B4091 /* InAppCompleteTransactionsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651A71241CD651AF000B4091 /* InAppCompleteTransactionsObserver.swift */; };
 		651A71261CD651AF000B4091 /* InAppCompleteTransactionsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651A71241CD651AF000B4091 /* InAppCompleteTransactionsObserver.swift */; };
 		653722811DB8282600C8F944 /* SKProduct+LocalizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653722801DB8282600C8F944 /* SKProduct+LocalizedPrice.swift */; };
@@ -36,9 +39,9 @@
 		65BB6CE91DDB018900218A0B /* SwiftyStoreKit+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BB6CE71DDB018900218A0B /* SwiftyStoreKit+Types.swift */; };
 		65BB6CEA1DDB018900218A0B /* SwiftyStoreKit+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BB6CE71DDB018900218A0B /* SwiftyStoreKit+Types.swift */; };
 		65F70AC71E2ECBB300BF040D /* PaymentTransactionObserverFake.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC61E2ECBB300BF040D /* PaymentTransactionObserverFake.swift */; };
-		65F70AC91E2EDC3700BF040D /* Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC81E2EDC3700BF040D /* Payments.swift */; };
-		65F70ACA1E2EDC3700BF040D /* Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC81E2EDC3700BF040D /* Payments.swift */; };
-		65F70ACB1E2EDC3700BF040D /* Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC81E2EDC3700BF040D /* Payments.swift */; };
+		65F70AC91E2EDC3700BF040D /* PaymentsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC81E2EDC3700BF040D /* PaymentsController.swift */; };
+		65F70ACA1E2EDC3700BF040D /* PaymentsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC81E2EDC3700BF040D /* PaymentsController.swift */; };
+		65F70ACB1E2EDC3700BF040D /* PaymentsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC81E2EDC3700BF040D /* PaymentsController.swift */; };
 		65F7DF711DCD4DF000835D30 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F7DF681DCD4DF000835D30 /* AppDelegate.swift */; };
 		65F7DF721DCD4DF000835D30 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65F7DF691DCD4DF000835D30 /* Assets.xcassets */; };
 		65F7DF731DCD4DF000835D30 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65F7DF6A1DCD4DF000835D30 /* LaunchScreen.storyboard */; };
@@ -135,6 +138,7 @@
 		6502F6241B98586A004E342D /* SwiftyStoreKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyStoreKit.swift; sourceTree = "<group>"; };
 		6502F62D1B985C40004E342D /* SwiftyStoreKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyStoreKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		650307F11E3163AA001332A4 /* RestorePurchasesControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestorePurchasesControllerTests.swift; sourceTree = "<group>"; };
+		650307F31E3177EF001332A4 /* RestorePurchasesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestorePurchasesController.swift; sourceTree = "<group>"; };
 		651A71241CD651AF000B4091 /* InAppCompleteTransactionsObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppCompleteTransactionsObserver.swift; sourceTree = "<group>"; };
 		653722801DB8282600C8F944 /* SKProduct+LocalizedPrice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKProduct+LocalizedPrice.swift"; sourceTree = "<group>"; };
 		658A08361E2EC24E0074A98F /* PaymentQueueController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentQueueController.swift; sourceTree = "<group>"; };
@@ -144,7 +148,7 @@
 		658A084B1E2EC5960074A98F /* PaymentQueueSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentQueueSpy.swift; sourceTree = "<group>"; };
 		65BB6CE71DDB018900218A0B /* SwiftyStoreKit+Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftyStoreKit+Types.swift"; sourceTree = "<group>"; };
 		65F70AC61E2ECBB300BF040D /* PaymentTransactionObserverFake.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentTransactionObserverFake.swift; sourceTree = "<group>"; };
-		65F70AC81E2EDC3700BF040D /* Payments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Payments.swift; sourceTree = "<group>"; };
+		65F70AC81E2EDC3700BF040D /* PaymentsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentsController.swift; sourceTree = "<group>"; };
 		65F7DF681DCD4DF000835D30 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		65F7DF691DCD4DF000835D30 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		65F7DF6B1DCD4DF000835D30 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
@@ -261,7 +265,8 @@
 				C40C680F1C29414C00B60B7E /* OS.swift */,
 				65F7DF931DCD536100835D30 /* Platforms */,
 				658A08361E2EC24E0074A98F /* PaymentQueueController.swift */,
-				65F70AC81E2EDC3700BF040D /* Payments.swift */,
+				65F70AC81E2EDC3700BF040D /* PaymentsController.swift */,
+				650307F31E3177EF001332A4 /* RestorePurchasesController.swift */,
 			);
 			path = SwiftyStoreKit;
 			sourceTree = "<group>";
@@ -582,7 +587,8 @@
 				54C0D5681CF7428400F90BCE /* SwiftyStoreKit.swift in Sources */,
 				54B069961CF744DC00BAFE38 /* OS.swift in Sources */,
 				54B069931CF742D300BAFE38 /* InAppReceiptRefreshRequest.swift in Sources */,
-				65F70ACB1E2EDC3700BF040D /* Payments.swift in Sources */,
+				65F70ACB1E2EDC3700BF040D /* PaymentsController.swift in Sources */,
+				650307F61E3177EF001332A4 /* RestorePurchasesController.swift in Sources */,
 				658A08391E2EC24E0074A98F /* PaymentQueueController.swift in Sources */,
 				653722831DB8290B00C8F944 /* SKProduct+LocalizedPrice.swift in Sources */,
 				54B069921CF742D100BAFE38 /* InAppReceipt.swift in Sources */,
@@ -611,7 +617,8 @@
 				6502F63A1B985C9E004E342D /* InAppProductPurchaseRequest.swift in Sources */,
 				6502F63B1B985CA1004E342D /* InAppProductQueryRequest.swift in Sources */,
 				C4083C571C2AB0A900295248 /* InAppReceiptRefreshRequest.swift in Sources */,
-				65F70AC91E2EDC3700BF040D /* Payments.swift in Sources */,
+				65F70AC91E2EDC3700BF040D /* PaymentsController.swift in Sources */,
+				650307F41E3177EF001332A4 /* RestorePurchasesController.swift in Sources */,
 				658A08371E2EC24E0074A98F /* PaymentQueueController.swift in Sources */,
 				653722811DB8282600C8F944 /* SKProduct+LocalizedPrice.swift in Sources */,
 				C4A7C7631C29B8D00053ED64 /* InAppReceipt.swift in Sources */,
@@ -644,7 +651,8 @@
 				C4D74BC31C24CEDC0071AD3E /* InAppProductPurchaseRequest.swift in Sources */,
 				C4D74BC41C24CEDC0071AD3E /* InAppProductQueryRequest.swift in Sources */,
 				C4F69A8A1C2E0D21009DD8BD /* InAppReceiptRefreshRequest.swift in Sources */,
-				65F70ACA1E2EDC3700BF040D /* Payments.swift in Sources */,
+				65F70ACA1E2EDC3700BF040D /* PaymentsController.swift in Sources */,
+				650307F51E3177EF001332A4 /* RestorePurchasesController.swift in Sources */,
 				658A08381E2EC24E0074A98F /* PaymentQueueController.swift in Sources */,
 				653722821DB8290A00C8F944 /* SKProduct+LocalizedPrice.swift in Sources */,
 				C4083C551C2AADB500295248 /* InAppReceipt.swift in Sources */,

+ 17 - 7
SwiftyStoreKit/PaymentQueueController.swift

@@ -25,6 +25,23 @@
 import Foundation
 import StoreKit
 
+
+public protocol TransactionController {
+    
+    /**
+     * - param transactions: transactions to process
+     * - param paymentQueue: payment queue for finishing transactions
+     * - return: array of unhandled transactions
+     */
+    func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction]
+}
+
+public enum TransactionResult {
+    case purchased(product: Product)
+    case restored(product: Product)
+    case failed(error: Error)
+}
+
 public protocol PaymentQueue: class {
 
     func add(_ observer: SKPaymentTransactionObserver)
@@ -156,10 +173,3 @@ public class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
     }
 
 }
-
-/*
- If more than one payment is queued for a given product Id,
- only the first callback should be called to ensure the content is delivered only once
- 
- 
- */

+ 21 - 84
SwiftyStoreKit/Payments.swift → SwiftyStoreKit/PaymentsController.swift

@@ -1,41 +1,31 @@
 //
-//  Payments.swift
-//  SwiftyStoreKit
+// PaymentsController.swift
+// SwiftyStoreKit
 //
-//  Created by Andrea Bizzotto on 17/01/2017.
-//  Copyright © 2017 musevisions. All rights reserved.
+// Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com)
 //
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
 
 import Foundation
 import StoreKit
 
-
-public protocol TransactionController {
-    
-    /**
-     * - param transactions: transactions to process
-     * - param paymentQueue: payment queue for finishing transactions
-     * - return: array of unhandled transactions
-     */
-    func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction]
-}
-
-public enum TransactionResult {
-    case purchased(product: Product)
-    case restored(product: Product)
-    case failed(error: Error)
-}
-
-public struct RestorePurchases {
-    public let atomically: Bool
-    public let callback: ([TransactionResult]) -> ()
-    
-    public init(atomically: Bool, callback: @escaping ([TransactionResult]) -> ()) {
-        self.atomically = atomically
-        self.callback = callback
-    }
-}
-
 public struct Payment: Hashable {
     public let product: SKProduct
     public let atomically: Bool
@@ -118,56 +108,3 @@ public class PaymentsController: TransactionController {
     }
 }
 
-public class RestorePurchasesController: TransactionController {
-
-    public var restorePurchases: RestorePurchases?
-    
-    public init() { }
-    
-    public func processTransaction(_ transaction: SKPaymentTransaction, atomically: Bool, on paymentQueue: PaymentQueue) -> Product? {
-        
-        let transactionState = transaction.transactionState
-
-        if transactionState == .restored {
-
-            let transactionProductIdentifier = transaction.payment.productIdentifier
-
-            let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically)
-            if atomically {
-                paymentQueue.finishTransaction(transaction)
-            }
-            return product
-        }
-        return nil
-    }
-    
-    public func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] {
-        
-        guard let restorePurchases = restorePurchases else {
-            return transactions
-        }
-        
-        var unhandledTransactions: [SKPaymentTransaction] = []
-        var restoredProducts: [TransactionResult] = []
-        for transaction in transactions {
-            if let restoredProduct = processTransaction(transaction, atomically: restorePurchases.atomically, on: paymentQueue) {
-                restoredProducts.append(.restored(product: restoredProduct))
-            }
-            else {
-                unhandledTransactions.append(transaction)
-            }
-        }
-        if restoredProducts.count > 0 {
-            restorePurchases.callback(restoredProducts)
-        }
-        return unhandledTransactions
-    }
-    
-    public func restoreCompletedTransactionsFailed(withError error: Error) {
-
-        guard let restorePurchases = restorePurchases else {
-            return
-        }
-        restorePurchases.callback([.failed(error: error)])
-    }
-}

+ 91 - 0
SwiftyStoreKit/RestorePurchasesController.swift

@@ -0,0 +1,91 @@
+//
+// RestorePurchasesController.swift
+// SwiftyStoreKit
+//
+// Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+import StoreKit
+
+public struct RestorePurchases {
+    public let atomically: Bool
+    public let callback: ([TransactionResult]) -> ()
+    
+    public init(atomically: Bool, callback: @escaping ([TransactionResult]) -> ()) {
+        self.atomically = atomically
+        self.callback = callback
+    }
+}
+
+
+public class RestorePurchasesController: TransactionController {
+    
+    public var restorePurchases: RestorePurchases?
+    
+    public init() { }
+    
+    public func processTransaction(_ transaction: SKPaymentTransaction, atomically: Bool, on paymentQueue: PaymentQueue) -> Product? {
+        
+        let transactionState = transaction.transactionState
+        
+        if transactionState == .restored {
+            
+            let transactionProductIdentifier = transaction.payment.productIdentifier
+            
+            let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically)
+            if atomically {
+                paymentQueue.finishTransaction(transaction)
+            }
+            return product
+        }
+        return nil
+    }
+    
+    public func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] {
+        
+        guard let restorePurchases = restorePurchases else {
+            return transactions
+        }
+        
+        var unhandledTransactions: [SKPaymentTransaction] = []
+        var restoredProducts: [TransactionResult] = []
+        for transaction in transactions {
+            if let restoredProduct = processTransaction(transaction, atomically: restorePurchases.atomically, on: paymentQueue) {
+                restoredProducts.append(.restored(product: restoredProduct))
+            }
+            else {
+                unhandledTransactions.append(transaction)
+            }
+        }
+        if restoredProducts.count > 0 {
+            restorePurchases.callback(restoredProducts)
+        }
+        return unhandledTransactions
+    }
+    
+    public func restoreCompletedTransactionsFailed(withError error: Error) {
+        
+        guard let restorePurchases = restorePurchases else {
+            return
+        }
+        restorePurchases.callback([.failed(error: error)])
+    }
+}

+ 9 - 4
SwiftyStoreKitTests/RestorePurchasesControllerTests.swift

@@ -29,7 +29,6 @@ import StoreKit
 
 class RestorePurchasesControllerTests: XCTestCase {
     
-    
     func testProcessTransactions_when_oneRestoredTransaction_then_finishesTransaction_callsCallback_noRemainingTransactions() {
         
         let productIdentifier = "com.SwiftyStoreKit.product1"
@@ -88,13 +87,19 @@ class RestorePurchasesControllerTests: XCTestCase {
             callbackCalled = true
             XCTAssertEqual(results.count, 2)
             let first = results.first!
-            let last = results.last!
-            if case .restored(let restoredProduct) = restored {
+            if case .restored(let restoredProduct) = first {
                 XCTAssertEqual(restoredProduct.productId, productIdentifier1)
             }
             else {
                 XCTFail("expected restored callback with product")
             }
+            let last = results.last!
+            if case .restored(let restoredProduct) = last {
+                XCTAssertEqual(restoredProduct.productId, productIdentifier2)
+            }
+            else {
+                XCTFail("expected restored callback with product")
+            }
         }
         
         let restorePurchasesController = makeRestorePurchasesController(restorePurchases: restorePurchases)
@@ -107,7 +112,7 @@ class RestorePurchasesControllerTests: XCTestCase {
         
         XCTAssertTrue(callbackCalled)
         
-        XCTAssertEqual(spy.finishTransactionCalledCount, 1)
+        XCTAssertEqual(spy.finishTransactionCalledCount, 2)
     }