Local Payment Methods

Client-Side Implementationanchor

important

The SSL certificates for all Braintree SDKs are set to expire by June 30, 2025. This will impact existing versions of the SDK in published versions of your app. To reduce the impact, upgrade the iOS SDK to version 6.17.0+ for the new SSL certifications.

If you do not decommission your app versions that include the older SDK versions or force upgrade your app with the updated certificates by the expiration date, 100% of your customer traffic will fail.

Setupanchor

Determine which merchant account to useanchor

This will determine which PayPal credentials are used for the transaction, and must match the merchant account specified with any other calls in the transaction lifecycle (e.g. Transaction.sale). Many merchants use a separate merchant account for local payments. Other merchants use separate merchant accounts for transactions in different currencies. It is not possible to switch merchant accounts between the start and finish of a local payment transaction, so it is important to determine the correct one from the start.

Get the SDKanchor

CocoaPodsanchor

Include Braintree/PaymentFlow in your podfile:

  1. Ruby
pod 'Braintree/PaymentFlow'

Swift Package Manageranchor

Include the BraintreePaymentFlow and PayPalDataCollector frameworks.

Carthageanchor

Include the BraintreeCore, BraintreePaymentFlow, PayPalDataCollector, and PPRiskMagnes frameworks.

Setup for app context switchinganchor

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 typeanchor

  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 typeanchor

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 URLanchor

In your AppDelegate's application:didFinishLaunchingWithOptions: implementation, use setReturnURLScheme: with the value you set above. //comment: this is still the same syntax Apple uses for Swift docs. see UIApplicationDelegate:

For example:

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

Handle app context switchinganchor

Then pass the payment authorization URL to Braintree for finalization.

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

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

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

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

Invoke payment flowanchor

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

The following is a list of valid paymentType, countryCodeAlpha2, and currencyCode values for each Local Payment Method:

Local Payment Method Payment type Country codes Currency codes
Bancontact bancontact BE EUR
BLIK blik PL PLN
EPS eps AT EUR
grabpay grabpay SG SGD
iDEAL ideal NL EUR
Klarna Pay Now / SOFORT sofort AT, BE, DE, IT, NL, ES, GB EUR (outside GB), GBP (GB only)
MyBank mybank IT EUR
Pay Upon Invoice pay_upon_invoice DE EUR
P24 p24 PL EUR, PLN

The paymentTypeCountryCode parameter is only required for payment methods with multiple potential country codes. If a paymentTypeCountryCode is not provided, then the countryCode value will be used as the paymentTypeCountryCode.

The following is a list of required parameters for each Local Payment Method, as well as any applicable transaction limits:

Local Payment Method Required parameters Customer transaction limits
Bancontact givenName, surname, currencyCode Min: 1.00 EUR
BLIK givenName, surname, currencyCode, email Min: 1.00 PLN
EPS givenName, surname, currencyCode Min: 1.00 EUR
grabpay givenName, surname, currencyCode Min: 1.00 SGD
iDEAL givenName, surname, currencyCode N/A
Klarna Pay Now / SOFORT givenName, surname, currencyCode, countryCodeAlpha2, paymentTypeCountryCode Min: 1.00 EUR (outside the United Kingdom), 1.00 GBP (United Kingdom only)
MyBank givenName, surname, currencyCode N/A
Pay Upon Invoice givenName, surname, currencyCode N/A
P24 givenName, surname, currencyCode, email Min: 1.00 PLN

BTPaymentFlowDriver has one delegate:

  • viewControllerPresentingDelegate: a required delegate that is responsible for presenting and dismissing an SFSafariViewController.
  1. Swift
var client: BTAPIClient!
var paymentFlowDriver: BTPaymentFlowDriver!

override func viewDidLoad() {
    super.viewDidLoad()

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

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

func idealButtonTapped(button: UIButton) {
    self.paymentFlowDriver = BTPaymentFlowDriver(apiClient: self.client)
    self.paymentFlowDriver.viewControllerPresentingDelegate = self

    let request = BTLocalPaymentRequest()
    request.paymentType = "ideal"
    request.currencyCode = "EUR"
    request.amount = "1.01"
    request.givenName = "Jon"
    request.surname = "Doe"
    request.phone = "639847934"
    request.address = BTPostalAddress()
    request.address.countryCodeAlpha2 = "NL"
    request.address.postalCode = "2585 GJ"
    request.address.streetAddress = "836486 of 22321 Park Lake"
    request.address.locality = "Den Haag"
    request.email = "lingo-buyer@paypal.com"
    request.shippingAddressRequired = false
    request.localPaymentFlowDelegate = self

    driver.startPaymentFlow(localPaymentRequest) { (result, error) in
        ...
    }
}

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)
}

func localPaymentStarted(_ request: BTLocalPaymentRequest, paymentID: String, start: @escaping () -> Void) {
    // Do preprocessing if necessary before calling start()
    start()
}
important

You must implement Braintree webhooks in order to accept Local Payment Methods. See Server-side for details.

Shipping addressesanchor

If you need a shipping address to ship physical goods, set shippingAddressRequired to true on your BTLocalPaymentRequest. This setting will prompt your customer to provide shipping details. If you have already collected these details from your customer, you can pass the shipping details within BTLocalPaymentRequest to avoid having your customer provide them again.

If you are not shipping physical goods, then use the default value of false for shippingAddressRequired. This setting will prompt your customer to provide just the basic information like givenName, surname, phone, and email. If you have already collected these details from your customer, you can pass them on BTLocalPaymentRequest to avoid having your customer provide them again.


Next Page: Server-side