├── Pods ├── Target Support Files │ ├── Alamofire │ │ ├── Alamofire.modulemap │ │ ├── Alamofire-dummy.m │ │ ├── Alamofire-prefix.pch │ │ ├── Alamofire-umbrella.h │ │ ├── Alamofire.xcconfig │ │ └── Info.plist │ ├── SwiftyStoreKit │ │ ├── SwiftyStoreKit.modulemap │ │ ├── SwiftyStoreKit-dummy.m │ │ ├── SwiftyStoreKit-prefix.pch │ │ ├── SwiftyStoreKit-umbrella.h │ │ ├── SwiftyStoreKit.xcconfig │ │ └── Info.plist │ ├── Pods-AppPurchasesDemo │ │ ├── Pods-AppPurchasesDemo.modulemap │ │ ├── Pods-AppPurchasesDemo-dummy.m │ │ ├── Pods-AppPurchasesDemo-umbrella.h │ │ ├── Pods-AppPurchasesDemo.debug.xcconfig │ │ ├── Pods-AppPurchasesDemo.release.xcconfig │ │ ├── Info.plist │ │ ├── Pods-AppPurchasesDemo-acknowledgements.markdown │ │ ├── Pods-AppPurchasesDemo-acknowledgements.plist │ │ ├── Pods-AppPurchasesDemo-frameworks.sh │ │ └── Pods-AppPurchasesDemo-resources.sh │ ├── Pods-AppPurchasesDemoTests │ │ ├── Pods-AppPurchasesDemoTests-acknowledgements.markdown │ │ ├── Pods-AppPurchasesDemoTests.modulemap │ │ ├── Pods-AppPurchasesDemoTests-dummy.m │ │ ├── Pods-AppPurchasesDemoTests-umbrella.h │ │ ├── Pods-AppPurchasesDemoTests.debug.xcconfig │ │ ├── Pods-AppPurchasesDemoTests.release.xcconfig │ │ ├── Info.plist │ │ ├── Pods-AppPurchasesDemoTests-acknowledgements.plist │ │ ├── Pods-AppPurchasesDemoTests-frameworks.sh │ │ └── Pods-AppPurchasesDemoTests-resources.sh │ └── Pods-AppPurchasesDemoUITests │ │ ├── Pods-AppPurchasesDemoUITests-acknowledgements.markdown │ │ ├── Pods-AppPurchasesDemoUITests.modulemap │ │ ├── Pods-AppPurchasesDemoUITests-dummy.m │ │ ├── Pods-AppPurchasesDemoUITests-umbrella.h │ │ ├── Pods-AppPurchasesDemoUITests.debug.xcconfig │ │ ├── Pods-AppPurchasesDemoUITests.release.xcconfig │ │ ├── Info.plist │ │ ├── Pods-AppPurchasesDemoUITests-acknowledgements.plist │ │ ├── Pods-AppPurchasesDemoUITests-frameworks.sh │ │ └── Pods-AppPurchasesDemoUITests-resources.sh ├── Manifest.lock ├── SwiftyStoreKit │ ├── LICENSE.md │ └── SwiftyStoreKit │ │ ├── OS.swift │ │ ├── SKProduct+LocalizedPrice.swift │ │ ├── ProductsInfoController.swift │ │ ├── CompleteTransactionsController.swift │ │ ├── InAppProductQueryRequest.swift │ │ ├── InAppReceiptRefreshRequest.swift │ │ ├── RestorePurchasesController.swift │ │ ├── PaymentsController.swift │ │ ├── InAppReceiptVerificator.swift │ │ ├── AppleReceiptValidator.swift │ │ ├── PaymentQueueController.swift │ │ ├── InAppReceipt.swift │ │ ├── SwiftyStoreKit+Types.swift │ │ └── SwiftyStoreKit.swift ├── Alamofire │ ├── LICENSE │ └── Source │ │ ├── DispatchQueue+Alamofire.swift │ │ ├── Notifications.swift │ │ ├── Timeline.swift │ │ ├── Result.swift │ │ └── NetworkReachabilityManager.swift └── Pods.xcodeproj │ └── xcuserdata │ └── xinxibin.xcuserdatad │ └── xcschemes │ ├── xcschememanagement.plist │ ├── Alamofire.xcscheme │ ├── SwiftyStoreKit.xcscheme │ ├── Pods-AppPurchasesDemo.xcscheme │ ├── Pods-AppPurchasesDemoTests.xcscheme │ └── Pods-AppPurchasesDemoUITests.xcscheme ├── AppPurchasesDemo.xcworkspace ├── xcuserdata │ └── xinxibin.xcuserdatad │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist └── contents.xcworkspacedata ├── AppPurchasesDemo.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── xinxibin.xcuserdatad │ └── xcschemes │ ├── xcschememanagement.plist │ └── AppPurchasesDemo.xcscheme ├── Podfile.lock ├── Podfile ├── AppPurchasesDemoTests ├── Info.plist └── AppPurchasesDemoTests.swift ├── AppPurchasesDemoUITests ├── Info.plist └── AppPurchasesDemoUITests.swift ├── AppPurchasesDemo ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── AppleReceiptValidatorX.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── AppDelegate.swift └── ViewController.swift └── README.md /Pods/Target Support Files/Alamofire/Alamofire.modulemap: -------------------------------------------------------------------------------- 1 | framework module Alamofire { 2 | umbrella header "Alamofire-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Alamofire : NSObject 3 | @end 4 | @implementation PodsDummy_Alamofire 5 | @end 6 | -------------------------------------------------------------------------------- /AppPurchasesDemo.xcworkspace/xcuserdata/xinxibin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit.modulemap: -------------------------------------------------------------------------------- 1 | framework module SwiftyStoreKit { 2 | umbrella header "SwiftyStoreKit-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_SwiftyStoreKit : NSObject 3 | @end 4 | @implementation PodsDummy_SwiftyStoreKit 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Pods-AppPurchasesDemo.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_AppPurchasesDemo { 2 | umbrella header "Pods-AppPurchasesDemo-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Pods-AppPurchasesDemo-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_AppPurchasesDemo : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_AppPurchasesDemo 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Pods-AppPurchasesDemoTests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Pods-AppPurchasesDemoTests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_AppPurchasesDemoTests { 2 | umbrella header "Pods-AppPurchasesDemoTests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Pods-AppPurchasesDemoUITests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /AppPurchasesDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Pods-AppPurchasesDemoTests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_AppPurchasesDemoTests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_AppPurchasesDemoTests 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Pods-AppPurchasesDemoUITests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_AppPurchasesDemoUITests { 2 | umbrella header "Pods-AppPurchasesDemoUITests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Pods-AppPurchasesDemoUITests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_AppPurchasesDemoUITests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_AppPurchasesDemoUITests 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /AppPurchasesDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.4.0) 3 | - SwiftyStoreKit (0.10.1) 4 | 5 | DEPENDENCIES: 6 | - Alamofire 7 | - SwiftyStoreKit 8 | 9 | SPEC CHECKSUMS: 10 | Alamofire: dc44b1600b800eb63da6a19039a0083d62a6a62d 11 | SwiftyStoreKit: 1efe75032fa16140176863c080268cf0319b520d 12 | 13 | PODFILE CHECKSUM: 165e8ca6ef5dfc9dba6f505f42941656044f12ff 14 | 15 | COCOAPODS: 1.2.0 16 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.4.0) 3 | - SwiftyStoreKit (0.10.1) 4 | 5 | DEPENDENCIES: 6 | - Alamofire 7 | - SwiftyStoreKit 8 | 9 | SPEC CHECKSUMS: 10 | Alamofire: dc44b1600b800eb63da6a19039a0083d62a6a62d 11 | SwiftyStoreKit: 1efe75032fa16140176863c080268cf0319b520d 12 | 13 | PODFILE CHECKSUM: 165e8ca6ef5dfc9dba6f505f42941656044f12ff 14 | 15 | COCOAPODS: 1.2.0 16 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double AlamofireVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double SwiftyStoreKitVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char SwiftyStoreKitVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Pods-AppPurchasesDemo-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_AppPurchasesDemoVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_AppPurchasesDemoVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Pods-AppPurchasesDemoTests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_AppPurchasesDemoTestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_AppPurchasesDemoTestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Pods-AppPurchasesDemoUITests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_AppPurchasesDemoUITestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_AppPurchasesDemoUITestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'AppPurchasesDemo' do 5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 6 | use_frameworks! 7 | pod 'SwiftyStoreKit' 8 | pod 'Alamofire' 9 | 10 | target 'AppPurchasesDemoTests' do 11 | inherit! :search_paths 12 | # Pods for testing 13 | end 14 | 15 | target 'AppPurchasesDemoUITests' do 16 | inherit! :search_paths 17 | # Pods for testing 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Alamofire 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftyStoreKit 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Pods-AppPurchasesDemoTests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit/SwiftyStoreKit.framework/Headers" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT}/Pods 8 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Pods-AppPurchasesDemoTests.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit/SwiftyStoreKit.framework/Headers" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT}/Pods 8 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Pods-AppPurchasesDemoUITests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit/SwiftyStoreKit.framework/Headers" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT}/Pods 8 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Pods-AppPurchasesDemoUITests.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit/SwiftyStoreKit.framework/Headers" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT}/Pods 8 | -------------------------------------------------------------------------------- /AppPurchasesDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /AppPurchasesDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /AppPurchasesDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /AppPurchasesDemo.xcodeproj/xcuserdata/xinxibin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AppPurchasesDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 410B2AE91ED55CA200B39984 16 | 17 | primary 18 | 19 | 20 | 410B2AFD1ED55CA200B39984 21 | 22 | primary 23 | 24 | 25 | 410B2B081ED55CA200B39984 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Pods-AppPurchasesDemo.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit/SwiftyStoreKit.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "SwiftyStoreKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Pods-AppPurchasesDemo.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftyStoreKit/SwiftyStoreKit.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "SwiftyStoreKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 4.4.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SwiftyStoreKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.10.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Pods-AppPurchasesDemoTests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Pods-AppPurchasesDemoUITests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016 Andrea Bizzotto bizz84@gmail.com 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /Pods/Alamofire/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /AppPurchasesDemoTests/AppPurchasesDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppPurchasesDemoTests.swift 3 | // AppPurchasesDemoTests 4 | // 5 | // Created by Xinxibin on 2017/5/24. 6 | // Copyright © 2017年 xiaoxin. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import AppPurchasesDemo 11 | 12 | class AppPurchasesDemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /AppPurchasesDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | LSRequiresIPhoneOS 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /AppPurchasesDemoUITests/AppPurchasesDemoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppPurchasesDemoUITests.swift 3 | // AppPurchasesDemoUITests 4 | // 5 | // Created by Xinxibin on 2017/5/24. 6 | // Copyright © 2017年 xiaoxin. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class AppPurchasesDemoUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/xinxibin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Alamofire.xcscheme 8 | 9 | isShown 10 | 11 | 12 | Pods-AppPurchasesDemo.xcscheme 13 | 14 | isShown 15 | 16 | 17 | Pods-AppPurchasesDemoTests.xcscheme 18 | 19 | isShown 20 | 21 | 22 | Pods-AppPurchasesDemoUITests.xcscheme 23 | 24 | isShown 25 | 26 | 27 | SwiftyStoreKit.xcscheme 28 | 29 | isShown 30 | 31 | 32 | 33 | SuppressBuildableAutocreation 34 | 35 | 1466215692438588FFB41A87BC8C0585 36 | 37 | primary 38 | 39 | 40 | 3063FE984709D7DC0FE0EAA472F728C1 41 | 42 | primary 43 | 44 | 45 | 88E9EC28B8B46C3631E6B242B50F4442 46 | 47 | primary 48 | 49 | 50 | A2AC4E3555E4CFF66B7BFE996BCA0BDA 51 | 52 | primary 53 | 54 | 55 | ADFE927CED1A4597086FF7AC4ED36A42 56 | 57 | primary 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/OS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OS.swift 3 | // SwiftyStoreKit 4 | // 5 | // Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import StoreKit 26 | 27 | // MARK: - missing SKMutablePayment init with product on OSX 28 | #if os(OSX) 29 | extension SKMutablePayment { 30 | convenience init(product: SKProduct) { 31 | self.init() 32 | self.productIdentifier = product.productIdentifier 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/SKProduct+LocalizedPrice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKProduct+LocalizedPrice.swift 3 | // SwiftyStoreKit 4 | // 5 | // Created by Andrea Bizzotto on 19/10/2016. 6 | // Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import StoreKit 27 | 28 | public extension SKProduct { 29 | 30 | public var localizedPrice: String? { 31 | let numberFormatter = NumberFormatter() 32 | numberFormatter.locale = self.priceLocale 33 | numberFormatter.numberStyle = .currency 34 | return numberFormatter.string(from: self.price) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AppPurchasesDemo/AppleReceiptValidatorX.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleReceiptValidator.swift 3 | // AppPurchasesDemo 4 | // 5 | // Created by Xinxibin on 2017/5/25. 6 | // Copyright © 2017年 xiaoxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftyStoreKit 11 | import Alamofire 12 | 13 | class AppleReceiptValidatorX: ReceiptValidator { 14 | 15 | public enum VerifyReceiptURLType: String { 16 | // 服务器地址这里使用了 Python 建立的服务器 17 | // 线上环境 18 | // ···case productionAppSotre = "https://sandbox.itunes.apple.com/verifyReceipt" 19 | case production = "http://192.168.1.157:5000/" 20 | // 测试环境 21 | case sandbox = "https://sandbox.itunes.apple.com/verifyReceipt" 22 | } 23 | 24 | public init(service: VerifyReceiptURLType = .production) { 25 | self.service = service 26 | } 27 | 28 | private let service: VerifyReceiptURLType 29 | 30 | func validate(receipt: String, password autoRenewPassword: String?, completion: @escaping (VerifyReceiptResult) -> Void) { 31 | 32 | var parame = ["receipt-data":receipt] 33 | if let password = autoRenewPassword { 34 | parame["password"] = password 35 | } 36 | 37 | Alamofire 38 | .request(service.rawValue, method: .post, parameters: parame, headers: nil) 39 | .responseJSON(completionHandler: { (response: DataResponse) in 40 | 41 | switch response.result { 42 | case .success(let value): 43 | completion(.success(receipt: value as! ReceiptInfo)) 44 | case .failure(_): 45 | completion(.error(error: .receiptInvalid(receipt: [:], status: ReceiptStatus.none))) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /AppPurchasesDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/DispatchQueue+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Dispatch 26 | import Foundation 27 | 28 | extension DispatchQueue { 29 | static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) } 30 | static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) } 31 | static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) } 32 | static var background: DispatchQueue { return DispatchQueue.global(qos: .background) } 33 | 34 | func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) { 35 | asyncAfter(deadline: .now() + delay, execute: closure) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/xinxibin.xcuserdatad/xcschemes/Alamofire.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/xinxibin.xcuserdatad/xcschemes/SwiftyStoreKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Pods-AppPurchasesDemo-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## SwiftyStoreKit 28 | 29 | Copyright (c) 2015-2016 Andrea Bizzotto bizz84@gmail.com 30 | 31 | 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: 32 | 33 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 34 | 35 | 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. 36 | 37 | Generated by CocoaPods - https://cocoapods.org 38 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension Notification.Name { 28 | /// Used as a namespace for all `URLSessionTask` related notifications. 29 | public struct Task { 30 | /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`. 31 | public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume") 32 | 33 | /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`. 34 | public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend") 35 | 36 | /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`. 37 | public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel") 38 | 39 | /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`. 40 | public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete") 41 | } 42 | } 43 | 44 | // MARK: - 45 | 46 | extension Notification { 47 | /// Used as a namespace for all `Notification` user info dictionary keys. 48 | public struct Key { 49 | /// User info dictionary key representing the `URLSessionTask` associated with the notification. 50 | public static let Task = "org.alamofire.notification.key.task" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/xinxibin.xcuserdatad/xcschemes/Pods-AppPurchasesDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/xinxibin.xcuserdatad/xcschemes/Pods-AppPurchasesDemoTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/xinxibin.xcuserdatad/xcschemes/Pods-AppPurchasesDemoUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/ProductsInfoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductsInfoController.swift 3 | // SwiftyStoreKit 4 | // 5 | // Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | import StoreKit 27 | 28 | class ProductsInfoController: NSObject { 29 | 30 | // MARK: Private declarations 31 | 32 | // As we can have multiple inflight queries and purchases, we store them in a dictionary by product id 33 | private var inflightQueries: [Set: InAppProductQueryRequest] = [:] 34 | 35 | private(set) var products: [String: SKProduct] = [:] 36 | 37 | private func addProduct(_ product: SKProduct) { 38 | products[product.productIdentifier] = product 39 | } 40 | 41 | private func allProductsMatching(_ productIds: Set) -> Set { 42 | 43 | return Set(productIds.flatMap { self.products[$0] }) 44 | } 45 | 46 | private func requestProducts(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) { 47 | 48 | inflightQueries[productIds] = InAppProductQueryRequest.startQuery(productIds) { result in 49 | 50 | self.inflightQueries[productIds] = nil 51 | for product in result.retrievedProducts { 52 | self.addProduct(product) 53 | } 54 | completion(result) 55 | } 56 | } 57 | 58 | func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) { 59 | 60 | let products = allProductsMatching(productIds) 61 | guard products.count == productIds.count else { 62 | 63 | requestProducts(productIds, completion: completion) 64 | return 65 | } 66 | completion(RetrieveResults(retrievedProducts: products, invalidProductIDs: [], error: nil)) 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /AppPurchasesDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AppPurchasesDemo 4 | // 5 | // Created by Xinxibin on 2017/5/24. 6 | // Copyright © 2017年 xiaoxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftyStoreKit 11 | 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 20 | 21 | SwiftyStoreKit.completeTransactions(atomically: true) { purchases in 22 | 23 | for purchase in purchases { 24 | 25 | if purchase.transaction.transactionState == .purchased || purchase.transaction.transactionState == .restored { 26 | 27 | if purchase.needsFinishTransaction { 28 | // Deliver content from server, then: 29 | SwiftyStoreKit.finishTransaction(purchase.transaction) 30 | } 31 | print("purchased: \(purchase)") 32 | } 33 | } 34 | } 35 | return true 36 | } 37 | 38 | func applicationWillResignActive(_ application: UIApplication) { 39 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 40 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 41 | } 42 | 43 | func applicationDidEnterBackground(_ application: UIApplication) { 44 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 45 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 46 | } 47 | 48 | func applicationWillEnterForeground(_ application: UIApplication) { 49 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 50 | } 51 | 52 | func applicationDidBecomeActive(_ application: UIApplication) { 53 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 54 | } 55 | 56 | func applicationWillTerminate(_ application: UIApplication) { 57 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 58 | } 59 | 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /AppPurchasesDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AppPurchasesDemo 4 | // 5 | // Created by Xinxibin on 2017/5/24. 6 | // Copyright © 2017年 xiaoxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftyStoreKit 11 | 12 | 13 | class ViewController: UIViewController { 14 | 15 | @IBAction func onBuyBtnClick(_ sender: Any) { 16 | 17 | SwiftyStoreKit.purchaseProduct("商品ID", quantity: 3, atomically: true) { result in 18 | switch result { 19 | case .success(let purchase): 20 | print("Purchase Success: \(purchase.productId)") 21 | 22 | let receipt = AppleReceiptValidatorX(service: .production) 23 | let password = "公共秘钥在 itunesConnect App 内购买项目查看" 24 | SwiftyStoreKit.verifyReceipt(using: receipt, password: password, completion: { (result) in 25 | switch result { 26 | case .success(let receipt): 27 | print("receipt--->\(receipt)") 28 | break 29 | case .error(let error): 30 | print("error--->\(error)") 31 | break 32 | } 33 | }) 34 | 35 | case .error(let error): 36 | switch error.code { 37 | case .unknown: print("Unknown error. Please contact support") 38 | case .clientInvalid: print("Not allowed to make the payment") 39 | case .paymentCancelled: break 40 | case .paymentInvalid: print("The purchase identifier was invalid") 41 | case .paymentNotAllowed: print("The device is not allowed to make the payment") 42 | case .storeProductNotAvailable: print("The product is not available in the current storefront") 43 | case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed") 44 | case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network") 45 | case .cloudServiceRevoked: print("User has revoked permission to use this cloud service") 46 | } 47 | } 48 | } 49 | } 50 | override func viewDidLoad() { 51 | super.viewDidLoad() 52 | 53 | getList() 54 | } 55 | 56 | func getList() { 57 | SwiftyStoreKit.retrieveProductsInfo(["商品ID"]) { result in 58 | if let product = result.retrievedProducts.first { 59 | let priceString = product.localizedPrice! 60 | print("Product: \(product.localizedDescription), price: \(priceString)") 61 | } else if let invalidProductId = result.invalidProductIDs.first { 62 | print("Invalid product identifier: \(invalidProductId)") 63 | } else { 64 | print("Error: \(result.error)") 65 | } 66 | } 67 | } 68 | 69 | override func didReceiveMemoryWarning() { 70 | super.didReceiveMemoryWarning() 71 | // Dispose of any resources that can be recreated. 72 | } 73 | 74 | 75 | } 76 | 77 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/CompleteTransactionsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompleteTransactionsController.swift 3 | // SwiftyStoreKit 4 | // 5 | // Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | import StoreKit 27 | 28 | struct CompleteTransactions { 29 | let atomically: Bool 30 | let callback: ([Purchase]) -> Void 31 | 32 | init(atomically: Bool, callback: @escaping ([Purchase]) -> Void) { 33 | self.atomically = atomically 34 | self.callback = callback 35 | } 36 | } 37 | 38 | class CompleteTransactionsController: TransactionController { 39 | 40 | var completeTransactions: CompleteTransactions? 41 | 42 | func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] { 43 | 44 | guard let completeTransactions = completeTransactions else { 45 | print("SwiftyStoreKit.completeTransactions() should be called once when the app launches.") 46 | return transactions 47 | } 48 | 49 | var unhandledTransactions: [SKPaymentTransaction] = [] 50 | var purchases: [Purchase] = [] 51 | 52 | for transaction in transactions { 53 | 54 | let transactionState = transaction.transactionState 55 | 56 | if transactionState != .purchasing { 57 | 58 | let purchase = Purchase(productId: transaction.payment.productIdentifier, quantity: transaction.payment.quantity, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !completeTransactions.atomically) 59 | 60 | purchases.append(purchase) 61 | 62 | print("Finishing transaction for payment \"\(transaction.payment.productIdentifier)\" with state: \(transactionState.debugDescription)") 63 | 64 | if completeTransactions.atomically { 65 | paymentQueue.finishTransaction(transaction) 66 | } 67 | } else { 68 | unhandledTransactions.append(transaction) 69 | } 70 | } 71 | if purchases.count > 0 { 72 | completeTransactions.callback(purchases) 73 | } 74 | 75 | return unhandledTransactions 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/InAppProductQueryRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InAppPurchaseProductRequest.swift 3 | // SwiftyStoreKit 4 | // 5 | // Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import StoreKit 26 | 27 | class InAppProductQueryRequest: NSObject, SKProductsRequestDelegate { 28 | 29 | typealias RequestCallback = (RetrieveResults) -> Void 30 | private let callback: RequestCallback 31 | private let request: SKProductsRequest 32 | // http://stackoverflow.com/questions/24011575/what-is-the-difference-between-a-weak-reference-and-an-unowned-reference 33 | deinit { 34 | request.delegate = nil 35 | } 36 | private init(productIds: Set, callback: @escaping RequestCallback) { 37 | 38 | self.callback = callback 39 | request = SKProductsRequest(productIdentifiers: productIds) 40 | super.init() 41 | request.delegate = self 42 | } 43 | 44 | class func startQuery(_ productIds: Set, callback: @escaping RequestCallback) -> InAppProductQueryRequest { 45 | let request = InAppProductQueryRequest(productIds: productIds, callback: callback) 46 | request.start() 47 | return request 48 | } 49 | 50 | func start() { 51 | request.start() 52 | } 53 | func cancel() { 54 | request.cancel() 55 | } 56 | 57 | // MARK: SKProductsRequestDelegate 58 | func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { 59 | 60 | let retrievedProducts = Set(response.products) 61 | let invalidProductIDs = Set(response.invalidProductIdentifiers) 62 | performCallback(RetrieveResults(retrievedProducts: retrievedProducts, 63 | invalidProductIDs: invalidProductIDs, error: nil)) 64 | } 65 | 66 | func requestDidFinish(_ request: SKRequest) { 67 | 68 | } 69 | 70 | func request(_ request: SKRequest, didFailWithError error: Error) { 71 | performCallback(RetrieveResults(retrievedProducts: Set(), invalidProductIDs: Set(), error: error)) 72 | } 73 | 74 | private func performCallback(_ results: RetrieveResults) { 75 | DispatchQueue.main.async { 76 | self.callback(results) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/InAppReceiptRefreshRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InAppReceiptRefreshRequest.swift 3 | // SwiftyStoreKit 4 | // 5 | // Created by phimage on 23/12/15. 6 | // Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import StoreKit 27 | import Foundation 28 | 29 | class InAppReceiptRefreshRequest: NSObject, SKRequestDelegate { 30 | 31 | enum ResultType { 32 | case success 33 | case error(e: Error) 34 | } 35 | 36 | typealias RequestCallback = (ResultType) -> Void 37 | typealias ReceiptRefresh = (_ receiptProperties: [String : Any]?, _ callback: @escaping RequestCallback) -> InAppReceiptRefreshRequest 38 | 39 | class func refresh(_ receiptProperties: [String : Any]? = nil, callback: @escaping RequestCallback) -> InAppReceiptRefreshRequest { 40 | let request = InAppReceiptRefreshRequest(receiptProperties: receiptProperties, callback: callback) 41 | request.start() 42 | return request 43 | } 44 | 45 | let refreshReceiptRequest: SKReceiptRefreshRequest 46 | let callback: RequestCallback 47 | 48 | deinit { 49 | refreshReceiptRequest.delegate = nil 50 | } 51 | 52 | init(receiptProperties: [String : Any]? = nil, callback: @escaping RequestCallback) { 53 | self.callback = callback 54 | self.refreshReceiptRequest = SKReceiptRefreshRequest(receiptProperties: receiptProperties) 55 | super.init() 56 | self.refreshReceiptRequest.delegate = self 57 | } 58 | 59 | func start() { 60 | self.refreshReceiptRequest.start() 61 | } 62 | 63 | func requestDidFinish(_ request: SKRequest) { 64 | /*if let resoreRequest = request as? SKReceiptRefreshRequest { 65 | let receiptProperties = resoreRequest.receiptProperties ?? [:] 66 | for (k, v) in receiptProperties { 67 | print("\(k): \(v)") 68 | } 69 | }*/ 70 | performCallback(.success) 71 | } 72 | func request(_ request: SKRequest, didFailWithError error: Error) { 73 | // XXX could here check domain and error code to return typed exception 74 | performCallback(.error(e: error)) 75 | } 76 | private func performCallback(_ result: ResultType) { 77 | DispatchQueue.main.async { 78 | self.callback(result) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /AppPurchasesDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Pods-AppPurchasesDemo-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | Alamofire 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Copyright (c) 2015-2016 Andrea Bizzotto bizz84@gmail.com 47 | 48 | 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: 49 | 50 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 51 | 52 | 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. 53 | 54 | License 55 | MIT 56 | Title 57 | SwiftyStoreKit 58 | Type 59 | PSGroupSpecifier 60 | 61 | 62 | FooterText 63 | Generated by CocoaPods - https://cocoapods.org 64 | Title 65 | 66 | Type 67 | PSGroupSpecifier 68 | 69 | 70 | StringsTable 71 | Acknowledgements 72 | Title 73 | Acknowledgements 74 | 75 | 76 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Pods-AppPurchasesDemoTests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 63 | 64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 65 | code_sign_cmd="$code_sign_cmd &" 66 | fi 67 | echo "$code_sign_cmd" 68 | eval "$code_sign_cmd" 69 | fi 70 | } 71 | 72 | # Strip invalid architectures 73 | strip_invalid_archs() { 74 | binary="$1" 75 | # Get architectures for current file 76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 77 | stripped="" 78 | for arch in $archs; do 79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 80 | # Strip non-valid architectures in-place 81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 82 | stripped="$stripped $arch" 83 | fi 84 | done 85 | if [[ "$stripped" ]]; then 86 | echo "Stripped $binary of architectures:$stripped" 87 | fi 88 | } 89 | 90 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 91 | wait 92 | fi 93 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Pods-AppPurchasesDemoUITests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 63 | 64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 65 | code_sign_cmd="$code_sign_cmd &" 66 | fi 67 | echo "$code_sign_cmd" 68 | eval "$code_sign_cmd" 69 | fi 70 | } 71 | 72 | # Strip invalid architectures 73 | strip_invalid_archs() { 74 | binary="$1" 75 | # Get architectures for current file 76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 77 | stripped="" 78 | for arch in $archs; do 79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 80 | # Strip non-valid architectures in-place 81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 82 | stripped="$stripped $arch" 83 | fi 84 | done 85 | if [[ "$stripped" ]]; then 86 | echo "Stripped $binary of architectures:$stripped" 87 | fi 88 | } 89 | 90 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 91 | wait 92 | fi 93 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Pods-AppPurchasesDemo-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 63 | 64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 65 | code_sign_cmd="$code_sign_cmd &" 66 | fi 67 | echo "$code_sign_cmd" 68 | eval "$code_sign_cmd" 69 | fi 70 | } 71 | 72 | # Strip invalid architectures 73 | strip_invalid_archs() { 74 | binary="$1" 75 | # Get architectures for current file 76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 77 | stripped="" 78 | for arch in $archs; do 79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 80 | # Strip non-valid architectures in-place 81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 82 | stripped="$stripped $arch" 83 | fi 84 | done 85 | if [[ "$stripped" ]]; then 86 | echo "Stripped $binary of architectures:$stripped" 87 | fi 88 | } 89 | 90 | 91 | if [[ "$CONFIGURATION" == "Debug" ]]; then 92 | install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework" 93 | install_framework "$BUILT_PRODUCTS_DIR/SwiftyStoreKit/SwiftyStoreKit.framework" 94 | fi 95 | if [[ "$CONFIGURATION" == "Release" ]]; then 96 | install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework" 97 | install_framework "$BUILT_PRODUCTS_DIR/SwiftyStoreKit/SwiftyStoreKit.framework" 98 | fi 99 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 100 | wait 101 | fi 102 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/RestorePurchasesController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RestorePurchasesController.swift 3 | // SwiftyStoreKit 4 | // 5 | // Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | import StoreKit 27 | 28 | struct RestorePurchases { 29 | let atomically: Bool 30 | let applicationUsername: String? 31 | let callback: ([TransactionResult]) -> Void 32 | 33 | init(atomically: Bool, applicationUsername: String? = nil, callback: @escaping ([TransactionResult]) -> Void) { 34 | self.atomically = atomically 35 | self.applicationUsername = applicationUsername 36 | self.callback = callback 37 | } 38 | } 39 | 40 | class RestorePurchasesController: TransactionController { 41 | 42 | public var restorePurchases: RestorePurchases? 43 | 44 | private var restoredPurchases: [TransactionResult] = [] 45 | 46 | func processTransaction(_ transaction: SKPaymentTransaction, atomically: Bool, on paymentQueue: PaymentQueue) -> Purchase? { 47 | 48 | let transactionState = transaction.transactionState 49 | 50 | if transactionState == .restored { 51 | 52 | let transactionProductIdentifier = transaction.payment.productIdentifier 53 | 54 | let purchase = Purchase(productId: transactionProductIdentifier, quantity: transaction.payment.quantity, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !atomically) 55 | if atomically { 56 | paymentQueue.finishTransaction(transaction) 57 | } 58 | return purchase 59 | } 60 | return nil 61 | } 62 | 63 | func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] { 64 | 65 | guard let restorePurchases = restorePurchases else { 66 | return transactions 67 | } 68 | 69 | var unhandledTransactions: [SKPaymentTransaction] = [] 70 | for transaction in transactions { 71 | if let restoredPurchase = processTransaction(transaction, atomically: restorePurchases.atomically, on: paymentQueue) { 72 | restoredPurchases.append(.restored(purchase: restoredPurchase)) 73 | } else { 74 | unhandledTransactions.append(transaction) 75 | } 76 | } 77 | 78 | return unhandledTransactions 79 | } 80 | 81 | func restoreCompletedTransactionsFailed(withError error: Error) { 82 | 83 | guard let restorePurchases = restorePurchases else { 84 | print("Callback already called. Returning") 85 | return 86 | } 87 | restoredPurchases.append(.failed(error: SKError(_nsError: error as NSError))) 88 | restorePurchases.callback(restoredPurchases) 89 | 90 | // Reset state after error received 91 | restoredPurchases = [] 92 | self.restorePurchases = nil 93 | 94 | } 95 | 96 | func restoreCompletedTransactionsFinished() { 97 | 98 | guard let restorePurchases = restorePurchases else { 99 | print("Callback already called. Returning") 100 | return 101 | } 102 | restorePurchases.callback(restoredPurchases) 103 | 104 | // Reset state after error transactions finished 105 | restoredPurchases = [] 106 | self.restorePurchases = nil 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/PaymentsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PaymentsController.swift 3 | // SwiftyStoreKit 4 | // 5 | // Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | import StoreKit 27 | 28 | struct Payment: Hashable { 29 | let product: SKProduct 30 | let quantity: Int 31 | let atomically: Bool 32 | let applicationUsername: String 33 | let callback: (TransactionResult) -> Void 34 | 35 | var hashValue: Int { 36 | return product.productIdentifier.hashValue 37 | } 38 | static func == (lhs: Payment, rhs: Payment) -> Bool { 39 | return lhs.product.productIdentifier == rhs.product.productIdentifier 40 | } 41 | } 42 | 43 | class PaymentsController: TransactionController { 44 | 45 | private var payments: [Payment] = [] 46 | 47 | private func findPaymentIndex(withProductIdentifier identifier: String) -> Int? { 48 | for payment in payments where payment.product.productIdentifier == identifier { 49 | return payments.index(of: payment) 50 | } 51 | return nil 52 | } 53 | 54 | func hasPayment(_ payment: Payment) -> Bool { 55 | return findPaymentIndex(withProductIdentifier: payment.product.productIdentifier) != nil 56 | } 57 | 58 | func append(_ payment: Payment) { 59 | payments.append(payment) 60 | } 61 | 62 | func processTransaction(_ transaction: SKPaymentTransaction, on paymentQueue: PaymentQueue) -> Bool { 63 | 64 | let transactionProductIdentifier = transaction.payment.productIdentifier 65 | 66 | guard let paymentIndex = findPaymentIndex(withProductIdentifier: transactionProductIdentifier) else { 67 | 68 | return false 69 | } 70 | let payment = payments[paymentIndex] 71 | 72 | let transactionState = transaction.transactionState 73 | 74 | if transactionState == .purchased { 75 | 76 | let purchase = PurchaseDetails(productId: transactionProductIdentifier, quantity: transaction.payment.quantity, product: payment.product, transaction: transaction, needsFinishTransaction: !payment.atomically) 77 | 78 | payment.callback(.purchased(purchase: purchase)) 79 | 80 | if payment.atomically { 81 | paymentQueue.finishTransaction(transaction) 82 | } 83 | payments.remove(at: paymentIndex) 84 | return true 85 | } 86 | if transactionState == .failed { 87 | 88 | payment.callback(.failed(error: transactionError(for: transaction.error as NSError?))) 89 | 90 | paymentQueue.finishTransaction(transaction) 91 | payments.remove(at: paymentIndex) 92 | return true 93 | } 94 | 95 | if transactionState == .restored { 96 | print("Unexpected restored transaction for payment \(transactionProductIdentifier)") 97 | } 98 | return false 99 | } 100 | 101 | func transactionError(for error: NSError?) -> SKError { 102 | let message = "Unknown error" 103 | let altError = NSError(domain: SKErrorDomain, code: SKError.unknown.rawValue, userInfo: [ NSLocalizedDescriptionKey: message ]) 104 | let nsError = error ?? altError 105 | return SKError(_nsError: nsError) 106 | } 107 | 108 | func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] { 109 | 110 | return transactions.filter { !processTransaction($0, on: paymentQueue) } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /AppPurchasesDemo.xcodeproj/xcuserdata/xinxibin.xcuserdatad/xcschemes/AppPurchasesDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/InAppReceiptVerificator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InAppReceiptVerificator.swift 3 | // SwiftyStoreKit 4 | // 5 | // Created by Andrea Bizzotto on 16/05/2017. 6 | // Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import Foundation 27 | 28 | class InAppReceiptVerificator: NSObject { 29 | 30 | let appStoreReceiptURL: URL? 31 | init(appStoreReceiptURL: URL? = Bundle.main.appStoreReceiptURL) { 32 | self.appStoreReceiptURL = appStoreReceiptURL 33 | } 34 | 35 | var appStoreReceiptData: Data? { 36 | guard let receiptDataURL = appStoreReceiptURL, 37 | let data = try? Data(contentsOf: receiptDataURL) else { 38 | return nil 39 | } 40 | return data 41 | } 42 | 43 | private var receiptRefreshRequest: InAppReceiptRefreshRequest? 44 | 45 | /** 46 | * Verify application receipt. This method does two things: 47 | * * If the receipt is missing, refresh it 48 | * * If the receipt is available or is refreshed, validate it 49 | * - Parameter validator: Validator to check the encrypted receipt and return the receipt in readable format 50 | * - Parameter password: Your app’s shared secret (a hexadecimal string). Only used for receipts that contain auto-renewable subscriptions. 51 | * - Parameter refresh: closure to perform receipt refresh (this is made explicit for testability) 52 | * - Parameter completion: handler for result 53 | */ 54 | public func verifyReceipt(using validator: ReceiptValidator, 55 | password: String? = nil, 56 | refresh: InAppReceiptRefreshRequest.ReceiptRefresh = InAppReceiptRefreshRequest.refresh, 57 | completion: @escaping (VerifyReceiptResult) -> Void) { 58 | 59 | if let receiptData = appStoreReceiptData { 60 | 61 | verify(receiptData: receiptData, using: validator, password: password, completion: completion) 62 | } else { 63 | 64 | receiptRefreshRequest = refresh(nil) { result in 65 | 66 | self.receiptRefreshRequest = nil 67 | 68 | switch result { 69 | case .success: 70 | if let receiptData = self.appStoreReceiptData { 71 | self.verify(receiptData: receiptData, using: validator, password: password, completion: completion) 72 | } else { 73 | completion(.error(error: .noReceiptData)) 74 | } 75 | case .error(let e): 76 | completion(.error(error: .networkError(error: e))) 77 | } 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * - Parameter receiptData: encrypted receipt data 84 | * - Parameter validator: Validator to check the encrypted receipt and return the receipt in readable format 85 | * - Parameter password: Your app’s shared secret (a hexadecimal string). Only used for receipts that contain auto-renewable subscriptions. 86 | * - Parameter completion: handler for result 87 | */ 88 | private func verify(receiptData: Data, using validator: ReceiptValidator, password: String? = nil, completion: @escaping (VerifyReceiptResult) -> Void) { 89 | 90 | // The base64 encoded receipt data. 91 | let base64EncodedString = receiptData.base64EncodedString(options: []) 92 | 93 | validator.validate(receipt: base64EncodedString, password: password) { result in 94 | 95 | DispatchQueue.main.async { 96 | completion(result) 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/AppleReceiptValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InAppReceipt.swift 3 | // SwiftyStoreKit 4 | // 5 | // Created by phimage on 22/12/15. 6 | // Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import Foundation 27 | 28 | // https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html 29 | 30 | public struct AppleReceiptValidator: ReceiptValidator { 31 | 32 | public enum VerifyReceiptURLType: String { 33 | case production = "https://buy.itunes.apple.com/verifyReceipt" 34 | case sandbox = "https://sandbox.itunes.apple.com/verifyReceipt" 35 | } 36 | 37 | public init(service: VerifyReceiptURLType = .production) { 38 | self.service = service 39 | } 40 | 41 | private let service: VerifyReceiptURLType 42 | 43 | public func validate( 44 | receipt: String, 45 | password autoRenewPassword: String? = nil, 46 | completion: @escaping (VerifyReceiptResult) -> Void) { 47 | 48 | let storeURL = URL(string: service.rawValue)! // safe (until no more) 49 | let storeRequest = NSMutableURLRequest(url: storeURL) 50 | storeRequest.httpMethod = "POST" 51 | 52 | let requestContents: NSMutableDictionary = [ "receipt-data": receipt ] 53 | // password if defined 54 | if let password = autoRenewPassword { 55 | requestContents.setValue(password, forKey: "password") 56 | } 57 | 58 | // Encore request body 59 | do { 60 | storeRequest.httpBody = try JSONSerialization.data(withJSONObject: requestContents, options: []) 61 | } catch let e { 62 | completion(.error(error: .requestBodyEncodeError(error: e))) 63 | return 64 | } 65 | 66 | // Remote task 67 | let task = URLSession.shared.dataTask(with: storeRequest as URLRequest) { data, _, error -> Void in 68 | 69 | // there is an error 70 | if let networkError = error { 71 | completion(.error(error: .networkError(error: networkError))) 72 | return 73 | } 74 | 75 | // there is no data 76 | guard let safeData = data else { 77 | completion(.error(error: .noRemoteData)) 78 | return 79 | } 80 | 81 | // cannot decode data 82 | guard let receiptInfo = try? JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? ReceiptInfo ?? [:] else { 83 | let jsonStr = String(data: safeData, encoding: String.Encoding.utf8) 84 | completion(.error(error: .jsonDecodeError(string: jsonStr))) 85 | return 86 | } 87 | 88 | // get status from info 89 | if let status = receiptInfo["status"] as? Int { 90 | /* 91 | * http://stackoverflow.com/questions/16187231/how-do-i-know-if-an-in-app-purchase-receipt-comes-from-the-sandbox 92 | * How do I verify my receipt (iOS)? 93 | * Always verify your receipt first with the production URL; proceed to verify 94 | * with the sandbox URL if you receive a 21007 status code. Following this 95 | * approach ensures that you do not have to switch between URLs while your 96 | * application is being tested or reviewed in the sandbox or is live in the 97 | * App Store. 98 | 99 | * Note: The 21007 status code indicates that this receipt is a sandbox receipt, 100 | * but it was sent to the production service for verification. 101 | */ 102 | let receiptStatus = ReceiptStatus(rawValue: status) ?? ReceiptStatus.unknown 103 | if case .testReceipt = receiptStatus { 104 | let sandboxValidator = AppleReceiptValidator(service: .sandbox) 105 | sandboxValidator.validate(receipt: receipt, password: autoRenewPassword, completion: completion) 106 | } else { 107 | if receiptStatus.isValid { 108 | completion(.success(receipt: receiptInfo)) 109 | } else { 110 | completion(.error(error: .receiptInvalid(receipt: receiptInfo, status: receiptStatus))) 111 | } 112 | } 113 | } else { 114 | completion(.error(error: .receiptInvalid(receipt: receiptInfo, status: ReceiptStatus.none))) 115 | } 116 | } 117 | task.resume() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemo/Pods-AppPurchasesDemo-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | 3) 22 | TARGET_DEVICE_ARGS="--target-device tv" 23 | ;; 24 | *) 25 | TARGET_DEVICE_ARGS="--target-device mac" 26 | ;; 27 | esac 28 | 29 | install_resource() 30 | { 31 | if [[ "$1" = /* ]] ; then 32 | RESOURCE_PATH="$1" 33 | else 34 | RESOURCE_PATH="${PODS_ROOT}/$1" 35 | fi 36 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 37 | cat << EOM 38 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 39 | EOM 40 | exit 1 41 | fi 42 | case $RESOURCE_PATH in 43 | *.storyboard) 44 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 45 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 46 | ;; 47 | *.xib) 48 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 49 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 50 | ;; 51 | *.framework) 52 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 53 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 54 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 55 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | ;; 57 | *.xcdatamodel) 58 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 59 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 60 | ;; 61 | *.xcdatamodeld) 62 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 63 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 64 | ;; 65 | *.xcmappingmodel) 66 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 67 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 68 | ;; 69 | *.xcassets) 70 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 71 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 72 | ;; 73 | *) 74 | echo "$RESOURCE_PATH" 75 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 76 | ;; 77 | esac 78 | } 79 | 80 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 81 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 82 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 83 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | fi 86 | rm -f "$RESOURCES_TO_COPY" 87 | 88 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 89 | then 90 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 91 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 92 | while read line; do 93 | if [[ $line != "${PODS_ROOT}*" ]]; then 94 | XCASSET_FILES+=("$line") 95 | fi 96 | done <<<"$OTHER_XCASSETS" 97 | 98 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 99 | fi 100 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoTests/Pods-AppPurchasesDemoTests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | 3) 22 | TARGET_DEVICE_ARGS="--target-device tv" 23 | ;; 24 | *) 25 | TARGET_DEVICE_ARGS="--target-device mac" 26 | ;; 27 | esac 28 | 29 | install_resource() 30 | { 31 | if [[ "$1" = /* ]] ; then 32 | RESOURCE_PATH="$1" 33 | else 34 | RESOURCE_PATH="${PODS_ROOT}/$1" 35 | fi 36 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 37 | cat << EOM 38 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 39 | EOM 40 | exit 1 41 | fi 42 | case $RESOURCE_PATH in 43 | *.storyboard) 44 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 45 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 46 | ;; 47 | *.xib) 48 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 49 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 50 | ;; 51 | *.framework) 52 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 53 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 54 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 55 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | ;; 57 | *.xcdatamodel) 58 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 59 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 60 | ;; 61 | *.xcdatamodeld) 62 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 63 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 64 | ;; 65 | *.xcmappingmodel) 66 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 67 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 68 | ;; 69 | *.xcassets) 70 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 71 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 72 | ;; 73 | *) 74 | echo "$RESOURCE_PATH" 75 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 76 | ;; 77 | esac 78 | } 79 | 80 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 81 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 82 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 83 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | fi 86 | rm -f "$RESOURCES_TO_COPY" 87 | 88 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 89 | then 90 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 91 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 92 | while read line; do 93 | if [[ $line != "${PODS_ROOT}*" ]]; then 94 | XCASSET_FILES+=("$line") 95 | fi 96 | done <<<"$OTHER_XCASSETS" 97 | 98 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 99 | fi 100 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-AppPurchasesDemoUITests/Pods-AppPurchasesDemoUITests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | 3) 22 | TARGET_DEVICE_ARGS="--target-device tv" 23 | ;; 24 | *) 25 | TARGET_DEVICE_ARGS="--target-device mac" 26 | ;; 27 | esac 28 | 29 | install_resource() 30 | { 31 | if [[ "$1" = /* ]] ; then 32 | RESOURCE_PATH="$1" 33 | else 34 | RESOURCE_PATH="${PODS_ROOT}/$1" 35 | fi 36 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 37 | cat << EOM 38 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 39 | EOM 40 | exit 1 41 | fi 42 | case $RESOURCE_PATH in 43 | *.storyboard) 44 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 45 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 46 | ;; 47 | *.xib) 48 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 49 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 50 | ;; 51 | *.framework) 52 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 53 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 54 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 55 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | ;; 57 | *.xcdatamodel) 58 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 59 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 60 | ;; 61 | *.xcdatamodeld) 62 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 63 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 64 | ;; 65 | *.xcmappingmodel) 66 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 67 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 68 | ;; 69 | *.xcassets) 70 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 71 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 72 | ;; 73 | *) 74 | echo "$RESOURCE_PATH" 75 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 76 | ;; 77 | esac 78 | } 79 | 80 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 81 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 82 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 83 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | fi 86 | rm -f "$RESOURCES_TO_COPY" 87 | 88 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 89 | then 90 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 91 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 92 | while read line; do 93 | if [[ $line != "${PODS_ROOT}*" ]]; then 94 | XCASSET_FILES+=("$line") 95 | fi 96 | done <<<"$OTHER_XCASSETS" 97 | 98 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 99 | fi 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## iOS 开发内购实现 2 | 3 | ## 代码环境 4 | - iOS IAP by Swift 3.1.1 5 | - Xcode 8.3.2 Really Really Easy to use 6 | 7 | ## 需要的轮子 8 | - [SwiftyStoreKit](https://github.com/bizz84/SwiftyStoreKit) 9 | - [Alamofire](https://github.com/Alamofire/Alamofire) 10 | 11 | ## 开始使用 (ItunesConnect) 12 | 13 | [Github Demo 地址](https://github.com/xinxibin/Swift-IAP-Use) 14 | 15 | 16 | ### 你需要有一个 App ( 肯定要有 付费的开发者账号 ) 17 | 18 | - 在 itunesconnect.apple.com,中配置内购项目,如图右侧有一个(查看公共秘钥)(验证购买时需要使用) 19 | ![](http://oahmyhzk1.bkt.clouddn.com/image/jpg14956962545142.jpg) 20 | - 点击加号新建购买项目 21 | ![](http://oahmyhzk1.bkt.clouddn.com/image/png4CB8F5CE-A1A5-4036-83DA-7ADEB3F2336E.png) 22 | 23 | - 根据你们产品的不同选择对应的项目 24 | - 创建就很简单了,每一项都有介绍这里就不多说了 25 | - 创建沙箱技术测试员用于内购测试使用 26 | ![](http://oahmyhzk1.bkt.clouddn.com/image/png08C982C8-C3C9-4405-8014-61947C73F3DD.png) 27 | - 内容可以随便填写,需要注意的是 邮箱 和 密码需要记住(后面需要使用) 28 | ![](http://oahmyhzk1.bkt.clouddn.com/image/png66BDEA86-FCE4-4510-BFBA-BF99A35F7206.png) 29 | 30 | 31 | ### 使用此 App 的bundleID 唯一标示 32 | - 创建一个项目,项目的 bundleID 要与 iTunesconnect 中项目的id相同。 33 | 34 | ### Cocoapods 导入 SwiftyStoreKit 35 | 36 | - pod 'SwiftyStoreKit' (内购轮子) 37 | - pod 'Alamofire' (网络请求轮子) 38 | 39 | ## 一切准备就绪-下面代码部分 40 | - AppDelegate 添加以下代码,在启动时添加应用程序的观察者可确保在应用程序的所有启动过程中都会持续,从而允许您的应用程序接收所有支付队列通知。如果此时有任何待处理的事务,将触发block,以便可以更新应用程序状态和UI。如果没有待处理的事务,则不会调用。 41 | 42 | ```swift 43 | import SwiftyStoreKit 44 | 45 | 46 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 47 | 48 | SwiftyStoreKit.completeTransactions(atomically: true) { purchases in 49 | 50 | for purchase in purchases { 51 | 52 | if purchase.transaction.transactionState == .purchased || purchase.transaction.transactionState == .restored { 53 | 54 | if purchase.needsFinishTransaction { 55 | // Deliver content from server, then: 56 | SwiftyStoreKit.finishTransaction(purchase.transaction) 57 | } 58 | print("purchased: \(purchase)") 59 | } 60 | } 61 | } 62 | return true 63 | } 64 | ``` 65 | 66 | - 获取内购项目列表 67 | 68 | ```swift 69 | func getList() { 70 | SwiftyStoreKit.retrieveProductsInfo(["图1 内购项目的 产品ID 这个一般存储在服务器里"]) { result in 71 | if let product = result.retrievedProducts.first { 72 | let priceString = product.localizedPrice! 73 | print("Product: \(product.localizedDescription), price: \(priceString)") 74 | } else if let invalidProductId = result.invalidProductIDs.first { 75 | print("Invalid product identifier: \(invalidProductId)") 76 | } else { 77 | print("Error: \(result.error)") 78 | } 79 | } 80 | } 81 | 82 | ``` 83 | - 这里是我的列表,因为就创建一个内购项目所以就一个 84 | ![](http://oahmyhzk1.bkt.clouddn.com/image/pngC65895BF-CF49-47B1-87DD-27BDB7609FED.png) 85 | 86 | - 购买 需要使用刚你在沙箱测试添加的邮箱密码登录(退出AppStore账号),购买的时候会提示你输入账号密码,此账号非appid账号,不能登录在appstore 走成功就说明购买成功了,简单点就是扣钱了,这时候是没有验证处理的。 87 | 88 | ```swift 89 | SwiftyStoreKit.purchaseProduct("产品ID", quantity: 1, atomically: true) { result in 90 | switch result { 91 | case .success(let purchase): 92 | print("Purchase Success: \(purchase.productId)") 93 | case .error(let error): 94 | switch error.code { 95 | case .unknown: print("Unknown error. Please contact support") 96 | case .clientInvalid: print("Not allowed to make the payment") 97 | case .paymentCancelled: break 98 | case .paymentInvalid: print("The purchase identifier was invalid") 99 | case .paymentNotAllowed: print("The device is not allowed to make the payment") 100 | case .storeProductNotAvailable: print("The product is not available in the current storefront") 101 | case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed") 102 | case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network") 103 | case .cloudServiceRevoked: print("User has revoked permission to use this cloud service") 104 | } 105 | } 106 | } 107 | ``` 108 | - 验证购买, 109 | * 本地验证 (不推荐,越狱设备可能存在刷单漏洞) 110 | * 服务端验证 (推荐使用) 111 | 112 | ```swift 113 | // 本地验证(SwiftyStoreKit 已经写好的类) AppleReceiptValidator 114 | // .production 苹果验证 .sandbox 本地验证 115 | let receipt = AppleReceiptValidator(service: .production) 116 | let password = "公共秘钥" 117 | SwiftyStoreKit.verifyReceipt(using: receipt, password: password, completion: { (result) in 118 | switch result { 119 | case .success(let receipt): 120 | print("receipt--->\(receipt)") 121 | break 122 | case .error(let error): 123 | print("error--->\(error)") 124 | break 125 | } 126 | }) 127 | ``` 128 | 129 | - 服务器验证 AppleReceiptValidatorX 是我重写的类,里面就是把得到的data发给服务器让服务器来验证,返回成功失败即可不需要其他数据。 130 | 131 | 132 | ## 完成了,是不是很简单,是不是很好理解。 133 | 134 | - SwiftyStoreKit 不知能做购买,还能恢复购买,具体使用方法见 [SwiftyStoreKit](https://github.com/bizz84/SwiftyStoreKit) 135 | 136 | 137 | ## 基本阅读 >SwiftyStoreKit 138 | 139 | * [Apple - WWDC16, Session 702: Using Store Kit for In-app Purchases with Swift 3](https://developer.apple.com/videos/play/wwdc2016/702/) 140 | * [Apple - TN2387: In-App Purchase Best Practices](https://developer.apple.com/library/content/technotes/tn2387/_index.html) 141 | * [Apple - About Receipt Validation](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html) 142 | * [Apple - Receipt Validation Programming Guide](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1) 143 | * [Apple - Validating Receipts Locally](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html) 144 | * [Apple - Working with Subscriptions](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html#//apple_ref/doc/uid/TP40008267-CH7-SW6) 145 | * [Apple - Offering Subscriptions](https://developer.apple.com/app-store/subscriptions/) 146 | * [Apple - Restoring Purchased Products](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Restoring.html#//apple_ref/doc/uid/TP40008267-CH8-SW9) 147 | * [objc.io - Receipt Validation](https://www.objc.io/issues/17-security/receipt-validation/) 148 | * [Apple TN 2413 - Why are my product identifiers being returned in the invalidProductIdentifiers array?](https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-TROUBLESHOOTING-WHY_ARE_MY_PRODUCT_IDENTIFIERS_BEING_RETURNED_IN_THE_INVALIDPRODUCTIDENTIFIERS_ARRAY_) 149 | * [Invalid Product IDs](http://troybrant.net/blog/2010/01/invalid-product-ids/): Checklist of common mistakes 150 | 151 | ## 延伸阅读 152 | 153 | [App 内购验证](http://www.cnblogs.com/zhaoqingqing/p/4597794.html) 154 | 155 | [官方文档] 156 | (https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html) 157 | 158 | ## 联系我 159 | 160 | - 我的邮箱 foryoudecode@gmail.com 161 | 162 | [我的博客](https://xinxibin.com) 163 | 164 | ## License 165 | AppPurchasesDemo is released under the MIT license. See LICENSE for details. 166 | 167 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Timeline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Timeline.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Responsible for computing the timing metrics for the complete lifecycle of a `Request`. 28 | public struct Timeline { 29 | /// The time the request was initialized. 30 | public let requestStartTime: CFAbsoluteTime 31 | 32 | /// The time the first bytes were received from or sent to the server. 33 | public let initialResponseTime: CFAbsoluteTime 34 | 35 | /// The time when the request was completed. 36 | public let requestCompletedTime: CFAbsoluteTime 37 | 38 | /// The time when the response serialization was completed. 39 | public let serializationCompletedTime: CFAbsoluteTime 40 | 41 | /// The time interval in seconds from the time the request started to the initial response from the server. 42 | public let latency: TimeInterval 43 | 44 | /// The time interval in seconds from the time the request started to the time the request completed. 45 | public let requestDuration: TimeInterval 46 | 47 | /// The time interval in seconds from the time the request completed to the time response serialization completed. 48 | public let serializationDuration: TimeInterval 49 | 50 | /// The time interval in seconds from the time the request started to the time response serialization completed. 51 | public let totalDuration: TimeInterval 52 | 53 | /// Creates a new `Timeline` instance with the specified request times. 54 | /// 55 | /// - parameter requestStartTime: The time the request was initialized. Defaults to `0.0`. 56 | /// - parameter initialResponseTime: The time the first bytes were received from or sent to the server. 57 | /// Defaults to `0.0`. 58 | /// - parameter requestCompletedTime: The time when the request was completed. Defaults to `0.0`. 59 | /// - parameter serializationCompletedTime: The time when the response serialization was completed. Defaults 60 | /// to `0.0`. 61 | /// 62 | /// - returns: The new `Timeline` instance. 63 | public init( 64 | requestStartTime: CFAbsoluteTime = 0.0, 65 | initialResponseTime: CFAbsoluteTime = 0.0, 66 | requestCompletedTime: CFAbsoluteTime = 0.0, 67 | serializationCompletedTime: CFAbsoluteTime = 0.0) 68 | { 69 | self.requestStartTime = requestStartTime 70 | self.initialResponseTime = initialResponseTime 71 | self.requestCompletedTime = requestCompletedTime 72 | self.serializationCompletedTime = serializationCompletedTime 73 | 74 | self.latency = initialResponseTime - requestStartTime 75 | self.requestDuration = requestCompletedTime - requestStartTime 76 | self.serializationDuration = serializationCompletedTime - requestCompletedTime 77 | self.totalDuration = serializationCompletedTime - requestStartTime 78 | } 79 | } 80 | 81 | // MARK: - CustomStringConvertible 82 | 83 | extension Timeline: CustomStringConvertible { 84 | /// The textual representation used when written to an output stream, which includes the latency, the request 85 | /// duration and the total duration. 86 | public var description: String { 87 | let latency = String(format: "%.3f", self.latency) 88 | let requestDuration = String(format: "%.3f", self.requestDuration) 89 | let serializationDuration = String(format: "%.3f", self.serializationDuration) 90 | let totalDuration = String(format: "%.3f", self.totalDuration) 91 | 92 | // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is 93 | // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. 94 | let timings = [ 95 | "\"Latency\": " + latency + " secs", 96 | "\"Request Duration\": " + requestDuration + " secs", 97 | "\"Serialization Duration\": " + serializationDuration + " secs", 98 | "\"Total Duration\": " + totalDuration + " secs" 99 | ] 100 | 101 | return "Timeline: { " + timings.joined(separator: ", ") + " }" 102 | } 103 | } 104 | 105 | // MARK: - CustomDebugStringConvertible 106 | 107 | extension Timeline: CustomDebugStringConvertible { 108 | /// The textual representation used when written to an output stream, which includes the request start time, the 109 | /// initial response time, the request completed time, the serialization completed time, the latency, the request 110 | /// duration and the total duration. 111 | public var debugDescription: String { 112 | let requestStartTime = String(format: "%.3f", self.requestStartTime) 113 | let initialResponseTime = String(format: "%.3f", self.initialResponseTime) 114 | let requestCompletedTime = String(format: "%.3f", self.requestCompletedTime) 115 | let serializationCompletedTime = String(format: "%.3f", self.serializationCompletedTime) 116 | let latency = String(format: "%.3f", self.latency) 117 | let requestDuration = String(format: "%.3f", self.requestDuration) 118 | let serializationDuration = String(format: "%.3f", self.serializationDuration) 119 | let totalDuration = String(format: "%.3f", self.totalDuration) 120 | 121 | // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is 122 | // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. 123 | let timings = [ 124 | "\"Request Start Time\": " + requestStartTime, 125 | "\"Initial Response Time\": " + initialResponseTime, 126 | "\"Request Completed Time\": " + requestCompletedTime, 127 | "\"Serialization Completed Time\": " + serializationCompletedTime, 128 | "\"Latency\": " + latency + " secs", 129 | "\"Request Duration\": " + requestDuration + " secs", 130 | "\"Serialization Duration\": " + serializationDuration + " secs", 131 | "\"Total Duration\": " + totalDuration + " secs" 132 | ] 133 | 134 | return "Timeline: { " + timings.joined(separator: ", ") + " }" 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Used to represent whether a request was successful or encountered an error. 28 | /// 29 | /// - success: The request and all post processing operations were successful resulting in the serialization of the 30 | /// provided associated value. 31 | /// 32 | /// - failure: The request encountered an error resulting in a failure. The associated values are the original data 33 | /// provided by the server as well as the error that caused the failure. 34 | public enum Result { 35 | case success(Value) 36 | case failure(Error) 37 | 38 | /// Returns `true` if the result is a success, `false` otherwise. 39 | public var isSuccess: Bool { 40 | switch self { 41 | case .success: 42 | return true 43 | case .failure: 44 | return false 45 | } 46 | } 47 | 48 | /// Returns `true` if the result is a failure, `false` otherwise. 49 | public var isFailure: Bool { 50 | return !isSuccess 51 | } 52 | 53 | /// Returns the associated value if the result is a success, `nil` otherwise. 54 | public var value: Value? { 55 | switch self { 56 | case .success(let value): 57 | return value 58 | case .failure: 59 | return nil 60 | } 61 | } 62 | 63 | /// Returns the associated error value if the result is a failure, `nil` otherwise. 64 | public var error: Error? { 65 | switch self { 66 | case .success: 67 | return nil 68 | case .failure(let error): 69 | return error 70 | } 71 | } 72 | } 73 | 74 | // MARK: - CustomStringConvertible 75 | 76 | extension Result: CustomStringConvertible { 77 | /// The textual representation used when written to an output stream, which includes whether the result was a 78 | /// success or failure. 79 | public var description: String { 80 | switch self { 81 | case .success: 82 | return "SUCCESS" 83 | case .failure: 84 | return "FAILURE" 85 | } 86 | } 87 | } 88 | 89 | // MARK: - CustomDebugStringConvertible 90 | 91 | extension Result: CustomDebugStringConvertible { 92 | /// The debug textual representation used when written to an output stream, which includes whether the result was a 93 | /// success or failure in addition to the value or error. 94 | public var debugDescription: String { 95 | switch self { 96 | case .success(let value): 97 | return "SUCCESS: \(value)" 98 | case .failure(let error): 99 | return "FAILURE: \(error)" 100 | } 101 | } 102 | } 103 | 104 | // MARK: - Functional APIs 105 | 106 | extension Result { 107 | /// Creates a `Result` instance from the result of a closure. 108 | /// 109 | /// A failure result is created when the closure throws, and a success result is created when the closure 110 | /// succeeds without throwing an error. 111 | /// 112 | /// func someString() throws -> String { ... } 113 | /// 114 | /// let result = Result(value: { 115 | /// return try someString() 116 | /// }) 117 | /// 118 | /// // The type of result is Result 119 | /// 120 | /// The trailing closure syntax is also supported: 121 | /// 122 | /// let result = Result { try someString() } 123 | /// 124 | /// - parameter value: The closure to execute and create the result for. 125 | public init(value: () throws -> Value) { 126 | do { 127 | self = try .success(value()) 128 | } catch { 129 | self = .failure(error) 130 | } 131 | } 132 | 133 | /// Returns the success value, or throws the failure error. 134 | /// 135 | /// let possibleString: Result = .success("success") 136 | /// try print(possibleString.unwrap()) 137 | /// // Prints "success" 138 | /// 139 | /// let noString: Result = .failure(error) 140 | /// try print(noString.unwrap()) 141 | /// // Throws error 142 | public func unwrap() throws -> Value { 143 | switch self { 144 | case .success(let value): 145 | return value 146 | case .failure(let error): 147 | throw error 148 | } 149 | } 150 | 151 | /// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter. 152 | /// 153 | /// Use the `map` method with a closure that does not throw. For example: 154 | /// 155 | /// let possibleData: Result = .success(Data()) 156 | /// let possibleInt = possibleData.map { $0.count } 157 | /// try print(possibleInt.unwrap()) 158 | /// // Prints "0" 159 | /// 160 | /// let noData: Result = .failure(error) 161 | /// let noInt = noData.map { $0.count } 162 | /// try print(noInt.unwrap()) 163 | /// // Throws error 164 | /// 165 | /// - parameter transform: A closure that takes the success value of the result instance. 166 | /// 167 | /// - returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the 168 | /// same failure. 169 | public func map(_ transform: (Value) -> T) -> Result { 170 | switch self { 171 | case .success(let value): 172 | return .success(transform(value)) 173 | case .failure(let error): 174 | return .failure(error) 175 | } 176 | } 177 | 178 | /// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter. 179 | /// 180 | /// Use the `flatMap` method with a closure that may throw an error. For example: 181 | /// 182 | /// let possibleData: Result = .success(Data(...)) 183 | /// let possibleObject = possibleData.flatMap { 184 | /// try JSONSerialization.jsonObject(with: $0) 185 | /// } 186 | /// 187 | /// - parameter transform: A closure that takes the success value of the instance. 188 | /// 189 | /// - returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the 190 | /// same failure. 191 | public func flatMap(_ transform: (Value) throws -> T) -> Result { 192 | switch self { 193 | case .success(let value): 194 | do { 195 | return try .success(transform(value)) 196 | } catch { 197 | return .failure(error) 198 | } 199 | case .failure(let error): 200 | return .failure(error) 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/PaymentQueueController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PaymentQueueController.swift 3 | // SwiftyStoreKit 4 | // 5 | // Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | import StoreKit 27 | 28 | protocol TransactionController { 29 | 30 | /** 31 | * - param transactions: transactions to process 32 | * - param paymentQueue: payment queue for finishing transactions 33 | * - return: array of unhandled transactions 34 | */ 35 | func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] 36 | } 37 | 38 | public enum TransactionResult { 39 | case purchased(purchase: PurchaseDetails) 40 | case restored(purchase: Purchase) 41 | case failed(error: SKError) 42 | } 43 | 44 | public protocol PaymentQueue: class { 45 | 46 | func add(_ observer: SKPaymentTransactionObserver) 47 | func remove(_ observer: SKPaymentTransactionObserver) 48 | 49 | func add(_ payment: SKPayment) 50 | 51 | func restoreCompletedTransactions(withApplicationUsername username: String?) 52 | 53 | func finishTransaction(_ transaction: SKPaymentTransaction) 54 | } 55 | 56 | extension SKPaymentQueue: PaymentQueue { } 57 | 58 | extension SKPaymentTransaction { 59 | 60 | open override var debugDescription: String { 61 | let transactionId = transactionIdentifier ?? "null" 62 | return "productId: \(payment.productIdentifier), transactionId: \(transactionId), state: \(transactionState), date: \(String(describing: transactionDate))" 63 | } 64 | } 65 | 66 | extension SKPaymentTransactionState: CustomDebugStringConvertible { 67 | 68 | public var debugDescription: String { 69 | 70 | switch self { 71 | case .purchasing: return "purchasing" 72 | case .purchased: return "purchased" 73 | case .failed: return "failed" 74 | case .restored: return "restored" 75 | case .deferred: return "deferred" 76 | } 77 | } 78 | } 79 | 80 | class PaymentQueueController: NSObject, SKPaymentTransactionObserver { 81 | 82 | private let paymentsController: PaymentsController 83 | 84 | private let restorePurchasesController: RestorePurchasesController 85 | 86 | private let completeTransactionsController: CompleteTransactionsController 87 | 88 | unowned let paymentQueue: PaymentQueue 89 | 90 | deinit { 91 | paymentQueue.remove(self) 92 | } 93 | 94 | init(paymentQueue: PaymentQueue = SKPaymentQueue.default(), 95 | paymentsController: PaymentsController = PaymentsController(), 96 | restorePurchasesController: RestorePurchasesController = RestorePurchasesController(), 97 | completeTransactionsController: CompleteTransactionsController = CompleteTransactionsController()) { 98 | 99 | self.paymentQueue = paymentQueue 100 | self.paymentsController = paymentsController 101 | self.restorePurchasesController = restorePurchasesController 102 | self.completeTransactionsController = completeTransactionsController 103 | super.init() 104 | paymentQueue.add(self) 105 | } 106 | 107 | func startPayment(_ payment: Payment) { 108 | 109 | let skPayment = SKMutablePayment(product: payment.product) 110 | skPayment.applicationUsername = payment.applicationUsername 111 | skPayment.quantity = payment.quantity 112 | paymentQueue.add(skPayment) 113 | 114 | paymentsController.append(payment) 115 | } 116 | 117 | func restorePurchases(_ restorePurchases: RestorePurchases) { 118 | 119 | if restorePurchasesController.restorePurchases != nil { 120 | return 121 | } 122 | 123 | paymentQueue.restoreCompletedTransactions(withApplicationUsername: restorePurchases.applicationUsername) 124 | 125 | restorePurchasesController.restorePurchases = restorePurchases 126 | } 127 | 128 | func completeTransactions(_ completeTransactions: CompleteTransactions) { 129 | 130 | guard completeTransactionsController.completeTransactions == nil else { 131 | print("SwiftyStoreKit.completeTransactions() should only be called once when the app launches. Ignoring this call") 132 | return 133 | } 134 | 135 | completeTransactionsController.completeTransactions = completeTransactions 136 | } 137 | 138 | func finishTransaction(_ transaction: PaymentTransaction) { 139 | guard let skTransaction = transaction as? SKPaymentTransaction else { 140 | print("Object is not a SKPaymentTransaction: \(transaction)") 141 | return 142 | } 143 | paymentQueue.finishTransaction(skTransaction) 144 | } 145 | 146 | // MARK: SKPaymentTransactionObserver 147 | func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { 148 | 149 | /* 150 | * Some notes about how requests are processed by SKPaymentQueue: 151 | * 152 | * SKPaymentQueue is used to queue payments or restore purchases requests. 153 | * Payments are processed serially and in-order and require user interaction. 154 | * Restore purchases requests don't require user interaction and can jump ahead of the queue. 155 | * SKPaymentQueue rejects multiple restore purchases calls. 156 | * Having one payment queue observer for each request causes extra processing 157 | * Failed transactions only ever belong to queued payment requests. 158 | * restoreCompletedTransactionsFailedWithError is always called when a restore purchases request fails. 159 | * paymentQueueRestoreCompletedTransactionsFinished is always called following 0 or more update transactions when a restore purchases request succeeds. 160 | * A complete transactions handler is require to catch any transactions that are updated when the app is not running. 161 | * Registering a complete transactions handler when the app launches ensures that any pending transactions can be cleared. 162 | * If a complete transactions handler is missing, pending transactions can be mis-attributed to any new incoming payments or restore purchases. 163 | * 164 | * The order in which transaction updates are processed is: 165 | * 1. payments (transactionState: .purchased and .failed for matching product identifiers) 166 | * 2. restore purchases (transactionState: .restored, or restoreCompletedTransactionsFailedWithError, or paymentQueueRestoreCompletedTransactionsFinished) 167 | * 3. complete transactions (transactionState: .purchased, .failed, .restored, .deferred) 168 | * Any transactions where state == .purchasing are ignored. 169 | */ 170 | var unhandledTransactions = paymentsController.processTransactions(transactions, on: paymentQueue) 171 | 172 | unhandledTransactions = restorePurchasesController.processTransactions(unhandledTransactions, on: paymentQueue) 173 | 174 | unhandledTransactions = completeTransactionsController.processTransactions(unhandledTransactions, on: paymentQueue) 175 | 176 | unhandledTransactions = unhandledTransactions.filter { $0.transactionState != .purchasing } 177 | if unhandledTransactions.count > 0 { 178 | let strings = unhandledTransactions.map { $0.debugDescription }.joined(separator: "\n") 179 | print("unhandledTransactions:\n\(strings)") 180 | } 181 | } 182 | 183 | func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) { 184 | 185 | } 186 | 187 | func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) { 188 | 189 | restorePurchasesController.restoreCompletedTransactionsFailed(withError: error) 190 | } 191 | 192 | func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { 193 | 194 | restorePurchasesController.restoreCompletedTransactionsFinished() 195 | } 196 | 197 | func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) { 198 | 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/NetworkReachabilityManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkReachabilityManager.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #if !os(watchOS) 26 | 27 | import Foundation 28 | import SystemConfiguration 29 | 30 | /// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both WWAN and 31 | /// WiFi network interfaces. 32 | /// 33 | /// Reachability can be used to determine background information about why a network operation failed, or to retry 34 | /// network requests when a connection is established. It should not be used to prevent a user from initiating a network 35 | /// request, as it's possible that an initial request may be required to establish reachability. 36 | public class NetworkReachabilityManager { 37 | /// Defines the various states of network reachability. 38 | /// 39 | /// - unknown: It is unknown whether the network is reachable. 40 | /// - notReachable: The network is not reachable. 41 | /// - reachable: The network is reachable. 42 | public enum NetworkReachabilityStatus { 43 | case unknown 44 | case notReachable 45 | case reachable(ConnectionType) 46 | } 47 | 48 | /// Defines the various connection types detected by reachability flags. 49 | /// 50 | /// - ethernetOrWiFi: The connection type is either over Ethernet or WiFi. 51 | /// - wwan: The connection type is a WWAN connection. 52 | public enum ConnectionType { 53 | case ethernetOrWiFi 54 | case wwan 55 | } 56 | 57 | /// A closure executed when the network reachability status changes. The closure takes a single argument: the 58 | /// network reachability status. 59 | public typealias Listener = (NetworkReachabilityStatus) -> Void 60 | 61 | // MARK: - Properties 62 | 63 | /// Whether the network is currently reachable. 64 | public var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi } 65 | 66 | /// Whether the network is currently reachable over the WWAN interface. 67 | public var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) } 68 | 69 | /// Whether the network is currently reachable over Ethernet or WiFi interface. 70 | public var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) } 71 | 72 | /// The current network reachability status. 73 | public var networkReachabilityStatus: NetworkReachabilityStatus { 74 | guard let flags = self.flags else { return .unknown } 75 | return networkReachabilityStatusForFlags(flags) 76 | } 77 | 78 | /// The dispatch queue to execute the `listener` closure on. 79 | public var listenerQueue: DispatchQueue = DispatchQueue.main 80 | 81 | /// A closure executed when the network reachability status changes. 82 | public var listener: Listener? 83 | 84 | private var flags: SCNetworkReachabilityFlags? { 85 | var flags = SCNetworkReachabilityFlags() 86 | 87 | if SCNetworkReachabilityGetFlags(reachability, &flags) { 88 | return flags 89 | } 90 | 91 | return nil 92 | } 93 | 94 | private let reachability: SCNetworkReachability 95 | private var previousFlags: SCNetworkReachabilityFlags 96 | 97 | // MARK: - Initialization 98 | 99 | /// Creates a `NetworkReachabilityManager` instance with the specified host. 100 | /// 101 | /// - parameter host: The host used to evaluate network reachability. 102 | /// 103 | /// - returns: The new `NetworkReachabilityManager` instance. 104 | public convenience init?(host: String) { 105 | guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil } 106 | self.init(reachability: reachability) 107 | } 108 | 109 | /// Creates a `NetworkReachabilityManager` instance that monitors the address 0.0.0.0. 110 | /// 111 | /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing 112 | /// status of the device, both IPv4 and IPv6. 113 | /// 114 | /// - returns: The new `NetworkReachabilityManager` instance. 115 | public convenience init?() { 116 | var address = sockaddr_in() 117 | address.sin_len = UInt8(MemoryLayout.size) 118 | address.sin_family = sa_family_t(AF_INET) 119 | 120 | guard let reachability = withUnsafePointer(to: &address, { pointer in 121 | return pointer.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size) { 122 | return SCNetworkReachabilityCreateWithAddress(nil, $0) 123 | } 124 | }) else { return nil } 125 | 126 | self.init(reachability: reachability) 127 | } 128 | 129 | private init(reachability: SCNetworkReachability) { 130 | self.reachability = reachability 131 | self.previousFlags = SCNetworkReachabilityFlags() 132 | } 133 | 134 | deinit { 135 | stopListening() 136 | } 137 | 138 | // MARK: - Listening 139 | 140 | /// Starts listening for changes in network reachability status. 141 | /// 142 | /// - returns: `true` if listening was started successfully, `false` otherwise. 143 | @discardableResult 144 | public func startListening() -> Bool { 145 | var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) 146 | context.info = Unmanaged.passUnretained(self).toOpaque() 147 | 148 | let callbackEnabled = SCNetworkReachabilitySetCallback( 149 | reachability, 150 | { (_, flags, info) in 151 | let reachability = Unmanaged.fromOpaque(info!).takeUnretainedValue() 152 | reachability.notifyListener(flags) 153 | }, 154 | &context 155 | ) 156 | 157 | let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue) 158 | 159 | listenerQueue.async { 160 | self.previousFlags = SCNetworkReachabilityFlags() 161 | self.notifyListener(self.flags ?? SCNetworkReachabilityFlags()) 162 | } 163 | 164 | return callbackEnabled && queueEnabled 165 | } 166 | 167 | /// Stops listening for changes in network reachability status. 168 | public func stopListening() { 169 | SCNetworkReachabilitySetCallback(reachability, nil, nil) 170 | SCNetworkReachabilitySetDispatchQueue(reachability, nil) 171 | } 172 | 173 | // MARK: - Internal - Listener Notification 174 | 175 | func notifyListener(_ flags: SCNetworkReachabilityFlags) { 176 | guard previousFlags != flags else { return } 177 | previousFlags = flags 178 | 179 | listener?(networkReachabilityStatusForFlags(flags)) 180 | } 181 | 182 | // MARK: - Internal - Network Reachability Status 183 | 184 | func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus { 185 | guard flags.contains(.reachable) else { return .notReachable } 186 | 187 | var networkStatus: NetworkReachabilityStatus = .notReachable 188 | 189 | if !flags.contains(.connectionRequired) { networkStatus = .reachable(.ethernetOrWiFi) } 190 | 191 | if flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic) { 192 | if !flags.contains(.interventionRequired) { networkStatus = .reachable(.ethernetOrWiFi) } 193 | } 194 | 195 | #if os(iOS) 196 | if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) } 197 | #endif 198 | 199 | return networkStatus 200 | } 201 | } 202 | 203 | // MARK: - 204 | 205 | extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {} 206 | 207 | /// Returns whether the two network reachability status values are equal. 208 | /// 209 | /// - parameter lhs: The left-hand side value to compare. 210 | /// - parameter rhs: The right-hand side value to compare. 211 | /// 212 | /// - returns: `true` if the two values are equal, `false` otherwise. 213 | public func ==( 214 | lhs: NetworkReachabilityManager.NetworkReachabilityStatus, 215 | rhs: NetworkReachabilityManager.NetworkReachabilityStatus) 216 | -> Bool 217 | { 218 | switch (lhs, rhs) { 219 | case (.unknown, .unknown): 220 | return true 221 | case (.notReachable, .notReachable): 222 | return true 223 | case let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)): 224 | return lhsConnectionType == rhsConnectionType 225 | default: 226 | return false 227 | } 228 | } 229 | 230 | #endif 231 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/InAppReceipt.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InAppReceipt.swift 3 | // SwiftyStoreKit 4 | // 5 | // Created by phimage on 22/12/15. 6 | // Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import Foundation 27 | 28 | extension Date { 29 | 30 | init?(millisecondsSince1970: String) { 31 | guard let millisecondsNumber = Double(millisecondsSince1970) else { 32 | return nil 33 | } 34 | self = Date(timeIntervalSince1970: millisecondsNumber / 1000) 35 | } 36 | } 37 | 38 | extension ReceiptItem { 39 | 40 | public init?(receiptInfo: ReceiptInfo) { 41 | guard 42 | let productId = receiptInfo["product_id"] as? String, 43 | let quantityString = receiptInfo["quantity"] as? String, 44 | let quantity = Int(quantityString), 45 | let transactionId = receiptInfo["transaction_id"] as? String, 46 | let originalTransactionId = receiptInfo["original_transaction_id"] as? String, 47 | let purchaseDate = ReceiptItem.parseDate(from: receiptInfo, key: "purchase_date_ms"), 48 | let originalPurchaseDate = ReceiptItem.parseDate(from: receiptInfo, key: "original_purchase_date_ms") 49 | else { 50 | print("could not parse receipt item: \(receiptInfo). Skipping...") 51 | return nil 52 | } 53 | self.productId = productId 54 | self.quantity = quantity 55 | self.transactionId = transactionId 56 | self.originalTransactionId = originalTransactionId 57 | self.purchaseDate = purchaseDate 58 | self.originalPurchaseDate = originalPurchaseDate 59 | self.webOrderLineItemId = receiptInfo["web_order_line_item_id"] as? String 60 | self.subscriptionExpirationDate = ReceiptItem.parseDate(from: receiptInfo, key: "expires_date_ms") 61 | self.cancellationDate = ReceiptItem.parseDate(from: receiptInfo, key: "cancellation_date_ms") 62 | if let isTrialPeriod = receiptInfo["is_trial_period"] as? String { 63 | self.isTrialPeriod = Bool(isTrialPeriod) ?? false 64 | } else { 65 | self.isTrialPeriod = false 66 | } 67 | } 68 | 69 | private static func parseDate(from receiptInfo: ReceiptInfo, key: String) -> Date? { 70 | 71 | guard 72 | let requestDateString = receiptInfo[key] as? String, 73 | let requestDateMs = Double(requestDateString) else { 74 | return nil 75 | } 76 | return Date(timeIntervalSince1970: requestDateMs / 1000) 77 | } 78 | } 79 | 80 | // MARK - receipt mangement 81 | internal class InAppReceipt { 82 | 83 | /** 84 | * Verify the purchase of a Consumable or NonConsumable product in a receipt 85 | * - Parameter productId: the product id of the purchase to verify 86 | * - Parameter inReceipt: the receipt to use for looking up the purchase 87 | * - return: either notPurchased or purchased 88 | */ 89 | class func verifyPurchase( 90 | productId: String, 91 | inReceipt receipt: ReceiptInfo 92 | ) -> VerifyPurchaseResult { 93 | 94 | // Get receipts info for the product 95 | let receipts = receipt["receipt"]?["in_app"] as? [ReceiptInfo] 96 | let receiptsInfo = filterReceiptsInfo(receipts: receipts, withProductId: productId) 97 | let nonCancelledReceiptsInfo = receiptsInfo.filter { receipt in receipt["cancellation_date"] == nil } 98 | 99 | let receiptItems = nonCancelledReceiptsInfo.flatMap { ReceiptItem(receiptInfo: $0) } 100 | // Verify that at least one receipt has the right product id 101 | if let firstItem = receiptItems.first { 102 | return .purchased(item: firstItem) 103 | } 104 | return .notPurchased 105 | } 106 | 107 | /** 108 | * Verify the purchase of a subscription (auto-renewable, free or non-renewing) in a receipt. This method extracts all transactions mathing the given productId and sorts them by date in descending order, then compares the first transaction expiry date against the validUntil value. 109 | * - parameter type: .autoRenewable or .nonRenewing(duration) 110 | * - Parameter productId: the product id of the purchase to verify 111 | * - Parameter inReceipt: the receipt to use for looking up the subscription 112 | * - Parameter validUntil: date to check against the expiry date of the subscription. If nil, no verification 113 | * - Parameter validDuration: the duration of the subscription. Only required for non-renewable subscription. 114 | * - return: either NotPurchased or Purchased / Expired with the expiry date found in the receipt 115 | */ 116 | class func verifySubscription( 117 | type: SubscriptionType, 118 | productId: String, 119 | inReceipt receipt: ReceiptInfo, 120 | validUntil date: Date = Date() 121 | ) -> VerifySubscriptionResult { 122 | 123 | // The values of the latest_receipt and latest_receipt_info keys are useful when checking whether an auto-renewable subscription is currently active. By providing any transaction receipt for the subscription and checking these values, you can get information about the currently-active subscription period. If the receipt being validated is for the latest renewal, the value for latest_receipt is the same as receipt-data (in the request) and the value for latest_receipt_info is the same as receipt. 124 | let (receipts, duration) = getReceiptsAndDuration(for: type, inReceipt: receipt) 125 | let receiptsInfo = filterReceiptsInfo(receipts: receipts, withProductId: productId) 126 | let nonCancelledReceiptsInfo = receiptsInfo.filter { receipt in receipt["cancellation_date"] == nil } 127 | if nonCancelledReceiptsInfo.count == 0 { 128 | return .notPurchased 129 | } 130 | 131 | let receiptDate = getReceiptRequestDate(inReceipt: receipt) ?? date 132 | 133 | let receiptItems = nonCancelledReceiptsInfo.flatMap { ReceiptItem(receiptInfo: $0) } 134 | 135 | if nonCancelledReceiptsInfo.count > receiptItems.count { 136 | print("receipt has \(nonCancelledReceiptsInfo.count) items, but only \(receiptItems.count) were parsed") 137 | } 138 | 139 | let sortedExpiryDatesAndItems = expiryDatesAndItems(receiptItems: receiptItems, duration: duration).sorted { a, b in 140 | return a.0 > b.0 141 | } 142 | 143 | guard let firstExpiryDateItemPair = sortedExpiryDatesAndItems.first else { 144 | return .notPurchased 145 | } 146 | 147 | let sortedReceiptItems = sortedExpiryDatesAndItems.map { $0.1 } 148 | if firstExpiryDateItemPair.0 > receiptDate { 149 | return .purchased(expiryDate: firstExpiryDateItemPair.0, items: sortedReceiptItems) 150 | } else { 151 | return .expired(expiryDate: firstExpiryDateItemPair.0, items: sortedReceiptItems) 152 | } 153 | } 154 | 155 | private class func expiryDatesAndItems(receiptItems: [ReceiptItem], duration: TimeInterval?) -> [(Date, ReceiptItem)] { 156 | 157 | if let duration = duration { 158 | return receiptItems.map { 159 | let expirationDate = Date(timeIntervalSince1970: $0.originalPurchaseDate.timeIntervalSince1970 + duration) 160 | return (expirationDate, $0) 161 | } 162 | } else { 163 | return receiptItems.flatMap { 164 | if let expirationDate = $0.subscriptionExpirationDate { 165 | return (expirationDate, $0) 166 | } 167 | return nil 168 | } 169 | } 170 | } 171 | 172 | private class func getReceiptsAndDuration(for subscriptionType: SubscriptionType, inReceipt receipt: ReceiptInfo) -> ([ReceiptInfo]?, TimeInterval?) { 173 | switch subscriptionType { 174 | case .autoRenewable: 175 | return (receipt["latest_receipt_info"] as? [ReceiptInfo], nil) 176 | case .nonRenewing(let duration): 177 | return (receipt["receipt"]?["in_app"] as? [ReceiptInfo], duration) 178 | } 179 | } 180 | 181 | private class func getReceiptRequestDate(inReceipt receipt: ReceiptInfo) -> Date? { 182 | 183 | guard let receiptInfo = receipt["receipt"] as? ReceiptInfo, 184 | let requestDateString = receiptInfo["request_date_ms"] as? String else { 185 | return nil 186 | } 187 | return Date(millisecondsSince1970: requestDateString) 188 | } 189 | 190 | /** 191 | * Get all the receipts info for a specific product 192 | * - Parameter receipts: the receipts array to grab info from 193 | * - Parameter productId: the product id 194 | */ 195 | private class func filterReceiptsInfo(receipts: [ReceiptInfo]?, withProductId productId: String) -> [ReceiptInfo] { 196 | 197 | guard let receipts = receipts else { 198 | return [] 199 | } 200 | 201 | // Filter receipts with matching product id 202 | let receiptsMatchingProductId = receipts 203 | .filter { (receipt) -> Bool in 204 | let product_id = receipt["product_id"] as? String 205 | return product_id == productId 206 | } 207 | 208 | return receiptsMatchingProductId 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/SwiftyStoreKit+Types.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyStoreKit+Types.swift 3 | // SwiftyStoreKit 4 | // 5 | // Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import StoreKit 26 | 27 | // MARK: Purchases 28 | 29 | // Purchased or restored product 30 | public struct Purchase { 31 | public let productId: String 32 | public let quantity: Int 33 | public let transaction: PaymentTransaction 34 | public let originalTransaction: PaymentTransaction? 35 | public let needsFinishTransaction: Bool 36 | } 37 | 38 | public struct PurchaseDetails { 39 | public let productId: String 40 | public let quantity: Int 41 | public let product: SKProduct 42 | public let transaction: PaymentTransaction 43 | public let needsFinishTransaction: Bool 44 | } 45 | 46 | //Conform to this protocol to provide custom receipt validator 47 | public protocol ReceiptValidator { 48 | func validate(receipt: String, password autoRenewPassword: String?, completion: @escaping (VerifyReceiptResult) -> Void) 49 | } 50 | 51 | // Payment transaction 52 | public protocol PaymentTransaction { 53 | var transactionState: SKPaymentTransactionState { get } 54 | var transactionIdentifier: String? { get } 55 | } 56 | 57 | // Add PaymentTransaction conformance to SKPaymentTransaction 58 | extension SKPaymentTransaction : PaymentTransaction { } 59 | 60 | // Products information 61 | public struct RetrieveResults { 62 | public let retrievedProducts: Set 63 | public let invalidProductIDs: Set 64 | public let error: Error? 65 | } 66 | 67 | // Purchase result 68 | public enum PurchaseResult { 69 | case success(purchase: PurchaseDetails) 70 | case error(error: SKError) 71 | } 72 | 73 | // Restore purchase results 74 | public struct RestoreResults { 75 | public let restoredPurchases: [Purchase] 76 | public let restoreFailedPurchases: [(SKError, String?)] 77 | } 78 | 79 | // MARK: Receipt verification 80 | 81 | // Info for receipt returned by server 82 | public typealias ReceiptInfo = [String: AnyObject] 83 | 84 | // Refresh receipt result 85 | public enum RefreshReceiptResult { 86 | case success(receiptData: Data) 87 | case error(error: Error) 88 | } 89 | 90 | // Verify receipt result 91 | public enum VerifyReceiptResult { 92 | case success(receipt: ReceiptInfo) 93 | case error(error: ReceiptError) 94 | } 95 | 96 | // Result for Consumable and NonConsumable 97 | public enum VerifyPurchaseResult { 98 | case purchased(item: ReceiptItem) 99 | case notPurchased 100 | } 101 | 102 | // Verify subscription result 103 | public enum VerifySubscriptionResult { 104 | case purchased(expiryDate: Date, items: [ReceiptItem]) 105 | case expired(expiryDate: Date, items: [ReceiptItem]) 106 | case notPurchased 107 | } 108 | 109 | public enum SubscriptionType { 110 | case autoRenewable 111 | case nonRenewing(validDuration: TimeInterval) 112 | } 113 | 114 | public struct ReceiptItem { 115 | // 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. 116 | public let productId: String 117 | // The number of items purchased. This value corresponds to the quantity property of the SKPayment object stored in the transaction’s payment property. 118 | public let quantity: Int 119 | // The transaction identifier of the item that was purchased. This value corresponds to the transaction’s transactionIdentifier property. 120 | public let transactionId: String 121 | // 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. 122 | public let originalTransactionId: String 123 | // The date and time that the item was purchased. This value corresponds to the transaction’s transactionDate property. 124 | public let purchaseDate: Date 125 | // 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. 126 | public let originalPurchaseDate: Date 127 | // The primary key for identifying subscription purchases. 128 | public let webOrderLineItemId: String? 129 | // 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. 130 | public let subscriptionExpirationDate: Date? 131 | // 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. 132 | public let cancellationDate: Date? 133 | 134 | public let isTrialPeriod: Bool 135 | } 136 | 137 | // Error when managing receipt 138 | public enum ReceiptError: Swift.Error { 139 | // No receipt data 140 | case noReceiptData 141 | // No data received 142 | case noRemoteData 143 | // Error when encoding HTTP body into JSON 144 | case requestBodyEncodeError(error: Swift.Error) 145 | // Error when proceeding request 146 | case networkError(error: Swift.Error) 147 | // Error when decoding response 148 | case jsonDecodeError(string: String?) 149 | // Receive invalid - bad status returned 150 | case receiptInvalid(receipt: ReceiptInfo, status: ReceiptStatus) 151 | } 152 | 153 | // Status code returned by remote server 154 | // see Table 2-1 Status codes 155 | public enum ReceiptStatus: Int { 156 | // Not decodable status 157 | case unknown = -2 158 | // No status returned 159 | case none = -1 160 | // valid statu 161 | case valid = 0 162 | // The App Store could not read the JSON object you provided. 163 | case jsonNotReadable = 21000 164 | // The data in the receipt-data property was malformed or missing. 165 | case malformedOrMissingData = 21002 166 | // The receipt could not be authenticated. 167 | case receiptCouldNotBeAuthenticated = 21003 168 | // The shared secret you provided does not match the shared secret on file for your account. 169 | case secretNotMatching = 21004 170 | // The receipt server is not currently available. 171 | case receiptServerUnavailable = 21005 172 | // 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. 173 | case subscriptionExpired = 21006 174 | // This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead. 175 | case testReceipt = 21007 176 | // This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead. 177 | case productionEnvironment = 21008 178 | 179 | var isValid: Bool { return self == .valid} 180 | } 181 | 182 | // Receipt field as defined in : https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1 183 | public enum ReceiptInfoField: String { 184 | // Bundle Identifier. This corresponds to the value of CFBundleIdentifier in the Info.plist file. 185 | case bundle_id 186 | // The app’s version number.This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist. 187 | case application_version 188 | // 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. 189 | case original_application_version 190 | // The date when the app receipt was created. 191 | case creation_date 192 | // The date that the app receipt expires. This key is present only for apps purchased through the Volume Purchase Program. 193 | case expiration_date 194 | 195 | // The receipt for an in-app purchase. 196 | case in_app 197 | 198 | public enum InApp: String { 199 | // The number of items purchased. This value corresponds to the quantity property of the SKPayment object stored in the transaction’s payment property. 200 | case quantity 201 | // 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. 202 | case product_id 203 | // The transaction identifier of the item that was purchased. This value corresponds to the transaction’s transactionIdentifier property. 204 | case transaction_id 205 | // 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. 206 | case original_transaction_id 207 | // The date and time that the item was purchased. This value corresponds to the transaction’s transactionDate property. 208 | case purchase_date 209 | // 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. 210 | case original_purchase_date 211 | // 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. 212 | case expires_date 213 | // 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. 214 | case cancellation_date 215 | #if os(iOS) || os(tvOS) 216 | // 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. 217 | case app_item_id 218 | #endif 219 | // An arbitrary number that uniquely identifies a revision of your application. This key is not present for receipts created in the test environment. 220 | case version_external_identifier 221 | // The primary key for identifying subscription purchases. 222 | case web_order_line_item_id 223 | } 224 | } 225 | 226 | #if os(OSX) 227 | public enum ReceiptExitCode: Int32 { 228 | // 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 229 | case notValid = 173 230 | } 231 | #endif 232 | -------------------------------------------------------------------------------- /Pods/SwiftyStoreKit/SwiftyStoreKit/SwiftyStoreKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyStoreKit.swift 3 | // SwiftyStoreKit 4 | // 5 | // Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import StoreKit 26 | 27 | public class SwiftyStoreKit { 28 | 29 | private let productsInfoController: ProductsInfoController 30 | 31 | private let paymentQueueController: PaymentQueueController 32 | 33 | fileprivate let receiptVerificator: InAppReceiptVerificator 34 | 35 | init(productsInfoController: ProductsInfoController = ProductsInfoController(), 36 | paymentQueueController: PaymentQueueController = PaymentQueueController(paymentQueue: SKPaymentQueue.default()), 37 | receiptVerificator: InAppReceiptVerificator = InAppReceiptVerificator()) { 38 | 39 | self.productsInfoController = productsInfoController 40 | self.paymentQueueController = paymentQueueController 41 | self.receiptVerificator = receiptVerificator 42 | } 43 | 44 | // MARK: Internal methods 45 | 46 | func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) { 47 | return productsInfoController.retrieveProductsInfo(productIds, completion: completion) 48 | } 49 | 50 | func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> Void) { 51 | 52 | if let product = productsInfoController.products[productId] { 53 | purchase(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, completion: completion) 54 | } else { 55 | retrieveProductsInfo(Set([productId])) { result -> Void in 56 | if let product = result.retrievedProducts.first { 57 | self.purchase(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, completion: completion) 58 | } else if let error = result.error { 59 | completion(.error(error: SKError(_nsError: error as NSError))) 60 | } else if let invalidProductId = result.invalidProductIDs.first { 61 | let userInfo = [ NSLocalizedDescriptionKey: "Invalid product id: \(invalidProductId)" ] 62 | let error = NSError(domain: SKErrorDomain, code: SKError.paymentInvalid.rawValue, userInfo: userInfo) 63 | completion(.error(error: SKError(_nsError: error))) 64 | } 65 | } 66 | } 67 | } 68 | 69 | func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> Void) { 70 | 71 | paymentQueueController.restorePurchases(RestorePurchases(atomically: atomically, applicationUsername: applicationUsername) { results in 72 | 73 | let results = self.processRestoreResults(results) 74 | completion(results) 75 | }) 76 | } 77 | 78 | func completeTransactions(atomically: Bool = true, completion: @escaping ([Purchase]) -> Void) { 79 | 80 | paymentQueueController.completeTransactions(CompleteTransactions(atomically: atomically, callback: completion)) 81 | } 82 | 83 | func finishTransaction(_ transaction: PaymentTransaction) { 84 | 85 | paymentQueueController.finishTransaction(transaction) 86 | } 87 | 88 | // MARK: private methods 89 | private func purchase(product: SKProduct, quantity: Int, atomically: Bool, applicationUsername: String = "", completion: @escaping (PurchaseResult) -> Void) { 90 | guard SwiftyStoreKit.canMakePayments else { 91 | let error = NSError(domain: SKErrorDomain, code: SKError.paymentNotAllowed.rawValue, userInfo: nil) 92 | completion(.error(error: SKError(_nsError: error))) 93 | return 94 | } 95 | 96 | paymentQueueController.startPayment(Payment(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername) { result in 97 | 98 | completion(self.processPurchaseResult(result)) 99 | }) 100 | } 101 | 102 | private func processPurchaseResult(_ result: TransactionResult) -> PurchaseResult { 103 | switch result { 104 | case .purchased(let purchase): 105 | return .success(purchase: purchase) 106 | case .failed(let error): 107 | return .error(error: error) 108 | case .restored(let purchase): 109 | return .error(error: storeInternalError(description: "Cannot restore product \(purchase.productId) from purchase path")) 110 | } 111 | } 112 | 113 | private func processRestoreResults(_ results: [TransactionResult]) -> RestoreResults { 114 | var restoredPurchases: [Purchase] = [] 115 | var restoreFailedPurchases: [(SKError, String?)] = [] 116 | for result in results { 117 | switch result { 118 | case .purchased(let purchase): 119 | let error = storeInternalError(description: "Cannot purchase product \(purchase.productId) from restore purchases path") 120 | restoreFailedPurchases.append((error, purchase.productId)) 121 | case .failed(let error): 122 | restoreFailedPurchases.append((error, nil)) 123 | case .restored(let purchase): 124 | restoredPurchases.append(purchase) 125 | } 126 | } 127 | return RestoreResults(restoredPurchases: restoredPurchases, restoreFailedPurchases: restoreFailedPurchases) 128 | } 129 | 130 | private func storeInternalError(code: SKError.Code = SKError.unknown, description: String = "") -> SKError { 131 | let error = NSError(domain: SKErrorDomain, code: code.rawValue, userInfo: [ NSLocalizedDescriptionKey: description ]) 132 | return SKError(_nsError: error) 133 | } 134 | } 135 | 136 | extension SwiftyStoreKit { 137 | 138 | // MARK: Singleton 139 | fileprivate static let sharedInstance = SwiftyStoreKit() 140 | 141 | // MARK: Public methods - Purchases 142 | 143 | public class var canMakePayments: Bool { 144 | return SKPaymentQueue.canMakePayments() 145 | } 146 | 147 | /** 148 | * Retrieve products information 149 | * - Parameter productIds: The set of product identifiers to retrieve corresponding products for 150 | * - Parameter completion: handler for result 151 | */ 152 | public class func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) { 153 | 154 | return sharedInstance.retrieveProductsInfo(productIds, completion: completion) 155 | } 156 | 157 | /** 158 | * Purchase a product 159 | * - Parameter productId: productId as specified in iTunes Connect 160 | * - Parameter quantity: quantity of the product to be purchased 161 | * - Parameter atomically: whether the product is purchased atomically (e.g. finishTransaction is called immediately) 162 | * - Parameter applicationUsername: an opaque identifier for the user’s account on your system 163 | * - Parameter completion: handler for result 164 | */ 165 | public class func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> Void) { 166 | 167 | sharedInstance.purchaseProduct(productId, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, completion: completion) 168 | } 169 | 170 | /** 171 | * Restore purchases 172 | * - Parameter atomically: whether the product is purchased atomically (e.g. finishTransaction is called immediately) 173 | * - Parameter applicationUsername: an opaque identifier for the user’s account on your system 174 | * - Parameter completion: handler for result 175 | */ 176 | public class func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> Void) { 177 | 178 | sharedInstance.restorePurchases(atomically: atomically, applicationUsername: applicationUsername, completion: completion) 179 | } 180 | 181 | /** 182 | * Complete transactions 183 | * - Parameter atomically: whether the product is purchased atomically (e.g. finishTransaction is called immediately) 184 | * - Parameter completion: handler for result 185 | */ 186 | public class func completeTransactions(atomically: Bool = true, completion: @escaping ([Purchase]) -> Void) { 187 | 188 | sharedInstance.completeTransactions(atomically: atomically, completion: completion) 189 | } 190 | 191 | /** 192 | * Finish a transaction 193 | * Once the content has been delivered, call this method to finish a transaction that was performed non-atomically 194 | * - Parameter transaction: transaction to finish 195 | */ 196 | public class func finishTransaction(_ transaction: PaymentTransaction) { 197 | 198 | sharedInstance.finishTransaction(transaction) 199 | } 200 | } 201 | 202 | extension SwiftyStoreKit { 203 | 204 | // MARK: Public methods - Receipt verification 205 | 206 | /** 207 | * Return receipt data from the application bundle. This is read from Bundle.main.appStoreReceiptURL 208 | */ 209 | public static var localReceiptData: Data? { 210 | return sharedInstance.receiptVerificator.appStoreReceiptData 211 | } 212 | 213 | /** 214 | * Verify application receipt 215 | * - Parameter validator: receipt validator to use 216 | * - Parameter password: Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string). 217 | * - Parameter completion: handler for result 218 | */ 219 | public class func verifyReceipt(using validator: ReceiptValidator, password: String? = nil, completion: @escaping (VerifyReceiptResult) -> Void) { 220 | 221 | sharedInstance.receiptVerificator.verifyReceipt(using: validator, password: password, completion: completion) 222 | } 223 | 224 | /** 225 | * Verify the purchase of a Consumable or NonConsumable product in a receipt 226 | * - Parameter productId: the product id of the purchase to verify 227 | * - Parameter inReceipt: the receipt to use for looking up the purchase 228 | * - return: either notPurchased or purchased 229 | */ 230 | public class func verifyPurchase(productId: String, inReceipt receipt: ReceiptInfo) -> VerifyPurchaseResult { 231 | 232 | return InAppReceipt.verifyPurchase(productId: productId, inReceipt: receipt) 233 | } 234 | 235 | /** 236 | * Verify the purchase of a subscription (auto-renewable, free or non-renewing) in a receipt. This method extracts all transactions mathing the given productId and sorts them by date in descending order, then compares the first transaction expiry date against the validUntil value. 237 | * - Parameter type: autoRenewable or nonRenewing 238 | * - Parameter productId: the product id of the purchase to verify 239 | * - Parameter inReceipt: the receipt to use for looking up the subscription 240 | * - Parameter validUntil: date to check against the expiry date of the subscription. If nil, no verification 241 | * - return: either .notPurchased or .purchased / .expired with the expiry date found in the receipt 242 | */ 243 | public class func verifySubscription(type: SubscriptionType, productId: String, inReceipt receipt: ReceiptInfo, validUntil date: Date = Date()) -> VerifySubscriptionResult { 244 | 245 | return InAppReceipt.verifySubscription(type: type, productId: productId, inReceipt: receipt, validUntil: date) 246 | } 247 | } 248 | --------------------------------------------------------------------------------