Skip to content

Commit

Permalink
Cleanup App Switch (#1381)
Browse files Browse the repository at this point in the history
* update app install check
* add LinkType enum; update linkType logic
* cleanup unneeded logic
* remove redundant enum type
* move constant first in conditional
* pass isPayPalAppInstalled into parameters; update tests; remove application from VaultRequest
  • Loading branch information
jaxdesmarais authored Aug 9, 2024
1 parent 38297e4 commit cc7317f
Show file tree
Hide file tree
Showing 16 changed files with 45 additions and 37 deletions.
4 changes: 4 additions & 0 deletions Braintree.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1066,6 +1067,7 @@
BED00CAD28A5419900D74AEC /* BTBinData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTBinData.swift; sourceTree = "<group>"; };
BED00CAF28A579D700D74AEC /* BTClientToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTClientToken.swift; sourceTree = "<group>"; };
BED00CB128A57AD400D74AEC /* BTClientTokenError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTClientTokenError.swift; sourceTree = "<group>"; };
BED3A2C52C5D74AC0034D9A6 /* LinkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkType.swift; sourceTree = "<group>"; };
BED7493528579BAC0074C818 /* BTURLUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTURLUtils.swift; sourceTree = "<group>"; };
BEDA919F28EDDE64007441D9 /* FakeAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeAnalyticsService.swift; sourceTree = "<group>"; };
BEDB820129B109EE00075AF3 /* BTApplePayAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTApplePayAnalytics.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1599,6 +1601,7 @@
BEE2E4E5290080BD00C03FDD /* BTAnalyticsServiceError.swift */,
BEF388C02BE52CD2000965C8 /* BTCoreAnalytics.swift */,
800E78C329E0DD5300D1B0FC /* FPTIBatchData.swift */,
BED3A2C52C5D74AC0034D9A6 /* LinkType.swift */,
457D7FC72C29CEC300EF6523 /* RepeatingTimer.swift */,
);
path = Analytics;
Expand Down Expand Up @@ -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 */,
Expand Down
7 changes: 7 additions & 0 deletions Sources/BraintreeCore/Analytics/LinkType.swift
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 2 additions & 2 deletions Sources/BraintreeCore/BTAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -320,7 +320,7 @@ import Foundation
eventName: eventName,
isConfigFromCache: isConfigFromCache,
isVaultRequest: isVaultRequest,
linkType: linkType,
linkType: linkType?.rawValue,
payPalContextID: payPalContextID
)
)
Expand Down
4 changes: 2 additions & 2 deletions Sources/BraintreePayPal/BTPayPalApprovalURLParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() ??
Expand Down
2 changes: 1 addition & 1 deletion Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
20 changes: 8 additions & 12 deletions Sources/BraintreePayPal/BTPayPalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/BraintreePayPal/BTPayPalRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Sources/BraintreePayPal/BTPayPalReturnURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/BraintreePayPal/BTPayPalVaultBaseRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
5 changes: 3 additions & 2 deletions Sources/BraintreePayPal/BTPayPalVaultRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/BraintreeVenmo/BTVenmoClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
}

Expand Down Expand Up @@ -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"))
}

Expand All @@ -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"))
}

Expand Down Expand Up @@ -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: "[email protected]",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import XCTest
@testable import BraintreeCore
@testable import BraintreePayPal
@testable import BraintreeTestShared

class BTPayPalVaultRequest_Tests: XCTestCase {

Expand Down Expand Up @@ -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+"))
Expand Down
4 changes: 2 additions & 2 deletions UnitTests/BraintreeTestShared/MockAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions UnitTests/BraintreeVenmoTests/BTVenmoClient_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand All @@ -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() {
Expand Down

0 comments on commit cc7317f

Please sign in to comment.