diff --git a/.swift-version b/.swift-version index a3ec5a4b..5186d070 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.2 +4.0 diff --git a/Buy.xcodeproj/project.pbxproj b/Buy.xcodeproj/project.pbxproj index bd93efc8..75308cd2 100644 --- a/Buy.xcodeproj/project.pbxproj +++ b/Buy.xcodeproj/project.pbxproj @@ -479,6 +479,7 @@ 9AF256451F6FEFCD005BB0C9 /* Optional+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC2EFD61F686DB90037E0D7 /* Optional+Input.swift */; }; 9AFA38EF1E64850A0056C5AA /* Buy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AEF60F31E5F42D90067FA90 /* Buy.framework */; }; 9AFA3B6D1E6D9A5E0056C5AA /* GraphQL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AFA3B6C1E6D9A5E0056C5AA /* GraphQL.swift */; }; + 9AFEF46E1F72DB64003FA8C5 /* MockPaySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AFEF46D1F72DB64003FA8C5 /* MockPaySession.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -688,6 +689,7 @@ 9AFA38EA1E64850A0056C5AA /* BuyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BuyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9AFA38EE1E64850A0056C5AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9AFA3B6C1E6D9A5E0056C5AA /* GraphQL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQL.swift; sourceTree = ""; }; + 9AFEF46D1F72DB64003FA8C5 /* MockPaySession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPaySession.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -958,6 +960,7 @@ 9AADF1C61EA63ED000D22740 /* MockPaymentMethod.swift */, 9AADF1C81EA63FB900D22740 /* MockPaymentToken.swift */, 9AADF1CA1EA640C000D22740 /* MockPayment.swift */, + 9AFEF46D1F72DB64003FA8C5 /* MockPaySession.swift */, ); path = Mocks; sourceTree = ""; @@ -1250,7 +1253,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0900; - LastUpgradeCheck = 0830; + LastUpgradeCheck = 0900; ORGANIZATIONNAME = "Shopify Inc."; TargetAttributes = { 9A0C80C51EAE73840020F187 = { @@ -1275,6 +1278,7 @@ ProvisioningStyle = Automatic; }; 9AF255B11F6FEE50005BB0C9 = { + LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; 9AFA38E91E64850A0056C5AA = { @@ -1402,6 +1406,7 @@ 9A4068EA1E8E7659000254CD /* PaySessionTests.swift in Sources */, 9A0C7FD91EA686A20020F187 /* PayAddressTests.swift in Sources */, 9A0C7FD71EA686240020F187 /* PayCurrencyTests.swift in Sources */, + 9AFEF46E1F72DB64003FA8C5 /* MockPaySession.swift in Sources */, 9A0C7FD51EA682640020F187 /* PayLineItemTests.swift in Sources */, 9A0C7FCD1EA6631B0020F187 /* Models.swift in Sources */, 9AEC90E41E9FFAA6008C6E1C /* MockAuthorizationController.swift in Sources */, @@ -1911,12 +1916,12 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=100"; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.shopify.Pay; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1939,7 +1944,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.shopify.Pay; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -1954,7 +1959,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.shopify.PayTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1969,7 +1974,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.shopify.PayTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -1977,7 +1982,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 3.1.0; DEFINES_MODULE = YES; @@ -1995,7 +2000,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = watchos; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; @@ -2004,7 +2009,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 3.1.0; DEFINES_MODULE = YES; @@ -2021,7 +2026,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = watchos; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Release; @@ -2035,7 +2040,9 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -2043,7 +2050,11 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -2088,7 +2099,9 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -2096,7 +2109,11 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -2143,7 +2160,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.shopify.Buy; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -2164,7 +2181,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.shopify.Buy; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -2191,7 +2208,8 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = appletvos; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; @@ -2218,7 +2236,8 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = appletvos; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; @@ -2234,7 +2253,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.shopify.BuyTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -2249,7 +2268,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.shopify.BuyTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Buy.xcodeproj/xcshareddata/xcschemes/Buy.xcscheme b/Buy.xcodeproj/xcshareddata/xcschemes/Buy.xcscheme index 8fadb061..6338877b 100644 --- a/Buy.xcodeproj/xcshareddata/xcschemes/Buy.xcscheme +++ b/Buy.xcodeproj/xcshareddata/xcschemes/Buy.xcscheme @@ -1,6 +1,6 @@ @@ -47,6 +48,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Buy.xcodeproj/xcshareddata/xcschemes/Documentation.xcscheme b/Buy.xcodeproj/xcshareddata/xcschemes/Documentation.xcscheme index e5487288..a3a44b16 100644 --- a/Buy.xcodeproj/xcshareddata/xcschemes/Documentation.xcscheme +++ b/Buy.xcodeproj/xcshareddata/xcschemes/Documentation.xcscheme @@ -1,6 +1,6 @@ @@ -36,6 +37,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Buy.xcodeproj/xcshareddata/xcschemes/Pay.xcscheme b/Buy.xcodeproj/xcshareddata/xcschemes/Pay.xcscheme index e596cc21..3aedfad2 100644 --- a/Buy.xcodeproj/xcshareddata/xcschemes/Pay.xcscheme +++ b/Buy.xcodeproj/xcshareddata/xcschemes/Pay.xcscheme @@ -1,6 +1,6 @@ @@ -56,6 +57,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Buy.xcodeproj/xcshareddata/xcschemes/PayTests.xcscheme b/Buy.xcodeproj/xcshareddata/xcschemes/PayTests.xcscheme index 6ec14a3f..ba0a18a0 100644 --- a/Buy.xcodeproj/xcshareddata/xcschemes/PayTests.xcscheme +++ b/Buy.xcodeproj/xcshareddata/xcschemes/PayTests.xcscheme @@ -1,6 +1,6 @@ @@ -47,6 +48,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Pay/PaySession.swift b/Pay/PaySession.swift index 04011489..d3cb65f2 100644 --- a/Pay/PaySession.swift +++ b/Pay/PaySession.swift @@ -189,6 +189,14 @@ extension PaySession: PKPaymentAuthorizationControllerDelegate { // ------------------------------------------------------- // MARK: - PKPaymentAuthorizationControllerDelegate - // + @available(iOS 11.0, *) + public func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) { + self.paymentAuthorizationController(controller, didAuthorizePayment: payment) { (status: PKPaymentAuthorizationStatus) in + let result = PKPaymentAuthorizationResult(status: status, errors: nil) + completion(result) + } + } + public func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) { var shippingRate: PayShippingRate? @@ -220,6 +228,16 @@ extension PaySession: PKPaymentAuthorizationControllerDelegate { } }) } + + @available(iOS 11.0, *) + public func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectShippingContact contact: PKContact, handler completion: @escaping (PKPaymentRequestShippingContactUpdate) -> Void) { + + self.paymentAuthorizationController(controller, didSelectShippingContact: contact) { status, shippingMethod, summaryItems in + let result = PKPaymentRequestShippingContactUpdate(errors: nil, paymentSummaryItems: summaryItems, shippingMethods: shippingMethod) + result.status = status + completion(result) + } + } public func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectShippingContact contact: PKContact, completion: @escaping (PKPaymentAuthorizationStatus, [PKShippingMethod], [PKPaymentSummaryItem]) -> Void) { Log("Selecting shipping contact...") @@ -293,6 +311,15 @@ extension PaySession: PKPaymentAuthorizationControllerDelegate { }) }) } + + @available(iOS 11.0, *) + public func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectShippingMethod shippingMethod: PKShippingMethod, handler completion: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void) { + self.paymentAuthorizationController(controller, didSelectShippingMethod: shippingMethod) { status, summaryItems in + let result = PKPaymentRequestShippingMethodUpdate(paymentSummaryItems: summaryItems) + result.status = status + completion(result) + } + } public func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectShippingMethod shippingMethod: PKShippingMethod, completion: @escaping (PKPaymentAuthorizationStatus, [PKPaymentSummaryItem]) -> Void) { Log("Selecting delivery method...") diff --git a/PayTests/Mocks/MockAuthorizationController.swift b/PayTests/Mocks/MockAuthorizationController.swift index 3a219f2c..a80ab770 100644 --- a/PayTests/Mocks/MockAuthorizationController.swift +++ b/PayTests/Mocks/MockAuthorizationController.swift @@ -60,7 +60,7 @@ final class MockAuthorizationController: PKPaymentAuthorizationController { static func invokeDidAuthorizePayment(_ payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) { self.instances.forEach { authorizationController in - authorizationController.delegate?.paymentAuthorizationController(authorizationController, didAuthorizePayment: payment, completion: completion) + authorizationController.delegate?.paymentAuthorizationController?(authorizationController, didAuthorizePayment: payment, completion: completion) } } @@ -83,4 +83,31 @@ final class MockAuthorizationController: PKPaymentAuthorizationController { authorizationController.delegate?.paymentAuthorizationController?(authorizationController, didSelectPaymentMethod: paymentMethod, completion: completion) } } + + // ---------------------------------- + // MARK: - iOS 11 - + // + @available(iOS 11.0, *) + static func invokeDidAuthorizePaymentHandler(_ payment: PKPayment, handler: @escaping (PKPaymentAuthorizationResult) -> Void) { + + self.instances.forEach { authorizationController in + authorizationController.delegate?.paymentAuthorizationController?(authorizationController, didAuthorizePayment: payment, handler: handler) + } + } + + @available(iOS 11.0, *) + static func invokeDidSelectShippingContactHandler(_ contact: PKContact, handler: @escaping (PKPaymentRequestShippingContactUpdate) -> Void) { + + self.instances.forEach { authorizationController in + authorizationController.delegate?.paymentAuthorizationController?(authorizationController, didSelectShippingContact: contact, handler: handler) + } + } + + @available(iOS 11.0, *) + static func invokeDidSelectShippingMethodHandler(_ shippingMethod: PKShippingMethod, handler: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void) { + + self.instances.forEach { authorizationController in + authorizationController.delegate?.paymentAuthorizationController?(authorizationController, didSelectShippingMethod: shippingMethod, handler: handler) + } + } } diff --git a/PayTests/Mocks/MockPaySession.swift b/PayTests/Mocks/MockPaySession.swift new file mode 100644 index 00000000..0a1a9268 --- /dev/null +++ b/PayTests/Mocks/MockPaySession.swift @@ -0,0 +1,76 @@ +// +// MockPaySession.swift +// PayTests +// +// Created by Dima Bart on 2017-09-20. +// Copyright © 2017 Shopify Inc. All rights reserved. +// + +import Foundation +import PassKit +@testable import Pay + +@available(iOS 11.0, *) +class MockPaySession: PaySession { + + enum Status { + case handled + case unhandled + } + + // ---------------------------------- + // MARK: - iOS 11 - + // + var didSelectShippingContactHandler: ((PKPaymentAuthorizationController, PKContact, @escaping (PKPaymentRequestShippingContactUpdate) -> Void) -> Status)? + var didSelectShippingMethodHandler: ((PKPaymentAuthorizationController, PKShippingMethod, @escaping (PKPaymentRequestShippingMethodUpdate) -> Void) -> Status)? + var didAuthorizePaymentHandler: ((PKPaymentAuthorizationController, PKPayment, @escaping (PKPaymentAuthorizationResult) -> Void) -> Status)? + + override func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectShippingContact contact: PKContact, handler: @escaping (PKPaymentRequestShippingContactUpdate) -> Void) { + let status = self.didSelectShippingContactHandler?(controller, contact, handler) ?? .unhandled + if status == .unhandled { + super.paymentAuthorizationController(controller, didSelectShippingContact: contact, handler: handler) + } + } + + override func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectShippingMethod shippingMethod: PKShippingMethod, handler: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void) { + let status = self.didSelectShippingMethodHandler?(controller, shippingMethod, handler) ?? .unhandled + if status == .unhandled { + super.paymentAuthorizationController(controller, didSelectShippingMethod: shippingMethod, handler: handler) + } + } + + override func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, handler: @escaping (PKPaymentAuthorizationResult) -> Void) { + let status = self.didAuthorizePaymentHandler?(controller, payment, handler) ?? .unhandled + if status == .unhandled { + super.paymentAuthorizationController(controller, didAuthorizePayment: payment, handler: handler) + } + } + + // ---------------------------------- + // MARK: - Delegate Callbacks - + // + var didSelectShippingContact: ((PKPaymentAuthorizationController, PKContact, @escaping (PKPaymentAuthorizationStatus, [PKShippingMethod], [PKPaymentSummaryItem]) -> Void) -> Status)? + var didSelectShippingMethod: ((PKPaymentAuthorizationController, PKShippingMethod, @escaping (PKPaymentAuthorizationStatus, [PKPaymentSummaryItem]) -> Void) -> Status)? + var didAuthorizePayment: ((PKPaymentAuthorizationController, PKPayment, @escaping (PKPaymentAuthorizationStatus) -> Void) -> Status)? + + override func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectShippingContact contact: PKContact, completion: @escaping (PKPaymentAuthorizationStatus, [PKShippingMethod], [PKPaymentSummaryItem]) -> Void) { + let status = self.didSelectShippingContact?(controller, contact, completion) ?? .unhandled + if status == .unhandled { + super.paymentAuthorizationController(controller, didSelectShippingContact: contact, completion: completion) + } + } + + override func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectShippingMethod shippingMethod: PKShippingMethod, completion: @escaping (PKPaymentAuthorizationStatus, [PKPaymentSummaryItem]) -> Void) { + let status = self.didSelectShippingMethod?(controller, shippingMethod, completion) ?? .unhandled + if status == .unhandled { + super.paymentAuthorizationController(controller, didSelectShippingMethod: shippingMethod, completion: completion) + } + } + + override func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) { + let status = self.didAuthorizePayment?(controller, payment, completion) ?? .unhandled + if status == .unhandled { + super.paymentAuthorizationController(controller, didAuthorizePayment: payment, completion: completion) + } + } +} diff --git a/PayTests/Models/Models.swift b/PayTests/Models/Models.swift index 1ac22b95..20a72da9 100644 --- a/PayTests/Models/Models.swift +++ b/PayTests/Models/Models.swift @@ -28,6 +28,7 @@ import Foundation import PassKit import Pay +@available(iOS 11.0, *) struct Models { static func createShippingMethod(identifier: String? = nil) -> PKShippingMethod { @@ -65,8 +66,8 @@ struct Models { // ---------------------------------- // MARK: - Pay Models - // - static func createSession(checkout: PayCheckout, currency: PayCurrency) -> PaySession { - return PaySession(checkout: checkout, currency: currency, merchantID: "com.merchant.identifier", controllerType: MockAuthorizationController.self) + static func createSession(checkout: PayCheckout, currency: PayCurrency) -> MockPaySession { + return MockPaySession(checkout: checkout, currency: currency, merchantID: "com.merchant.identifier", controllerType: MockAuthorizationController.self) } static func createDiscount() -> PayDiscount { diff --git a/PayTests/PayAddressTests.swift b/PayTests/PayAddressTests.swift index a82a07e5..0f37cb9c 100644 --- a/PayTests/PayAddressTests.swift +++ b/PayTests/PayAddressTests.swift @@ -28,6 +28,7 @@ import XCTest import PassKit @testable import Pay +@available(iOS 11.0, *) class PayAddressTests: XCTestCase { // ---------------------------------- diff --git a/PayTests/PayAuthorizationTests.swift b/PayTests/PayAuthorizationTests.swift index acf3d6c2..021c7aa5 100644 --- a/PayTests/PayAuthorizationTests.swift +++ b/PayTests/PayAuthorizationTests.swift @@ -27,6 +27,7 @@ import XCTest @testable import Pay +@available(iOS 11.0, *) class PayAuthorizationTests: XCTestCase { // ---------------------------------- diff --git a/PayTests/PayCheckoutTests.swift b/PayTests/PayCheckoutTests.swift index cf7c846f..de11f73b 100644 --- a/PayTests/PayCheckoutTests.swift +++ b/PayTests/PayCheckoutTests.swift @@ -27,6 +27,7 @@ import XCTest @testable import Pay +@available(iOS 11.0, *) class PayCheckoutTests: XCTestCase { // ---------------------------------- diff --git a/PayTests/PaySessionTests.swift b/PayTests/PaySessionTests.swift index 36681908..29e31ec2 100644 --- a/PayTests/PaySessionTests.swift +++ b/PayTests/PaySessionTests.swift @@ -28,6 +28,7 @@ import XCTest import PassKit @testable import Pay +@available(iOS 11.0, *) class PaySessionTests: XCTestCase { // ---------------------------------- @@ -246,7 +247,6 @@ class PaySessionTests: XCTestCase { let shippingContact = Models.createContact() let shippingAddress = Models.createAddress() - let shippingRate = Models.createShippingRate() let addressCheckout = Models.createCheckout(requiresShipping: false, shippingAddress: shippingAddress) let e1 = self.expectation(description: "") @@ -453,6 +453,116 @@ class PaySessionTests: XCTestCase { self.wait(for: [expectation], timeout: 10) } + // ---------------------------------- + // MARK: - iOS 11 Support - + // + @available(iOS 11.0, *) + func testSessionForwardsShippingContact() { + let checkout = Models.createCheckout(requiresShipping: true) + let session = Models.createSession(checkout: checkout, currency: Models.createCurrency()) + + let shippingContact = Models.createContact() + let shippingMethods = [Models.createShippingMethod()] + let summaryItems = checkout.summaryItems + + let e2 = self.expectation(description: "") + session.didSelectShippingContactHandler = { controller, contact, handler in + XCTAssertTrue(contact === shippingContact) + e2.fulfill() + return .unhandled + } + + let e1 = self.expectation(description: "") + session.didSelectShippingContact = { controller, contact, completion in + completion(.success, shippingMethods, summaryItems) + e1.fulfill() + return .handled + } + + session.authorize() + + let e3 = self.expectation(description: "") + MockAuthorizationController.invokeDidSelectShippingContactHandler(shippingContact) { result in + + XCTAssertEqual(result.status, .success) + XCTAssertEqual(result.shippingMethods, shippingMethods) + XCTAssertEqual(result.paymentSummaryItems, summaryItems) + + e3.fulfill() + } + + self.wait(for: [e1, e2, e3], timeout: 2.0) + } + + @available(iOS 11.0, *) + func testSessionForwardsShippingMethod() { + + let shippingMethod = Models.createShippingMethod() + let checkout = Models.createCheckout(requiresShipping: true, shippingRate: Models.createShippingRate()) + let session = Models.createSession(checkout: checkout, currency: Models.createCurrency()) + let summaryItems = checkout.summaryItems + + let e2 = self.expectation(description: "") + session.didSelectShippingMethodHandler = { controller, method, handler in + XCTAssertTrue(method === shippingMethod) + e2.fulfill() + return .unhandled + } + + let e1 = self.expectation(description: "") + session.didSelectShippingMethod = { controller, shippingMethod, completion in + completion(.success, summaryItems) + e1.fulfill() + return .handled + } + + session.authorize() + + let e3 = self.expectation(description: "") + MockAuthorizationController.invokeDidSelectShippingMethodHandler(shippingMethod) { result in + XCTAssertEqual(result.status, .success) + XCTAssertEqual(result.paymentSummaryItems, summaryItems) + e3.fulfill() + } + + self.wait(for: [e1, e2, e3], timeout: 2.0) + } + + @available(iOS 11.0, *) + func testSessionForwardsAuthorizedPayment() { + + let checkout = Models.createCheckout(requiresShipping: true, shippingRate: Models.createShippingRate()) + let session = Models.createSession(checkout: checkout, currency: Models.createCurrency()) + + let method = MockPaymentMethod(displayName: "Test Payment", network: .amex, type: .credit) + let token = MockPaymentToken(paymentMethod: method) + let payment = MockPayment(token: token) + + let e2 = self.expectation(description: "") + session.didAuthorizePaymentHandler = { controller, currentPayment, handler in + XCTAssertTrue(currentPayment === payment) + e2.fulfill() + return .unhandled + } + + let e1 = self.expectation(description: "") + session.didAuthorizePayment = { controller, payment, completion in + completion(.success) + e1.fulfill() + return .handled + } + + session.authorize() + + let e3 = self.expectation(description: "") + MockAuthorizationController.invokeDidAuthorizePaymentHandler(payment) { result in + XCTAssertEqual(result.status, .success) + e3.fulfill() + } + + self.wait(for: [e1, e2, e3], timeout: 2.0) + } + // ---------------------------------- // MARK: - Session Delegate - // diff --git a/PayTests/PayShippingRateTests.swift b/PayTests/PayShippingRateTests.swift index 3d75b4a9..a2908242 100644 --- a/PayTests/PayShippingRateTests.swift +++ b/PayTests/PayShippingRateTests.swift @@ -28,6 +28,7 @@ import XCTest import PassKit @testable import Pay +@available(iOS 11.0, *) class PayShippingRateTests: XCTestCase { // ---------------------------------- diff --git a/Sample Apps/Storefront/Storefront.xcodeproj/project.pbxproj b/Sample Apps/Storefront/Storefront.xcodeproj/project.pbxproj index 00675a85..9c90b52e 100644 --- a/Sample Apps/Storefront/Storefront.xcodeproj/project.pbxproj +++ b/Sample Apps/Storefront/Storefront.xcodeproj/project.pbxproj @@ -122,6 +122,20 @@ remoteGlobalIDString = 9AFA38EA1E64850A0056C5AA; remoteInfo = BuyTests; }; + 9AFEF4671F72A7CC003FA8C5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9AEF61BF1E606C710067FA90 /* Buy.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9AC2EFC81F6818180037E0D7; + remoteInfo = "Buy watchOS"; + }; + 9AFEF4691F72A7CC003FA8C5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9AEF61BF1E606C710067FA90 /* Buy.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9AF256421F6FEE50005BB0C9; + remoteInfo = "Buy tvOS"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -493,8 +507,10 @@ isa = PBXGroup; children = ( 9AEF61C41E606C710067FA90 /* Buy.framework */, - 9AFA3B7B1E6DB6FE0056C5AA /* BuyTests.xctest */, + 9AFEF4681F72A7CC003FA8C5 /* Buy.framework */, + 9AFEF46A1F72A7CC003FA8C5 /* Buy.framework */, 9A4069021E8E76AB000254CD /* Pay.framework */, + 9AFA3B7B1E6DB6FE0056C5AA /* BuyTests.xctest */, 9A4069041E8E76AB000254CD /* PayTests.xctest */, ); name = Products; @@ -618,6 +634,20 @@ remoteRef = 9AFA3B7A1E6DB6FE0056C5AA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 9AFEF4681F72A7CC003FA8C5 /* Buy.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Buy.framework; + remoteRef = 9AFEF4671F72A7CC003FA8C5 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 9AFEF46A1F72A7CC003FA8C5 /* Buy.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Buy.framework; + remoteRef = 9AFEF4691F72A7CC003FA8C5 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ @@ -840,7 +870,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.shopify.buysdk.storefront; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -854,7 +884,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.shopify.buysdk.storefront; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Sample Apps/Storefront/Storefront.xcodeproj/xcshareddata/xcschemes/Storefront.xcscheme b/Sample Apps/Storefront/Storefront.xcodeproj/xcshareddata/xcschemes/Storefront.xcscheme index 97725387..64c19595 100644 --- a/Sample Apps/Storefront/Storefront.xcodeproj/xcshareddata/xcschemes/Storefront.xcscheme +++ b/Sample Apps/Storefront/Storefront.xcodeproj/xcshareddata/xcschemes/Storefront.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES"> @@ -45,6 +46,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Sample Apps/Storefront/Storefront/CartButton.swift b/Sample Apps/Storefront/Storefront/CartButton.swift index d340071e..ebae07bc 100644 --- a/Sample Apps/Storefront/Storefront/CartButton.swift +++ b/Sample Apps/Storefront/Storefront/CartButton.swift @@ -82,7 +82,7 @@ class CartButton: UIBarButtonItem { NotificationCenter.default.removeObserver(self) } - private dynamic func itemCountDidChange(_ notification: Notification) { + @objc private func itemCountDidChange(_ notification: Notification) { self.updateBadgeCount(animated: true) print("Badge count change notification received") } @@ -113,7 +113,7 @@ class CartButton: UIBarButtonItem { // ---------------------------------- // MARK: - Actions - // - dynamic private func cartAction(_ sender: Any) { + @objc private func cartAction(_ sender: Any) { if let target = self.target, let selector = self.action { @@ -300,7 +300,7 @@ private class BadgeView: UIView { private func initialize() { self.clipsToBounds = true - self.backgroundColor = UIColor(colorLiteralRed: 0.9372, green: 0.3372, blue: 0.2156, alpha: 1.0).withAlphaComponent(0.9) + self.backgroundColor = #colorLiteral(red: 0.937254902, green: 0.337254902, blue: 0.2156862745, alpha: 1).withAlphaComponent(0.9) self.initLabel() } diff --git a/Sample Apps/Storefront/Storefront/CartViewController.swift b/Sample Apps/Storefront/Storefront/CartViewController.swift index 7644fcfe..c33ce791 100644 --- a/Sample Apps/Storefront/Storefront/CartViewController.swift +++ b/Sample Apps/Storefront/Storefront/CartViewController.swift @@ -96,7 +96,7 @@ class CartViewController: ParallaxViewController { NotificationCenter.default.removeObserver(self) } - private dynamic func cartControllerItemsDidChange(_ notification: Notification) { + @objc private func cartControllerItemsDidChange(_ notification: Notification) { self.updateSubtotal() } @@ -114,7 +114,7 @@ class CartViewController: ParallaxViewController { func openSafariFor(_ checkout: CheckoutViewModel) { let safari = SFSafariViewController(url: checkout.webURL) safari.navigationItem.title = "Checkout" - self.navigationController?.show(safari, sender: self) + self.navigationController?.present(safari, animated: true, completion: nil) } func authorizePaymentWith(_ checkout: CheckoutViewModel) { diff --git a/Sample Apps/Storefront/Storefront/ProductViewModel.swift b/Sample Apps/Storefront/Storefront/ProductViewModel.swift index 435bdf52..62e28749 100644 --- a/Sample Apps/Storefront/Storefront/ProductViewModel.swift +++ b/Sample Apps/Storefront/Storefront/ProductViewModel.swift @@ -49,7 +49,7 @@ final class ProductViewModel: ViewModel { self.cursor = model.cursor let variants = model.node.variants.edges.viewModels.sorted { - $0.0.price < $0.1.price + $0.price < $1.price } let lowestPrice = variants.first?.price diff --git a/Sample Apps/Storefront/Storefront/String+HTML.swift b/Sample Apps/Storefront/Storefront/String+HTML.swift index 6f8c3199..aa34e38b 100644 --- a/Sample Apps/Storefront/Storefront/String+HTML.swift +++ b/Sample Apps/Storefront/Storefront/String+HTML.swift @@ -39,9 +39,9 @@ extension String { let styledHTML = self.trimmingCharacters(in: CharacterSet.newlines).appending(style) let htmlData = styledHTML.data(using: .utf8)! - let options: [String: Any] = [ - NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType, - NSCharacterEncodingDocumentAttribute : String.Encoding.utf8.rawValue, + let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [ + NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html, + NSAttributedString.DocumentReadingOptionKey.characterEncoding : String.Encoding.utf8.rawValue, ] return try? NSAttributedString(data: htmlData, options: options, documentAttributes: nil) diff --git a/Sample Apps/Storefront/Storefront/SubtitleButton.swift b/Sample Apps/Storefront/Storefront/SubtitleButton.swift index d9c216bb..a8f2ac14 100644 --- a/Sample Apps/Storefront/Storefront/SubtitleButton.swift +++ b/Sample Apps/Storefront/Storefront/SubtitleButton.swift @@ -45,16 +45,16 @@ class SubtitleButton: RoundedButton { style.lineBreakMode = .byClipping let attributedTitle = NSMutableAttributedString(string: title, attributes: [ - NSFontAttributeName : currentFont, - NSParagraphStyleAttributeName : style, - NSForegroundColorAttributeName: currentColor - ]) + NSAttributedStringKey.font : currentFont, + NSAttributedStringKey.paragraphStyle : style, + NSAttributedStringKey.foregroundColor: currentColor + ]) let attributedSubtitle = NSAttributedString(string: "\n\(self.subtitle)", attributes: [ - NSFontAttributeName : UIFont(descriptor: currentFont.fontDescriptor, size: currentFont.pointSize * 0.6), - NSParagraphStyleAttributeName : style, - NSForegroundColorAttributeName: currentColor.withAlphaComponent(0.6) - ]) + NSAttributedStringKey.font : UIFont(descriptor: currentFont.fontDescriptor, size: currentFont.pointSize * 0.6), + NSAttributedStringKey.paragraphStyle : style, + NSAttributedStringKey.foregroundColor: currentColor.withAlphaComponent(0.6) + ]) attributedTitle.append(attributedSubtitle) diff --git a/Sample Apps/Storefront/Storefront/TotalsViewController.swift b/Sample Apps/Storefront/Storefront/TotalsViewController.swift index 9ac4d530..e4199192 100644 --- a/Sample Apps/Storefront/Storefront/TotalsViewController.swift +++ b/Sample Apps/Storefront/Storefront/TotalsViewController.swift @@ -76,7 +76,7 @@ class TotalsViewController: UIViewController { self.buttonStackView.addArrangedSubview(webCheckout) if PKPaymentAuthorizationController.canMakePayments() { - let applePay = PKPaymentButton(type: .buy, style: .black) + let applePay = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black) applePay.addTarget(self, action: #selector(applePayAction(_:)), for: .touchUpInside) self.buttonStackView.addArrangedSubview(applePay) } @@ -85,11 +85,11 @@ class TotalsViewController: UIViewController { // ---------------------------------- // MARK: - Actions - // - dynamic func webCheckoutAction(_ sender: Any) { + @objc func webCheckoutAction(_ sender: Any) { self.delegate?.totalsController(self, didRequestPaymentWith: .webCheckout) } - dynamic func applePayAction(_ sender: Any) { + @objc func applePayAction(_ sender: Any) { self.delegate?.totalsController(self, didRequestPaymentWith: .applePay) } } diff --git a/circle.yml b/circle.yml index 4e98c2bb..742002dd 100644 --- a/circle.yml +++ b/circle.yml @@ -1,6 +1,6 @@ machine: xcode: - version: 8.3 + version: 9.0 checkout: post: