Просмотр исходного кода

Merge pull request #6 from phimage/feature-receipt

Validating Receipts With the App Store
Andrea Bizzotto 9 лет назад
Родитель
Сommit
1f66e2aa9a

+ 27 - 11
SwiftyStoreDemo/Base.lproj/Main.storyboard

@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8191" systemVersion="14E11f" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="14F27" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
     </dependencies>
     <scenes>
         <!--View Controller-->
@@ -22,22 +22,37 @@
                             </imageView>
                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="R2x-3P-rjx" userLabel="Opaque">
                                 <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                                <subviews>
+                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Hpv-ED-Dlg">
+                                        <rect key="frame" x="233" y="391" width="135" height="36"/>
+                                        <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
+                                        <state key="normal" title="Verify Receipt">
+                                            <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+                                        </state>
+                                        <connections>
+                                            <action selector="verifyReceipt" destination="BYZ-38-t0r" eventType="touchUpInside" id="61I-gF-E2O"/>
+                                        </connections>
+                                    </button>
+                                </subviews>
                                 <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
+                                <constraints>
+                                    <constraint firstItem="Hpv-ED-Dlg" firstAttribute="centerX" secondItem="R2x-3P-rjx" secondAttribute="centerX" id="Zsj-pM-1iO"/>
+                                </constraints>
                             </view>
                             <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SwiftyStoreKit" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FTs-3H-z8C">
-                                <rect key="frame" x="218" y="40" width="163" height="32.5"/>
+                                <rect key="frame" x="218" y="40" width="163" height="33"/>
                                 <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
                                 <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
                                 <nil key="highlightedColor"/>
                             </label>
                             <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Lightweight In App Purchases framework for iOS 8.0+" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gg1-bw-Mzz">
-                                <rect key="frame" x="10" y="74.5" width="580" height="16"/>
+                                <rect key="frame" x="10" y="75" width="580" height="16"/>
                                 <fontDescription key="fontDescription" type="system" pointSize="13"/>
                                 <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
                                 <nil key="highlightedColor"/>
                             </label>
                             <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available purchases:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="s8k-6i-mKn">
-                                <rect key="frame" x="222" y="143.5" width="156" height="20.5"/>
+                                <rect key="frame" x="222" y="143" width="156" height="21"/>
                                 <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                 <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
                                 <nil key="highlightedColor"/>
@@ -49,13 +64,13 @@
                                         <rect key="frame" x="0.0" y="0.0" width="91" height="113"/>
                                         <subviews>
                                             <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="5 days" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uz9-cT-1WH">
-                                                <rect key="frame" x="0.0" y="0.0" width="91" height="20.5"/>
+                                                <rect key="frame" x="0.0" y="0.0" width="91" height="21"/>
                                                 <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                 <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                                 <nil key="highlightedColor"/>
                                             </label>
                                             <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="miS-cF-iGP">
-                                                <rect key="frame" x="0.0" y="35.5" width="91" height="36"/>
+                                                <rect key="frame" x="0.0" y="36" width="91" height="36"/>
                                                 <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
                                                 <state key="normal" title="Get Info">
                                                     <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -65,7 +80,7 @@
                                                 </connections>
                                             </button>
                                             <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DnE-MP-9Li">
-                                                <rect key="frame" x="0.0" y="76.5" width="91" height="36"/>
+                                                <rect key="frame" x="0.0" y="77" width="91" height="36"/>
                                                 <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
                                                 <state key="normal" title="Purchase">
                                                     <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -107,13 +122,13 @@
                                         <rect key="frame" x="191" y="0.0" width="91" height="113"/>
                                         <subviews>
                                             <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="10 days" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6UI-uh-w5n">
-                                                <rect key="frame" x="0.0" y="0.0" width="91" height="20.5"/>
+                                                <rect key="frame" x="0.0" y="0.0" width="91" height="21"/>
                                                 <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                 <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                                 <nil key="highlightedColor"/>
                                             </label>
                                             <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="L02-jg-tgd">
-                                                <rect key="frame" x="0.0" y="35.5" width="91" height="36"/>
+                                                <rect key="frame" x="0.0" y="36" width="91" height="36"/>
                                                 <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
                                                 <state key="normal" title="Get Info">
                                                     <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -123,7 +138,7 @@
                                                 </connections>
                                             </button>
                                             <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XP6-KY-8rd">
-                                                <rect key="frame" x="0.0" y="76.5" width="91" height="36"/>
+                                                <rect key="frame" x="0.0" y="77" width="91" height="36"/>
                                                 <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
                                                 <state key="normal" title="Purchase">
                                                     <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -184,6 +199,7 @@
                         </subviews>
                         <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
                         <constraints>
