The PayPal authorization flow includes PayPal One Touch™, which allows customers to pay by presenting the PayPal website.

Setup

Before you can add PayPal:

  1. Integrate the Braintree iOS SDK into your app
  2. Create, verify, and link your PayPal account in the Braintree Control Panel

Get the SDK

For CocoaPods integrations, include Braintree in your podfile:

Ruby
pod 'Braintree'

For Carthage integrations, include the BraintreeCore, BraintreePayPal, PayPalDataCollector, and PPRiskMagnes frameworks.

availability

Support for Swift Package Manager was added in v5 of the iOS SDK. See the migration guide for details.

Setup for app context switching

To handle workflows that involve switching to another app or SFSafariViewController for authentication, you must register a URL type and configure your app to handle return URLs.

Register a URL type

  1. In Xcode, click on your project in the Project Navigator and navigate to App Target > Info > URL Types
  2. Click [+] to add a new URL type
  3. Under URL Schemes, enter your app switch return URL scheme. This scheme must start with your app's Bundle ID and be dedicated to Braintree app switch returns. For example, if the app bundle ID is com.your-company.your-app, then your URL scheme could be com.your-company.your-app.payments.

important

If you have multiple app targets, be sure to add the return URL type for all of the targets.

Testing the URL type

You can test out your new URL scheme by opening up a URL that starts with it (e.g. com.your-company.your-app.payments://test) in Mobile Safari on your iOS Device or Simulator.

In addition, always test your app switching on a real device.

Set your return URL

In your AppDelegate's application:didFinishLaunchingWithOptions: implementation, use setReturnURLScheme: with the value you set above.

For example:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    BTAppSwitch.setReturnURLScheme("com.your-company.your-app.payments")
    return true
}

Handle app context switching

Then pass the payment authorization URL to Braintree for finalization.

If you're using UISceneDelegate (introduced in iOS 13), call BTAppSwitch's handleOpenURLContext method from within the scene:openURLContexts scene delegate method.

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    URLContexts.forEach { context in
        if context.url.scheme?.localizedCaseInsensitiveCompare("com.your-company.your-app.payments") == .orderedSame {
            BTAppSwitch.handleOpenURLContext(context)
        }
    }
}

Otherwise, if you aren't using UISceneDelegate, call BTAppSwitch's handleOpenURL method from within the application:openURL:options app delegate method.

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    if url.scheme?.localizedCaseInsensitiveCompare("com.your-company.your-app.payments") == .orderedSame {
        return BTAppSwitch.handleOpen(url, options: options)
    }
    return false
}

Showing a PayPal button

Using our Drop-in UI

If you plan to use the Vault flow, you can use our Drop-in UI. This UI will show a PayPal payment option alongside any other payment methods you've enabled.

For more details, see the Drop-in UI guide.

Using a custom UI

You can implement a custom UI, such as your own PayPal button.

var braintreeClient: BTAPIClient!

