Skip to content

Commit

Permalink
Merge branch 'v7' into remove-card-add-challenge
Browse files Browse the repository at this point in the history
# Conflicts:
#	Sources/BraintreeThreeDSecure/BTThreeDSecureRequest.swift
  • Loading branch information
jaxdesmarais committed Dec 18, 2024
2 parents 387fb39 + d1b559d commit c51a54c
Show file tree
Hide file tree
Showing 19 changed files with 141 additions and 142 deletions.
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Thank you for your contribution to Braintree.
### Checklist

- [ ] Added a changelog entry
- [ ] Tested and confirmed payment flows affected by this change are functioning as expected

### Authors
> List GitHub usernames for everyone who contributed to this pull request.
Expand Down
2 changes: 1 addition & 1 deletion Braintree.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Braintree"
s.version = "6.24.0"
s.version = "6.25.0"
s.summary = "Braintree iOS SDK: Helps you accept card and alternative payments in your iOS app."
s.description = <<-DESC
Braintree is a full-stack payments platform for developers
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@
* BraintreePayPal
* Update PayPal app URL query scheme from `paypal-app-switch-checkout` to `paypal`

## unreleased
## 6.25.0 (2024-12-11)
* BraintreePayPal
* Add `BTPayPalRequest.userPhoneNumber` optional property
* Send `url` in `event_params` for App Switch events to PayPal's analytics service (FPTI)
* BraintreeVenmo
* Send `url` in `event_params` for App Switch events to PayPal's analytics service (FPTI)
* Add `BTVenmoClient(apiClient:universalLink:)` to use Universal Links when redirecting back from the Venmo flow
* BraintreeCore
* Deprecate `BTAppContextSwitcher.sharedInstance.returnURLScheme`
* BraintreeThreeDSecure
* Add `BTThreeDSecureRequest.requestorAppURL`

## 6.24.0 (2024-10-15)
* BraintreePayPal
Expand Down
18 changes: 14 additions & 4 deletions Demo/Application/Features/VenmoViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import UIKit
import BraintreeVenmo