+                            <constraint firstItem="Hpv-ED-Dlg" firstAttribute="top" secondItem="CG8-Ue-vcg" secondAttribute="bottom" constant="8" id="1yX-Os-AKT"/>
                             <constraint firstItem="FII-Z2-VOo" firstAttribute="top" secondItem="s8k-6i-mKn" secondAttribute="bottom" constant="30" id="4jU-Ih-evH"/>
                             <constraint firstItem="FII-Z2-VOo" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="50" id="5RH-5E-DHg"/>
                             <constraint firstAttribute="trailing" secondItem="JDz-7n-4vD" secondAttribute="trailing" id="GBV-Mt-YQc"/>

+ 61 - 1
SwiftyStoreDemo/ViewController.swift

@@ -56,7 +56,36 @@ class ViewController: UIViewController {
             self.showAlert(self.alertForRestorePurchases(result))
         }
     }
-    
+
+    @IBAction func verifyReceipt() {
+
+        NetworkActivityIndicatorManager.networkOperationStarted()
+        SwiftyStoreKit.verifyReceipt() { result in
+            NetworkActivityIndicatorManager.networkOperationFinished()
+
+            self.showAlert(self.alertForVerifyReceipt(result))
+
+            switch result {
+            case .Error(let error):
+                switch (error) {
+                case .NoReceiptData :
+                    self.refreshReceipt()
+                default: break
+                }
+            default: break
+            }
+
+        }
+    }
+
+    func refreshReceipt() {
+        SwiftyStoreKit.refreshReceipt { (result) -> () in
+
+            self.showAlert(self.alertForRefreshReceipt(result))
+
+        }
+    }
+
     override func preferredStatusBarStyle() -> UIStatusBarStyle {
         return .LightContent
     }
@@ -125,5 +154,36 @@ extension ViewController {
             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")
+        }
+    }
+
 }
 

+ 29 - 16
SwiftyStoreKit.xcodeproj/project.pbxproj

@@ -19,12 +19,16 @@
 		6502F63B1B985CA1004E342D /* InAppProductQueryRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6502F6231B98586A004E342D /* InAppProductQueryRequest.swift */; };
 		6502F63C1B985CA4004E342D /* SwiftyStoreKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6502F6241B98586A004E342D /* SwiftyStoreKit.swift */; };
 		65C1B5DF1BB9DE9B00F7BF4E /* NetworkActivityIndicatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65C1B5DE1BB9DE9B00F7BF4E /* NetworkActivityIndicatorManager.swift */; };
+		C4083C551C2AADB500295248 /* InAppReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A7C7621C29B8D00053ED64 /* InAppReceipt.swift */; };
+		C4083C571C2AB0A900295248 /* InAppReceiptRefreshRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4083C561C2AB0A900295248 /* InAppReceiptRefreshRequest.swift */; };
 		C40C68101C29414C00B60B7E /* OS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C680F1C29414C00B60B7E /* OS.swift */; };
 		C40C68111C29419500B60B7E /* OS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C680F1C29414C00B60B7E /* OS.swift */; };
+		C4A7C7631C29B8D00053ED64 /* InAppReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A7C7621C29B8D00053ED64 /* InAppReceipt.swift */; };
 		C4D74BBE1C24CECA0071AD3E /* SwiftyStoreKitOSX.h in Headers */ = {isa = PBXBuildFile; fileRef = C4D74BBD1C24CECA0071AD3E /* SwiftyStoreKitOSX.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		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 */; };
@@ -90,11 +94,13 @@
 		6502F62F1B985C40004E342D /* SwiftyStoreKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftyStoreKit.h; sourceTree = "<group>"; };
 		6502F6311B985C40004E342D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		65C1B5DE1BB9DE9B00F7BF4E /* NetworkActivityIndicatorManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkActivityIndicatorManager.swift; sourceTree = "<group>"; };
+		C4083C561C2AB0A900295248 /* InAppReceiptRefreshRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppReceiptRefreshRequest.swift; sourceTree = "<group>"; };
 		C40C680F1C29414C00B60B7E /* OS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OS.swift; sourceTree = "<group>"; };
+		C4A7C7621C29B8D00053ED64 /* InAppReceipt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppReceipt.swift; sourceTree = "<group>"; };
 		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>"; };
@@ -142,7 +148,7 @@
 				6502F6001B985833004E342D /* SwiftyStoreKit */,
 				6502F6131B985858004E342D /* SwiftyStoreDemo */,
 				C4D74BBC1C24CECA0071AD3E /* SwiftyStoreKitOSX */,
