浏览代码

Started implementing PaymentQueueController and associated tests

Andrea Bizzotto 8 年之前
父节点
当前提交
ca5239b289

+ 146 - 1
SwiftyStoreKit.xcodeproj/project.pbxproj

@@ -25,9 +25,16 @@
 		653722811DB8282600C8F944 /* SKProduct+LocalizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653722801DB8282600C8F944 /* SKProduct+LocalizedPrice.swift */; };
 		653722821DB8290A00C8F944 /* SKProduct+LocalizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653722801DB8282600C8F944 /* SKProduct+LocalizedPrice.swift */; };
 		653722831DB8290B00C8F944 /* SKProduct+LocalizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653722801DB8282600C8F944 /* SKProduct+LocalizedPrice.swift */; };
+		658A08371E2EC24E0074A98F /* PaymentQueueController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658A08361E2EC24E0074A98F /* PaymentQueueController.swift */; };
+		658A08381E2EC24E0074A98F /* PaymentQueueController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658A08361E2EC24E0074A98F /* PaymentQueueController.swift */; };
+		658A08391E2EC24E0074A98F /* PaymentQueueController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658A08361E2EC24E0074A98F /* PaymentQueueController.swift */; };
+		658A08431E2EC5120074A98F /* SwiftyStoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6502F62D1B985C40004E342D /* SwiftyStoreKit.framework */; };
+		658A084A1E2EC5350074A98F /* PaymentQueueControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658A08491E2EC5350074A98F /* PaymentQueueControllerTests.swift */; };
+		658A084C1E2EC5960074A98F /* PaymentQueueSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658A084B1E2EC5960074A98F /* PaymentQueueSpy.swift */; };
 		65BB6CE81DDB018900218A0B /* SwiftyStoreKit+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BB6CE71DDB018900218A0B /* SwiftyStoreKit+Types.swift */; };
 		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 */; };
 		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 */; };
@@ -57,6 +64,20 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
+		658A08441E2EC5120074A98F /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 6502F5F61B985833004E342D /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 6502F62C1B985C40004E342D;
+			remoteInfo = SwiftyStoreKit_iOS;
+		};
+		658A084D1E2EC83F0074A98F /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 6502F5F61B985833004E342D /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 6502F5FD1B985833004E342D;
+			remoteInfo = SwiftyStoreKit_iOSDemo;
+		};
 		65F7DF901DCD524300835D30 /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = 6502F5F61B985833004E342D /* Project object */;
@@ -108,7 +129,13 @@
 		6502F62D1B985C40004E342D /* SwiftyStoreKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyStoreKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		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>"; };
+		658A083E1E2EC5120074A98F /* SwiftyStoreKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftyStoreKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		658A08421E2EC5120074A98F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		658A08491E2EC5350074A98F /* PaymentQueueControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentQueueControllerTests.swift; sourceTree = "<group>"; };
+		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>"; };
 		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>"; };
@@ -157,6 +184,14 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		658A083B1E2EC5120074A98F /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				658A08431E2EC5120074A98F /* SwiftyStoreKit.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		C4D74BB71C24CEC90071AD3E /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
@@ -181,6 +216,7 @@
 				6502F6001B985833004E342D /* SwiftyStoreKit */,
 				65F7DF671DCD4DF000835D30 /* SwiftyStoreKit-iOS-Demo */,
 				65F7DF7D1DCD4FC500835D30 /* SwiftyStoreKit-macOS-Demo */,
+				658A083F1E2EC5120074A98F /* SwiftyStoreKitTests */,
 				6502F5FF1B985833004E342D /* Products */,
 			);
 			sourceTree = "<group>";
@@ -193,6 +229,7 @@
 				C4D74BBB1C24CEC90071AD3E /* SwiftyStoreKit.framework */,
 				C4FD3A011C2954C10035CFF3 /* SwiftyStoreKit_macOSDemo.app */,
 				54C0D52C1CF7404500F90BCE /* SwiftyStoreKit.framework */,
+				658A083E1E2EC5120074A98F /* SwiftyStoreKitTests.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -211,10 +248,22 @@
 				65BB6CE71DDB018900218A0B /* SwiftyStoreKit+Types.swift */,
 				C40C680F1C29414C00B60B7E /* OS.swift */,
 				65F7DF931DCD536100835D30 /* Platforms */,
+				658A08361E2EC24E0074A98F /* PaymentQueueController.swift */,
 			);
 			path = SwiftyStoreKit;
 			sourceTree = "<group>";
 		};
