diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index da061a903b..cd6871774a 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -337,6 +337,7 @@ BED00CAE28A5419900D74AEC /* BTBinData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BED00CAD28A5419900D74AEC /* BTBinData.swift */; }; BED00CB028A579D700D74AEC /* BTClientToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = BED00CAF28A579D700D74AEC /* BTClientToken.swift */; }; BED00CB228A57AD400D74AEC /* BTClientTokenError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BED00CB128A57AD400D74AEC /* BTClientTokenError.swift */; }; + BED3A2C62C5D74B20034D9A6 /* LinkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BED3A2C52C5D74AC0034D9A6 /* LinkType.swift */; }; BED7493628579BAC0074C818 /* BTURLUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BED7493528579BAC0074C818 /* BTURLUtils.swift */; }; BEDA91A028EDDE64007441D9 /* FakeAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEDA919F28EDDE64007441D9 /* FakeAnalyticsService.swift */; }; BEDB820229B109EE00075AF3 /* BTApplePayAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEDB820129B109EE00075AF3 /* BTApplePayAnalytics.swift */; }; @@ -1066,6 +1067,7 @@ BED00CAD28A5419900D74AEC /* BTBinData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTBinData.swift; sourceTree = ""; }; BED00CAF28A579D700D74AEC /* BTClientToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTClientToken.swift; sourceTree = ""; }; BED00CB128A57AD400D74AEC /* BTClientTokenError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTClientTokenError.swift; sourceTree = ""; }; + BED3A2C52C5D74AC0034D9A6 /* LinkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkType.swift; sourceTree = ""; }; BED7493528579BAC0074C818 /* BTURLUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTURLUtils.swift; sourceTree = ""; }; BEDA919F28EDDE64007441D9 /* FakeAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeAnalyticsService.swift; sourceTree = ""; }; BEDB820129B109EE00075AF3 /* BTApplePayAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTApplePayAnalytics.swift; sourceTree = ""; }; @@ -1599,6 +1601,7 @@ BEE2E4E5290080BD00C03FDD /* BTAnalyticsServiceError.swift */, BEF388C02BE52CD2000965C8 /* BTCoreAnalytics.swift */, 800E78C329E0DD5300D1B0FC /* FPTIBatchData.swift */, + BED3A2C52C5D74AC0034D9A6 /* LinkType.swift */, 457D7FC72C29CEC300EF6523 /* RepeatingTimer.swift */, ); path = Analytics; @@ -3312,6 +3315,7 @@ BE63A3A7288F3026001936DA /* BTPostalAddress.swift in Sources */, BE2F98D028A2BCCD008EF189 /* BTConfiguration.swift in Sources */, 804DC45D2B2D08FF00F17A15 /* BTConfigurationRequest.swift in Sources */, + BED3A2C62C5D74B20034D9A6 /* LinkType.swift in Sources */, BED00CB228A57AD400D74AEC /* BTClientTokenError.swift in Sources */, BE24C67328E73E810067B11A /* BTAPIClientHTTPType.swift in Sources */, 457D7FC82C29CEC300EF6523 /* RepeatingTimer.swift in Sources */, diff --git a/Sources/BraintreeCore/Analytics/LinkType.swift b/Sources/BraintreeCore/Analytics/LinkType.swift new file mode 100644 index 0000000000..0fb70292ce --- /dev/null +++ b/Sources/BraintreeCore/Analytics/LinkType.swift @@ -0,0 +1,7 @@ +/// Used to describe the link type for analytics +/// :nodoc: This class is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. +@_documentation(visibility: private) +public enum LinkType: String { + case universal + case deeplink +} diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 4479a94a05..3b565a6d4e 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -310,7 +310,7 @@ import Foundation errorDescription: String? = nil, isConfigFromCache: Bool? = nil, isVaultRequest: Bool? = nil, - linkType: String? = nil, + linkType: LinkType? = nil, payPalContextID: String? = nil ) { analyticsService.sendAnalyticsEvent( @@ -320,7 +320,7 @@ import Foundation eventName: eventName, isConfigFromCache: isConfigFromCache, isVaultRequest: isVaultRequest, - linkType: linkType, + linkType: linkType?.rawValue, payPalContextID: payPalContextID ) ) diff --git a/Sources/BraintreePayPal/BTPayPalApprovalURLParser.swift b/Sources/BraintreePayPal/BTPayPalApprovalURLParser.swift index cf0c28a820..1958702399 100644 --- a/Sources/BraintreePayPal/BTPayPalApprovalURLParser.swift +++ b/Sources/BraintreePayPal/BTPayPalApprovalURLParser.swift @@ -45,8 +45,8 @@ struct BTPayPalApprovalURLParser { return nil } - init?(body: BTJSON, linkType: String?) { - if linkType == "universal", let payPalAppRedirectURL = body["agreementSetup"]["paypalAppApprovalUrl"].asURL() { + init?(body: BTJSON) { + if let payPalAppRedirectURL = body["agreementSetup"]["paypalAppApprovalUrl"].asURL() { redirectType = .payPalApp(url: payPalAppRedirectURL) url = payPalAppRedirectURL } else if let approvalURL = body["paymentResource"]["redirectUrl"].asURL() ?? diff --git a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift index adeb355c9b..7032253a99 100644 --- a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift @@ -120,7 +120,7 @@ import BraintreeCore /// :nodoc: Exposed publicly for use by PayPal Native Checkout module. This method is not covered by semantic versioning. @_documentation(visibility: private) - public override func parameters(with configuration: BTConfiguration, universalLink: URL? = nil) -> [String: Any] { + public override func parameters(with configuration: BTConfiguration, universalLink: URL? = nil, isPayPalAppInstalled: Bool = false) -> [String: Any] { var baseParameters = super.parameters(with: configuration) var checkoutParameters: [String: Any] = [ "intent": intent.stringValue, diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 6cf56ed62f..8ea2136b6c 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -37,9 +37,6 @@ import BraintreeDataCollector /// This allows us to set and return a completion in our methods that otherwise cannot require a completion. var appSwitchCompletion: (BTPayPalAccountNonce?, Error?) -> Void = { _, _ in } - /// Exposed for testing to check if the PayPal app is installed - var payPalAppInstalled: Bool = false - /// True if `tokenize()` was called with a Vault request object type var isVaultRequest: Bool = false @@ -65,7 +62,7 @@ import BraintreeDataCollector private var isConfigFromCache: Bool? /// Used for sending the type of flow, universal vs deeplink to FPTI - private var linkType: String? = nil + private var linkType: LinkType? = nil // MARK: - Initializer @@ -311,8 +308,7 @@ import BraintreeDataCollector request: BTPayPalRequest, completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void ) { - payPalAppInstalled = application.isPayPalAppInstalled() - linkType = (request as? BTPayPalVaultRequest)?.enablePayPalAppSwitch == true && payPalAppInstalled ? "universal" : "deeplink" + linkType = (request as? BTPayPalVaultRequest)?.enablePayPalAppSwitch == true ? .universal : .deeplink apiClient.sendAnalyticsEvent(BTPayPalAnalytics.tokenizeStarted, isVaultRequest: isVaultRequest, linkType: linkType) apiClient.fetchOrReturnRemoteConfiguration { configuration, error in @@ -333,14 +329,14 @@ import BraintreeDataCollector return } - if !self.payPalAppInstalled { - (request as? BTPayPalVaultRequest)?.enablePayPalAppSwitch = false - } - self.payPalRequest = request self.apiClient.post( request.hermesPath, - parameters: request.parameters(with: configuration, universalLink: self.universalLink) + parameters: request.parameters( + with: configuration, + universalLink: self.universalLink, + isPayPalAppInstalled: self.application.isPayPalAppInstalled() + ) ) { body, response, error in if let error = error as? NSError { guard let jsonResponseBody = error.userInfo[BTCoreConstants.jsonResponseBodyKey] as? BTJSON else { @@ -355,7 +351,7 @@ import BraintreeDataCollector return } - guard let body, let approvalURL = BTPayPalApprovalURLParser(body: body, linkType: self.linkType) else { + guard let body, let approvalURL = BTPayPalApprovalURLParser(body: body) else { self.notifyFailure(with: BTPayPalError.invalidURL("Missing approval URL in gateway response."), completion: completion) return } diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index 2ec43edfdb..a2aefcec84 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -135,7 +135,7 @@ import BraintreeCore /// :nodoc: Exposed publicly for use by PayPal Native Checkout module. This method is not covered by semantic versioning. @_documentation(visibility: private) - public func parameters(with configuration: BTConfiguration, universalLink: URL? = nil) -> [String: Any] { + public func parameters(with configuration: BTConfiguration, universalLink: URL? = nil, isPayPalAppInstalled: Bool = false) -> [String: Any] { var experienceProfile: [String: Any] = [:] experienceProfile["no_shipping"] = !isShippingAddressRequired diff --git a/Sources/BraintreePayPal/BTPayPalReturnURL.swift b/Sources/BraintreePayPal/BTPayPalReturnURL.swift index ab4e964133..9537c41885 100644 --- a/Sources/BraintreePayPal/BTPayPalReturnURL.swift +++ b/Sources/BraintreePayPal/BTPayPalReturnURL.swift @@ -42,7 +42,7 @@ struct BTPayPalReturnURL { url.scheme == "https" && (url.path.contains("cancel") || url.path.contains("success")) } - static func isValidURLAction(url: URL, linkType: String?) -> Bool { + static func isValidURLAction(url: URL, linkType: LinkType?) -> Bool { guard let host = url.host, let scheme = url.scheme, !scheme.isEmpty else { return false } @@ -59,7 +59,7 @@ struct BTPayPalReturnURL { /// If we are using the deeplink/ASWeb based PayPal flow we want to check that the host and path matches /// the static callbackURLHostAndPath. For the universal link flow we do not care about this check. - if hostAndPath != BTPayPalRequest.callbackURLHostAndPath && linkType == "deeplink" { + if hostAndPath != BTPayPalRequest.callbackURLHostAndPath && linkType == .deeplink { return false } diff --git a/Sources/BraintreePayPal/BTPayPalVaultBaseRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultBaseRequest.swift index 7d2fbe7743..4a8f5b58a4 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultBaseRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultBaseRequest.swift @@ -27,7 +27,7 @@ import BraintreeCore /// :nodoc: Exposed publicly for use by PayPal Native Checkout module. This method is not covered by semantic versioning. @_documentation(visibility: private) - public override func parameters(with configuration: BTConfiguration, universalLink: URL? = nil) -> [String: Any] { + public override func parameters(with configuration: BTConfiguration, universalLink: URL? = nil, isPayPalAppInstalled: Bool = false) -> [String: Any] { let baseParameters = super.parameters(with: configuration) var vaultParameters: [String: Any] = ["offer_paypal_credit": offerCredit] diff --git a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index ae7d8657bf..4114ba940e 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift @@ -46,20 +46,21 @@ import BraintreeCore super.init(offerCredit: offerCredit) } - public override func parameters(with configuration: BTConfiguration, universalLink: URL? = nil) -> [String: Any] { + public override func parameters(with configuration: BTConfiguration, universalLink: URL? = nil, isPayPalAppInstalled: Bool = false) -> [String: Any] { var baseParameters = super.parameters(with: configuration) if let userAuthenticationEmail { baseParameters["payer_email"] = userAuthenticationEmail } - if enablePayPalAppSwitch, let universalLink { + if let universalLink, enablePayPalAppSwitch, isPayPalAppInstalled { let appSwitchParameters: [String: Any] = [ "launch_paypal_app": enablePayPalAppSwitch, "os_version": UIDevice.current.systemVersion, "os_type": UIDevice.current.systemName, "merchant_app_return_url": universalLink.absoluteString ] + return baseParameters.merging(appSwitchParameters) { $1 } } diff --git a/Sources/BraintreeVenmo/BTVenmoClient.swift b/Sources/BraintreeVenmo/BTVenmoClient.swift index d4ccdd2cf3..479e9fa41d 100644 --- a/Sources/BraintreeVenmo/BTVenmoClient.swift +++ b/Sources/BraintreeVenmo/BTVenmoClient.swift @@ -42,7 +42,7 @@ import BraintreeCore private var payPalContextID: String? = nil /// Used for sending the type of flow, universal vs deeplink to FPTI - private var linkType: String? = nil + private var linkType: LinkType? = nil // MARK: - Initializer @@ -64,7 +64,7 @@ import BraintreeCore /// If the user cancels out of the flow, the error code will be `.canceled`. @objc(tokenizeWithVenmoRequest:completion:) public func tokenize(_ request: BTVenmoRequest, completion: @escaping (BTVenmoAccountNonce?, Error?) -> Void) { - linkType = request.fallbackToWeb ? "universal" : "deeplink" + linkType = request.fallbackToWeb ? .universal : .deeplink apiClient.sendAnalyticsEvent(BTVenmoAnalytics.tokenizeStarted, isVaultRequest: shouldVault, linkType: linkType) let returnURLScheme = BTAppContextSwitcher.sharedInstance.returnURLScheme diff --git a/UnitTests/BraintreeCoreTests/Analytics/FPTIBatchData_Tests.swift b/UnitTests/BraintreeCoreTests/Analytics/FPTIBatchData_Tests.swift index 66a8ffb58f..c9b7c0e6ca 100644 --- a/UnitTests/BraintreeCoreTests/Analytics/FPTIBatchData_Tests.swift +++ b/UnitTests/BraintreeCoreTests/Analytics/FPTIBatchData_Tests.swift @@ -25,7 +25,7 @@ final class FPTIBatchData_Tests: XCTestCase { eventName: "fake-event-1", isConfigFromCache: false, isVaultRequest: false, - linkType: "universal", + linkType: LinkType.universal.rawValue, payPalContextID: "fake-order-id", requestStartTime: 456, startTime: 999888777666 diff --git a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift index 2fc9d7e7d6..7fff4ad489 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift @@ -232,7 +232,6 @@ class BTPayPalClient_Tests: XCTestCase { func testTokenize_whenPayPalAppApprovalURLContainsPayPalContextID_sendsPayPalContextIDAndLinkTypeInAnalytics() { let fakeApplication = FakeApplication() payPalClient.application = fakeApplication - payPalClient.payPalAppInstalled = true payPalClient.webAuthenticationSession = MockWebAuthenticationSession() let vaultRequest = BTPayPalVaultRequest( @@ -252,7 +251,7 @@ class BTPayPalClient_Tests: XCTestCase { payPalClient.handleReturnURL(returnURL) XCTAssertEqual(mockAPIClient.postedPayPalContextID, "BA-Random-Value") - XCTAssertEqual(mockAPIClient.postedLinkType, "universal") + XCTAssertEqual(mockAPIClient.postedLinkType, .universal) XCTAssertNotNil(payPalClient.clientMetadataID) } @@ -282,7 +281,7 @@ class BTPayPalClient_Tests: XCTestCase { payPalClient.tokenize(request) { _, _ in } XCTAssertEqual(mockAPIClient.postedPayPalContextID, "BA-Random-Value") - XCTAssertEqual(mockAPIClient.postedLinkType, "deeplink") + XCTAssertEqual(mockAPIClient.postedLinkType, .deeplink) XCTAssertTrue(mockAPIClient.postedAnalyticsEvents.contains("paypal:tokenize:handle-return:started")) } @@ -301,7 +300,7 @@ class BTPayPalClient_Tests: XCTestCase { payPalClient.tokenize(request) { _, _ in } XCTAssertEqual(mockAPIClient.postedPayPalContextID, "A_FAKE_BA_TOKEN") - XCTAssertEqual(mockAPIClient.postedLinkType, "deeplink") + XCTAssertEqual(mockAPIClient.postedLinkType, .deeplink) XCTAssertTrue(mockAPIClient.postedAnalyticsEvents.contains("paypal:tokenize:handle-return:started")) } @@ -931,8 +930,8 @@ class BTPayPalClient_Tests: XCTestCase { func testIsiOSAppSwitchAvailable_whenApplicationCanOpenPayPalInAppURL_returnsTrueAndSendsAnalytics() { let fakeApplication = FakeApplication() + fakeApplication.cannedCanOpenURL = true payPalClient.application = fakeApplication - payPalClient.payPalAppInstalled = true let vaultRequest = BTPayPalVaultRequest( userAuthenticationEmail: "fake@gmail.com", diff --git a/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift index 2036a75bb0..dedae48fee 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift @@ -1,6 +1,7 @@ import XCTest @testable import BraintreeCore @testable import BraintreePayPal +@testable import BraintreeTestShared class BTPayPalVaultRequest_Tests: XCTestCase { @@ -76,7 +77,7 @@ class BTPayPalVaultRequest_Tests: XCTestCase { enablePayPalAppSwitch: true ) - let parameters = request.parameters(with: configuration, universalLink: URL(string: "some-url")!) + let parameters = request.parameters(with: configuration, universalLink: URL(string: "some-url")!, isPayPalAppInstalled: true) XCTAssertEqual(parameters["launch_paypal_app"] as? Bool, true) XCTAssertTrue((parameters["os_version"] as! String).matches("\\d+\\.\\d+")) diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index 7f9e03f9eb..f46932572b 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -13,7 +13,7 @@ public class MockAPIClient: BTAPIClient { public var postedAnalyticsEvents : [String] = [] public var postedPayPalContextID: String? = nil - public var postedLinkType: String? = nil + public var postedLinkType: LinkType? = nil public var postedIsVaultRequest = false @objc public var cannedConfigurationResponseBody : BTJSON? = nil @@ -94,7 +94,7 @@ public class MockAPIClient: BTAPIClient { errorDescription: String? = nil, isConfigFromCache: Bool? = nil, isVaultRequest: Bool? = nil, - linkType: String? = nil, + linkType: LinkType? = nil, payPalContextID: String? = nil ) { postedPayPalContextID = payPalContextID diff --git a/UnitTests/BraintreeVenmoTests/BTVenmoClient_Tests.swift b/UnitTests/BraintreeVenmoTests/BTVenmoClient_Tests.swift index d1889ba17a..16ef497bd4 100644 --- a/UnitTests/BraintreeVenmoTests/BTVenmoClient_Tests.swift +++ b/UnitTests/BraintreeVenmoTests/BTVenmoClient_Tests.swift @@ -584,7 +584,7 @@ class BTVenmoClient_Tests: XCTestCase { XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.last!, BTVenmoAnalytics.tokenizeSucceeded) XCTAssertEqual(mockAPIClient.postedPayPalContextID, "some-resource-id") - XCTAssertEqual(mockAPIClient.postedLinkType, "deeplink") + XCTAssertEqual(mockAPIClient.postedLinkType, .deeplink) } func testTokenizeVenmoAccount_fallbackToWebTrue_sendsSuccessAnalyticsEvent() { @@ -625,7 +625,7 @@ class BTVenmoClient_Tests: XCTestCase { XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.last!, BTVenmoAnalytics.tokenizeSucceeded) XCTAssertEqual(mockAPIClient.postedPayPalContextID, "some-resource-id") - XCTAssertEqual(mockAPIClient.postedLinkType, "universal") + XCTAssertEqual(mockAPIClient.postedLinkType, .universal) } func testTokenizeVenmoAccount_vaultTrue_sendsFailureAnalyticsEvent() { @@ -651,7 +651,7 @@ class BTVenmoClient_Tests: XCTestCase { XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.last!, BTVenmoAnalytics.tokenizeFailed) XCTAssertEqual(mockAPIClient.postedPayPalContextID, "some-resource-id") - XCTAssertEqual(mockAPIClient.postedLinkType, "deeplink") + XCTAssertEqual(mockAPIClient.postedLinkType, .deeplink) } func testTokenizeVenmoAccount_whenAppSwitchCanceled_callsBackWithCancelError() {