-				C4FD3A021C2954C10035CFF3 /* SwiftStoreOSXDemo */,
+				C4FD3A021C2954C10035CFF3 /* SwiftyStoreOSXDemo */,
 				6502F5FF1B985833004E342D /* Products */,
 			);
 			sourceTree = "<group>";
@@ -153,7 +159,7 @@
 				6502F5FE1B985833004E342D /* SwiftyStoreDemo.app */,
 				6502F62D1B985C40004E342D /* SwiftyStoreKit.framework */,
 				C4D74BBB1C24CEC90071AD3E /* SwiftyStoreKit.framework */,
-				C4FD3A011C2954C10035CFF3 /* SwiftStoreOSXDemo.app */,
+				C4FD3A011C2954C10035CFF3 /* SwiftyStoreOSXDemo.app */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -163,6 +169,8 @@
 			children = (
 				6502F6221B98586A004E342D /* InAppProductPurchaseRequest.swift */,
 				6502F6231B98586A004E342D /* InAppProductQueryRequest.swift */,
+				C4083C561C2AB0A900295248 /* InAppReceiptRefreshRequest.swift */,
+				C4A7C7621C29B8D00053ED64 /* InAppReceipt.swift */,
 				6502F6241B98586A004E342D /* SwiftyStoreKit.swift */,
 				C40C680F1C29414C00B60B7E /* OS.swift */,
 				6502F62F1B985C40004E342D /* SwiftyStoreKit.h */,
@@ -194,7 +202,7 @@
 			path = SwiftyStoreKitOSX;
 			sourceTree = "<group>";
 		};
-		C4FD3A021C2954C10035CFF3 /* SwiftStoreOSXDemo */ = {
+		C4FD3A021C2954C10035CFF3 /* SwiftyStoreOSXDemo */ = {
 			isa = PBXGroup;
 			children = (
 				C4FD3A031C2954C10035CFF3 /* AppDelegate.swift */,
@@ -203,7 +211,7 @@
 				C4FD3A091C2954C10035CFF3 /* Main.storyboard */,
 				C4FD3A0C1C2954C10035CFF3 /* Info.plist */,
 			);
-			path = SwiftStoreOSXDemo;
+			path = SwiftyStoreOSXDemo;
 			sourceTree = "<group>";
 		};
 /* End PBXGroup section */
@@ -283,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 */,
@@ -297,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 */
@@ -341,7 +349,7 @@
 			targets = (
 				6502F5FD1B985833004E342D /* SwiftyStoreDemo */,
 				6502F62C1B985C40004E342D /* SwiftyStoreKit */,
-				C4FD3A001C2954C10035CFF3 /* SwiftStoreOSXDemo */,
+				C4FD3A001C2954C10035CFF3 /* SwiftyStoreOSXDemo */,
 				C4D74BBA1C24CEC90071AD3E /* SwiftyStoreKitOSX */,
 			);
 		};
@@ -398,9 +406,11 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				6502F63A1B985C9E004E342D /* InAppProductPurchaseRequest.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;
@@ -409,9 +419,11 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				C4D74BC31C24CEDC0071AD3E /* InAppProductPurchaseRequest.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;
@@ -674,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;
@@ -690,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;
@@ -738,13 +750,14 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
-		C4FD3A0D1C2954C10035CFF3 /* Build configuration list for PBXNativeTarget "SwiftStoreOSXDemo" */ = {
+		C4FD3A0D1C2954C10035CFF3 /* Build configuration list for PBXNativeTarget "SwiftyStoreOSXDemo" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
 				C4FD3A0E1C2954C10035CFF3 /* Debug */,
 				C4FD3A0F1C2954C10035CFF3 /* Release */,
 			);
 			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
 		};
 /* End XCConfigurationList section */
 	};

+ 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>

+ 239 - 0
SwiftyStoreKit/InAppReceipt.swift