+		658A083F1E2EC5120074A98F /* SwiftyStoreKitTests */ = {
+			isa = PBXGroup;
+			children = (
+				658A08421E2EC5120074A98F /* Info.plist */,
+				658A08491E2EC5350074A98F /* PaymentQueueControllerTests.swift */,
+				658A084B1E2EC5960074A98F /* PaymentQueueSpy.swift */,
+				65F70AC61E2ECBB300BF040D /* PaymentTransactionObserverFake.swift */,
+			);
+			path = SwiftyStoreKitTests;
+			sourceTree = "<group>";
+		};
 		65F7DF671DCD4DF000835D30 /* SwiftyStoreKit-iOS-Demo */ = {
 			isa = PBXGroup;
 			children = (
@@ -339,6 +388,25 @@
 			productReference = 6502F62D1B985C40004E342D /* SwiftyStoreKit.framework */;
 			productType = "com.apple.product-type.framework";
 		};
+		658A083D1E2EC5120074A98F /* SwiftyStoreKitTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 658A08461E2EC5120074A98F /* Build configuration list for PBXNativeTarget "SwiftyStoreKitTests" */;
+			buildPhases = (
+				658A083A1E2EC5120074A98F /* Sources */,
+				658A083B1E2EC5120074A98F /* Frameworks */,
+				658A083C1E2EC5120074A98F /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				658A08451E2EC5120074A98F /* PBXTargetDependency */,
+				658A084E1E2EC83F0074A98F /* PBXTargetDependency */,
+			);
+			name = SwiftyStoreKitTests;
+			productName = SwiftyStoreKitTests;
+			productReference = 658A083E1E2EC5120074A98F /* SwiftyStoreKitTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
 		C4D74BBA1C24CEC90071AD3E /* SwiftyStoreKit_macOS */ = {
 			isa = PBXNativeTarget;
 			buildConfigurationList = C4D74BC21C24CECA0071AD3E /* Build configuration list for PBXNativeTarget "SwiftyStoreKit_macOS" */;
@@ -382,7 +450,7 @@
 		6502F5F61B985833004E342D /* Project object */ = {
 			isa = PBXProject;
 			attributes = {
-				LastSwiftUpdateCheck = 0720;
+				LastSwiftUpdateCheck = 0820;
 				LastUpgradeCheck = 0810;
 				ORGANIZATIONNAME = musevisions;
 				TargetAttributes = {
@@ -398,6 +466,11 @@
 						CreatedOnToolsVersion = 7.0;
 						LastSwiftMigration = 0800;
 					};
+					658A083D1E2EC5120074A98F = {
+						CreatedOnToolsVersion = 8.2.1;
+						ProvisioningStyle = Automatic;
+						TestTargetID = 6502F5FD1B985833004E342D;
+					};
 					C4D74BBA1C24CEC90071AD3E = {
 						CreatedOnToolsVersion = 7.2;
 					};
@@ -425,6 +498,7 @@
 				54C0D52B1CF7404500F90BCE /* SwiftyStoreKit_tvOS */,
 				6502F5FD1B985833004E342D /* SwiftyStoreKit_iOSDemo */,
 				C4FD3A001C2954C10035CFF3 /* SwiftyStoreKit_macOSDemo */,
+				658A083D1E2EC5120074A98F /* SwiftyStoreKitTests */,
 			);
 		};
 /* End PBXProject section */
@@ -454,6 +528,13 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		658A083C1E2EC5120074A98F /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		C4D74BB91C24CEC90071AD3E /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -483,6 +564,7 @@
 				54C0D5681CF7428400F90BCE /* SwiftyStoreKit.swift in Sources */,
 				54B069961CF744DC00BAFE38 /* OS.swift in Sources */,
 				54B069931CF742D300BAFE38 /* InAppReceiptRefreshRequest.swift in Sources */,
+				658A08391E2EC24E0074A98F /* PaymentQueueController.swift in Sources */,
 				653722831DB8290B00C8F944 /* SKProduct+LocalizedPrice.swift in Sources */,
 				54B069921CF742D100BAFE38 /* InAppReceipt.swift in Sources */,
 				65BB6CEA1DDB018900218A0B /* SwiftyStoreKit+Types.swift in Sources */,
@@ -510,6 +592,7 @@
 				6502F63A1B985C9E004E342D /* InAppProductPurchaseRequest.swift in Sources */,
 				6502F63B1B985CA1004E342D /* InAppProductQueryRequest.swift in Sources */,
 				C4083C571C2AB0A900295248 /* InAppReceiptRefreshRequest.swift in Sources */,
+				658A08371E2EC24E0074A98F /* PaymentQueueController.swift in Sources */,
 				653722811DB8282600C8F944 /* SKProduct+LocalizedPrice.swift in Sources */,
 				C4A7C7631C29B8D00053ED64 /* InAppReceipt.swift in Sources */,
 				65BB6CE81DDB018900218A0B /* SwiftyStoreKit+Types.swift in Sources */,
@@ -517,6 +600,16 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		658A083A1E2EC5120074A98F /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				65F70AC71E2ECBB300BF040D /* PaymentTransactionObserverFake.swift in Sources */,
+				658A084A1E2EC5350074A98F /* PaymentQueueControllerTests.swift in Sources */,
+				658A084C1E2EC5960074A98F /* PaymentQueueSpy.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		C4D74BB61C24CEC90071AD3E /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -527,6 +620,7 @@
 				C4D74BC31C24CEDC0071AD3E /* InAppProductPurchaseRequest.swift in Sources */,
 				C4D74BC41C24CEDC0071AD3E /* InAppProductQueryRequest.swift in Sources */,
 				C4F69A8A1C2E0D21009DD8BD /* InAppReceiptRefreshRequest.swift in Sources */,
+				658A08381E2EC24E0074A98F /* PaymentQueueController.swift in Sources */,
 				653722821DB8290A00C8F944 /* SKProduct+LocalizedPrice.swift in Sources */,
 				C4083C551C2AADB500295248 /* InAppReceipt.swift in Sources */,
 				65BB6CE91DDB018900218A0B /* SwiftyStoreKit+Types.swift in Sources */,
@@ -546,6 +640,16 @@
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXTargetDependency section */
+		658A08451E2EC5120074A98F /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 6502F62C1B985C40004E342D /* SwiftyStoreKit_iOS */;
+			targetProxy = 658A08441E2EC5120074A98F /* PBXContainerItemProxy */;
+		};
+		658A084E1E2EC83F0074A98F /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 6502F5FD1B985833004E342D /* SwiftyStoreKit_iOSDemo */;
+			targetProxy = 658A084D1E2EC83F0074A98F /* PBXContainerItemProxy */;
+		};
 		65F7DF911DCD524300835D30 /* PBXTargetDependency */ = {
 			isa = PBXTargetDependency;
 			target = 6502F62C1B985C40004E342D /* SwiftyStoreKit_iOS */;
@@ -806,6 +910,38 @@
 			};
 			name = Release;
 		};