class VenmoViewController: PaymentButtonBaseViewController {

// swiftlint:disable:next implicitly_unwrapped_optional
var venmoClient: BTVenmoClient!

let vaultToggle = Toggle(title: "Vault")

let universalLinkReturnToggle = Toggle(title: "Use Universal Link Return")

override func viewDidLoad() {
super.heightConstraint = 150
super.viewDidLoad()
venmoClient = BTVenmoClient(apiClient: apiClient)
title = "Custom Venmo Button"
Expand All @@ -17,7 +19,7 @@ class VenmoViewController: PaymentButtonBaseViewController {
override func createPaymentButton() -> UIView {
let venmoButton = createButton(title: "Venmo", action: #selector(tappedVenmo))

let stackView = UIStackView(arrangedSubviews: [vaultToggle, venmoButton])
let stackView = UIStackView(arrangedSubviews: [vaultToggle, universalLinkReturnToggle, venmoButton])
stackView.axis = .vertical
stackView.spacing = 15
stackView.alignment = .fill
Expand All @@ -29,13 +31,21 @@ class VenmoViewController: PaymentButtonBaseViewController {

@objc func tappedVenmo() {
self.progressBlock("Tapped Venmo - initiating Venmo auth")

let isVaultingEnabled = vaultToggle.isOn
let venmoRequest = BTVenmoRequest(
paymentMethodUsage: .multiUse,
vault: isVaultingEnabled
)

if universalLinkReturnToggle.isOn {
venmoClient = BTVenmoClient(
apiClient: apiClient,
// swiftlint:disable:next force_unwrapping
universalLink: URL(string: "https://mobile-sdk-demo-site-838cead5d3ab.herokuapp.com/braintree-payments")!
)
}

Task {
do {
let venmoAccount = try await venmoClient.tokenize(venmoRequest)
Expand Down
4 changes: 2 additions & 2 deletions Demo/Application/Supporting Files/Braintree-Demo-Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>6.24.0</string>
<string>6.25.0</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand All @@ -56,7 +56,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>6.24.0</string>
<string>6.25.0</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>com.braintreepayments.Demo.payments</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,18 @@ internal extension XCUIApplication {
return buttons["Tokenize and Verify New Card"]
}

var webViewPasswordTextField: XCUIElement {
return webViews.element.otherElements.children(matching: .other).children(matching: .secureTextField).element
}

var webViewSubmitButton: XCUIElement {
return webViews.element.otherElements.children(matching: .other).children(matching: .other).buttons["Submit"]
}

var cardinalSubmitButton: XCUIElement {
return buttons["SUBMIT"]
}

var liabilityShiftedMessage: XCUIElement {
return buttons["Liability shift possible and liability shifted"]
}

var authenticationFailedMessage: XCUIElement {
return buttons["Failed to authenticate, please try a different form of payment."]
}


var liabilityCouldNotBeShiftedMessage: XCUIElement {
return buttons["3D Secure authentication was attempted but liability shift is not possible"]
}

var unexpectedErrorMessage: XCUIElement {
return buttons["An unexpected error occurred"]
}

var internalErrorMessage: XCUIElement {
return buttons["Internal Error."]
}

func enterCardDetailsWith(cardNumber: String, expirationDate: String = UITestDateGenerator.sharedInstance.futureDate()) {
cardNumberTextField.tap()
cardNumberTextField.typeText(cardNumber)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,6 @@ class ThreeDSecure_V2_UITests: XCTestCase {
app.launch()
}

func testThreeDSecurePaymentFlowV2_frictionlessFlow_andTransacts() {
waitForElementToAppear(app.cardNumberTextField)
app.enterCardDetailsWith(cardNumber: "4000000000001000", expirationDate: expirationDate)
app.tokenizeButton.tap()
sleep(2)

waitForElementToAppear(app.liabilityShiftedMessage)
}

func testThreeDSecurePaymentFlowV2_challengeFlow_andTransacts() {
waitForElementToAppear(app.cardNumberTextField)
app.enterCardDetailsWith(cardNumber: "4000000000001091", expirationDate: expirationDate)
Expand All @@ -44,16 +35,7 @@ class ThreeDSecure_V2_UITests: XCTestCase {

waitForElementToAppear(app.liabilityShiftedMessage)
}

func testThreeDSecurePaymentFlowV2_noChallenge_andFails() {
waitForElementToAppear(app.cardNumberTextField)
app.enterCardDetailsWith(cardNumber: "5200000000001013", expirationDate: expirationDate)
app.tokenizeButton.tap()
sleep(2)

waitForElementToAppear(app.liabilityCouldNotBeShiftedMessage)
}


func testThreeDSecurePaymentFlowV2_challengeFlow_andFails() {
waitForElementToAppear(app.cardNumberTextField)
app.enterCardDetailsWith(cardNumber: "4000000000001109", expirationDate: expirationDate)
Expand All @@ -74,26 +56,6 @@ class ThreeDSecure_V2_UITests: XCTestCase {
waitForElementToAppear(app.liabilityCouldNotBeShiftedMessage, timeout: 30)
}

func testThreeDSecurePaymentFlowV2_acceptsPassword_failsToAuthenticateNonce_dueToCardinalError() {
waitForElementToAppear(app.cardNumberTextField)
app.enterCardDetailsWith(cardNumber: "4000000000001125")
app.tokenizeButton.tap()
sleep(2)

waitForElementToAppear(app.staticTexts["Purchase Authentication"], timeout: .threeDSecureTimeout)

let textField = app.textFields.element(boundBy: 0)
waitForElementToBeHittable(textField)
textField.forceTapElement()
sleep(2)
textField.typeText("1234")

app.cardinalSubmitButton.forceTapElement()
sleep(2)

waitForElementToAppear(app.internalErrorMessage, timeout: 30)
}

func testThreeDSecurePaymentFlowV2_returnsToApp_whenCancelTapped() {
waitForElementToAppear(app.cardNumberTextField)
app.enterCardDetailsWith(cardNumber: "4000000000001091")
Expand All @@ -106,31 +68,4 @@ class ThreeDSecure_V2_UITests: XCTestCase {

waitForElementToAppear(app.buttons["Canceled 🎲"])
}

func testThreeDSecurePaymentFlowV2_bypassedAuthentication() {
waitForElementToAppear(app.cardNumberTextField)
app.enterCardDetailsWith(cardNumber: "4000000000001083")
app.tokenizeButton.tap()
sleep(2)

waitForElementToAppear(app.liabilityCouldNotBeShiftedMessage)
}

func testThreeDSecurePaymentFlowV2_lookupError() {
waitForElementToAppear(app.cardNumberTextField)
app.enterCardDetailsWith(cardNumber: "4000000000001034")
app.tokenizeButton.tap()
sleep(2)

waitForElementToAppear(app.liabilityCouldNotBeShiftedMessage)
}

func testThreeDSecurePaymentFlowV2_timeout() {
waitForElementToAppear(app.cardNumberTextField)
app.enterCardDetailsWith(cardNumber: "4000000000001075")
app.tokenizeButton.tap()
sleep(2)

waitForElementToAppear(app.liabilityCouldNotBeShiftedMessage, timeout: 45)
}
}
17 changes: 16 additions & 1 deletion Sources/BraintreeCore/BTAppContextSwitcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,22 @@ import UIKit
/// The URL scheme to return to this app after switching to another app or opening a SFSafariViewController.
/// This URL scheme must be registered as a URL Type in the app's info.plist, and it must start with the app's bundle ID.
/// - Note: This property should only be used for the Venmo flow.
public var returnURLScheme: String = ""
@available(
*,
deprecated,
message: "returnURLScheme is deprecated and will be removed in a future version. Use BTVenmoClient(apiClient:universalLink:)."
)
public var returnURLScheme: String {
get { _returnURLScheme }
set { _returnURLScheme = newValue }
}

// swiftlint:disable identifier_name
/// :nodoc: This method 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.
/// Property for `returnURLScheme`. Created to avoid deprecation warnings upon accessing
/// `returnURLScheme` directly within our SDK. Use this value instead.
public var _returnURLScheme: String = ""
// swiftlint:enable identifier_name

// MARK: - Private Properties

Expand Down
2 changes: 1 addition & 1 deletion Sources/BraintreeCore/BTCoreConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Foundation
@objcMembers public class BTCoreConstants: NSObject {

/// :nodoc: This property 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.
public static var braintreeSDKVersion: String = "6.24.0"
public static var braintreeSDKVersion: String = "6.25.0"

/// :nodoc: This property 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.
public static let callbackURLScheme: String = "sdk.ios.braintree"
Expand Down
4 changes: 2 additions & 2 deletions Sources/BraintreeCore/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>6.24.0</string>
<string>6.25.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>6.24.0</string>
<string>6.25.0</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
Expand Down
7 changes: 3 additions & 4 deletions Sources/BraintreeDataCollector/BTDataCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,14 @@ import BraintreeCore
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
if status == errSecSuccess,
let existingItem = item as? [String: Any],
let data = existingItem[kSecValueData as String] as? Data,
let identifier = String(data: data, encoding: String.Encoding.utf8) {
let data = item as? Data,
let identifier = String(data: data, encoding: .utf8) {
return identifier
}

// If not, generate a new one and save it
let newIdentifier = UUID().uuidString
query[kSecValueData as String] = newIdentifier
query[kSecValueData as String] = newIdentifier.data(using: .utf8)
query[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
SecItemAdd(query as CFDictionary, nil)
return newIdentifier
Expand Down
46 changes: 24 additions & 22 deletions Sources/BraintreePayPal/BTPayPalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,29 @@ import BraintreeDataCollector
performSwitchRequest(appSwitchURL: url, paymentType: paymentType, completion: completion)
}

func invokedOpenURLSuccessfully(_ success: Bool, url: URL, completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void) {
if success {
apiClient.sendAnalyticsEvent(
BTPayPalAnalytics.appSwitchSucceeded,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID,
appSwitchURL: url
)
BTPayPalClient.payPalClient = self
appSwitchCompletion = completion
} else {
apiClient.sendAnalyticsEvent(
BTPayPalAnalytics.appSwitchFailed,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID,
appSwitchURL: url
)
notifyFailure(with: BTPayPalError.appSwitchFailed, completion: completion)
}
}

// MARK: - App Switch Methods

func handleReturnURL(_ url: URL) {
Expand Down Expand Up @@ -404,28 +427,7 @@ import BraintreeDataCollector
}

application.open(redirectURL) { success in
self.invokedOpenURLSuccessfully(success, completion: completion)
}
}

private func invokedOpenURLSuccessfully(_ success: Bool, completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void) {
if success {
apiClient.sendAnalyticsEvent(
BTPayPalAnalytics.appSwitchSucceeded,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
)
BTPayPalClient.payPalClient = self
appSwitchCompletion = completion
} else {
apiClient.sendAnalyticsEvent(
BTPayPalAnalytics.appSwitchFailed,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
)
notifyFailure(with: BTPayPalError.appSwitchFailed, completion: completion)
self.invokedOpenURLSuccessfully(success, url: redirectURL, completion: completion)
}
}

Expand Down
7 changes: 6 additions & 1 deletion Sources/BraintreeThreeDSecure/BTThreeDSecureRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import BraintreeCore
let shippingMethod: BTThreeDSecureShippingMethod
let uiType: BTThreeDSecureUIType
let v2UICustomization: BTThreeDSecureV2UICustomization?
let requestorAppURL: String?

var dfReferenceID: String?

Expand All @@ -54,6 +55,8 @@ import BraintreeCore
/// - shippingMethod: Optional. The shipping method chosen for the transaction
/// - uiType: Optional: Sets all UI types that the device supports for displaying specific challenge user interfaces in the 3D Secure challenge. Defaults to `.both`
/// - v2UICustomization: Optional. UI Customization for 3DS2 challenge views.
/// - requestorAppURL: Optional. Three DS Requester APP URL Merchant app declaring their URL within the CReq message
/// so that the Authentication app can call the Merchant app after out of band authentication has occurred.
public init(
amount: String,
nonce: String,
Expand All @@ -72,7 +75,8 @@ import BraintreeCore
requestedExemptionType: BTThreeDSecureRequestedExemptionType = .unspecified,
shippingMethod: BTThreeDSecureShippingMethod = .unspecified,
uiType: BTThreeDSecureUIType = .both,
v2UICustomization: BTThreeDSecureV2UICustomization? = nil
v2UICustomization: BTThreeDSecureV2UICustomization? = nil,
requestorAppURL: String? = nil
) {
self.amount = amount
self.nonce = nonce
Expand All @@ -92,5 +96,6 @@ import BraintreeCore
self.shippingMethod = shippingMethod
self.uiType = uiType
self.v2UICustomization = v2UICustomization
self.requestorAppURL = requestorAppURL
}
}
Loading

0 comments on commit c51a54c

Please sign in to comment.