@@ -0,0 +1,239 @@
+//
+//  InAppReceipt.swift
+//  SwiftyStoreKit
+//
+//  Created by phimage on 22/12/15.
+// Copyright (c) 2015 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
+
+// Info for receipt returned by server
+public typealias ReceiptInfo = [String: AnyObject]
+
+// MARK: - Enumeration
+extension SwiftyStoreKit {
+    public enum VerifyReceiptResultType {
+        case Success(receipt: ReceiptInfo)
+        case Error(error: ReceiptError)
+    }
+}
+
+// Error when managing receipt
+public enum ReceiptError: ErrorType {
+    // No receipt data
+    case NoReceiptData
+    // No data receice
+    case NoRemoteData
+    // Error when encoding HTTP body into JSON
+    case RequestBodyEncodeError(error: ErrorType)
+    // Error when proceeding request
+    case NetworkError(error: ErrorType)
+    // Error when decoding response
+    case JSONDecodeError(string: String?)
+    // Receive invalid - bad status returned
+    case ReceiptInvalid(receipt: ReceiptInfo, status: ReceiptStatus)
+}
+
+// Status code returned by remote server
+// see Table 2-1  Status codes
+public enum ReceiptStatus: Int {
+    // Not decodable status
+    case Unknown = -2
+    // No status returned
+    case None = -1
+    // valid statu
+    case Valid = 0
+    // The App Store could not read the JSON object you provided.
+    case JSONNotReadable = 21000
+    // The data in the receipt-data property was malformed or missing.
+    case MalformedOrMissingData = 21002
+    // The receipt could not be authenticated.
+    case ReceiptCouldNotBeAuthenticated = 21003
+    // The shared secret you provided does not match the shared secret on file for your account.
+    case SecretNotMatching = 21004
+    // The receipt server is not currently available.
+    case ReceiptServerUnavailable = 21005
+    // This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.
+    case SubscriptionExpired = 21006
+    //  This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.
+    case TestReceipt = 21007
+    // This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.
+    case ProductionEnvironment = 21008
+
+    var isValid: Bool { return self == .Valid}
+}
+
+// Receipt field as defined in : https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1
+public enum ReceiptInfoField: String {
+     // Bundle Identifier. This corresponds to the value of CFBundleIdentifier in the Info.plist file.
+    case bundle_id
+    // The app’s version number.This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist.
+    case application_version
+    // The version of the app that was originally purchased. This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist file when the purchase was originally made.
+    case original_application_version
+    // The date when the app receipt was created.
+    case creation_date
+    // The date that the app receipt expires. This key is present only for apps purchased through the Volume Purchase Program.
+    case expiration_date
+
+    // The receipt for an in-app purchase.
+    case in_app
+
+    public enum InApp: String {
+        // The number of items purchased. This value corresponds to the quantity property of the SKPayment object stored in the transaction’s payment property.
+        case quantity
+        // The product identifier of the item that was purchased. This value corresponds to the productIdentifier property of the SKPayment object stored in the transaction’s payment property.
+        case product_id
+        // The transaction identifier of the item that was purchased. This value corresponds to the transaction’s transactionIdentifier property.
+        case transaction_id
+        // For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier. This value corresponds to the original transaction’s transactionIdentifier property. All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field.
+        case original_transaction_id
+        // The date and time that the item was purchased. This value corresponds to the transaction’s transactionDate property.
+        case purchase_date
+        // For a transaction that restores a previous transaction, the date of the original transaction. This value corresponds to the original transaction’s transactionDate property. In an auto-renewable subscription receipt, this indicates the beginning of the subscription period, even if the subscription has been renewed.
+        case original_purchase_date
+        // The expiration date for the subscription, expressed as the number of milliseconds since January 1, 1970, 00:00:00 GMT. This key is only present for auto-renewable subscription receipts.
+        case expires_date
+        // For a transaction that was canceled by Apple customer support, the time and date of the cancellation. Treat a canceled receipt the same as if no purchase had ever been made.
+        case cancellation_date
+        #if os(iOS)
+        // A string that the App Store uses to uniquely identify the application that created the transaction. If your server supports multiple applications, you can use this value to differentiate between them. Apps are assigned an identifier only in the production environment, so this key is not present for receipts created in the test environment. This field is not present for Mac apps. See also Bundle Identifier.
+        case app_item_id
+        #endif
+        // An arbitrary number that uniquely identifies a revision of your application. This key is not present for receipts created in the test environment.
+        case version_external_identifier
+        // The primary key for identifying subscription purchases.
+        case web_order_line_item_id
+    }
+}
+
+// URL used to verify remotely receipt
+public enum ReceiptVerifyURL: String {
+    case Production = "https://buy.itunes.apple.com/verifyReceipt"
+    case Test = "https://sandbox.itunes.apple.com/verifyReceipt"
+}
+
+#if os(OSX)
+    public enum ReceiptExitCode: Int32 {
+        // If validation fails in OS X, call exit with a status of 173. This exit status notifies the system that your application has determined that its receipt is invalid. At this point, the system attempts to obtain a valid receipt and may prompt for the user’s iTunes credentials
+        case NotValid = 173
+    }
+#endif
+
+// MARK - receipt mangement
+internal class InAppReceipt {
+
+    static var URL: NSURL? {
+        return NSBundle.mainBundle().appStoreReceiptURL
+    }
+
+    static var data: NSData? {
+        if let receiptDataURL = URL, data = NSData(contentsOfURL: receiptDataURL) {
+            return data
+        }
+        return nil
+    }
+
+    // The base64 encoded receipt data.
+    static var base64EncodedString: String? {
+        return data?.base64EncodedStringWithOptions([])
+    }
+
+    // https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html
+
+   /**
+    *  - Parameter receiptVerifyURL: receipt verify url (default: Test)
+    *  - Parameter password: Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
+    *  - Parameter session: the session used to make remote call.
+    *  - Parameter completion: handler for result
+    */
+    class func verify(
+        receiptVerifyURL url: ReceiptVerifyURL = .Test,
+        password autoRenewPassword: String? = nil,
+        session: NSURLSession = NSURLSession.sharedSession(),
+        completion:(result: SwiftyStoreKit.VerifyReceiptResultType) -> ()) {
+
+            // If no receipt is present, validation fails.
+            guard let base64EncodedString = self.base64EncodedString else {
+                completion(result: .Error(error: .NoReceiptData))
+                return
+            }
+
+            // Create request
+            let storeURL = NSURL(string: url.rawValue)! // safe (until no more)
+            let storeRequest = NSMutableURLRequest(URL: storeURL)
+            storeRequest.HTTPMethod = "POST"
+
+
+            let requestContents :NSMutableDictionary = [ "receipt-data" : base64EncodedString]
+            // password if defined
+            if let password = autoRenewPassword {
+                requestContents.setValue(password, forKey: "password")
+            }
+
+            // Encore request body
+            do {
+                storeRequest.HTTPBody = try NSJSONSerialization.dataWithJSONObject(requestContents, options: [])
+            } catch let e {
+                completion(result: .Error(error: .RequestBodyEncodeError(error: e)))
+                return
+            }
+
+            // Remote task
+            let task = session.dataTaskWithRequest(storeRequest) { data, response, error -> Void in
+
+                // there is an error
+                if let networkError = error {
+                    completion(result: .Error(error: .NetworkError(error: networkError)))
+                    return
+                }
+
+                // there is no data
+                guard let safeData = data else {
+                    completion(result:.Error(error: .NoRemoteData))
+                    return
+                }
+
+                // cannot decode data
+                guard let receiptInfo = try? NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves) as? ReceiptInfo ?? [:] else {
+                    let jsonStr = String(data: safeData, encoding: NSUTF8StringEncoding)
+                    completion(result: .Error(error: .JSONDecodeError(string: jsonStr)))
+                    return
+                }
+
+                // get status from info
+                if let status = receiptInfo["status"] as? Int {
+                    let receiptStatus = ReceiptStatus(rawValue: status) ?? ReceiptStatus.Unknown
+                    if receiptStatus.isValid {
+                        completion(result: .Success(receipt: receiptInfo))
+                    }
+                    else {
+                        completion(result: .Error(error: .ReceiptInvalid(receipt: receiptInfo, status: receiptStatus)))
+                    }
+                }
+                else {
+                    completion(result: .Error(error: .ReceiptInvalid(receipt: receiptInfo, status: ReceiptStatus.None)))
+                }
+            }
+            task.resume()
+    }
+    
+}