override func viewDidLoad() {
    super.viewDidLoad()

    self.braintreeClient = BTAPIClient(authorization: "<#CLIENT_AUTHORIZATION#>")

    let customPayPalButton = UIButton(frame: CGRect(x: 0, y: 0, width: 60, height: 120))
    customPayPalButton.addTarget(self, action: #selector(customPayPalButtonTapped(button:)), for: UIControlEvents.touchUpInside)
    self.view.addSubview(customPayPalButton)
}

func customPayPalButtonTapped(button: UIButton) {
    let payPalDriver = BTPayPalDriver(apiClient: self.braintreeClient)
    payPalDriver.viewControllerPresentingDelegate = self
    payPalDriver.appSwitchDelegate = self

    // Important! Choose either Vault or Checkout flow
    // Start the Vault flow, or...
    payPalDriver.authorizeAccount() { (tokenizedPayPalAccount, error) in
        // ...
    }

    // ...start the Checkout flow
    let payPalRequest = BTPayPalRequest(amount: "1.00")
    payPalDriver.requestOneTimePayment(payPalRequest) { (tokenizedPayPalAccount, error) in
        // ...
    }
}

Implementing delegate protocols

BTPayPalDriver has two delegates:

  • viewControllerPresentingDelegate: a required delegate that is responsible for presenting and dismissing an SFSafariViewController on iOS 9+ devices.
  • appSwitchDelegate: an optional delegate that receives messages when switching to the PayPal website via Mobile Safari. This can be used to present/dismiss a loading activity indicator.

Here is an example:

// MARK: - BTViewControllerPresentingDelegate

func paymentDriver(_ driver: Any, requestsPresentationOf viewController: UIViewController) {
    present(viewController, animated: true, completion: nil)
}

func paymentDriver(_ driver: Any, requestsDismissalOf viewController: UIViewController) {
    viewController.dismiss(animated: true, completion: nil)
}

// MARK: - BTAppSwitchDelegate


// Optional - display and hide loading indicator UI
func appSwitcherWillPerformAppSwitch(_ appSwitcher: Any) {
    showLoadingUI()

    NotificationCenter.default.addObserver(self, selector: #selector(hideLoadingUI), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
}

func appSwitcherWillProcessPaymentInfo(_ appSwitcher: Any) {
    hideLoadingUI()
}

func appSwitcher(_ appSwitcher: Any, didPerformSwitchTo target: BTAppSwitchTarget) {

}

// MARK: - Private methods

func showLoadingUI() {
    // ...
}

func hideLoadingUI() {
    NotificationCenter
        .default
        .removeObserver(self, name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
    // ...
}

Collecting additional data

You can gather additional data about your customers as they complete the payment process.

Billing address

To collect billing addresses you will need to enable the PayPal Billing Address Request feature in your PayPal account. To enable this feature, contact PayPal.

When using the Checkout flow, you will receive the billing address via BTPayPalAccountNonce's billingAddress property:

let payPalDriver = BTPayPalDriver(apiClient: self.braintreeClient)
payPalDriver.viewControllerPresentingDelegate = self
payPalDriver.appSwitchDelegate = self

let request = BTPayPalRequest(amount: "1.00")

payPalDriver.requestOneTimePayment(request) { (tokenizedPayPalAccount, error) in
    guard let tokenizedPayPalAccount = tokenizedPayPalAccount else {
        if let error = error {
            // Handle error
        } else {
            // User canceled
        }
        return
    }
    print("Got a nonce! \(tokenizedPayPalAccount.nonce)")
    if let address = tokenizedPayPalAccount.billingAddress {
        print("Billing address:\n\(address.streetAddress)\n\(address.extendedAddress)\n\(address.locality) \(address.region)\n\(address.postalCode) \(address.countryCodeAlpha2)")
    }
}

If you are using the Vault flow, you can request the billing address by specifying the address scope during the account authorization flow. If a credit card is not the underlying funding source of the transaction, the billing address will not be returned; instead, the customer's primary account address will be returned via BTPayPalAccountNonce's shippingAddress property:

let payPalDriver = BTPayPalDriver(apiClient: self.braintreeClient)
payPalDriver.viewControllerPresentingDelegate = self
payPalDriver.appSwitchDelegate = self

payPalDriver.authorizeAccount(withAdditionalScopes: Set(["address"])) { (tokenizedPayPalAccount, error) in
    guard let tokenizedPayPalAccount = tokenizedPayPalAccount else {
        if let error = error {
            // Handle error
        } else {
            // User canceled
        }
        return
    }
    if let address = tokenizedPayPalAccount.billingAddress ?? tokenizedPayPalAccount.shippingAddress {
        print("Billing address:\n\(address.streetAddress)\n\(address.extendedAddress)\n\(address.locality) \(address.region)\n\(address.postalCode) \(address.countryCodeAlpha2)")
    }
}

If a credit card is not the underlying funding source of the transaction, the customer's primary account address will be returned instead.

note

See Braintree iOS Client SDK PayPal header files for in-depth documentation and additional custom PayPal integration options.

Next: Choose your integration

The rest of your configuration will be determined by how you'd like to use PayPal.

  • Want easy payments for repeat customers? Have a subscription model? Use our Vault.
  • Want a one-time payment checkout? Use Checkout with PayPal.

See a detailed comparison of Vault vs. Checkout.