Эх сурвалжийг харах

Merge remote-tracking branch 'origin/master' into feature-receipt

Conflicts:
	SwiftStoreOSXDemo/ViewController.swift
phimage 9 жил өмнө
parent
commit
7b45c5908c

+ 12 - 4
README.md

@@ -1,5 +1,5 @@
 # SwiftyStoreKit
-SwiftyStoreKit is a lightweight In App Purchases framework for iOS 8.0+, written in Swift 2.0.
+SwiftyStoreKit is a lightweight In App Purchases framework for iOS 8.0+ and OSX 9.0+, written in Swift 2.0.
 
 ### Preview
 
@@ -58,6 +58,7 @@ The framework provides a simple block based API with robust error handling on to
 
 ## Installation
 SwiftyStoreKit can be installed as a Cocoapod and builds as a Swift framework. To install, include this in your Podfile.
+
 ```
 use_frameworks!
 
@@ -66,16 +67,18 @@ pod 'SwiftyStoreKit'
 Once installed, just ```import SwiftyStoreKit``` in your classes and you're good to go.
 
 ## Sample Code
-The project includes a demo app showing [how to use](https://github.com/bizz84/SwiftyStoreKit/blob/master/SwiftyStoreDemo/ViewController.swift) SwiftyStoreKit.
-Note that the pre-registered in app purchases in the demo app are for illustration purposes only and may not work as iTunes Connect may invalidate them.
+The project includes demo apps [for iOS](https://github.com/bizz84/SwiftyStoreKit/blob/master/SwiftyStoreDemo/ViewController.swift) [and OSX](https://github.com/bizz84/SwiftyStoreKit/blob/master/SwiftyStoreOSXDemo/ViewController.swift) showing how to use SwiftyStoreKit.
+Note that the pre-registered in app purchases in the demo apps are for illustration purposes only and may not work as iTunes Connect may invalidate them.
 
 #### Features
 - Super easy to use block based API
 - enum-based error handling
 - Support for non-consumable in app purchases
 
-#### Missing Features
+#### Planned Features
 - Receipt verification
+
+#### Missing Features
 - Ask To Buy
 
 #### Untested Features
@@ -84,7 +87,9 @@ Note that the pre-registered in app purchases in the demo app are for illustrati
 
 ## Implementation Details
 In order to make a purchase, two operations are needed:
+
 - Obtain the ```SKProduct``` corresponding to the productId that identifies the app purchase, via ```SKProductRequest```.
+ 
 - Submit the payment for that product via ```SKPaymentQueue```.
 
 The framework takes care of caching SKProducts so that future requests for the same ```SKProduct``` don't need to perform a new ```SKProductRequest```.
@@ -120,6 +125,9 @@ enum TransactionResult {
 The ```SwiftyStoreKit``` class can then map the returned ```TransactionResult``` into either a success or failure case and pass this back to the client.
 Note that along with the success and failure case, the result of a restore purchases operation also has a ```NothingToRestore``` case. This is so that the client can know that the operation returned, but no purchases were restored.
 
+## Credits
+Many thanks to [phimage](https://github.com/phimage) for adding OSX support and receipt verification.
+
 ## License
 
 Copyright (c) 2015 Andrea Bizzotto bizz84@gmail.com

+ 0 - 128
SwiftStoreOSXDemo/ViewController.swift

@@ -1,128 +0,0 @@
-//
-//  ViewController.swift
-//  SwiftStoreOSXDemo
-//
-//  Created by phimage on 22/12/15.
-//  Copyright © 2015 musevisions. All rights reserved.
-//
-
-import Cocoa
-import StoreKit
-import SwiftyStoreKit
-
-class ViewController: NSViewController {
-
-    let AppBundleId = "com.musevisions.OSX.SwiftyStoreKit"
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-    }
-
-    func showMessage(title: String, message: String, handler: ((NSModalResponse) -> Void)? = nil) {
-        let alert: NSAlert = NSAlert()
-        alert.messageText = title
-        alert.informativeText = message
-        alert.alertStyle = NSAlertStyle.InformationalAlertStyle
-        if let window = NSApplication.sharedApplication().keyWindow {
-            alert.beginSheetModalForWindow(window) { (response: NSModalResponse) in
-                handler?(response)
-            }
-        } else {
-            let response = alert.runModal()
-            handler?(response)
-        }
-        return
-    }
-    // MARK: actions
-    @IBAction func getInfo1(sender: AnyObject?) {
-        getInfo("1")
-    }
-    @IBAction func getInfo2(sender: AnyObject!) {
-        getInfo("2")
-    }
-    @IBAction func purchase1(sender: AnyObject!) {
-        purchase("1")
-    }
-    @IBAction func purchase2(sender: AnyObject!) {
-        purchase("2")
-    }
-    func getInfo(no: String) {
-
-        SwiftyStoreKit.retrieveProductInfo(AppBundleId + ".purchase" + no) { result in
-
-            switch result {
-            case .Success(let product):
-                let priceString = NSNumberFormatter.localizedStringFromNumber(product.price ?? 0, numberStyle: .CurrencyStyle)
-                self.showMessage(product.localizedTitle ?? "no title", message: "\(product.localizedDescription) - \(priceString)")
-                break
-            case .Error(let error):
-                self.showMessage("Could not retrieve product info", message: String(error))
-                break
-            }
-        }
-    }
-
-    func purchase(no: String) {
-
-        SwiftyStoreKit.purchaseProduct(AppBundleId + ".purchase" + no) { result in
-
-            switch result {
-            case .Success(let productId):
-                self.showMessage("Thank You", message: "Purchase completed")
-                print("Purchase Success: \(productId)")
-                break
-            case .Error(let error):
-                if case ResponseError.RequestFailed(let internalError) = error where internalError.domain == SKErrorDomain {
-                    self.showMessage("Purchase failed", message: "Please check your Internet connection or try again later")
-                }
-                else if (error as NSError).domain == SKErrorDomain {
-                    self.showMessage("Purchase failed", message: "Please check your Internet connection or try again later")
-                }
-                else {
-                    self.showMessage("Purchase failed", message: "Unknown error. Please contact support")
-                }
-                print("Purchase Failed: \(error)")
-                break
-            }
-        }
-    }
-
-    @IBAction func restorePurchases(sender: AnyObject?) {
-
-        SwiftyStoreKit.restorePurchases() { result in
-            switch result {
-            case .Success(let productId):
-                self.showMessage("Purchases Restored", message: "All purchases have been restored")
-                print("Restore Success: \(productId)")
-                break
-            case .NothingToRestore:
-                self.showMessage("Nothing to restore", message: "No previous purchases were found")
-                print("Nothing to Restore")
-                break
-            case .Error(let error):
-                print("Restore Failed: \(error)")
-                break
-            }
-        }
-    }
-
-    @IBAction func verifyReceipt(ender: AnyObject?) {
-
-        SwiftyStoreKit.verifyReceipt() { result in
-            switch result {
-            case .Success(let receipt):
-                self.showMessage("Receipt verified", message: "Receipt verified remotly")
-                print("Verify receipt Success: \(receipt)")
-                break
-            case .Error(let error):
-                print("Verify receipt Failed: \(error)")
-                self.showMessage("Receipt verification failed", message: "The application will exit to create receipt data. You must have signed the application for app store with your developper id to test") { response in
-                    exit(ReceiptExitCode.NotValid.rawValue)
-                }
-                break
-            }
-        }
-    }
-
-}
-

+ 115 - 73
SwiftyStoreDemo/ViewController.swift

@@ -14,19 +14,6 @@ class ViewController: UIViewController {
 
     let AppBundleId = "com.musevisions.iOS.SwiftyStoreKit"
     
-    override func viewDidLoad() {
-        super.viewDidLoad()
-    }
-    
-    func showMessage(title: String, message: String) {
-        
-        guard let _ = self.presentedViewController else {
-            let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
-            alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
-            self.presentViewController(alert, animated: true, completion: nil)
-            return
-        }
-    }
     // MARK: actions
     @IBAction func getInfo1() {
         getInfo("1")
@@ -40,21 +27,14 @@ class ViewController: UIViewController {
     @IBAction func purchase2() {
         purchase("2")
     }
+    
     func getInfo(no: String) {
         
         NetworkActivityIndicatorManager.networkOperationStarted()
         SwiftyStoreKit.retrieveProductInfo(AppBundleId + ".purchase" + no) { result in
             NetworkActivityIndicatorManager.networkOperationFinished()
             
-            switch result {
-            case .Success(let product):
-                let priceString = NSNumberFormatter.localizedStringFromNumber(product.price, numberStyle: .CurrencyStyle)
-                self.showMessage(product.localizedTitle, message: "\(product.localizedDescription) - \(priceString)")
-                break
-            case .Error(let error):
-                self.showMessage("Could not retrieve product info", message: String(error))
-                break
-            }
+            self.showAlert(self.alertForProductRetrievalInfo(result))
         }
     }
     
@@ -64,24 +44,7 @@ class ViewController: UIViewController {
         SwiftyStoreKit.purchaseProduct(AppBundleId + ".purchase" + no) { result in
             NetworkActivityIndicatorManager.networkOperationFinished()
             
-            switch result {
-            case .Success(let productId):
-                self.showMessage("Thank You", message: "Purchase completed")
-                print("Purchase Success: \(productId)")
-                break
-            case .Error(let error):
-                if case ResponseError.RequestFailed(let internalError) = error where internalError.domain == SKErrorDomain {
-                    self.showMessage("Purchase failed", message: "Please check your Internet connection or try again later")
-                }
-                else if (error as NSError).domain == SKErrorDomain {
-                    self.showMessage("Purchase failed", message: "Please check your Internet connection or try again later")
-                }
-                else {
-                    self.showMessage("Purchase failed", message: "Unknown error. Please contact support")
-                }
-                print("Purchase Failed: \(error)")
-                break
-            }
+            self.showAlert(self.alertForPurchaseResult(result))
         }
     }
     @IBAction func restorePurchases() {
@@ -89,19 +52,8 @@ class ViewController: UIViewController {
         NetworkActivityIndicatorManager.networkOperationStarted()
         SwiftyStoreKit.restorePurchases() { result in
             NetworkActivityIndicatorManager.networkOperationFinished()
-            switch result {
-            case .Success(let productId):
-                self.showMessage("Purchases Restored", message: "All purchases have been restored")
-                print("Restore Success: \(productId)")
-                break
-            case .NothingToRestore:
-                self.showMessage("Nothing to restore", message: "No previous purchases were found")
-                print("Nothing to Restore")
-                break
-            case .Error(let error):
-                print("Restore Failed: \(error)")
-                break
-            }
+            
+            self.showAlert(self.alertForRestorePurchases(result))
         }
     }
 
@@ -110,33 +62,27 @@ class ViewController: UIViewController {
         NetworkActivityIndicatorManager.networkOperationStarted()
         SwiftyStoreKit.verifyReceipt() { result in
             NetworkActivityIndicatorManager.networkOperationFinished()
+
+            self.showAlert(self.alertForVerifyReceipt(result))
+
             switch result {
-            case .Success(let receipt):
-                self.showMessage("Receipt verified", message: "Receipt verified remotly")
-                print("Verify receipt Success: \(receipt)")
-                break
             case .Error(let error):
-                print("Verify receipt Failed: \(error)")
                 switch (error) {
                 case .NoReceiptData :
-                    self.showMessage("Receipt verification", message: "No receipt data, application will try to get a new one. Try again.")
-
-                    SwiftyStoreKit.receiptRefresh { (result) -> () in
-                        switch result {
-                        case .Success:
-                            self.showMessage("Receipt refreshed", message: "Receipt refreshed with success")
-                            print("Receipt refreshed Success")
-                            break
-                        case .Error(let error):
-                            print("Receipt refreshed Failed: \(error)")
-                            break
-                        }
-                    }
-
+                    self.refreshReceipt()
                 default: break
                 }
-                break
+            default: break
             }
+
+        }
+    }
+
+    func refreshReceipt() {
+        SwiftyStoreKit.receiptRefresh { (result) -> () in
+
+            self.showAlert(self.alertForRefreshReceipt(result))
+
         }
     }
 
@@ -145,3 +91,99 @@ class ViewController: UIViewController {
     }
 }
 
+// MARK: User facing alerts
+extension ViewController {
+    
+    func alertWithTitle(title: String, message: String) -> UIAlertController {
+        
+        let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
+        alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
+        return alert
+    }
+    
+    func showAlert(alert: UIAlertController) {
+        guard let _ = self.presentedViewController else {
+            self.presentViewController(alert, animated: true, completion: nil)
+            return
+        }
+    }
+
+    func alertForProductRetrievalInfo(result: SwiftyStoreKit.RetrieveResultType) -> UIAlertController {
+        
+        switch result {
+        case .Success(let product):
+            let priceString = NSNumberFormatter.localizedStringFromNumber(product.price, numberStyle: .CurrencyStyle)
+            return alertWithTitle(product.localizedTitle, message: "\(product.localizedDescription) - \(priceString)")
+        case .Error(let error):
+            return alertWithTitle("Could not retrieve product info", message: String(error))
+        }
+    }
+
+    func alertForPurchaseResult(result: SwiftyStoreKit.PurchaseResultType) -> UIAlertController {
+        switch result {
+        case .Success(let productId):
+            print("Purchase Success: \(productId)")
+            return alertWithTitle("Thank You", message: "Purchase completed")
+        case .NoProductIdentifier:
+            return alertWithTitle("Purchase failed", message: "Product not found")
+        case .PaymentNotAllowed:
+            return alertWithTitle("Payments not enabled", message: "You are not allowed to make payments")
+        case .Error(let error):
+            print("Purchase Failed: \(error)")
+            if case ResponseError.RequestFailed(let internalError) = error where internalError.domain == SKErrorDomain {
+                return alertWithTitle("Purchase failed", message: "Please check your Internet connection or try again later")
+            }
+            if (error as NSError).domain == SKErrorDomain {
+                return alertWithTitle("Purchase failed", message: "Please check your Internet connection or try again later")
+            }
+            return alertWithTitle("Purchase failed", message: "Unknown error. Please contact support")
+        }
+    }
+    
+    func alertForRestorePurchases(result: SwiftyStoreKit.RestoreResultType) -> UIAlertController {
+        
+        switch result {
+        case .Success(let productId):
+            print("Restore Success: \(productId)")
+            return alertWithTitle("Purchases Restored", message: "All purchases have been restored")
+        case .NothingToRestore:
+            print("Nothing to Restore")
+            return alertWithTitle("Nothing to restore", message: "No previous purchases were found")
+        case .Error(let error):
+            print("Restore Failed: \(error)")
+            return alertWithTitle("Restore failed", message: "Unknown error. Please contact support")
+        }
+    }
+
+
+    func alertForVerifyReceipt(result: SwiftyStoreKit.VerifyReceiptResultType) -> UIAlertController{
+
+        switch result {
+        case .Success(let receipt):
+            print("Verify receipt Success: \(receipt)")
+            return alertWithTitle("Receipt verified", message: "Receipt verified remotly")
+        case .Error(let error):
+            print("Verify receipt Failed: \(error)")
+            switch (error) {
+            case .NoReceiptData :
+                return alertWithTitle("Receipt verification", message: "No receipt data, application will try to get a new one. Try again.")
+            default:
+                return alertWithTitle("Receipt verification", message: "Receipt verification failed")
+            }
+        }
+
+    }
+
+    func alertForRefreshReceipt(result: SwiftyStoreKit.RefreshReceiptResultType) -> UIAlertController {
+        switch result {
+        case .Success:
+            print("Receipt refreshed Success")
+            return self.alertWithTitle("Receipt refreshed", message: "Receipt refreshed with success")
+        case .Error(let error):
+            print("Receipt refreshed Failed: \(error)")
+            return self.alertWithTitle("Receipt refreshed Failed", message: "Receipt refreshed Failed")
+        }
+    }
+
+}
+

+ 1 - 1
SwiftyStoreKit.podspec

@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name         = 'SwiftyStoreKit'
-  s.version      = '0.1.5'
+  s.version      = '0.1.6'
   s.summary      = 'Lightweight In App Purchases Swift framework for iOS 8.0+ and OSX 9.0+'
   s.license      = 'MIT'
   s.homepage     = 'https://github.com/bizz84/SwiftyStoreKit'

+ 20 - 18
SwiftyStoreKit.xcodeproj/project.pbxproj

@@ -28,6 +28,7 @@
 		C4D74BC31C24CEDC0071AD3E /* InAppProductPurchaseRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6502F6221B98586A004E342D /* InAppProductPurchaseRequest.swift */; };
 		C4D74BC41C24CEDC0071AD3E /* InAppProductQueryRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6502F6231B98586A004E342D /* InAppProductQueryRequest.swift */; };
 		C4D74BC51C24CEDC0071AD3E /* SwiftyStoreKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6502F6241B98586A004E342D /* SwiftyStoreKit.swift */; };
+		C4F69A8A1C2E0D21009DD8BD /* InAppReceiptRefreshRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4083C561C2AB0A900295248 /* InAppReceiptRefreshRequest.swift */; };
 		C4FD3A041C2954C10035CFF3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FD3A031C2954C10035CFF3 /* AppDelegate.swift */; };
 		C4FD3A061C2954C10035CFF3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FD3A051C2954C10035CFF3 /* ViewController.swift */; };
 		C4FD3A081C2954C10035CFF3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C4FD3A071C2954C10035CFF3 /* Assets.xcassets */; };
@@ -99,7 +100,7 @@
 		C4D74BBB1C24CEC90071AD3E /* SwiftyStoreKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyStoreKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		C4D74BBD1C24CECA0071AD3E /* SwiftyStoreKitOSX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftyStoreKitOSX.h; sourceTree = "<group>"; };
 		C4D74BBF1C24CECA0071AD3E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		C4FD3A011C2954C10035CFF3 /* SwiftStoreOSXDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftStoreOSXDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		C4FD3A011C2954C10035CFF3 /* SwiftyStoreOSXDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftyStoreOSXDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		C4FD3A031C2954C10035CFF3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		C4FD3A051C2954C10035CFF3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
 		C4FD3A071C2954C10035CFF3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -147,7 +148,7 @@
 				6502F6001B985833004E342D /* SwiftyStoreKit */,
 				6502F6131B985858004E342D /* SwiftyStoreDemo */,
 				C4D74BBC1C24CECA0071AD3E /* SwiftyStoreKitOSX */,
-				C4FD3A021C2954C10035CFF3 /* SwiftStoreOSXDemo */,
+				C4FD3A021C2954C10035CFF3 /* SwiftyStoreOSXDemo */,
 				6502F5FF1B985833004E342D /* Products */,
 			);
 			sourceTree = "<group>";
@@ -158,7 +159,7 @@
 				6502F5FE1B985833004E342D /* SwiftyStoreDemo.app */,
 				6502F62D1B985C40004E342D /* SwiftyStoreKit.framework */,
 				C4D74BBB1C24CEC90071AD3E /* SwiftyStoreKit.framework */,
-				C4FD3A011C2954C10035CFF3 /* SwiftStoreOSXDemo.app */,
+				C4FD3A011C2954C10035CFF3 /* SwiftyStoreOSXDemo.app */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -201,7 +202,7 @@
 			path = SwiftyStoreKitOSX;
 			sourceTree = "<group>";
 		};
-		C4FD3A021C2954C10035CFF3 /* SwiftStoreOSXDemo */ = {
+		C4FD3A021C2954C10035CFF3 /* SwiftyStoreOSXDemo */ = {
 			isa = PBXGroup;
 			children = (
 				C4FD3A031C2954C10035CFF3 /* AppDelegate.swift */,
@@ -210,7 +211,7 @@
 				C4FD3A091C2954C10035CFF3 /* Main.storyboard */,
 				C4FD3A0C1C2954C10035CFF3 /* Info.plist */,
 			);
-			path = SwiftStoreOSXDemo;
+			path = SwiftyStoreOSXDemo;
 			sourceTree = "<group>";
 		};
 /* End PBXGroup section */
@@ -290,9 +291,9 @@
 			productReference = C4D74BBB1C24CEC90071AD3E /* SwiftyStoreKit.framework */;
 			productType = "com.apple.product-type.framework";
 		};
-		C4FD3A001C2954C10035CFF3 /* SwiftStoreOSXDemo */ = {
+		C4FD3A001C2954C10035CFF3 /* SwiftyStoreOSXDemo */ = {
 			isa = PBXNativeTarget;
-			buildConfigurationList = C4FD3A0D1C2954C10035CFF3 /* Build configuration list for PBXNativeTarget "SwiftStoreOSXDemo" */;
+			buildConfigurationList = C4FD3A0D1C2954C10035CFF3 /* Build configuration list for PBXNativeTarget "SwiftyStoreOSXDemo" */;
 			buildPhases = (
 				C4FD39FD1C2954C10035CFF3 /* Sources */,
 				C4FD39FE1C2954C10035CFF3 /* Frameworks */,
@@ -304,9 +305,9 @@
 			dependencies = (
 				C4FD3A131C2954CD0035CFF3 /* PBXTargetDependency */,
 			);
-			name = SwiftStoreOSXDemo;
-			productName = SwiftStoreOSXDemo;
-			productReference = C4FD3A011C2954C10035CFF3 /* SwiftStoreOSXDemo.app */;
+			name = SwiftyStoreOSXDemo;
+			productName = SwiftyStoreOSXDemo;
+			productReference = C4FD3A011C2954C10035CFF3 /* SwiftyStoreOSXDemo.app */;
 			productType = "com.apple.product-type.application";
 		};
 /* End PBXNativeTarget section */
@@ -348,7 +349,7 @@
 			targets = (
 				6502F5FD1B985833004E342D /* SwiftyStoreDemo */,
 				6502F62C1B985C40004E342D /* SwiftyStoreKit */,
-				C4FD3A001C2954C10035CFF3 /* SwiftStoreOSXDemo */,
+				C4FD3A001C2954C10035CFF3 /* SwiftyStoreOSXDemo */,
 				C4D74BBA1C24CEC90071AD3E /* SwiftyStoreKitOSX */,
 			);
 		};
@@ -405,11 +406,11 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				6502F63A1B985C9E004E342D /* InAppProductPurchaseRequest.swift in Sources */,
-				C4A7C7631C29B8D00053ED64 /* InAppReceipt.swift in Sources */,
 				C40C68101C29414C00B60B7E /* OS.swift in Sources */,
+				6502F63A1B985C9E004E342D /* InAppProductPurchaseRequest.swift in Sources */,
 				6502F63B1B985CA1004E342D /* InAppProductQueryRequest.swift in Sources */,
 				C4083C571C2AB0A900295248 /* InAppReceiptRefreshRequest.swift in Sources */,
+				C4A7C7631C29B8D00053ED64 /* InAppReceipt.swift in Sources */,
 				6502F63C1B985CA4004E342D /* SwiftyStoreKit.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -418,10 +419,11 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				C4D74BC31C24CEDC0071AD3E /* InAppProductPurchaseRequest.swift in Sources */,
-				C4083C551C2AADB500295248 /* InAppReceipt.swift in Sources */,
 				C40C68111C29419500B60B7E /* OS.swift in Sources */,
+				C4D74BC31C24CEDC0071AD3E /* InAppProductPurchaseRequest.swift in Sources */,
 				C4D74BC41C24CEDC0071AD3E /* InAppProductQueryRequest.swift in Sources */,
+				C4F69A8A1C2E0D21009DD8BD /* InAppReceiptRefreshRequest.swift in Sources */,
+				C4083C551C2AADB500295248 /* InAppReceipt.swift in Sources */,
 				C4D74BC51C24CEDC0071AD3E /* SwiftyStoreKit.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -684,7 +686,7 @@
 				CODE_SIGN_IDENTITY = "-";
 				COMBINE_HIDPI_IMAGES = YES;
 				EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
-				INFOPLIST_FILE = SwiftStoreOSXDemo/Info.plist;
+				INFOPLIST_FILE = SwiftyStoreOSXDemo/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
 				MACOSX_DEPLOYMENT_TARGET = 10.10;
 				PRODUCT_BUNDLE_IDENTIFIER = com.musevisions.OSX.SwiftyStoreDemo;
@@ -700,7 +702,7 @@
 				CODE_SIGN_IDENTITY = "-";
 				COMBINE_HIDPI_IMAGES = YES;
 				EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
-				INFOPLIST_FILE = SwiftStoreOSXDemo/Info.plist;
+				INFOPLIST_FILE = SwiftyStoreOSXDemo/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
 				MACOSX_DEPLOYMENT_TARGET = 10.10;
 				PRODUCT_BUNDLE_IDENTIFIER = com.musevisions.OSX.SwiftyStoreDemo;
@@ -748,7 +750,7 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
-		C4FD3A0D1C2954C10035CFF3 /* Build configuration list for PBXNativeTarget "SwiftStoreOSXDemo" */ = {
+		C4FD3A0D1C2954C10035CFF3 /* Build configuration list for PBXNativeTarget "SwiftyStoreOSXDemo" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
 				C4FD3A0E1C2954C10035CFF3 /* Debug */,

+ 80 - 0
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKit.xcscheme

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0720"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "6502F62C1B985C40004E342D"
+               BuildableName = "SwiftyStoreKit.framework"
+               BlueprintName = "SwiftyStoreKit"
+               ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "6502F62C1B985C40004E342D"
+            BuildableName = "SwiftyStoreKit.framework"
+            BlueprintName = "SwiftyStoreKit"
+            ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "6502F62C1B985C40004E342D"
+            BuildableName = "SwiftyStoreKit.framework"
+            BlueprintName = "SwiftyStoreKit"
+            ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 80 - 0
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKitOSX.xcscheme

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0720"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "C4D74BBA1C24CEC90071AD3E"
+               BuildableName = "SwiftyStoreKit.framework"
+               BlueprintName = "SwiftyStoreKitOSX"
+               ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "C4D74BBA1C24CEC90071AD3E"
+            BuildableName = "SwiftyStoreKit.framework"
+            BlueprintName = "SwiftyStoreKitOSX"
+            ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "C4D74BBA1C24CEC90071AD3E"
+            BuildableName = "SwiftyStoreKit.framework"
+            BlueprintName = "SwiftyStoreKitOSX"
+            ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 6 - 5
SwiftyStoreKit/InAppReceipt.swift

@@ -29,10 +29,11 @@ import Foundation
 public typealias ReceiptInfo = [String: AnyObject]
 
 // MARK: - Enumeration
-
-public enum ValidReceiptResultType {
-    case Success(receipt: ReceiptInfo)
-    case Error(error: ReceiptError)
+extension SwiftyStoreKit {
+    public enum VerifyReceiptResultType {
+        case Success(receipt: ReceiptInfo)
+        case Error(error: ReceiptError)
+    }
 }
 
 // Error when managing receipt
@@ -168,7 +169,7 @@ internal class InAppReceipt {
         receiptVerifyURL url: ReceiptVerifyURL = .Test,
         password autoRenewPassword: String? = nil,
         session: NSURLSession = NSURLSession.sharedSession(),
-        completion:(result: ValidReceiptResultType) -> ()) {
+        completion:(result: SwiftyStoreKit.VerifyReceiptResultType) -> ()) {
 
             // If no receipt is present, validation fails.
             guard let base64EncodedString = self.base64EncodedString else {

+ 12 - 2
SwiftyStoreKit/SwiftyStoreKit.swift

@@ -48,6 +48,8 @@ public class SwiftyStoreKit {
     public enum PurchaseResultType {
         case Success(productId: String)
         case Error(error: ErrorType)
+        case NoProductIdentifier
+        case PaymentNotAllowed
     }
     public enum RetrieveResultType {
         case Success(product: SKProduct)
@@ -127,8 +129,13 @@ public class SwiftyStoreKit {
         receiptVerifyURL url: ReceiptVerifyURL = .Test,
         password: String? = nil,
         session: NSURLSession = NSURLSession.sharedSession(),
+<<<<<<< HEAD
+        completion:(result: VerifyReceiptResultType) -> ()) {
+            InAppReceipt.verify(test, password: password, session: session, completion: completion)
+=======
         completion:(result: ValidReceiptResultType) -> ()) {
             InAppReceipt.verify(receiptVerifyURL: url, password: password, session: session, completion: completion)
+>>>>>>> Use receipt URL enum instead of boolean in verify method
     }
 
     #if os(iOS)
@@ -156,9 +163,12 @@ public class SwiftyStoreKit {
 
     // MARK: private methods
     private func purchase(product product: SKProduct, completion: (result: PurchaseResultType) -> ()) {
+        guard SwiftyStoreKit.canMakePayments else {
+            completion(result: PurchaseResultType.PaymentNotAllowed)
+            return
+        }
         guard let productIdentifier = product._productIdentifier else {
-            let error = NSError(domain: SKErrorDomain, code: 0, userInfo: [ NSLocalizedDescriptionKey: "No product identifier" ])
-            completion(result: PurchaseResultType.Error(error: error))
+            completion(result: PurchaseResultType.NoProductIdentifier)
             return
         }
 

+ 0 - 0
SwiftStoreOSXDemo/AppDelegate.swift → SwiftyStoreOSXDemo/AppDelegate.swift


+ 0 - 0
SwiftStoreOSXDemo/Assets.xcassets/AppIcon.appiconset/Contents.json → SwiftyStoreOSXDemo/Assets.xcassets/AppIcon.appiconset/Contents.json


+ 0 - 0
SwiftStoreOSXDemo/Assets.xcassets/Contents.json → SwiftyStoreOSXDemo/Assets.xcassets/Contents.json


+ 0 - 0
SwiftStoreOSXDemo/Base.lproj/Main.storyboard → SwiftyStoreOSXDemo/Base.lproj/Main.storyboard


+ 0 - 0
SwiftStoreOSXDemo/Info.plist → SwiftyStoreOSXDemo/Info.plist


+ 152 - 0
SwiftyStoreOSXDemo/ViewController.swift

@@ -0,0 +1,152 @@
+//
+//  ViewController.swift
+//  SwiftStoreOSXDemo
+//
+//  Created by phimage on 22/12/15.
+//  Copyright © 2015 musevisions. All rights reserved.
+//
+
+import Cocoa
+import StoreKit
+import SwiftyStoreKit
+
+class ViewController: NSViewController {
+
+    let AppBundleId = "com.musevisions.OSX.SwiftyStoreKit"
+    
+    // MARK: actions
+    @IBAction func getInfo1(sender: AnyObject?) {
+        getInfo("1")
+    }
+    @IBAction func getInfo2(sender: AnyObject!) {
+        getInfo("2")
+    }
+    @IBAction func purchase1(sender: AnyObject!) {
+        purchase("1")
+    }
+    @IBAction func purchase2(sender: AnyObject!) {
+        purchase("2")
+    }
+    func getInfo(no: String) {
+
+        SwiftyStoreKit.retrieveProductInfo(AppBundleId + ".purchase" + no) { result in
+
+            self.showAlert(self.alertForProductRetrievalInfo(result))
+        }
+    }
+
+    func purchase(no: String) {
+
+        SwiftyStoreKit.purchaseProduct(AppBundleId + ".purchase" + no) { result in
+
+            self.showAlert(self.alertForPurchaseResult(result))
+        }
+    }
+
+    @IBAction func restorePurchases(sender: AnyObject?) {
+
+        SwiftyStoreKit.restorePurchases() { result in
+            
+            self.showAlert(self.alertForRestorePurchases(result))
+        }
+    }
+
+    @IBAction func verifyReceipt(ender: AnyObject?) {
+
+        SwiftyStoreKit.verifyReceipt() { result in
+
+            self.showAlert(self.alertForVerifyReceipt(result)) { response in
+
+                exit(ReceiptExitCode.NotValid.rawValue)
+            }
+        }
+    }
+
+
+}
+
+// MARK: User facing alerts
+extension ViewController {
+    
+    func alertWithTitle(title: String, message: String) -> NSAlert {
+        
+        let alert: NSAlert = NSAlert()
+        alert.messageText = title
+        alert.informativeText = message
+        alert.alertStyle = NSAlertStyle.InformationalAlertStyle
+        return alert
+    }
+    func showAlert(alert: NSAlert, handler: ((NSModalResponse) -> Void)? = nil) {
+        
+        if let window = NSApplication.sharedApplication().keyWindow {
+            alert.beginSheetModalForWindow(window)  { (response: NSModalResponse) in
+                handler?(response)
+            }
+        } else {
+            let response = alert.runModal()
+            handler?(response)
+        }
+    }
+
+    func alertForProductRetrievalInfo(result: SwiftyStoreKit.RetrieveResultType) -> NSAlert {
+        
+        switch result {
+        case .Success(let product):
+            let priceString = NSNumberFormatter.localizedStringFromNumber(product.price ?? 0, numberStyle: .CurrencyStyle)
+            return alertWithTitle(product.localizedTitle ?? "no title", message: "\(product.localizedDescription) - \(priceString)")
+        case .Error(let error):
+            return alertWithTitle("Could not retrieve product info", message: String(error))
+        }
+    }
+    
+    func alertForPurchaseResult(result: SwiftyStoreKit.PurchaseResultType) -> NSAlert {
+
+        switch result {
+        case .Success(let productId):
+            print("Purchase Success: \(productId)")
+            return alertWithTitle("Thank You", message: "Purchase completed")
+        case .NoProductIdentifier:
+            return alertWithTitle("Purchase failed", message: "Product not found")
+        case .PaymentNotAllowed:
+            return alertWithTitle("Payments not enabled", message: "You are not allowed to make payments")
+        case .Error(let error):
+            print("Purchase Failed: \(error)")
+            if case ResponseError.RequestFailed(let internalError) = error where internalError.domain == SKErrorDomain {
+                return alertWithTitle("Purchase failed", message: "Please check your Internet connection or try again later")
+            }
+            if (error as NSError).domain == SKErrorDomain {
+                return alertWithTitle("Purchase failed", message: "Please check your Internet connection or try again later")
+            }
+            return alertWithTitle("Purchase failed", message: "Unknown error. Please contact support")
+        }
+    }
+    
+    func alertForRestorePurchases(result: SwiftyStoreKit.RestoreResultType) -> NSAlert {
+        
+        switch result {
+        case .Success(let productId):
+            print("Restore Success: \(productId)")
+            return alertWithTitle("Purchases Restored", message: "All purchases have been restored")
+        case .NothingToRestore:
+            print("Nothing to Restore")
+            return alertWithTitle("Nothing to restore", message: "No previous purchases were found")
+        case .Error(let error):
+            print("Restore Failed: \(error)")
+            return alertWithTitle("Restore failed", message: "Unknown error. Please contact support")
+        }
+    }
+
+    func alertForVerifyReceipt(result: SwiftyStoreKit.VerifyReceiptResultType) -> NSAlert {
+
+        switch result {
+        case .Success(let receipt):
+            print("Verify receipt Success: \(receipt)")
+            return self.alertWithTitle("Receipt verified", message: "Receipt verified remotly")
+        case .Error(let error):
+            print("Verify receipt Failed: \(error)")
+            return  self.alertWithTitle("Receipt verification failed", message: "The application will exit to create receipt data. You must have signed the application with your developper id to test and be outside of XCode")
+        }
+    }
+
+}
+