+ 74 - 0
SwiftyStoreKit/InAppReceiptRefreshRequest.swift

@@ -0,0 +1,74 @@
+//
+//  InAppReceiptRefreshRequest.swift
+//  SwiftyStoreKit
+//
+//  Created by phimage on 23/12/15.
+// Copyright (c) 2015 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 StoreKit
+import Foundation
+
+#if os(iOS)
+    class InAppReceiptRefreshRequest: NSObject, SKRequestDelegate {
+
+        enum ResultType {
+            case Success
+            case Error(e: NSError)
+        }
+
+        typealias RequestCallback = (result: ResultType) -> ()
+
+        class func refresh(receiptProperties: [String : AnyObject]? = nil, callback: RequestCallback) -> InAppReceiptRefreshRequest {
+            let request = InAppReceiptRefreshRequest(receiptProperties: receiptProperties, callback: callback)
+            request.start()
+            return request
+        }
+
+        let refreshReceiptRequest: SKReceiptRefreshRequest
+        let callback: RequestCallback
+
+        init(receiptProperties: [String : AnyObject]? = nil, callback: RequestCallback) {
+            self.callback = callback
+            self.refreshReceiptRequest = SKReceiptRefreshRequest(receiptProperties: receiptProperties)
+            super.init()
+            self.refreshReceiptRequest.delegate = self
+        }
+
+        func start() {
+            self.refreshReceiptRequest.start()
+        }
+
+        func requestDidFinish(request: SKRequest) {
+            /*if let resoreRequest = request as? SKReceiptRefreshRequest {
+                let receiptProperties = resoreRequest.receiptProperties ?? [:]
+                for (k, v) in receiptProperties {
+                    print("\(k): \(v)")
+                }
+            }*/
+            callback(result: .Success)
+        }
+        func request(request: SKRequest, didFailWithError error: NSError) {
+            // XXX could here check domain and error code to return typed exception
+            callback(result: .Error(e: error))
+        }
+        
+    }
+#endif

