diff --git a/README.md b/README.md index 62fa551d..160906c7 100644 --- a/README.md +++ b/README.md @@ -274,11 +274,12 @@ RMStore requires iOS 5.0 or above and ARC. RMStore is in initial development and its public API should not be considered stable. Future enhancements will include: -* [Better OS X support](https://github.com/robotmedia/RMStore/issues/4) +* Tests for OS X +* Example app for OS X ##License - Copyright 2013-2014 [Robot Media SL](http://www.robotmedia.net) + Copyright 2013-2016 [Robot Media SL](http://www.robotmedia.net) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/RMStore.podspec b/RMStore.podspec index 3e89f892..bdefcff2 100644 --- a/RMStore.podspec +++ b/RMStore.podspec @@ -1,16 +1,18 @@ Pod::Spec.new do |s| s.name = 'RMStore' - s.version = '0.7.1' + s.version = '0.8.0' s.license = 'Apache 2.0' s.summary = 'A lightweight iOS library for In-App Purchases that adds blocks and notifications to StoreKit, plus verification, persistence and downloads.' s.homepage = 'https://github.com/robotmedia/RMStore' s.author = 'Hermes Pique' s.social_media_url = 'https://twitter.com/hpique' s.source = { :git => 'https://github.com/robotmedia/RMStore.git', :tag => "v#{s.version}" } - s.platform = :ios, '7.0' s.frameworks = 'StoreKit' s.requires_arc = true s.default_subspec = 'Core' + + s.ios.deployment_target = '7.0' + s.osx.deployment_target = '10.7' s.subspec 'Core' do |core| core.source_files = 'RMStore/*.{h,m}' @@ -29,14 +31,10 @@ Pod::Spec.new do |s| s.subspec 'AppReceiptVerifier' do |arv| arv.dependency 'RMStore/Core' - arv.platform = :ios, '7.0' arv.source_files = 'RMStore/Optional/RMStoreAppReceiptVerifier.{h,m}', 'RMStore/Optional/RMAppReceipt.{h,m}' - arv.dependency 'OpenSSL', '~> 1.0' - end - - s.subspec 'TransactionReceiptVerifier' do |trv| - trv.dependency 'RMStore/Core' - trv.source_files = 'RMStore/Optional/RMStoreTransactionReceiptVerifier.{h,m}' + arv.dependency 'OpenSSL-Universal', '~> 1.0' + arv.osx.frameworks = 'Security', 'IOKit' + arv.resources = 'RMStore/Optional/AppleIncRootCertificate.cer' end end diff --git a/RMStore.xcodeproj/project.pbxproj b/RMStore.xcodeproj/project.pbxproj index f7da0a58..a6e17975 100644 --- a/RMStore.xcodeproj/project.pbxproj +++ b/RMStore.xcodeproj/project.pbxproj @@ -7,6 +7,22 @@ objects = { /* Begin PBXBuildFile section */ + 52E207651D5CE26100E89C8F /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52E207641D5CE26100E89C8F /* IOKit.framework */; }; + 52FE63A41D3531B00050814B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 52FE63A31D3531B00050814B /* AppDelegate.m */; }; + 52FE63A71D3531B00050814B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 52FE63A61D3531B00050814B /* main.m */; }; + 52FE63AA1D3531B00050814B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 52FE63A91D3531B00050814B /* ViewController.m */; }; + 52FE63AC1D3531B00050814B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52FE63AB1D3531B00050814B /* Assets.xcassets */; }; + 52FE63AF1D3531B00050814B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 52FE63AD1D3531B00050814B /* Main.storyboard */; }; + 52FE63B41D35333F0050814B /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8793E797180C2ABE005D7A66 /* libcrypto.a */; }; + 52FE63B51D35333F0050814B /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8793E798180C2ABE005D7A66 /* libssl.a */; }; + 52FE63B91D3534390050814B /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52FE63B81D3534390050814B /* StoreKit.framework */; }; + 52FE63BB1D3534450050814B /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52FE63BA1D3534450050814B /* Security.framework */; }; + 52FE63BE1D3535A00050814B /* RMAppReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = 8793E801180D512E005D7A66 /* RMAppReceipt.m */; }; + 52FE63C01D3535A00050814B /* RMStoreAppReceiptVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 8793E803180D512E005D7A66 /* RMStoreAppReceiptVerifier.m */; }; + 52FE63C21D3535A00050814B /* RMStoreKeychainPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 876046481812FB7500C9B78C /* RMStoreKeychainPersistence.m */; }; + 52FE63C41D3535A00050814B /* RMStoreTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 876631F8180EEBF40049B368 /* RMStoreTransaction.m */; }; + 52FE63C81D3535A00050814B /* RMStoreUserDefaultsPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 87A2A3A8180E82BB00376773 /* RMStoreUserDefaultsPersistence.m */; }; + 52FE63CA1D3535A00050814B /* RMStore.m in Sources */ = {isa = PBXBuildFile; fileRef = A0AF3D1217A802F300D2E836 /* RMStore.m */; }; 8700D1C117DCA548005C8F5D /* NSNotification+RMStoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8700D1C017DCA548005C8F5D /* NSNotification+RMStoreTests.m */; }; 8700D1D717DCB011005C8F5D /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8700D1D617DCB011005C8F5D /* libOCMock.a */; }; 876046491812FB7500C9B78C /* RMStoreKeychainPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 876046481812FB7500C9B78C /* RMStoreKeychainPersistence.m */; }; @@ -14,7 +30,6 @@ 8760464D18130DD400C9B78C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8760464C18130DD400C9B78C /* Security.framework */; }; 8760464E18130DD800C9B78C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8760464C18130DD400C9B78C /* Security.framework */; }; 8760465018131B2600C9B78C /* RMStoreKeychainPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 876046481812FB7500C9B78C /* RMStoreKeychainPersistence.m */; }; - 8760465118131B4800C9B78C /* RMStoreTransactionReceiptVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 8793E805180D512E005D7A66 /* RMStoreTransactionReceiptVerifier.m */; }; 876631F9180EEBF40049B368 /* RMStoreTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 876631F8180EEBF40049B368 /* RMStoreTransaction.m */; }; 8793E799180C2ABE005D7A66 /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8793E797180C2ABE005D7A66 /* libcrypto.a */; }; 8793E79A180C2ABE005D7A66 /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8793E798180C2ABE005D7A66 /* libssl.a */; }; @@ -22,10 +37,8 @@ 8793E79E180C2C90005D7A66 /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8793E797180C2ABE005D7A66 /* libcrypto.a */; }; 8793E808180D512E005D7A66 /* RMAppReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = 8793E801180D512E005D7A66 /* RMAppReceipt.m */; }; 8793E809180D512E005D7A66 /* RMStoreAppReceiptVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 8793E803180D512E005D7A66 /* RMStoreAppReceiptVerifier.m */; }; - 8793E80A180D512E005D7A66 /* RMStoreTransactionReceiptVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 8793E805180D512E005D7A66 /* RMStoreTransactionReceiptVerifier.m */; }; 8793E80B180D5133005D7A66 /* RMAppReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = 8793E801180D512E005D7A66 /* RMAppReceipt.m */; }; 8793E80C180D5136005D7A66 /* RMStoreAppReceiptVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 8793E803180D512E005D7A66 /* RMStoreAppReceiptVerifier.m */; }; - 87950C2317E127A4001DF541 /* RMStoreTransactionReceiptVerifierTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 87950C2217E127A4001DF541 /* RMStoreTransactionReceiptVerifierTests.m */; }; 87A2A3A0180D7B0400376773 /* RMAppReceiptTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 87A2A39F180D7B0400376773 /* RMAppReceiptTests.m */; }; 87A2A3A3180D817600376773 /* RMAppReceiptIAPTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 87A2A3A2180D817600376773 /* RMAppReceiptIAPTests.m */; }; 87A2A3A5180D82EF00376773 /* RMStoreAppReceiptVerifierTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 87A2A3A4180D82EF00376773 /* RMStoreAppReceiptVerifierTests.m */; }; @@ -84,6 +97,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 52E207641D5CE26100E89C8F /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/IOKit.framework; sourceTree = DEVELOPER_DIR; }; + 52FE63A01D3531B00050814B /* RMStoreDemo_OSX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RMStoreDemo_OSX.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 52FE63A21D3531B00050814B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 52FE63A31D3531B00050814B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 52FE63A61D3531B00050814B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 52FE63A81D3531B00050814B /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 52FE63A91D3531B00050814B /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 52FE63AB1D3531B00050814B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 52FE63AE1D3531B00050814B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 52FE63B01D3531B00050814B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 52FE63B81D3534390050814B /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; + 52FE63BA1D3534450050814B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; 8700D1C017DCA548005C8F5D /* NSNotification+RMStoreTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNotification+RMStoreTests.m"; sourceTree = ""; }; 8700D1CF17DCB011005C8F5D /* NSNotificationCenter+OCMAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+OCMAdditions.h"; sourceTree = ""; }; 8700D1D017DCB011005C8F5D /* OCMArg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMArg.h; sourceTree = ""; }; @@ -104,9 +129,6 @@ 8793E801180D512E005D7A66 /* RMAppReceipt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMAppReceipt.m; sourceTree = ""; }; 8793E802180D512E005D7A66 /* RMStoreAppReceiptVerifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMStoreAppReceiptVerifier.h; sourceTree = ""; }; 8793E803180D512E005D7A66 /* RMStoreAppReceiptVerifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMStoreAppReceiptVerifier.m; sourceTree = ""; }; - 8793E804180D512E005D7A66 /* RMStoreTransactionReceiptVerifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMStoreTransactionReceiptVerifier.h; sourceTree = ""; }; - 8793E805180D512E005D7A66 /* RMStoreTransactionReceiptVerifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMStoreTransactionReceiptVerifier.m; sourceTree = ""; }; - 87950C2217E127A4001DF541 /* RMStoreTransactionReceiptVerifierTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMStoreTransactionReceiptVerifierTests.m; sourceTree = ""; }; 87A2A39F180D7B0400376773 /* RMAppReceiptTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMAppReceiptTests.m; sourceTree = ""; }; 87A2A3A1180D7E2900376773 /* RMStoreTests-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RMStoreTests-Prefix.pch"; sourceTree = ""; }; 87A2A3A2180D817600376773 /* RMAppReceiptIAPTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMAppReceiptIAPTests.m; sourceTree = ""; }; @@ -147,6 +169,18 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 52FE639D1D3531B00050814B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 52E207651D5CE26100E89C8F /* IOKit.framework in Frameworks */, + 52FE63BB1D3534450050814B /* Security.framework in Frameworks */, + 52FE63B91D3534390050814B /* StoreKit.framework in Frameworks */, + 52FE63B41D35333F0050814B /* libcrypto.a in Frameworks */, + 52FE63B51D35333F0050814B /* libssl.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A0AF3D0517A802F300D2E836 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -189,6 +223,29 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 52FE63A11D3531B00050814B /* RMStoreDemo_OSX */ = { + isa = PBXGroup; + children = ( + 52FE63A21D3531B00050814B /* AppDelegate.h */, + 52FE63A31D3531B00050814B /* AppDelegate.m */, + 52FE63A81D3531B00050814B /* ViewController.h */, + 52FE63A91D3531B00050814B /* ViewController.m */, + 52FE63AB1D3531B00050814B /* Assets.xcassets */, + 52FE63AD1D3531B00050814B /* Main.storyboard */, + 52FE63B01D3531B00050814B /* Info.plist */, + 52FE63A51D3531B00050814B /* Supporting Files */, + ); + path = RMStoreDemo_OSX; + sourceTree = ""; + }; + 52FE63A51D3531B00050814B /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 52FE63A61D3531B00050814B /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; 8700D1CC17DCB011005C8F5D /* usr */ = { isa = PBXGroup; children = ( @@ -239,8 +296,6 @@ 876046481812FB7500C9B78C /* RMStoreKeychainPersistence.m */, 876631F7180EEBF40049B368 /* RMStoreTransaction.h */, 876631F8180EEBF40049B368 /* RMStoreTransaction.m */, - 8793E804180D512E005D7A66 /* RMStoreTransactionReceiptVerifier.h */, - 8793E805180D512E005D7A66 /* RMStoreTransactionReceiptVerifier.m */, 87A2A3A7180E82BB00376773 /* RMStoreUserDefaultsPersistence.h */, 87A2A3A8180E82BB00376773 /* RMStoreUserDefaultsPersistence.m */, ); @@ -253,6 +308,7 @@ A0AF3D0D17A802F300D2E836 /* RMStore */, A0AF3D2217A802F300D2E836 /* RMStoreTests */, A0AF3D7617A8085900D2E836 /* RMStoreDemo */, + 52FE63A11D3531B00050814B /* RMStoreDemo_OSX */, A0AF3D0A17A802F300D2E836 /* Frameworks */, A0AF3D0917A802F300D2E836 /* Products */, ); @@ -264,6 +320,7 @@ A0AF3D0817A802F300D2E836 /* libRMStore.a */, A0AF3D1917A802F300D2E836 /* RMStoreTests.xctest */, A0AF3D7217A8085900D2E836 /* RMStoreDemo.app */, + 52FE63A01D3531B00050814B /* RMStoreDemo_OSX.app */, ); name = Products; sourceTree = ""; @@ -271,6 +328,9 @@ A0AF3D0A17A802F300D2E836 /* Frameworks */ = { isa = PBXGroup; children = ( + 52E207641D5CE26100E89C8F /* IOKit.framework */, + 52FE63BA1D3534450050814B /* Security.framework */, + 52FE63B81D3534390050814B /* StoreKit.framework */, C90EF0F019A20C8200A9E738 /* XCTest.framework */, A0AF3D3D17A807FF00D2E836 /* CoreGraphics.framework */, A0AF3D0B17A802F300D2E836 /* Foundation.framework */, @@ -312,7 +372,6 @@ 87A2A3A4180D82EF00376773 /* RMStoreAppReceiptVerifierTests.m */, 8760464A18130CBB00C9B78C /* RMStoreKeychainPersistenceTests.m */, A0AF3D2917A802F300D2E836 /* RMStoreTests.m */, - 87950C2217E127A4001DF541 /* RMStoreTransactionReceiptVerifierTests.m */, 87B7853E18105E6A00B5E54E /* RMStoreTransactionTests.m */, 87A2A3AB180E8AF500376773 /* RMStoreUserDefaultsPersistenceTests.m */, A0AF3D2317A802F300D2E836 /* Supporting Files */, @@ -362,6 +421,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 52FE639F1D3531B00050814B /* RMStoreDemo_OSX */ = { + isa = PBXNativeTarget; + buildConfigurationList = 52FE63B31D3531B00050814B /* Build configuration list for PBXNativeTarget "RMStoreDemo_OSX" */; + buildPhases = ( + 52FE639C1D3531B00050814B /* Sources */, + 52FE639D1D3531B00050814B /* Frameworks */, + 52FE639E1D3531B00050814B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RMStoreDemo_OSX; + productName = RMStoreDemo_OSX; + productReference = 52FE63A01D3531B00050814B /* RMStoreDemo_OSX.app */; + productType = "com.apple.product-type.application"; + }; A0AF3D0717A802F300D2E836 /* RMStore */ = { isa = PBXNativeTarget; buildConfigurationList = A0AF3D2D17A802F300D2E836 /* Build configuration list for PBXNativeTarget "RMStore" */; @@ -422,6 +498,11 @@ attributes = { LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Robot Media"; + TargetAttributes = { + 52FE639F1D3531B00050814B = { + CreatedOnToolsVersion = 7.3; + }; + }; }; buildConfigurationList = A0AF3D0317A802F300D2E836 /* Build configuration list for PBXProject "RMStore" */; compatibilityVersion = "Xcode 3.2"; @@ -429,6 +510,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = A0AF3CFF17A802F300D2E836; productRefGroup = A0AF3D0917A802F300D2E836 /* Products */; @@ -438,11 +520,21 @@ A0AF3D0717A802F300D2E836 /* RMStore */, A0AF3D1817A802F300D2E836 /* RMStoreTests */, A0AF3D7117A8085900D2E836 /* RMStoreDemo */, + 52FE639F1D3531B00050814B /* RMStoreDemo_OSX */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 52FE639E1D3531B00050814B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 52FE63AC1D3531B00050814B /* Assets.xcassets in Resources */, + 52FE63AF1D3531B00050814B /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A0AF3D1617A802F300D2E836 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -466,13 +558,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 52FE639C1D3531B00050814B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 52FE63BE1D3535A00050814B /* RMAppReceipt.m in Sources */, + 52FE63C01D3535A00050814B /* RMStoreAppReceiptVerifier.m in Sources */, + 52FE63C21D3535A00050814B /* RMStoreKeychainPersistence.m in Sources */, + 52FE63C41D3535A00050814B /* RMStoreTransaction.m in Sources */, + 52FE63C81D3535A00050814B /* RMStoreUserDefaultsPersistence.m in Sources */, + 52FE63CA1D3535A00050814B /* RMStore.m in Sources */, + 52FE63AA1D3531B00050814B /* ViewController.m in Sources */, + 52FE63A71D3531B00050814B /* main.m in Sources */, + 52FE63A41D3531B00050814B /* AppDelegate.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A0AF3D0417A802F300D2E836 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A0AF3D1317A802F300D2E836 /* RMStore.m in Sources */, 876046491812FB7500C9B78C /* RMStoreKeychainPersistence.m in Sources */, - 8793E80A180D512E005D7A66 /* RMStoreTransactionReceiptVerifier.m in Sources */, 8793E809180D512E005D7A66 /* RMStoreAppReceiptVerifier.m in Sources */, 87A2A3A9180E82BB00376773 /* RMStoreUserDefaultsPersistence.m in Sources */, 8793E808180D512E005D7A66 /* RMAppReceipt.m in Sources */, @@ -490,7 +597,6 @@ 8700D1C117DCA548005C8F5D /* NSNotification+RMStoreTests.m in Sources */, 87B7853F18105E6A00B5E54E /* RMStoreTransactionTests.m in Sources */, 87D5A74217DE893E000E2B6C /* RMProducstRequestDelegateTests.m in Sources */, - 87950C2317E127A4001DF541 /* RMStoreTransactionReceiptVerifierTests.m in Sources */, 87A2A3A0180D7B0400376773 /* RMAppReceiptTests.m in Sources */, 87A2A3A3180D817600376773 /* RMAppReceiptIAPTests.m in Sources */, 87A2A3AC180E8AF500376773 /* RMStoreUserDefaultsPersistenceTests.m in Sources */, @@ -507,7 +613,6 @@ A0AF3DA217A8094600D2E836 /* RMStoreViewController.m in Sources */, 8793E80C180D5136005D7A66 /* RMStoreAppReceiptVerifier.m in Sources */, A0AF3DA717A8095F00D2E836 /* RMPurchasesViewController.m in Sources */, - 8760465118131B4800C9B78C /* RMStoreTransactionReceiptVerifier.m in Sources */, A0AF3DA917A80B2F00D2E836 /* RMStore.m in Sources */, 8793E80B180D5133005D7A66 /* RMAppReceipt.m in Sources */, ); @@ -524,6 +629,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 52FE63AD1D3531B00050814B /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 52FE63AE1D3531B00050814B /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; A0AF3D2517A802F300D2E836 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -543,6 +656,86 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 52FE63B11D3531B00050814B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "RMStore/Optional/openssl-1.0.1e/include", + ); + INFOPLIST_FILE = RMStoreDemo_OSX/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/RMStore/Optional/openssl-1.0.1e/lib", + ); + MACOSX_DEPLOYMENT_TARGET = 10.7; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = "net.robotmedia.RMStoreDemo-OSX"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + VALID_ARCHS = "i386 x86_64"; + }; + name = Debug; + }; + 52FE63B21D3531B00050814B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "RMStore/Optional/openssl-1.0.1e/include", + ); + INFOPLIST_FILE = RMStoreDemo_OSX/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/RMStore/Optional/openssl-1.0.1e/lib", + ); + MACOSX_DEPLOYMENT_TARGET = 10.7; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "net.robotmedia.RMStoreDemo-OSX"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + VALID_ARCHS = "i386 x86_64"; + }; + name = Release; + }; A0AF3D2B17A802F300D2E836 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -756,6 +949,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 52FE63B31D3531B00050814B /* Build configuration list for PBXNativeTarget "RMStoreDemo_OSX" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 52FE63B11D3531B00050814B /* Debug */, + 52FE63B21D3531B00050814B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; A0AF3D0317A802F300D2E836 /* Build configuration list for PBXProject "RMStore" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/RMStore/Optional/RMAppReceipt.h b/RMStore/Optional/RMAppReceipt.h index 9b45fe33..1eb05727 100644 --- a/RMStore/Optional/RMAppReceipt.h +++ b/RMStore/Optional/RMAppReceipt.h @@ -22,7 +22,6 @@ /** Represents the app receipt. */ -__attribute__((availability(ios,introduced=7.0))) @interface RMAppReceipt : NSObject /** The app’s bundle identifier. diff --git a/RMStore/Optional/RMAppReceipt.m b/RMStore/Optional/RMAppReceipt.m index 68aa7147..fb831b38 100644 --- a/RMStore/Optional/RMAppReceipt.m +++ b/RMStore/Optional/RMAppReceipt.m @@ -19,12 +19,24 @@ // #import "RMAppReceipt.h" -#import #import #import #import #import +#if TARGET_OS_IPHONE +#import +#elif TARGET_OS_MAC +#import +#import +#endif + +#if DEBUG +#define RMAppReceiptLog(...) NSLog(@"RMAppReceipt: %@", [NSString stringWithFormat:__VA_ARGS__]); +#else +#define RMAppReceiptLog(...) +#endif + // From https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1 NSInteger const RMAppReceiptASN1TypeBundleIdentifier = 2; NSInteger const RMAppReceiptASN1TypeAppVersion = 3; @@ -101,6 +113,114 @@ static int RMASN1ReadInteger(const uint8_t **pp, long omax) return RMASN1ReadString(pp, omax, V_ASN1_IA5STRING, NSASCIIStringEncoding); } +#if TARGET_OS_MAC && !TARGET_OS_IPHONE + +// Returns a CFData object, containing the computer's GUID. +static CFDataRef CopyMACAddressData() +{ + kern_return_t kernResult; + mach_port_t master_port; + CFMutableDictionaryRef matchingDict; + io_iterator_t iterator; + io_object_t service; + CFDataRef macAddress = nil; + + kernResult = IOMasterPort(MACH_PORT_NULL, &master_port); + if (kernResult != KERN_SUCCESS) { + RMAppReceiptLog(@"IOMasterPort returned %d", kernResult); + return nil; + } + + matchingDict = IOBSDNameMatching(master_port, 0, "en0"); + if (!matchingDict) { + RMAppReceiptLog(@"IOBSDNameMatching returned empty dictionary"); + return nil; + } + + kernResult = IOServiceGetMatchingServices(master_port, matchingDict, &iterator); + if (kernResult != KERN_SUCCESS) { + RMAppReceiptLog(@"IOServiceGetMatchingServices returned %d", kernResult); + return nil; + } + + while((service = IOIteratorNext(iterator)) != 0) { + io_object_t parentService; + + kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, + &parentService); + if (kernResult == KERN_SUCCESS) { + if (macAddress) CFRelease(macAddress); + + macAddress = (CFDataRef) IORegistryEntryCreateCFProperty(parentService, + CFSTR("IOMACAddress"), kCFAllocatorDefault, 0); + IOObjectRelease(parentService); + } else { + RMAppReceiptLog(@"IORegistryEntryGetParentEntry returned %d", kernResult); + } + + IOObjectRelease(service); + } + IOObjectRelease(iterator); + + return macAddress; +} + +static inline SecCertificateRef AppleRootCAFromKeychain( void ) +{ + SecKeychainRef roots = NULL; + SecKeychainSearchRef search = NULL; + SecCertificateRef cert = NULL; + BOOL cfReleaseKeychain = YES; + + // there's a GC bug with this guy it seems + OSStatus err = SecKeychainOpen( "/System/Library/Keychains/SystemRootCertificates.keychain", &roots ); + + if ( err != noErr ) + { + CFStringRef errStr = SecCopyErrorMessageString( err, NULL ); + RMAppReceiptLog( @"Error: %d (%@)", err, errStr ); + CFRelease( errStr ); + return NULL; + } + + SecKeychainAttribute labelAttr = { .tag = kSecLabelItemAttr, .length = 13, .data = (void *)"Apple Root CA" }; + SecKeychainAttributeList attrs = { .count = 1, .attr = &labelAttr }; + + err = SecKeychainSearchCreateFromAttributes( roots, kSecCertificateItemClass, &attrs, &search ); + if ( err != noErr ) + { + CFStringRef errStr = SecCopyErrorMessageString( err, NULL ); + RMAppReceiptLog( @"Error: %d (%@)", err, errStr ); + CFRelease( errStr ); + if ( cfReleaseKeychain ) + CFRelease( roots ); + return NULL; + } + + SecKeychainItemRef item = NULL; + err = SecKeychainSearchCopyNext( search, &item ); + if ( err != noErr ) + { + CFStringRef errStr = SecCopyErrorMessageString( err, NULL ); + RMAppReceiptLog( @"Error: %d (%@)", err, errStr ); + CFRelease( errStr ); + if ( cfReleaseKeychain ) + CFRelease( roots ); + + return NULL; + } + + cert = (SecCertificateRef)item; + CFRelease( search ); + + if ( cfReleaseKeychain ) + CFRelease( roots ); + + return ( cert ); +} + +#endif + static NSURL *_appleRootCertificateURL = nil; @implementation RMAppReceipt @@ -117,17 +237,17 @@ - (instancetype)initWithASN1Data:(NSData*)asn1Data switch (type) { case RMAppReceiptASN1TypeBundleIdentifier: - _bundleIdentifierData = data; - _bundleIdentifier = RMASN1ReadUTF8String(&s, length); + self->_bundleIdentifierData = data; + self->_bundleIdentifier = RMASN1ReadUTF8String(&s, length); break; case RMAppReceiptASN1TypeAppVersion: - _appVersion = RMASN1ReadUTF8String(&s, length); + self->_appVersion = RMASN1ReadUTF8String(&s, length); break; case RMAppReceiptASN1TypeOpaqueValue: - _opaqueValue = data; + self->_opaqueValue = data; break; case RMAppReceiptASN1TypeHash: - _receiptHash = data; + self->_receiptHash = data; break; case RMAppReceiptASN1TypeInAppPurchaseReceipt: { @@ -136,12 +256,12 @@ - (instancetype)initWithASN1Data:(NSData*)asn1Data break; } case RMAppReceiptASN1TypeOriginalAppVersion: - _originalAppVersion = RMASN1ReadUTF8String(&s, length); + self->_originalAppVersion = RMASN1ReadUTF8String(&s, length); break; case RMAppReceiptASN1TypeExpirationDate: { NSString *string = RMASN1ReadIA5SString(&s, length); - _expirationDate = [RMAppReceipt formatRFC3339String:string]; + self->_expirationDate = [RMAppReceipt formatRFC3339String:string]; break; } } @@ -180,13 +300,19 @@ -(BOOL)containsActiveAutoRenewableSubscriptionOfProductIdentifier:(NSString *)pr - (BOOL)verifyReceiptHash { // TODO: Getting the uuid in Mac is different. See: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5 - NSUUID *uuid = [UIDevice currentDevice].identifierForVendor; - unsigned char uuidBytes[16]; - [uuid getUUIDBytes:uuidBytes]; // Order taken from: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5 + NSMutableData *data = [NSMutableData data]; +#if TARGET_OS_IPHONE + NSUUID *uuid = [UIDevice currentDevice].identifierForVendor; + unsigned char uuidBytes[16]; + [uuid getUUIDBytes:uuidBytes]; [data appendBytes:uuidBytes length:sizeof(uuidBytes)]; +#elif TARGET_OS_MAC + [data appendData:(__bridge NSData * _Nonnull)(CopyMACAddressData())]; +#endif + [data appendData:self.opaqueValue]; [data appendData:self.bundleIdentifierData]; @@ -228,10 +354,30 @@ + (NSData*)dataFromPCKS7Path:(NSString*)path if (!p7) return nil; - NSData *data; + NSData *certificateData = nil; +#if TARGET_OS_IPHONE NSURL *certificateURL = _appleRootCertificateURL ? : [[NSBundle mainBundle] URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"]; - NSData *certificateData = [NSData dataWithContentsOfURL:certificateURL]; - if (!certificateData || [self verifyPCKS7:p7 withCertificateData:certificateData]) + certificateData = [NSData dataWithContentsOfURL:certificateURL]; +#elif TARGET_OS_MAC + if (_appleRootCertificateURL) + { + certificateData = [NSData dataWithContentsOfURL:_appleRootCertificateURL]; + } + else + { + // get the Apple root CA from http://www.apple.com/certificateauthority and load it into b_X509 + //NSData * root = [NSData dataWithContentsOfURL: [NSURL URLWithString: @"http://www.apple.com/certificateauthority/AppleComputerRootCertificate.cer"]]; + SecCertificateRef cert = AppleRootCAFromKeychain(); + NSAssert(cert != NULL, @"Failed to load Apple Root CA from keychain"); + certificateData = CFBridgingRelease(SecCertificateCopyData(cert)); + CFRelease(cert); + } +#endif + + NSAssert(certificateData != nil, @"Certificate AppleRootCA is missed, add it to the bundle (ios) or provide access to keychain (osx) or specify url `setAppleRootCertificateURL`"); + + NSData *data = nil; + if (certificateData && [self verifyPKCS7:p7 withCertificateData:certificateData]) { struct pkcs7_st *contents = p7->d.sign->contents; if (PKCS7_type_is_data(contents)) @@ -244,8 +390,9 @@ + (NSData*)dataFromPCKS7Path:(NSString*)path return data; } -+ (BOOL)verifyPCKS7:(PKCS7*)container withCertificateData:(NSData*)certificateData -{ // Based on: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW17 ++ (BOOL)verifyPKCS7:(PKCS7*)container withCertificateData:(NSData*)certificateData +{ + // Based on: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW17 static int verified = 1; int result = 0; OpenSSL_add_all_digests(); // Required for PKCS7_verify to work @@ -336,42 +483,42 @@ - (instancetype)initWithASN1Data:(NSData*)asn1Data switch (type) { case RMAppReceiptASN1TypeQuantity: - _quantity = RMASN1ReadInteger(&p, length); + self->_quantity = RMASN1ReadInteger(&p, length); break; case RMAppReceiptASN1TypeProductIdentifier: - _productIdentifier = RMASN1ReadUTF8String(&p, length); + self->_productIdentifier = RMASN1ReadUTF8String(&p, length); break; case RMAppReceiptASN1TypeTransactionIdentifier: - _transactionIdentifier = RMASN1ReadUTF8String(&p, length); + self->_transactionIdentifier = RMASN1ReadUTF8String(&p, length); break; case RMAppReceiptASN1TypePurchaseDate: { NSString *string = RMASN1ReadIA5SString(&p, length); - _purchaseDate = [RMAppReceipt formatRFC3339String:string]; + self->_purchaseDate = [RMAppReceipt formatRFC3339String:string]; break; } case RMAppReceiptASN1TypeOriginalTransactionIdentifier: - _originalTransactionIdentifier = RMASN1ReadUTF8String(&p, length); + self->_originalTransactionIdentifier = RMASN1ReadUTF8String(&p, length); break; case RMAppReceiptASN1TypeOriginalPurchaseDate: { NSString *string = RMASN1ReadIA5SString(&p, length); - _originalPurchaseDate = [RMAppReceipt formatRFC3339String:string]; + self->_originalPurchaseDate = [RMAppReceipt formatRFC3339String:string]; break; } case RMAppReceiptASN1TypeSubscriptionExpirationDate: { NSString *string = RMASN1ReadIA5SString(&p, length); - _subscriptionExpirationDate = [RMAppReceipt formatRFC3339String:string]; + self->_subscriptionExpirationDate = [RMAppReceipt formatRFC3339String:string]; break; } case RMAppReceiptASN1TypeWebOrderLineItemID: - _webOrderLineItemID = RMASN1ReadInteger(&p, length); + self->_webOrderLineItemID = RMASN1ReadInteger(&p, length); break; case RMAppReceiptASN1TypeCancellationDate: { NSString *string = RMASN1ReadIA5SString(&p, length); - _cancellationDate = [RMAppReceipt formatRFC3339String:string]; + self->_cancellationDate = [RMAppReceipt formatRFC3339String:string]; break; } } diff --git a/RMStore/Optional/RMStoreAppReceiptVerifier.h b/RMStore/Optional/RMStoreAppReceiptVerifier.h index 204c15f2..e7c58239 100644 --- a/RMStore/Optional/RMStoreAppReceiptVerifier.h +++ b/RMStore/Optional/RMStoreAppReceiptVerifier.h @@ -24,7 +24,6 @@ /** Reference implementation of an app receipt verifier. If security is a concern you might want to avoid using a verifier whose code is open source. */ -__attribute__((availability(ios,introduced=7.0))) @interface RMStoreAppReceiptVerifier : NSObject /** diff --git a/RMStore/Optional/RMStoreAppReceiptVerifier.m b/RMStore/Optional/RMStoreAppReceiptVerifier.m index bcad2226..6757df88 100644 --- a/RMStore/Optional/RMStoreAppReceiptVerifier.m +++ b/RMStore/Optional/RMStoreAppReceiptVerifier.m @@ -24,8 +24,8 @@ @implementation RMStoreAppReceiptVerifier - (void)verifyTransaction:(SKPaymentTransaction*)transaction - success:(void (^)())successBlock - failure:(void (^)(NSError *error))failureBlock + success:(void (^)(void))successBlock + failure:(void (^)(NSError *error))failureBlock { RMAppReceipt *receipt = [RMAppReceipt bundleReceipt]; const BOOL verified = [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:nil]; // failureBlock is nil intentionally. See below. @@ -87,8 +87,8 @@ - (BOOL)verifyAppReceipt:(RMAppReceipt*)receipt - (BOOL)verifyTransaction:(SKPaymentTransaction*)transaction inReceipt:(RMAppReceipt*)receipt - success:(void (^)())successBlock - failure:(void (^)(NSError *error))failureBlock + success:(void (^)(void))successBlock + failure:(void (^)(NSError *error))failureBlock { const BOOL receiptVerified = [self verifyAppReceipt:receipt]; if (!receiptVerified) diff --git a/RMStore/Optional/RMStoreKeychainPersistence.h b/RMStore/Optional/RMStoreKeychainPersistence.h index 31f9ca86..6dc8364f 100644 --- a/RMStore/Optional/RMStoreKeychainPersistence.h +++ b/RMStore/Optional/RMStoreKeychainPersistence.h @@ -25,6 +25,10 @@ */ @interface RMStoreKeychainPersistence : NSObject +/** Returns the singleton store instance. + */ ++ (RMStoreKeychainPersistence*)defaultPersistence; + /** Remove all persisted transactions from the keychain. */ - (void)removeTransactions; diff --git a/RMStore/Optional/RMStoreKeychainPersistence.m b/RMStore/Optional/RMStoreKeychainPersistence.m index 54195b3b..26cf69ee 100644 --- a/RMStore/Optional/RMStoreKeychainPersistence.m +++ b/RMStore/Optional/RMStoreKeychainPersistence.m @@ -21,6 +21,13 @@ #import "RMStoreKeychainPersistence.h" #import +#if DEBUG +#define RMStoreKeychainPersistenceLog(...) NSLog(@"RMStoreKeychainPersistence: %@", [NSString stringWithFormat:__VA_ARGS__]); +#else +#define RMStoreKeychainPersistenceLog(...) +#endif + + NSString* const RMStoreTransactionsKeychainKey = @"RMStoreTransactions"; #pragma mark - Keychain @@ -64,7 +71,7 @@ void RMKeychainSetValue(NSData *value, NSString *key) } if (status != errSecSuccess) { - NSLog(@"RMStoreKeychainPersistence: failed to set key %@ with error %ld.", key, (long)status); + RMStoreKeychainPersistenceLog(@"failed to set key %@ with error %ld.", key, (long)status); } } @@ -78,15 +85,28 @@ void RMKeychainSetValue(NSData *value, NSString *key) OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, (CFTypeRef *)&value); if (status != errSecSuccess && status != errSecItemNotFound) { - NSLog(@"RMStoreKeychainPersistence: failed to get key %@ with error %ld.", key, (long)status); + RMStoreKeychainPersistenceLog(@"failed to get key %@ with error %ld.", key, (long)status); } return (__bridge NSData*)value; } @implementation RMStoreKeychainPersistence { +@private NSDictionary *_transactionsDictionary; } +#pragma mark - Lifecycle + ++ (RMStoreKeychainPersistence *)defaultPersistence +{ + static RMStoreKeychainPersistence *_defaultPersistence = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _defaultPersistence = [[RMStoreKeychainPersistence alloc] init]; + }); + return _defaultPersistence; +} + #pragma mark - RMStoreTransactionPersistor - (void)persistTransaction:(SKPaymentTransaction*)paymentTransaction @@ -158,7 +178,7 @@ - (NSDictionary*)transactionsDictionary transactions = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (!transactions) { - NSLog(@"RMStoreKeychainPersistence: failed to read JSON data with error %@", error); + RMStoreKeychainPersistenceLog(@"failed to read JSON data with error %@", error); } } _transactionsDictionary = transactions; @@ -177,7 +197,7 @@ - (void)setTransactionsDictionary:(NSDictionary*)dictionary data = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error]; if (!data) { - NSLog(@"RMStoreKeychainPersistence: failed to write JSON data with error %@", error); + RMStoreKeychainPersistenceLog(@"failed to write JSON data with error %@", error); } } RMKeychainSetValue(data, RMStoreTransactionsKeychainKey); diff --git a/RMStore/Optional/RMStoreTransaction.h b/RMStore/Optional/RMStoreTransaction.h index d97d1394..92f6892e 100644 --- a/RMStore/Optional/RMStoreTransaction.h +++ b/RMStore/Optional/RMStoreTransaction.h @@ -27,9 +27,6 @@ @property(nonatomic, copy) NSString *productIdentifier; @property(nonatomic, copy) NSDate *transactionDate; @property(nonatomic, copy) NSString *transactionIdentifier; -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 -@property(nonatomic, strong) NSData *transactionReceipt; -#endif - (instancetype)initWithPaymentTransaction:(SKPaymentTransaction*)paymentTransaction; diff --git a/RMStore/Optional/RMStoreTransaction.m b/RMStore/Optional/RMStoreTransaction.m index 6855809a..e22b2c98 100644 --- a/RMStore/Optional/RMStoreTransaction.m +++ b/RMStore/Optional/RMStoreTransaction.m @@ -24,9 +24,6 @@ NSString* const RMStoreCoderProductIdentifierKey = @"productIdentifier"; NSString* const RMStoreCoderTransactionDateKey = @"transactionDate"; NSString* const RMStoreCoderTransactionIdentifierKey = @"transactionIdentifier"; -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 -NSString* const RMStoreCoderTransactionReceiptKey = @"transactionReceipt"; -#endif @implementation RMStoreTransaction @@ -37,9 +34,6 @@ - (instancetype)initWithPaymentTransaction:(SKPaymentTransaction*)paymentTransac _productIdentifier = paymentTransaction.payment.productIdentifier; _transactionDate = paymentTransaction.transactionDate; _transactionIdentifier = paymentTransaction.transactionIdentifier; -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 - _transactionReceipt = paymentTransaction.transactionReceipt; -#endif } return self; } @@ -52,9 +46,6 @@ - (instancetype)initWithCoder:(NSCoder *)decoder _productIdentifier = [decoder decodeObjectForKey:RMStoreCoderProductIdentifierKey]; _transactionDate = [decoder decodeObjectForKey:RMStoreCoderTransactionDateKey]; _transactionIdentifier = [decoder decodeObjectForKey:RMStoreCoderTransactionIdentifierKey]; -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 - _transactionReceipt = [decoder decodeObjectForKey:RMStoreCoderTransactionReceiptKey]; -#endif } return self; } @@ -64,10 +55,9 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeBool:self.consumed forKey:RMStoreCoderConsumedKey]; [coder encodeObject:self.productIdentifier forKey:RMStoreCoderProductIdentifierKey]; [coder encodeObject:self.transactionDate forKey:RMStoreCoderTransactionDateKey]; - if (self.transactionIdentifier != nil) { [coder encodeObject:self.transactionIdentifier forKey:RMStoreCoderTransactionIdentifierKey]; } -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 - if (self.transactionReceipt != nil) { [coder encodeObject:self.transactionReceipt forKey:RMStoreCoderTransactionReceiptKey]; } -#endif + if (self.transactionIdentifier != nil) { + [coder encodeObject:self.transactionIdentifier forKey:RMStoreCoderTransactionIdentifierKey]; + } } @end diff --git a/RMStore/Optional/RMStoreTransactionReceiptVerifier.h b/RMStore/Optional/RMStoreTransactionReceiptVerifier.h deleted file mode 100644 index 3418ed67..00000000 --- a/RMStore/Optional/RMStoreTransactionReceiptVerifier.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// RMStoreTransactionReceiptVerifier.h -// RMStore -// -// Created by Hermes Pique on 7/31/13. -// Copyright (c) 2013 Robot Media SL (http://www.robotmedia.net) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import -#import "RMStore.h" - -__attribute__((availability(ios,deprecated=7.0))) -@interface RMStoreTransactionReceiptVerifier : NSObject - -@end diff --git a/RMStore/Optional/RMStoreTransactionReceiptVerifier.m b/RMStore/Optional/RMStoreTransactionReceiptVerifier.m deleted file mode 100644 index e7e06359..00000000 --- a/RMStore/Optional/RMStoreTransactionReceiptVerifier.m +++ /dev/null @@ -1,201 +0,0 @@ -// -// RMStoreTransactionReceiptVerifier.m -// RMStore -// -// Created by Hermes Pique on 7/31/13. -// Copyright (c) 2013 Robot Media SL (http://www.robotmedia.net) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "RMStoreTransactionReceiptVerifier.h" - -#ifdef DEBUG -#define RMStoreLog(...) NSLog(@"RMStore: %@", [NSString stringWithFormat:__VA_ARGS__]); -#else -#define RMStoreLog(...) -#endif - -@interface NSData(rm_base64) - -- (NSString *)rm_stringByBase64Encoding; - -@end - -@implementation NSData(rm_base64) - -static const char _base64EncodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -- (NSString *)rm_stringByBase64Encoding -{ // From: http://stackoverflow.com/a/4727124/143378 - const unsigned char * objRawData = self.bytes; - char * objPointer; - char * strResult; - - // Get the Raw Data length and ensure we actually have data - NSInteger intLength = self.length; - if (intLength == 0) return nil; - - // Setup the String-based Result placeholder and pointer within that placeholder - strResult = (char *)calloc((((intLength + 2) / 3) * 4) + 1, sizeof(char)); - objPointer = strResult; - - // Iterate through everything - while (intLength > 2) { // keep going until we have less than 24 bits - *objPointer++ = _base64EncodingTable[objRawData[0] >> 2]; - *objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)]; - *objPointer++ = _base64EncodingTable[((objRawData[1] & 0x0f) << 2) + (objRawData[2] >> 6)]; - *objPointer++ = _base64EncodingTable[objRawData[2] & 0x3f]; - - // we just handled 3 octets (24 bits) of data - objRawData += 3; - intLength -= 3; - } - - // now deal with the tail end of things - if (intLength != 0) { - *objPointer++ = _base64EncodingTable[objRawData[0] >> 2]; - if (intLength > 1) { - *objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)]; - *objPointer++ = _base64EncodingTable[(objRawData[1] & 0x0f) << 2]; - *objPointer++ = '='; - } else { - *objPointer++ = _base64EncodingTable[(objRawData[0] & 0x03) << 4]; - *objPointer++ = '='; - *objPointer++ = '='; - } - } - - // Terminate the string-based result - *objPointer = '\0'; - - // Create result NSString object - NSString *base64String = @(strResult); - - // Free memory - free(strResult); - - return base64String; -} - -@end - - -@implementation RMStoreTransactionReceiptVerifier - -- (void)verifyTransaction:(SKPaymentTransaction*)transaction - success:(void (^)())successBlock - failure:(void (^)(NSError *error))failureBlock -{ - NSString *receipt = [transaction.transactionReceipt rm_stringByBase64Encoding]; - if (receipt == nil) - { - if (failureBlock != nil) - { - NSError *error = [NSError errorWithDomain:RMStoreErrorDomain code:0 userInfo:nil]; - failureBlock(error); - } - return; - } - static NSString *receiptDataKey = @"receipt-data"; - NSDictionary *jsonReceipt = @{receiptDataKey : receipt}; - - NSError *error; - NSData *requestData = [NSJSONSerialization dataWithJSONObject:jsonReceipt options:0 error:&error]; - if (!requestData) - { - RMStoreLog(@"Failed to serialize receipt into JSON"); - if (failureBlock != nil) - { - failureBlock(error); - } - return; - } - - static NSString *productionURL = @"https://buy.itunes.apple.com/verifyReceipt"; - - [self verifyRequestData:requestData url:productionURL success:successBlock failure:failureBlock]; -} - -- (void)verifyRequestData:(NSData*)requestData - url:(NSString*)urlString - success:(void (^)())successBlock - failure:(void (^)(NSError *error))failureBlock -{ - NSURL *url = [NSURL URLWithString:urlString]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; - request.HTTPBody = requestData; - static NSString *requestMethod = @"POST"; - request.HTTPMethod = requestMethod; - - dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSError *error; - NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; - dispatch_async(dispatch_get_main_queue(), ^{ - if (!data) - { - RMStoreLog(@"Server Connection Failed"); - NSError *wrapperError = [NSError errorWithDomain:RMStoreErrorDomain code:RMStoreErrorCodeUnableToCompleteVerification userInfo:@{NSUnderlyingErrorKey : error, NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"Connection to Apple failed. Check the underlying error for more info.", @"RMStore", @"Error description")}]; - if (failureBlock != nil) - { - failureBlock(wrapperError); - } - return; - } - NSError *jsonError; - NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - if (!responseJSON) - { - RMStoreLog(@"Failed To Parse Server Response"); - if (failureBlock != nil) - { - failureBlock(jsonError); - } - } - - static NSString *statusKey = @"status"; - NSInteger statusCode = [responseJSON[statusKey] integerValue]; - - static NSInteger successCode = 0; - static NSInteger sandboxCode = 21007; - if (statusCode == successCode) - { - if (successBlock != nil) - { - successBlock(); - } - } - else if (statusCode == sandboxCode) - { - RMStoreLog(@"Verifying Sandbox Receipt"); - // From: https://developer.apple.com/library/ios/#technotes/tn2259/_index.html - // See also: http://stackoverflow.com/questions/9677193/ios-storekit-can-i-detect-when-im-in-the-sandbox - // Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store. - - static NSString *sandboxURL = @"https://sandbox.itunes.apple.com/verifyReceipt"; - [self verifyRequestData:requestData url:sandboxURL success:successBlock failure:failureBlock]; - } - else - { - RMStoreLog(@"Verification Failed With Code %ld", (long)statusCode); - NSError *serverError = [NSError errorWithDomain:RMStoreErrorDomain code:statusCode userInfo:nil]; - if (failureBlock != nil) - { - failureBlock(serverError); - } - } - }); - }); -} - -@end diff --git a/RMStore/RMStore.h b/RMStore/RMStore.h index 1a7a6622..7ab0f4d9 100755 --- a/RMStore/RMStore.h +++ b/RMStore/RMStore.h @@ -76,7 +76,7 @@ extern NSInteger const RMStoreErrorCodeUnableToCompleteVerification; - (void)addPayment:(NSString*)productIdentifier user:(NSString*)userIdentifier success:(void (^)(SKPaymentTransaction *transaction))successBlock - failure:(void (^)(SKPaymentTransaction *transaction, NSError *error))failureBlock __attribute__((availability(ios,introduced=7.0))); + failure:(void (^)(SKPaymentTransaction *transaction, NSError *error))failureBlock; /** Request localized information about a set of products from the Apple App Store. @param identifiers The set of product identifiers for the products you wish to retrieve information of. @@ -111,7 +111,7 @@ extern NSInteger const RMStoreErrorCodeUnableToCompleteVerification; */ - (void)restoreTransactionsOfUser:(NSString*)userIdentifier onSuccess:(void (^)(NSArray *transactions))successBlock - failure:(void (^)(NSError *error))failureBlock __attribute__((availability(ios,introduced=7.0))); + failure:(void (^)(NSError *error))failureBlock; #pragma mark Receipt ///--------------------------------------------- @@ -122,18 +122,18 @@ extern NSInteger const RMStoreErrorCodeUnableToCompleteVerification; If this method returns `nil` you should refresh the receipt by calling `refreshReceipt`. @see refreshReceipt */ -+ (NSURL*)receiptURL __attribute__((availability(ios,introduced=7.0))); ++ (NSURL*)receiptURL; /** Request to refresh the App Store receipt in case the receipt is invalid or missing. */ -- (void)refreshReceipt __attribute__((availability(ios,introduced=7.0))); +- (void)refreshReceipt; /** Request to refresh the App Store receipt in case the receipt is invalid or missing. `successBlock` will be called if the refresh receipt request is successful, `failureBlock` if it isn't. @param successBlock The block to be called if the refresh receipt request is sucessful. Can be `nil`. @param failureBlock The block to be called if the refresh receipt request fails. Can be `nil`. */ -- (void)refreshReceiptOnSuccess:(void (^)())successBlock - failure:(void (^)(NSError *error))failureBlock __attribute__((availability(ios,introduced=7.0))); +- (void)refreshReceiptOnSuccess:(void (^)(void))successBlock + failure:(void (^)(NSError *error))failureBlock; ///--------------------------------------------- /// @name Setting Delegates @@ -197,7 +197,7 @@ extern NSInteger const RMStoreErrorCodeUnableToCompleteVerification; @discussion Hosted content from Apple’s server (@c SKDownload) is handled automatically by RMStore. */ - (void)downloadContentForTransaction:(SKPaymentTransaction*)transaction - success:(void (^)())successBlock + success:(void (^)(void))successBlock progress:(void (^)(float progress))progressBlock failure:(void (^)(NSError *error))failureBlock; @@ -217,7 +217,7 @@ extern NSInteger const RMStoreErrorCodeUnableToCompleteVerification; @param failureBlock Called if the transaction failed verification. If verification could not be completed (e.g., due to connection issues), then error must be of code RMStoreErrorCodeUnableToCompleteVerification to prevent RMStore to finish the transaction. Must be called in the main queu. */ - (void)verifyTransaction:(SKPaymentTransaction*)transaction - success:(void (^)())successBlock + success:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock; @end @@ -229,36 +229,36 @@ extern NSInteger const RMStoreErrorCodeUnableToCompleteVerification; Tells the observer that a download has been canceled. @discussion Only for Apple-hosted downloads. */ -- (void)storeDownloadCanceled:(NSNotification*)notification __attribute__((availability(ios,introduced=6.0))); +- (void)storeDownloadCanceled:(NSNotification*)notification; /** Tells the observer that a download has failed. Use @c storeError to get the cause. */ -- (void)storeDownloadFailed:(NSNotification*)notification __attribute__((availability(ios,introduced=6.0))); +- (void)storeDownloadFailed:(NSNotification*)notification; /** Tells the observer that a download has finished. */ -- (void)storeDownloadFinished:(NSNotification*)notification __attribute__((availability(ios,introduced=6.0))); +- (void)storeDownloadFinished:(NSNotification*)notification; /** Tells the observer that a download has been paused. @discussion Only for Apple-hosted downloads. */ -- (void)storeDownloadPaused:(NSNotification*)notification __attribute__((availability(ios,introduced=6.0))); +- (void)storeDownloadPaused:(NSNotification*)notification; /** Tells the observer that a download has been updated. Use @c downloadProgress to get the progress. */ -- (void)storeDownloadUpdated:(NSNotification*)notification __attribute__((availability(ios,introduced=6.0))); +- (void)storeDownloadUpdated:(NSNotification*)notification; - (void)storePaymentTransactionDeferred:(NSNotification*)notification __attribute__((availability(ios,introduced=8.0))); - (void)storePaymentTransactionFailed:(NSNotification*)notification; - (void)storePaymentTransactionFinished:(NSNotification*)notification; - (void)storeProductsRequestFailed:(NSNotification*)notification; - (void)storeProductsRequestFinished:(NSNotification*)notification; -- (void)storeRefreshReceiptFailed:(NSNotification*)notification __attribute__((availability(ios,introduced=7.0))); -- (void)storeRefreshReceiptFinished:(NSNotification*)notification __attribute__((availability(ios,introduced=7.0))); +- (void)storeRefreshReceiptFailed:(NSNotification*)notification; +- (void)storeRefreshReceiptFinished:(NSNotification*)notification; - (void)storeRestoreTransactionsFailed:(NSNotification*)notification; - (void)storeRestoreTransactionsFinished:(NSNotification*)notification; @@ -291,7 +291,7 @@ extern NSInteger const RMStoreErrorCodeUnableToCompleteVerification; /** Used in @c storeDownload*:. */ -@property (nonatomic, readonly) SKDownload *rm_storeDownload __attribute__((availability(ios,introduced=6.0))); +@property (nonatomic, readonly) SKDownload *rm_storeDownload NS_AVAILABLE(10_8, 6_0); /** Used in @c storeDownloadFailed:, @c storePaymentTransactionFailed:, @c storeProductsRequestFailed:, @c storeRefreshReceiptFailed: and @c storeRestoreTransactionsFailed:. */ diff --git a/RMStore/RMStore.m b/RMStore/RMStore.m index ece43bd9..e78050a4 100755 --- a/RMStore/RMStore.m +++ b/RMStore/RMStore.m @@ -61,47 +61,47 @@ typedef void (^RMSKProductsRequestFailureBlock)(NSError *error); typedef void (^RMSKProductsRequestSuccessBlock)(NSArray *products, NSArray *invalidIdentifiers); typedef void (^RMStoreFailureBlock)(NSError *error); -typedef void (^RMStoreSuccessBlock)(); +typedef void (^RMStoreSuccessBlock)(void); @implementation NSNotification(RMStore) - (float)rm_downloadProgress { - return [self.userInfo[RMStoreNotificationDownloadProgress] floatValue]; + return [[self.userInfo objectForKey:RMStoreNotificationDownloadProgress] floatValue]; } - (NSArray*)rm_invalidProductIdentifiers { - return (self.userInfo)[RMStoreNotificationInvalidProductIdentifiers]; + return [self.userInfo objectForKey:RMStoreNotificationInvalidProductIdentifiers]; } - (NSString*)rm_productIdentifier { - return (self.userInfo)[RMStoreNotificationProductIdentifier]; + return [self.userInfo objectForKey:RMStoreNotificationProductIdentifier]; } - (NSArray*)rm_products { - return (self.userInfo)[RMStoreNotificationProducts]; + return [self.userInfo objectForKey:RMStoreNotificationProducts]; } - (SKDownload*)rm_storeDownload { - return (self.userInfo)[RMStoreNotificationStoreDownload]; + return [self.userInfo objectForKey:RMStoreNotificationStoreDownload]; } - (NSError*)rm_storeError { - return (self.userInfo)[RMStoreNotificationStoreError]; + return [self.userInfo objectForKey:RMStoreNotificationStoreError]; } - (SKPaymentTransaction*)rm_transaction { - return (self.userInfo)[RMStoreNotificationTransaction]; + return [self.userInfo objectForKey:RMStoreNotificationTransaction]; } - (NSArray*)rm_transactions { - return (self.userInfo)[RMStoreNotificationTransactions]; + return [self.userInfo objectForKey:RMStoreNotificationTransactions]; } @end @@ -129,7 +129,9 @@ @interface RMStore() @end -@implementation RMStore { +@implementation RMStore +{ +@private NSMutableDictionary *_addPaymentParameters; // HACK: We use a dictionary of product identifiers because the returned SKPayment is different from the one we add to the queue. Bad Apple. NSMutableDictionary *_products; NSMutableSet *_productsRequestDelegates; @@ -139,9 +141,9 @@ @implementation RMStore { NSInteger _pendingRestoredTransactionsCount; BOOL _restoredCompletedTransactionsFinished; - SKReceiptRefreshRequest *_refreshReceiptRequest; + SKReceiptRefreshRequest *_refreshReceiptRequest NS_AVAILABLE(10_9, 7_0); void (^_refreshReceiptFailureBlock)(NSError* error); - void (^_refreshReceiptSuccessBlock)(); + void (^_refreshReceiptSuccessBlock)(void); void (^_restoreTransactionsFailureBlock)(NSError* error); void (^_restoreTransactionsSuccessBlock)(NSArray* transactions); @@ -213,13 +215,16 @@ - (void)addPayment:(NSString*)productIdentifier SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; if ([payment respondsToSelector:@selector(setApplicationUsername:)]) { - payment.applicationUsername = userIdentifier; + if (@available(macOS 10.9, *)) + { + payment.applicationUsername = userIdentifier; + } } RMAddPaymentParameters *parameters = [[RMAddPaymentParameters alloc] init]; parameters.successBlock = successBlock; parameters.failureBlock = failureBlock; - _addPaymentParameters[productIdentifier] = parameters; + [_addPaymentParameters setObject:parameters forKey:productIdentifier]; [[SKPaymentQueue defaultQueue] addPayment:payment]; } @@ -270,7 +275,10 @@ - (void)restoreTransactionsOfUser:(NSString*)userIdentifier _pendingRestoredTransactionsCount = 0; _restoreTransactionsSuccessBlock = successBlock; _restoreTransactionsFailureBlock = failureBlock; - [[SKPaymentQueue defaultQueue] restoreCompletedTransactionsWithApplicationUsername:userIdentifier]; + if (@available(macOS 10.9, *)) + { + [[SKPaymentQueue defaultQueue] restoreCompletedTransactionsWithApplicationUsername:userIdentifier]; + } } #pragma mark Receipt @@ -278,7 +286,6 @@ - (void)restoreTransactionsOfUser:(NSString*)userIdentifier + (NSURL*)receiptURL { // The general best practice of weak linking using the respondsToSelector: method cannot be used here. Prior to iOS 7, the method was implemented as private API, but that implementation called the doesNotRecognizeSelector: method. - NSAssert(floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1, @"appStoreReceiptURL not supported in this iOS version."); NSURL *url = [NSBundle mainBundle].appStoreReceiptURL; return url; } @@ -293,7 +300,10 @@ - (void)refreshReceiptOnSuccess:(RMStoreSuccessBlock)successBlock { _refreshReceiptFailureBlock = failureBlock; _refreshReceiptSuccessBlock = successBlock; - _refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}]; + if (@available(macOS 10.9, *)) + { + _refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}]; + } _refreshReceiptRequest.delegate = self; [_refreshReceiptRequest start]; } @@ -302,7 +312,7 @@ - (void)refreshReceiptOnSuccess:(RMStoreSuccessBlock)successBlock - (SKProduct*)productForIdentifier:(NSString*)productIdentifier { - return _products[productIdentifier]; + return [_products objectForKey:productIdentifier]; } + (NSString*)localizedPriceOfProduct:(SKProduct*)product @@ -412,11 +422,15 @@ - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedW [[NSNotificationCenter defaultCenter] postNotificationName:RMSKRestoreTransactionsFailed object:self userInfo:userInfo]; } -- (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads +- (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads API_AVAILABLE(macos(10.8)) { for (SKDownload *download in downloads) { +#if TARGET_OS_IPHONE switch (download.downloadState) +#elif TARGET_OS_MAC + switch (download.state) +#endif { case SKDownloadStateActive: [self didUpdateDownload:download queue:queue]; @@ -442,79 +456,102 @@ - (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloa #pragma mark Download State -- (void)didCancelDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue +- (void)didCancelDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue API_AVAILABLE(macos(10.8)) { - SKPaymentTransaction *transaction = download.transaction; - RMStoreLog(@"download %@ for product %@ canceled", download.contentIdentifier, download.transaction.payment.productIdentifier); + if (@available(macOS 10.11, *)) + { + SKPaymentTransaction *transaction = download.transaction; + RMStoreLog(@"download %@ for product %@ canceled", download.contentIdentifier, download.transaction.payment.productIdentifier); - [self postNotificationWithName:RMSKDownloadCanceled download:download userInfoExtras:nil]; + [self postNotificationWithName:RMSKDownloadCanceled download:download userInfoExtras:nil]; - NSError *error = [NSError errorWithDomain:RMStoreErrorDomain code:RMStoreErrorCodeDownloadCanceled userInfo:@{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Download canceled", @"RMStore", @"Error description")}]; + NSError *error = [NSError errorWithDomain:RMStoreErrorDomain code:RMStoreErrorCodeDownloadCanceled userInfo:@{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Download canceled", @"RMStore", @"Error description")}]; - const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction]; - if (!hasPendingDownloads) - { - [self didFailTransaction:transaction queue:queue error:error]; + const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction]; + if (!hasPendingDownloads) + { + [self didFailTransaction:transaction queue:queue error:error]; + } } } -- (void)didFailDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue +- (void)didFailDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue API_AVAILABLE(macos(10.8)) { NSError *error = download.error; - SKPaymentTransaction *transaction = download.transaction; - RMStoreLog(@"download %@ for product %@ failed with error %@", download.contentIdentifier, transaction.payment.productIdentifier, error.debugDescription); + if (@available(macOS 10.11, *)) + { + SKPaymentTransaction *transaction = download.transaction; + RMStoreLog(@"download %@ for product %@ failed with error %@", download.contentIdentifier, transaction.payment.productIdentifier, error.debugDescription); - NSDictionary *extras = error ? @{RMStoreNotificationStoreError : error} : nil; - [self postNotificationWithName:RMSKDownloadFailed download:download userInfoExtras:extras]; + NSDictionary *extras = error ? @{RMStoreNotificationStoreError : error} : nil; + [self postNotificationWithName:RMSKDownloadFailed download:download userInfoExtras:extras]; - const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction]; - if (!hasPendingDownloads) - { - [self didFailTransaction:transaction queue:queue error:error]; + const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction]; + if (!hasPendingDownloads) + { + [self didFailTransaction:transaction queue:queue error:error]; + } } } -- (void)didFinishDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue +- (void)didFinishDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue API_AVAILABLE(macos(10.8)) { - SKPaymentTransaction *transaction = download.transaction; - RMStoreLog(@"download %@ for product %@ finished", download.contentIdentifier, transaction.payment.productIdentifier); - - [self postNotificationWithName:RMSKDownloadFinished download:download userInfoExtras:nil]; - - const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction]; - if (!hasPendingDownloads) + if (@available(macOS 10.11, *)) { - [self finishTransaction:download.transaction queue:queue]; + SKPaymentTransaction *transaction = download.transaction; + RMStoreLog(@"download %@ for product %@ finished", download.contentIdentifier, transaction.payment.productIdentifier); + + [self postNotificationWithName:RMSKDownloadFinished download:download userInfoExtras:nil]; + + const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction]; + + if (!hasPendingDownloads) + { + [self finishTransaction:download.transaction queue:queue]; + } } } -- (void)didPauseDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue +- (void)didPauseDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue API_AVAILABLE(macos(10.8)) { - RMStoreLog(@"download %@ for product %@ paused", download.contentIdentifier, download.transaction.payment.productIdentifier); + if (@available(macOS 10.11, *)) + { + RMStoreLog(@"download %@ for product %@ paused", download.contentIdentifier, download.transaction.payment.productIdentifier); + } [self postNotificationWithName:RMSKDownloadPaused download:download userInfoExtras:nil]; } -- (void)didUpdateDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue +- (void)didUpdateDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue API_AVAILABLE(macos(10.8)) { - RMStoreLog(@"download %@ for product %@ updated", download.contentIdentifier, download.transaction.payment.productIdentifier); + if (@available(macOS 10.11, *)) + { + RMStoreLog(@"download %@ for product %@ updated", download.contentIdentifier, download.transaction.payment.productIdentifier); + } NSDictionary *extras = @{RMStoreNotificationDownloadProgress : @(download.progress)}; [self postNotificationWithName:RMSKDownloadUpdated download:download userInfoExtras:extras]; } + (BOOL)hasPendingDownloadsInTransaction:(SKPaymentTransaction*)transaction { - for (SKDownload *download in transaction.downloads) + if (@available(macOS 10.8, *)) { - switch (download.downloadState) + for (SKDownload *download in transaction.downloads) { - case SKDownloadStateActive: - case SKDownloadStatePaused: - case SKDownloadStateWaiting: - return YES; - case SKDownloadStateCancelled: - case SKDownloadStateFailed: - case SKDownloadStateFinished: - continue; +#if TARGET_OS_IPHONE + switch (download.downloadState) +#elif TARGET_OS_MAC + switch (download.state) +#endif + { + case SKDownloadStateActive: + case SKDownloadStatePaused: + case SKDownloadStateWaiting: + return YES; + case SKDownloadStateCancelled: + case SKDownloadStateFailed: + case SKDownloadStateFinished: + continue; + } } } return NO; @@ -616,15 +653,18 @@ - (void)didVerifyTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymen - (void)didDownloadSelfHostedContentForTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue { - NSArray *downloads = [transaction respondsToSelector:@selector(downloads)] ? transaction.downloads : @[]; - if (downloads.count > 0) - { - RMStoreLog(@"starting downloads for product %@ started", transaction.payment.productIdentifier); - [queue startDownloads:downloads]; - } - else + if (@available(macOS 10.8, *)) { - [self finishTransaction:transaction queue:queue]; + NSArray *downloads = [transaction respondsToSelector:@selector(downloads)] ? transaction.downloads : @[]; + if (downloads.count > 0) + { + RMStoreLog(@"starting downloads for product %@ started", transaction.payment.productIdentifier); + [queue startDownloads:downloads]; + } + else + { + [self finishTransaction:transaction queue:queue]; + } } } @@ -671,7 +711,7 @@ - (void)notifyRestoreTransactionFinishedIfApplicableAfterTransaction:(SKPaymentT - (RMAddPaymentParameters*)popAddPaymentParametersForIdentifier:(NSString*)identifier { - RMAddPaymentParameters *parameters = _addPaymentParameters[identifier]; + RMAddPaymentParameters *parameters = [_addPaymentParameters objectForKey:identifier]; [_addPaymentParameters removeObjectForKey:identifier]; return parameters; } @@ -711,22 +751,25 @@ - (void)request:(SKRequest *)request didFailWithError:(NSError *)error - (void)addProduct:(SKProduct*)product { - _products[product.productIdentifier] = product; + [_products setObject:product forKey:product.productIdentifier]; } -- (void)postNotificationWithName:(NSString*)notificationName download:(SKDownload*)download userInfoExtras:(NSDictionary*)extras +- (void)postNotificationWithName:(NSString*)notificationName download:(SKDownload *)download userInfoExtras:(NSDictionary*)extras API_AVAILABLE(macos(10.8)) { NSMutableDictionary *mutableExtras = extras ? [NSMutableDictionary dictionaryWithDictionary:extras] : [NSMutableDictionary dictionary]; mutableExtras[RMStoreNotificationStoreDownload] = download; - [self postNotificationWithName:notificationName transaction:download.transaction userInfoExtras:mutableExtras]; + if (@available(macOS 10.11, *)) + { + [self postNotificationWithName:notificationName transaction:download.transaction userInfoExtras:mutableExtras]; + } } - (void)postNotificationWithName:(NSString*)notificationName transaction:(SKPaymentTransaction*)transaction userInfoExtras:(NSDictionary*)extras { NSString *productIdentifier = transaction.payment.productIdentifier; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - userInfo[RMStoreNotificationTransaction] = transaction; - userInfo[RMStoreNotificationProductIdentifier] = productIdentifier; + [userInfo setObject:transaction forKey:RMStoreNotificationTransaction]; + [userInfo setObject:productIdentifier forKey:RMStoreNotificationProductIdentifier]; if (extras) { [userInfo addEntriesFromDictionary:extras]; diff --git a/RMStoreDemo/RMAppDelegate.m b/RMStoreDemo/RMAppDelegate.m index 34bc7e55..9bfe5938 100644 --- a/RMStoreDemo/RMAppDelegate.m +++ b/RMStoreDemo/RMAppDelegate.m @@ -22,7 +22,6 @@ #import "RMStoreViewController.h" #import "RMPurchasesViewController.h" #import "RMStore.h" -#import "RMStoreTransactionReceiptVerifier.h" #import "RMStoreAppReceiptVerifier.h" #import "RMStoreKeychainPersistence.h" @@ -51,8 +50,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( - (void)configureStore { - const BOOL iOS7OrHigher = floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1; - _receiptVerifier = iOS7OrHigher ? [[RMStoreAppReceiptVerifier alloc] init] : [[RMStoreTransactionReceiptVerifier alloc] init]; + _receiptVerifier = [[RMStoreAppReceiptVerifier alloc] init]; [RMStore defaultStore].receiptVerifier = _receiptVerifier; _persistence = [[RMStoreKeychainPersistence alloc] init]; diff --git a/RMStoreDemo_OSX/AppDelegate.h b/RMStoreDemo_OSX/AppDelegate.h new file mode 100644 index 00000000..af1ddca0 --- /dev/null +++ b/RMStoreDemo_OSX/AppDelegate.h @@ -0,0 +1,15 @@ +// +// AppDelegate.h +// RMStoreDemo_OSX +// +// Created by Sergey P on 12.07.16. +// Copyright © 2016 Robot Media. All rights reserved. +// + +#import + +@interface AppDelegate : NSObject + + +@end + diff --git a/RMStoreDemo_OSX/AppDelegate.m b/RMStoreDemo_OSX/AppDelegate.m new file mode 100644 index 00000000..23e81077 --- /dev/null +++ b/RMStoreDemo_OSX/AppDelegate.m @@ -0,0 +1,34 @@ +// +// AppDelegate.m +// RMStoreDemo_OSX +// +// Created by Sergey P on 12.07.16. +// Copyright © 2016 Robot Media. All rights reserved. +// + +#import "AppDelegate.h" +#import "RMStore.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + // Insert code here to initialize your application + + NSSet *products = [NSSet setWithArray:@[@"fabulousIdol", @"rootBeer", @"rubberChicken"]]; + [[RMStore defaultStore] requestProducts:products success:^(NSArray *products, NSArray *invalidProductIdentifiers) { + NSLog(@"Products loaded"); + } failure:^(NSError *error) { + NSLog(@"Something went wrong"); + }]; +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + +@end diff --git a/RMStoreDemo_OSX/Assets.xcassets/AppIcon.appiconset/Contents.json b/RMStoreDemo_OSX/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..2db2b1c7 --- /dev/null +++ b/RMStoreDemo_OSX/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/RMStoreDemo_OSX/Base.lproj/Main.storyboard b/RMStoreDemo_OSX/Base.lproj/Main.storyboard new file mode 100644 index 00000000..2fa68bb1 --- /dev/null +++ b/RMStoreDemo_OSX/Base.lproj/Main.storyboard @@ -0,0 +1,681 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RMStoreDemo_OSX/Info.plist b/RMStoreDemo_OSX/Info.plist new file mode 100644 index 00000000..2b599d83 --- /dev/null +++ b/RMStoreDemo_OSX/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2016 Robot Media. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/RMStoreDemo_OSX/ViewController.h b/RMStoreDemo_OSX/ViewController.h new file mode 100644 index 00000000..153a10d9 --- /dev/null +++ b/RMStoreDemo_OSX/ViewController.h @@ -0,0 +1,15 @@ +// +// ViewController.h +// RMStoreDemo_OSX +// +// Created by Sergey P on 12.07.16. +// Copyright © 2016 Robot Media. All rights reserved. +// + +#import + +@interface ViewController : NSViewController + + +@end + diff --git a/RMStoreDemo_OSX/ViewController.m b/RMStoreDemo_OSX/ViewController.m new file mode 100644 index 00000000..1bc7429d --- /dev/null +++ b/RMStoreDemo_OSX/ViewController.m @@ -0,0 +1,25 @@ +// +// ViewController.m +// RMStoreDemo_OSX +// +// Created by Sergey P on 12.07.16. +// Copyright © 2016 Robot Media. All rights reserved. +// + +#import "ViewController.h" + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Do any additional setup after loading the view. +} + +- (void)setRepresentedObject:(id)representedObject { + [super setRepresentedObject:representedObject]; + + // Update the view, if already loaded. +} + +@end diff --git a/RMStoreDemo_OSX/main.m b/RMStoreDemo_OSX/main.m new file mode 100644 index 00000000..7f6c0f9e --- /dev/null +++ b/RMStoreDemo_OSX/main.m @@ -0,0 +1,13 @@ +// +// main.m +// RMStoreDemo_OSX +// +// Created by Sergey P on 12.07.16. +// Copyright © 2016 Robot Media. All rights reserved. +// + +#import + +int main(int argc, const char * argv[]) { + return NSApplicationMain(argc, argv); +} diff --git a/RMStoreTests/RMStoreTransactionReceiptVerifierTests.m b/RMStoreTests/RMStoreTransactionReceiptVerifierTests.m deleted file mode 100644 index 6c79da5e..00000000 --- a/RMStoreTests/RMStoreTransactionReceiptVerifierTests.m +++ /dev/null @@ -1,66 +0,0 @@ -// -// RMStoreTransactionReceiptVerifierTests.m -// RMStore -// -// Created by Hermes on 9/12/13. -// Copyright (c) 2013 Robot Media. All rights reserved. -// - -#import -#import "RMStoreTransactionReceiptVerifier.h" -#import - -@interface RMStoreTransactionReceiptVerifierTests : XCTestCase - -@end - -@implementation RMStoreTransactionReceiptVerifierTests { - RMStoreTransactionReceiptVerifier *_verifier; -} - -- (void)setUp -{ - _verifier = [[RMStoreTransactionReceiptVerifier alloc] init]; -} - -- (void)testVerifyTransaction_NoReceipt_Nil_Nil -{ - id transaction = [self mockPaymentTransactionWithReceipt:nil]; - [_verifier verifyTransaction:transaction success:nil failure:nil]; -} - -- (void)testVerifyTransaction_NoReceipt -{ - id transaction = [self mockPaymentTransactionWithReceipt:nil]; - [_verifier verifyTransaction:transaction success:^{ - XCTFail(@""); - } failure:^(NSError *error) { - XCTAssertNotNil(error, @""); - }]; -} - -- (void)testVerifyTransaction_Receipt -{ - NSData *receipt = [@"receipt" dataUsingEncoding:NSUTF8StringEncoding]; - id transaction = [self mockPaymentTransactionWithReceipt:receipt]; - [_verifier verifyTransaction:transaction success:^{ - XCTFail(@""); - } failure:^(NSError *error) { - }]; -} - -- (void)testVerifyTransaction_Receipt_Nil_Nil -{ - NSData *receipt = [@"receipt" dataUsingEncoding:NSUTF8StringEncoding]; - id transaction = [self mockPaymentTransactionWithReceipt:receipt]; - [_verifier verifyTransaction:transaction success:nil failure:nil]; -} - -- (id)mockPaymentTransactionWithReceipt:(NSData*)receipt -{ - id transaction = [OCMockObject mockForClass:[SKPaymentTransaction class]]; - [[[transaction stub] andReturn:receipt] transactionReceipt]; - return transaction; -} - -@end diff --git a/RMStoreTests/RMStoreTransactionTests.m b/RMStoreTests/RMStoreTransactionTests.m index ada0aaba..fc571593 100644 --- a/RMStoreTests/RMStoreTransactionTests.m +++ b/RMStoreTests/RMStoreTransactionTests.m @@ -33,9 +33,7 @@ - (void)testInitWithPaymentTransaction XCTAssertEqualObjects(transaction.productIdentifier, payment.productIdentifier, @""); XCTAssertEqualObjects(transaction.transactionDate, paymentTransaction.transactionDate, @""); XCTAssertEqualObjects(transaction.transactionIdentifier, paymentTransaction.transactionIdentifier, @""); -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 - XCTAssertEqualObjects(transaction.transactionReceipt, paymentTransaction.transactionReceipt, @""); -#endif + XCTAssertFalse(transaction.consumed, @""); } @@ -47,9 +45,7 @@ - (void)testCoding transaction.productIdentifier = @"test"; transaction.transactionDate = [NSDate date]; transaction.transactionIdentifier = @"transaction"; -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 - transaction.transactionReceipt = [NSData data]; -#endif + transaction.consumed = YES; NSMutableData *data = [[NSMutableData alloc] init]; @@ -65,9 +61,7 @@ - (void)testCoding XCTAssertEqualObjects(decodedTransaction.productIdentifier, transaction.productIdentifier, @""); XCTAssertEqualObjects(decodedTransaction.transactionDate, transaction.transactionDate, @""); XCTAssertEqualObjects(decodedTransaction.transactionIdentifier, transaction.transactionIdentifier, @""); -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 - XCTAssertEqualObjects(decodedTransaction.transactionReceipt, transaction.transactionReceipt, @""); -#endif + XCTAssertEqual(decodedTransaction.consumed, transaction.consumed, @""); } @@ -78,9 +72,7 @@ - (SKPaymentTransaction*)mockPaymentTransactionOfProductIdentifer:(NSString*)pro id transaction = [OCMockObject mockForClass:[SKPaymentTransaction class]]; [[[transaction stub] andReturn:[NSDate date]] transactionDate]; [[[transaction stub] andReturn:@"transaction"] transactionIdentifier]; -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 - [[[transaction stub] andReturn:[NSData data]] transactionReceipt]; -#endif + id payment = [OCMockObject mockForClass:[SKPayment class]]; [[[payment stub] andReturn:productIdentifier] productIdentifier]; [[[transaction stub] andReturn:payment] payment]; diff --git a/RMStoreTests/RMStoreUserDefaultsPersistenceTests.m b/RMStoreTests/RMStoreUserDefaultsPersistenceTests.m index 3176049c..032c397b 100644 --- a/RMStoreTests/RMStoreUserDefaultsPersistenceTests.m +++ b/RMStoreTests/RMStoreUserDefaultsPersistenceTests.m @@ -202,9 +202,7 @@ - (void)compareTransaction:(RMStoreTransaction*)transaction1 withTransaction:(RM XCTAssertEqualObjects(transaction1.productIdentifier, transaction2.productIdentifier, @""); XCTAssertEqualObjects(transaction1.transactionDate, transaction2.transactionDate, @""); XCTAssertEqualObjects(transaction1.transactionIdentifier, transaction2.transactionIdentifier, @""); -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 - XCTAssertEqualObjects(transaction1.transactionReceipt, transaction2.transactionReceipt, @""); -#endif + XCTAssertEqual(transaction1.consumed, transaction2.consumed, @""); } @@ -215,9 +213,7 @@ - (RMStoreTransaction*)sampleTransaction transaction.productIdentifier = @"test"; transaction.transactionDate = [NSDate date]; transaction.transactionIdentifier = @"transaction"; -#if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 - transaction.transactionReceipt = [NSData data]; -#endif + transaction.consumed = YES; return transaction; }