+		658A08471E2EC5120074A98F /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				INFOPLIST_FILE = SwiftyStoreKitTests/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 10.2;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.musevisions.iOS.SwiftyStoreKitTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_VERSION = 3.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftyStoreKit_iOSDemo.app/SwiftyStoreKit_iOSDemo";
+			};
+			name = Debug;
+		};
+		658A08481E2EC5120074A98F /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				INFOPLIST_FILE = SwiftyStoreKitTests/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 10.2;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.musevisions.iOS.SwiftyStoreKitTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+				SWIFT_VERSION = 3.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftyStoreKit_iOSDemo.app/SwiftyStoreKit_iOSDemo";
+			};
+			name = Release;
+		};
 		C4D74BC01C24CECA0071AD3E /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -927,6 +1063,15 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		658A08461E2EC5120074A98F /* Build configuration list for PBXNativeTarget "SwiftyStoreKitTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				658A08471E2EC5120074A98F /* Debug */,
+				658A08481E2EC5120074A98F /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 		C4D74BC21C24CECA0071AD3E /* Build configuration list for PBXNativeTarget "SwiftyStoreKit_macOS" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (

+ 10 - 0
SwiftyStoreKit.xcodeproj/xcuserdata/andrea.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -39,6 +39,11 @@
 			<key>orderHint</key>
 			<integer>1</integer>
 		</dict>
+		<key>SwiftyStoreKitTests.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>5</integer>
+		</dict>
 	</dict>
 	<key>SuppressBuildableAutocreation</key>
 	<dict>
@@ -57,6 +62,11 @@
 			<key>primary</key>
 			<true/>
 		</dict>
+		<key>658A083D1E2EC5120074A98F</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
 		<key>C4D74BBA1C24CEC90071AD3E</key>
 		<dict>
 			<key>primary</key>

+ 102 - 0
SwiftyStoreKit/PaymentQueueController.swift

@@ -0,0 +1,102 @@
+//
+// PaymentQueueController.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 protocol PaymentQueue: class {
+
+    func add(_ observer: SKPaymentTransactionObserver)
+    func remove(_ observer: SKPaymentTransactionObserver)
+
+    func add(_ payment: SKPayment)
+    
+    func restoreCompletedTransactions()
+}
+
+extension SKPaymentQueue: PaymentQueue { }
+
+public class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
+
+    public enum TransactionResult {
+        case purchased(product: Product)
+        case restored(product: Product)
+        case failed(error: Error)
+    }
+    
+    public struct Payment {
+        public let product: SKProduct
+        public let atomically: Bool
+        public let applicationUsername: String
+        public let callback: (TransactionResult) -> ()
+    }
+    
+    public struct RestorePurchases {
+        let atomically: Bool
+        let callback: ([TransactionResult]) -> ()
+    }
+
+    unowned let paymentQueue: PaymentQueue
+
+    deinit {
+        paymentQueue.remove(self)
+    }
+
+    public init(paymentQueue: PaymentQueue = SKPaymentQueue.default()) {
+     
+        self.paymentQueue = paymentQueue
+        super.init()
+        paymentQueue.add(self)
+    }
+    
+    public func startPayment(_ payment: Payment) {
+        
+        let skPayment = SKMutablePayment(product: payment.product)
+        skPayment.applicationUsername = payment.applicationUsername
+        paymentQueue.add(skPayment)
+    }
+    
+    
+    // MARK: SKPaymentTransactionObserver
+    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
+
+    }
+    
+    public func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
+        
+    }
+    
+    public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
+        
+    }
+    
+    public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
+
+    }
+    
+    public func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
+        
+    }
+
+}

+ 22 - 0
SwiftyStoreKitTests/Info.plist

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>

+ 88 - 0
SwiftyStoreKitTests/PaymentQueueControllerTests.swift

@@ -0,0 +1,88 @@
+//
+// PaymentQueueControllerTests.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 XCTest
+import SwiftyStoreKit
+import StoreKit
+
+extension PaymentQueueController.Payment {
+    public init(product: SKProduct, atomically: Bool, applicationUsername: String, callback: @escaping (PaymentQueueController.TransactionResult) -> ()) {
+        self.product = product
+        self.atomically = atomically
+        self.applicationUsername = applicationUsername
+        self.callback = callback
+    }
+}
+
+class PaymentQueueControllerTests: XCTestCase {
+
+    class TestProduct: SKProduct {
+        
+        var _productIdentifier: String = ""
+        
+        override var productIdentifier: String {
+            return _productIdentifier
+        }
+        
+        init(productIdentifier: String) {
+            _productIdentifier = productIdentifier
+            super.init()
+        }
+    }
+    
+    // MARK: init/deinit
+    func testInit_registersAsObserver() {
+        
+        let spy = PaymentQueueSpy()
+        
+        let paymentQueueController = PaymentQueueController(paymentQueue: spy)
+        
+        XCTAssertTrue(spy.observer === paymentQueueController)
+    }
+    
+    func testDeinit_removesObserver() {
+        
+        let spy = PaymentQueueSpy()
+        
+        let _ = PaymentQueueController(paymentQueue: spy)
+        
+        XCTAssertNil(spy.observer)
+    }
+    
+    // MARK: Start payment
+    
+    func testStartTransaction_QueuesOnePayment() {
+        
+        let spy = PaymentQueueSpy()
+        
+        let paymentQueueController = PaymentQueueController(paymentQueue: spy)
+
+        let product = TestProduct(productIdentifier: "com.SwiftyStoreKit.product1")
+        let payment = PaymentQueueController.Payment(product: product, atomically: true, applicationUsername: "", callback: { result in })
+
+        paymentQueueController.startPayment(payment)
+        
+        XCTAssertEqual(spy.payments.count, 1)
+    }
+}

+ 41 - 0
SwiftyStoreKitTests/PaymentQueueSpy.swift

@@ -0,0 +1,41 @@
+//
+//  PaymentQueueSpy.swift
+//  SwiftyStoreKit
+//
+//  Created by Andrea Bizzotto on 17/01/2017.
+//  Copyright © 2017 musevisions. All rights reserved.
+//
+
+import SwiftyStoreKit
+import StoreKit
+
+class PaymentQueueSpy: PaymentQueue {
+    
+    weak var observer: SKPaymentTransactionObserver?
+    
+    var payments: [SKPayment] = []
+    
+    var restoreCompletedTransactionCalledCount = 0
+
+    func add(_ observer: SKPaymentTransactionObserver) {
+        
+        self.observer = observer
+    }
+    func remove(_ observer: SKPaymentTransactionObserver) {
+        
+        if self.observer === observer {
+            self.observer = nil
+        }
+    }
+    
+    func add(_ payment: SKPayment) {
+        
+        payments.append(payment)
+    }
+    
+    func restoreCompletedTransactions() {
+        
+        restoreCompletedTransactionCalledCount += 1
+    }
+
+}

+ 13 - 0
SwiftyStoreKitTests/PaymentTransactionObserverFake.swift

@@ -0,0 +1,13 @@
+//
+//  PaymentTransactionObserverFake.swift
+//  SwiftyStoreKit
+//
+//  Created by Andrea Bizzotto on 17/01/2017.
+//  Copyright © 2017 musevisions. All rights reserved.
+//
+
+import UIKit
+
+class PaymentTransactionObserverFake: NSObject {
+
+}