+ 51 - 1
SwiftyStoreKit/SwiftyStoreKit.swift

@@ -41,7 +41,9 @@ public class SwiftyStoreKit {
     private var inflightQueries: [String: InAppProductQueryRequest] = [:]
     private var inflightPurchases: [String: InAppProductPurchaseRequest] = [:]
     private var restoreRequest: InAppProductPurchaseRequest?
-
+    #if os(iOS)
+    private var receiptRefreshRequest: InAppReceiptRefreshRequest?
+    #endif
     // MARK: Enums
     public enum PurchaseResultType {
         case Success(productId: String)
@@ -58,6 +60,11 @@ public class SwiftyStoreKit {
         case Error(error: ErrorType)
         case NothingToRestore
     }
+    public enum RefreshReceiptResultType {
+        case Success
+        case Error(error: ErrorType)
+    }
+
 
     // MARK: Singleton
     private static let sharedInstance = SwiftyStoreKit()
@@ -111,6 +118,49 @@ public class SwiftyStoreKit {
         }
     }
 
+    /**
+     *  Verify application receipt
+     *  - Parameter receiptVerifyURL: receipt verify url (default: Test)
+     *  - Parameter password: Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
+     *  - Parameter session: the session used to make remote call.
+     *  - Parameter completion: handler for result
+     */
+    public class func verifyReceipt(
+        receiptVerifyURL url: ReceiptVerifyURL = .Test,
+        password: String? = nil,
+        session: NSURLSession = NSURLSession.sharedSession(),
+        completion:(result: VerifyReceiptResultType) -> ()) {
+            InAppReceipt.verify(receiptVerifyURL: url, password: password, session: session, completion: completion)
+    }
+
+    #if os(iOS) || os(tvOS)
+    // After verifying receive and have `ReceiptError.NoReceiptData`, refresh receipt using this method
+    public class func refreshReceipt(receiptProperties: [String : AnyObject]? = nil, completion: (result: RefreshReceiptResultType) -> ()) {
+        sharedInstance.receiptRefreshRequest = InAppReceiptRefreshRequest.refresh(receiptProperties) { result in
+
+            sharedInstance.receiptRefreshRequest = nil
+
+            switch result {
+            case .Success:
+                if InAppReceipt.data == nil {
+                    completion(result: .Error(error: ReceiptError.NoReceiptData))
+                } else {
+                    completion(result: .Success)
+                }
+                break
+            case .Error(let e):
+                completion(result: .Error(error: e))
+                break
+            }
+        }
+    }
+    #elseif os(OSX)
+     // Call exit with a status of 173. This exit status notifies the system that your application has determined that its receipt is invalid. At this point, the system attempts to obtain a valid receipt and may prompt for the user’s iTunes credentials
+    public class func refreshReceipt() {
+         exit(ReceiptExitCode.NotValid.rawValue)
+    }
+    #endif
+
     // MARK: private methods
     private func purchase(product product: SKProduct, completion: (result: PurchaseResultType) -> ()) {
         guard SwiftyStoreKit.canMakePayments else {

+ 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


+ 30 - 18
SwiftStoreOSXDemo/Base.lproj/Main.storyboard → SwiftyStoreOSXDemo/Base.lproj/Main.storyboard

@@ -669,11 +669,11 @@
             <objects>
                 <viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="SwiftStoreOSXDemo" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" id="m2S-Jp-Qdl">
-                        <rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
+                        <rect key="frame" x="0.0" y="0.0" width="480" height="305"/>
                         <autoresizingMask key="autoresizingMask"/>
                         <subviews>
                             <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4ix-eb-y5G">
-                                <rect key="frame" x="162" y="13" width="156" height="32"/>
+                                <rect key="frame" x="162" y="48" width="156" height="32"/>
                                 <buttonCell key="cell" type="push" title="Restore Purchases" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="mmO-hG-Xyq">
                                     <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                     <font key="font" metaFont="system"/>
@@ -683,7 +683,7 @@
                                 </buttonCell>
                             </button>
                             <customView translatesAutoresizingMaskIntoConstraints="NO" id="aBh-sv-MN6">
-                                <rect key="frame" x="0.0" y="49" width="480" height="176"/>
+                                <rect key="frame" x="0.0" y="84" width="480" height="176"/>
                                 <subviews>
                                     <splitView focusRingType="none" dividerStyle="thin" vertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dZR-88-4H6">
                                         <rect key="frame" x="0.0" y="0.0" width="480" height="176"/>
@@ -692,8 +692,8 @@
                                                 <rect key="frame" x="0.0" y="0.0" width="254" height="176"/>
                                                 <autoresizingMask key="autoresizingMask"/>
                                                 <subviews>
-                                                    <button verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ypp-fu-oEb">
-                                                        <rect key="frame" x="80" y="103" width="90" height="32"/>
+                                                    <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ypp-fu-oEb">
+                                                        <rect key="frame" x="82" y="103" width="90" height="32"/>
                                                         <buttonCell key="cell" type="push" title="Get Info" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="vSi-fu-wrI">
                                                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                                             <font key="font" metaFont="system"/>
@@ -702,16 +702,16 @@
                                                             <action selector="getInfo1:" target="XfG-lQ-9wD" id="PTC-RY-Aom"/>
                                                         </connections>
                                                     </button>
-                                                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="23w-eX-lGC">
-                                                        <rect key="frame" x="102" y="139" width="45" height="17"/>
+                                                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="23w-eX-lGC">
+                                                        <rect key="frame" x="105" y="139" width="45" height="17"/>
                                                         <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="5 days" id="U2h-U7-cdn">
                                                             <font key="font" metaFont="system"/>
                                                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
                                                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                                                         </textFieldCell>
                                                     </textField>
-                                                    <button verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Hez-z0-TRG">
-                                                        <rect key="frame" x="76" y="70" width="98" height="32"/>
+                                                    <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Hez-z0-TRG">
+                                                        <rect key="frame" x="78" y="70" width="98" height="32"/>
                                                         <buttonCell key="cell" type="push" title="Purchase" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="5bl-kX-gwW">
                                                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                                             <font key="font" metaFont="system"/>
@@ -734,16 +734,16 @@
                                                 <rect key="frame" x="255" y="0.0" width="225" height="176"/>
                                                 <autoresizingMask key="autoresizingMask"/>
                                                 <subviews>
-                                                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gj2-Z2-1qo">
-                                                        <rect key="frame" x="84" y="139" width="53" height="17"/>
+                                                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gj2-Z2-1qo">
+                                                        <rect key="frame" x="86" y="139" width="53" height="17"/>
                                                         <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="10 days" id="XkC-cw-jpP">
                                                             <font key="font" metaFont="system"/>
                                                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
                                                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                                                         </textFieldCell>
                                                     </textField>
-                                                    <button verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="32D-7s-IOR">
-                                                        <rect key="frame" x="66" y="103" width="90" height="32"/>
+                                                    <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="32D-7s-IOR">
+                                                        <rect key="frame" x="68" y="103" width="90" height="32"/>
                                                         <buttonCell key="cell" type="push" title="Get Info" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="F8O-DF-BZX">
                                                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                                             <font key="font" metaFont="system"/>
@@ -752,8 +752,8 @@
                                                             <action selector="getInfo2:" target="XfG-lQ-9wD" id="u4g-Qb-RyE"/>
                                                         </connections>
                                                     </button>
-                                                    <button verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GkW-aD-XKc">
-                                                        <rect key="frame" x="62" y="70" width="98" height="32"/>
+                                                    <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="GkW-aD-XKc">
+                                                        <rect key="frame" x="64" y="70" width="98" height="32"/>
                                                         <buttonCell key="cell" type="push" title="Purchase" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="TDW-1n-a4d">
                                                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                                             <font key="font" metaFont="system"/>
@@ -787,19 +787,31 @@
                                 </constraints>
                             </customView>
                             <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="fQC-8G-INF">
-                                <rect key="frame" x="175" y="233" width="131" height="17"/>
+                                <rect key="frame" x="175" y="268" width="131" height="17"/>
                                 <textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Available purchases:" id="Ntg-PD-43y">
                                     <font key="font" metaFont="system"/>
                                     <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
                                     <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                                 </textFieldCell>
                             </textField>
+                            <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Yzl-nj-7ol">
+                                <rect key="frame" x="178" y="15" width="125" height="32"/>
+                                <buttonCell key="cell" type="push" title="Verify Receipt" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6bY-Fv-W6P">
+                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                    <font key="font" metaFont="system"/>
+                                </buttonCell>
+                                <connections>
+                                    <action selector="verifyReceipt:" target="XfG-lQ-9wD" id="uf9-Pe-MRz"/>
+                                </connections>
+                            </button>
                         </subviews>
                         <constraints>
+                            <constraint firstAttribute="bottom" secondItem="Yzl-nj-7ol" secondAttribute="bottom" constant="22" id="979-EH-fNb"/>
+                            <constraint firstItem="Yzl-nj-7ol" firstAttribute="top" secondItem="4ix-eb-y5G" secondAttribute="bottom" constant="12" id="B88-P1-tBj"/>
                             <constraint firstAttribute="trailing" secondItem="aBh-sv-MN6" secondAttribute="trailing" id="F8t-R2-cad"/>
                             <constraint firstItem="fQC-8G-INF" firstAttribute="centerX" secondItem="m2S-Jp-Qdl" secondAttribute="centerX" id="RDD-02-SB0"/>
                             <constraint firstItem="4ix-eb-y5G" firstAttribute="top" secondItem="aBh-sv-MN6" secondAttribute="bottom" constant="8" id="b6U-zT-POc"/>
-                            <constraint firstAttribute="bottom" secondItem="4ix-eb-y5G" secondAttribute="bottom" constant="20" id="crv-3H-ZKn"/>
+                            <constraint firstItem="Yzl-nj-7ol" firstAttribute="centerX" secondItem="m2S-Jp-Qdl" secondAttribute="centerX" id="dQi-Vh-Bwb"/>
                             <constraint firstItem="aBh-sv-MN6" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" id="enN-Hg-kUA"/>
                             <constraint firstItem="fQC-8G-INF" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" constant="20" id="kMt-Ye-T67"/>
                             <constraint firstItem="aBh-sv-MN6" firstAttribute="top" secondItem="fQC-8G-INF" secondAttribute="bottom" constant="8" id="rz5-by-FYa"/>
@@ -809,7 +821,7 @@
                 </viewController>
                 <customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
             </objects>
-            <point key="canvasLocation" x="75" y="655"/>
+            <point key="canvasLocation" x="75" y="672.5"/>
         </scene>
     </scenes>
 </document>

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


+ 32 - 5
SwiftStoreOSXDemo/ViewController.swift → SwiftyStoreOSXDemo/ViewController.swift

@@ -42,7 +42,7 @@ class ViewController: NSViewController {
             self.showAlert(self.alertForPurchaseResult(result))
         }
     }
-    
+
     @IBAction func restorePurchases(sender: AnyObject?) {
 
         SwiftyStoreKit.restorePurchases() { result in
@@ -51,6 +51,18 @@ class ViewController: NSViewController {
         }
     }
 
+    @IBAction func verifyReceipt(ender: AnyObject?) {
+
+        SwiftyStoreKit.verifyReceipt() { result in
+
+            self.showAlert(self.alertForVerifyReceipt(result)) { response in
+
+                SwiftyStoreKit.refreshReceipt()
+            }
+        }
+    }
+
+
 }
 
 // MARK: User facing alerts
@@ -64,16 +76,18 @@ extension ViewController {
         alert.alertStyle = NSAlertStyle.InformationalAlertStyle
         return alert
     }
-    func showAlert(alert: NSAlert) {
+    func showAlert(alert: NSAlert, handler: ((NSModalResponse) -> Void)? = nil) {
         
         if let window = NSApplication.sharedApplication().keyWindow {
-            alert.beginSheetModalForWindow(window) { (response: NSModalResponse) in
+            alert.beginSheetModalForWindow(window)  { (response: NSModalResponse) in
+                handler?(response)
             }
         } else {
-            alert.runModal()
+            let response = alert.runModal()
+            handler?(response)
         }
     }
-    
+
     func alertForProductRetrievalInfo(result: SwiftyStoreKit.RetrieveResultType) -> NSAlert {
         
         switch result {
@@ -121,5 +135,18 @@ extension ViewController {
             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")
+        }
+    }
+
 }