Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QL] Consolidate Return URL Logic #1271

Merged
merged 11 commits into from
Apr 11, 2024
16 changes: 8 additions & 8 deletions Braintree.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@
BE698EA428AD2C10001D9B10 /* BTCoreConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE698EA328AD2C10001D9B10 /* BTCoreConstants.swift */; };
BE698EA628B3CDAD001D9B10 /* BTCacheDateValidator_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE698EA528B3CDAD001D9B10 /* BTCacheDateValidator_Tests.swift */; };
BE6BC22C2BA9C67600C3E321 /* BTPayPalVaultBaseRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6BC22B2BA9C67600C3E321 /* BTPayPalVaultBaseRequest.swift */; };
BE6BC22E2BA9CFFC00C3E321 /* BTPayPalAppSwitchReturnURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6BC22D2BA9CFFC00C3E321 /* BTPayPalAppSwitchReturnURL.swift */; };
BE6BC22E2BA9CFFC00C3E321 /* BTPayPalReturnURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6BC22D2BA9CFFC00C3E321 /* BTPayPalReturnURL.swift */; };
BE70A963284FA3F000F6D3F7 /* BTDataCollectorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE70A962284FA3F000F6D3F7 /* BTDataCollectorError.swift */; };
BE70A965284FA9DE00F6D3F7 /* MockBTDataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE70A964284FA9DE00F6D3F7 /* MockBTDataCollector.swift */; };
BE70A983284FC07C00F6D3F7 /* BraintreeDataCollector.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A76D7C001BB1CAB00000FA6A /* BraintreeDataCollector.framework */; };
Expand Down Expand Up @@ -265,7 +265,7 @@
BE9FB82B2898324C00D6FE2F /* BTPaymentMethodNonce.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9FB82A2898324C00D6FE2F /* BTPaymentMethodNonce.swift */; };
BE9FB82D28984ADE00D6FE2F /* BTPaymentMethodNonceParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9FB82C28984ADE00D6FE2F /* BTPaymentMethodNonceParser.swift */; };
BEB9BF532A26872B00A3673E /* BTWebAuthenticationSessionClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEB9BF522A26872B00A3673E /* BTWebAuthenticationSessionClient.swift */; };
BEBA590F2BB1B5B9005FA8A2 /* BTPayPalAppSwitchReturnURL_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEBA590E2BB1B5B9005FA8A2 /* BTPayPalAppSwitchReturnURL_Tests.swift */; };
BEBA590F2BB1B5B9005FA8A2 /* BTPayPalReturnURL_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEBA590E2BB1B5B9005FA8A2 /* BTPayPalReturnURL_Tests.swift */; };
BEBC222728D25BB400D83186 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DBE69423A931A600373230 /* Helpers.swift */; };
BEBC6E4B29258FD4004E25A0 /* BraintreeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 570B93AC285397520041BAFE /* BraintreeCore.framework */; };
BEBC6E5E2927CF59004E25A0 /* Braintree.h in Headers */ = {isa = PBXBuildFile; fileRef = BEBC6E5D2927CF59004E25A0 /* Braintree.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -870,7 +870,7 @@
BE698EA528B3CDAD001D9B10 /* BTCacheDateValidator_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTCacheDateValidator_Tests.swift; sourceTree = "<group>"; };
BE698EAA28B50F41001D9B10 /* BTClientToken_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTClientToken_Tests.swift; sourceTree = "<group>"; };
BE6BC22B2BA9C67600C3E321 /* BTPayPalVaultBaseRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalVaultBaseRequest.swift; sourceTree = "<group>"; };
BE6BC22D2BA9CFFC00C3E321 /* BTPayPalAppSwitchReturnURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalAppSwitchReturnURL.swift; sourceTree = "<group>"; };
BE6BC22D2BA9CFFC00C3E321 /* BTPayPalReturnURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalReturnURL.swift; sourceTree = "<group>"; };
BE70A962284FA3F000F6D3F7 /* BTDataCollectorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTDataCollectorError.swift; sourceTree = "<group>"; };
BE70A964284FA9DE00F6D3F7 /* MockBTDataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBTDataCollector.swift; sourceTree = "<group>"; };
BE7A9643299FC5DE009AB920 /* BTConfiguration+ApplePay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BTConfiguration+ApplePay.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -902,7 +902,7 @@
BE9FB82A2898324C00D6FE2F /* BTPaymentMethodNonce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPaymentMethodNonce.swift; sourceTree = "<group>"; };
BE9FB82C28984ADE00D6FE2F /* BTPaymentMethodNonceParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPaymentMethodNonceParser.swift; sourceTree = "<group>"; };
BEB9BF522A26872B00A3673E /* BTWebAuthenticationSessionClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTWebAuthenticationSessionClient.swift; sourceTree = "<group>"; };
BEBA590E2BB1B5B9005FA8A2 /* BTPayPalAppSwitchReturnURL_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalAppSwitchReturnURL_Tests.swift; sourceTree = "<group>"; };
BEBA590E2BB1B5B9005FA8A2 /* BTPayPalReturnURL_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalReturnURL_Tests.swift; sourceTree = "<group>"; };
BEBC6E5D2927CF59004E25A0 /* Braintree.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Braintree.h; sourceTree = "<group>"; };
BEBC6F252937A510004E25A0 /* BTClientMetadata_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTClientMetadata_Tests.swift; sourceTree = "<group>"; };
BEBC6F272937BD1F004E25A0 /* BTGraphQLHTTP_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTGraphQLHTTP_Tests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1197,7 +1197,7 @@
57544F572952298900DEB7B0 /* BTPayPalAccountNonce.swift */,
3B7A261029C0CAA40087059D /* BTPayPalAnalytics.swift */,
8014221B2BAE935B009F9999 /* BTPayPalApprovalURLParser.swift */,
BE6BC22D2BA9CFFC00C3E321 /* BTPayPalAppSwitchReturnURL.swift */,
BE6BC22D2BA9CFFC00C3E321 /* BTPayPalReturnURL.swift */,
BE8E5CEE294B6937001BF017 /* BTPayPalCheckoutRequest.swift */,
57544F5929524E4D00DEB7B0 /* BTPayPalClient.swift */,
5754481F294A2EBE00DEB7B0 /* BTPayPalCreditFinancing.swift */,
Expand Down Expand Up @@ -1713,13 +1713,13 @@
A95229C624FD949D006F7D25 /* BTConfiguration+PayPal_Tests.swift */,
BEDEAF102AC1D049004EA970 /* BTPayPalAccountNonce_Tests.swift */,
3B7A261229C35B670087059D /* BTPayPalAnalytics_Tests.swift */,
BEBA590E2BB1B5B9005FA8A2 /* BTPayPalReturnURL_Tests.swift */,
42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */,
427F32DF25D1D62D00435294 /* BTPayPalClient_Tests.swift */,
BECB10C52B5999EE008D398E /* BTPayPalLineItem_Tests.swift */,
42FC218A25CDE0290047C49A /* BTPayPalRequest_Tests.swift */,
427F328F25D1A7B900435294 /* BTPayPalVaultRequest_Tests.swift */,
A9E5C1E424FD665D00EE691F /* Info.plist */,
BEBA590E2BB1B5B9005FA8A2 /* BTPayPalAppSwitchReturnURL_Tests.swift */,
);
path = BraintreePayPalTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -2794,7 +2794,7 @@
BE349113294B798300D2CF68 /* BTPayPalRequest.swift in Sources */,
57544F5C295254A500DEB7B0 /* BTJSON+PayPal.swift in Sources */,
3B7A261129C0CAA40087059D /* BTPayPalAnalytics.swift in Sources */,
BE6BC22E2BA9CFFC00C3E321 /* BTPayPalAppSwitchReturnURL.swift in Sources */,
BE6BC22E2BA9CFFC00C3E321 /* BTPayPalReturnURL.swift in Sources */,
BE8E5CEF294B6937001BF017 /* BTPayPalCheckoutRequest.swift in Sources */,
5754481E294A2A1D00DEB7B0 /* BTPayPalCreditFinancingAmount.swift in Sources */,
57D9436E2968A8080079EAB1 /* BTPayPalLocaleCode.swift in Sources */,
Expand Down Expand Up @@ -3118,7 +3118,7 @@
BECB10C62B5999EE008D398E /* BTPayPalLineItem_Tests.swift in Sources */,
3B7A261429C35BD00087059D /* BTPayPalAnalytics_Tests.swift in Sources */,
A95229C724FD949D006F7D25 /* BTConfiguration+PayPal_Tests.swift in Sources */,
BEBA590F2BB1B5B9005FA8A2 /* BTPayPalAppSwitchReturnURL_Tests.swift in Sources */,
BEBA590F2BB1B5B9005FA8A2 /* BTPayPalReturnURL_Tests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<array>
<string>com.braintreepayments.Demo.payments</string>
<string>com.venmo.touch.v2</string>
<string>paypal</string>
<string>paypal-in-app-checkout</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
Expand Down
41 changes: 0 additions & 41 deletions Sources/BraintreePayPal/BTPayPalAppSwitchReturnURL.swift

This file was deleted.

4 changes: 2 additions & 2 deletions Sources/BraintreePayPal/BTPayPalApprovalURLParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ struct BTPayPalApprovalURLParser {
}
}

init?(body: BTJSON) {
if let payPalAppRedirectURL = body["agreementSetup"]["paypalAppApprovalUrl"].asURL() {
init?(body: BTJSON, linkType: String?) {
if linkType == "universal", let payPalAppRedirectURL = body["agreementSetup"]["paypalAppApprovalUrl"].asURL() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit for another time - maybe another indicator we should move this to an enum

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I don't think the BTPayPalApprovalURLParser class should care if the merchant has opted into universal links or not. It's only determining which URL type it got back from the GW response.

The flow logic for falling back to the web flow already is taken care of in BTPayPalClient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of Thursday, we were still getting the paypalAppApprovalUrl from the Gateway regardless of the BTPayPalClient. This may have been because we were working with a mock and an update may have been deployed to just allow our client side handling. Worth digging into but I was still getting this URL with the PPBeta app uninstalled.

redirectType = .payPalApp(url: payPalAppRedirectURL)
} else if let approvalURL = body["paymentResource"]["redirectUrl"].asURL() ??
body["agreementSetup"]["approvalUrl"].asURL() {
Expand Down
110 changes: 39 additions & 71 deletions Sources/BraintreePayPal/BTPayPalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ import BraintreeDataCollector

// MARK: - Private Properties

/// URL Scheme for PayPal In-App Checkout
private let payPalInAppScheme: String = "paypal-in-app-checkout://"

/// Indicates if the user returned back to the merchant app from the `BTWebAuthenticationSession`
/// Will only be `true` if the user proceed through the `UIAlertController`
private var webSessionReturned: Bool = false
Expand All @@ -59,9 +62,6 @@ import BraintreeDataCollector
/// Used for sending the type of flow, universal vs deeplink to FPTI
private var linkType: String? = nil

/// URL Scheme for PayPal In-App Checkout
private let payPalInAppScheme: String = "paypal-in-app-checkout://"

// MARK: - Initializer

/// Initialize a new PayPal client instance.
Expand Down Expand Up @@ -182,17 +182,30 @@ import BraintreeDataCollector
payPalContextID: payPalContextID,
payPalInstalled: payPalAppInstalled
)
guard let url, isValidURLAction(url: url) else {

guard let url, BTPayPalReturnURL.isValidURLAction(url: url, linkType: linkType) else {
notifyFailure(with: BTPayPalError.invalidURLAction, completion: completion)
return
}

guard let response = responseDictionary(from: url) else {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The responseDictionary method was a bit odd because it was both building the dictionary to pass into the post parameters as well as doing a check that the URL did not contain cancel. The only part that used the return URL was the cancel check so I broke this out to build the dictionary here which made more sense.

guard !url.absoluteString.contains("cancel") else {
notifyCancel(completion: completion)
return
}

var account: [String: Any] = response

let clientDictionary: [String: String] = [
"platform": "iOS",
"product_name": "PayPal",
"paypal_sdk_version": "version"
]

let responseDictionary: [String: String] = ["webURL": url.absoluteString]

var account: [String: Any] = [
"client": clientDictionary,
"response": responseDictionary,
"response_type": "web"
]

if paymentType == .checkout {
account["options"] = ["validate": false]
Expand Down Expand Up @@ -256,14 +269,16 @@ import BraintreeDataCollector
// MARK: - App Switch Methods

func handleReturnURL(_ url: URL) {
guard let returnURL = BTPayPalAppSwitchReturnURL(url: url) else {
guard let returnURL = BTPayPalReturnURL(.payPalApp(url: url)) else {
notifyFailure(with: BTPayPalError.invalidURL("App Switch return URL cannot be nil"), completion: appSwitchCompletion)
return
}

switch returnURL.state {
case .succeeded, .canceled:
case .succeeded:
handleReturn(url, paymentType: .vault, completion: appSwitchCompletion)
case .canceled:
notifyCancel(completion: appSwitchCompletion)
case .unknownPath:
notifyFailure(with: BTPayPalError.appSwitchReturnURLPathInvalid, completion: appSwitchCompletion)
}
Expand Down Expand Up @@ -314,7 +329,7 @@ import BraintreeDataCollector
return
}

guard let body, let approvalURL = BTPayPalApprovalURLParser(body: body) else {
guard let body, let approvalURL = BTPayPalApprovalURLParser(body: body, linkType: self.linkType) else {
self.notifyFailure(with: BTPayPalError.invalidURL("Missing approval URL in gateway response."), completion: completion)
return
}
Expand Down Expand Up @@ -409,7 +424,19 @@ import BraintreeDataCollector
return
}

handleReturn(url, paymentType: paymentType, completion: completion)
guard let url, let returnURL = BTPayPalReturnURL(.webBrowser(url: url)) else {
notifyFailure(with: BTPayPalError.invalidURL("ASWebAuthenticationSession return URL cannot be nil"), completion: completion)
return
}

switch returnURL.state {
case .succeeded:
handleReturn(url, paymentType: .vault, completion: completion)
case .canceled:
notifyCancel(completion: completion)
case .unknownPath:
notifyFailure(with: BTPayPalError.asWebAuthenticationSessionURLInvalid(url.absoluteString), completion: completion)
}
} sessionDidAppear: { [self] didAppear in
if didAppear {
apiClient.sendAnalyticsEvent(
Expand Down Expand Up @@ -443,65 +470,6 @@ import BraintreeDataCollector
return
}
}

private func isValidURLAction(url: URL) -> Bool {
guard let host = url.host, let scheme = url.scheme, !scheme.isEmpty else {
return false
}

var hostAndPath = host
.appending(url.path)
.components(separatedBy: "/")
.dropLast(1) // remove the action (`success`, `cancel`, etc)
.joined(separator: "/")

if hostAndPath.count > 0 {
hostAndPath.append("/")
}

if hostAndPath != BTPayPalRequest.callbackURLHostAndPath && (payPalRequest as? BTPayPalVaultRequest)?.universalLink == nil {
return false
}

guard let action = action(from: url),
let query = url.query,
query.count > 0,
action.count >= 0,
["success", "cancel", "authenticate"].contains(action) else {
return false
}

return true
}

private func responseDictionary(from url: URL) -> [String : Any]? {
if let action = action(from: url), action == "cancel" {
return nil
}

let clientDictionary: [String: String] = [
"platform": "iOS",
"product_name": "PayPal",
"paypal_sdk_version": "version"
]

let responseDictionary: [String: String] = ["webURL": url.absoluteString]

return [
"client": clientDictionary,
"response": responseDictionary,
"response_type": "web"
]
}

private func action(from url: URL) -> String? {
guard let action = url.lastPathComponent.components(separatedBy: "?").first,
!action.isEmpty else {
return url.host
}

return action
}

// MARK: - Analytics Helper Methods

Expand Down Expand Up @@ -554,6 +522,6 @@ extension BTPayPalClient: BTAppContextSwitchClient {
/// :nodoc:
@_documentation(visibility: private)
@objc public static func canHandleReturnURL(_ url: URL) -> Bool {
BTPayPalAppSwitchReturnURL.isValid(url)
BTPayPalReturnURL.isValid(url)
}
}
Loading
Loading