Local Payment Methods

Client-Side Implementation

Important

The SSL certificates for Braintree Mobile (iOS and Android) SDKs are set to expire on March 30, 2026. This will impact existing versions of the SDK in published versions of your app. To reduce the impact, upgrade the Android SDK to version 4.45.0+ or version 5.0.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.

SetupAnchorIcon

In order to add Local Payment Methods to your Android application, you must declare a URL scheme in your AndroidManifest.

Get the SDKAnchorIcon

Add the following in your app-level build.gradle:

  1. Kotlin
  2. Groovy
dependencies {
    implementation("com.braintreepayments.api:local-payment:5.8.0")
}

Determine which merchant account to useAnchorIcon

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.

Invoke payment flowAnchorIcon

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 MethodPayment typeCountry codesCurrency codes
BancontactbancontactBEEUR
BLIKblikPLPLN
EPSepsATEUR
grabpaygrabpaySGSGD
iDEALidealNLEUR
Klarna Pay Now / SOFORTsofortAT, BE, DE, IT, NL, ES, GBEUR (outside GB), GBP (GB only)
MyBankmybankITEUR
Pay Upon Invoicepay_upon_invoiceDEEUR
P24p24PLEUR, 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 MethodRequired parametersCustomer transaction limits
BancontactgivenName, surname, currencyCodeMin: 1.00 EUR
BLIKgivenName, surname, currencyCode, emailMin: 1.00 PLN
EPSgivenName, surname, currencyCodeMin: 1.00 EUR
grabpaygivenName, surname, currencyCodeMin: 1.00 SGD
iDEALgivenName, surname, currencyCodeN/A
Klarna Pay Now / SOFORTgivenName, surname, currencyCode, countryCodeAlpha2, paymentTypeCountryCodeMin: 1.00 EUR (outside the United Kingdom), 1.00 GBP (United Kingdom only)
MyBankgivenName, surname, currencyCode, address, email, billingAddress, birthDate, phone, phoneCountryCodeN/A
Pay Upon InvoicegivenName, surname, currencyCodeN/A
P24givenName, surname, currencyCode, emailMin: 1.00 PLN
  1. Kotlin
class LocalPaymentActivity : AppCompatActivity() {
    private lateinit var localPaymentClient: LocalPaymentClient
    private lateinit var localPaymentLauncher: LocalPaymentLauncher

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // must be initialized on onCreate()
        localPaymentLauncher = LocalPaymentLauncher()

        // can be initialized outside onCreate() if desired
        localPaymentClient = LocalPaymentClient(
            context = requireContext(),
            authorization = "TOKENIZATION_KEY or CLIENT_TOKEN",
            appLinkReturnUrl = Uri.parse("https://merchant-app.com") // Merchant App Link
        )
    }

    // ONLY REQUIRED IF YOUR ACTIVITY LAUNCH MODE IS SINGLE_TOP
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        handleReturnToApp(intent)
    }

    // ALL OTHER ACTIVITY LAUNCH MODES
    override fun onResume() {
        super.onResume()
        handleReturnToApp(intent)
    }

    private fun handleReturnToApp(intent: Intent) {
        // fetch stored pendingRequest
        val pendingRequest: LocalPaymentPendingRequest.Started = fetchStoredPendingRequest()
        pendingRequest?.let {
            val paymentAuthResult: LocalPaymentAuthResult =
                localPaymentLauncher.handleReturnToApp(
                    pendingRequest = it,
                    intent = intent
                )
            if (paymentAuthResult is LocalPaymentAuthResult.Success) {
                localPaymentClient.tokenize(
                    context = requireContext(),
                    localPaymentAuthResult = paymentAuthResult
                ) { localPaymentResult: LocalPaymentResult? ->
                    localPaymentResult?.let {
                        this.handleLocalPaymentResult(it)
                    }
                }
            } else {
                // handle error - User did not complete local payment flow
            }
            // clear pendingRequest
        }
    }

    fun startLocalPayment() {
        val request: LocalPaymentRequest = localPaymentRequest

        localPaymentClient.createPaymentAuthRequest(
            request = request
        ) { paymentAuthRequest: LocalPaymentAuthRequest ->
            if (paymentAuthRequest is LocalPaymentAuthRequest.ReadyToLaunch) {
                val pendingRequest = localPaymentLauncher.launch(
                    activity = requireActivity(),
                    localPaymentAuthRequest = paymentAuthRequest
                )
                if (pendingRequest is LocalPaymentPendingRequest.Started) {
                    // store pendingRequest for future use
                } else if (pendingRequest is LocalPaymentPendingRequest.Failure) {
                    // handleError - pendingRequest.error
                }
            } else if (paymentAuthRequest is LocalPaymentAuthRequest.Failure) {
                // handleError - paymentAuthRequest.error
            }
        }
    }

    protected fun handleLocalPaymentResult(localPaymentResult: LocalPaymentResult) {
        when (localPaymentResult) {
            is LocalPaymentResult.Success -> { /* handle localPaymentResult.nonce */ }
            is LocalPaymentResult.Failure -> { /* handle localPaymentResult.error */ }
            is LocalPaymentResult.Cancel -> { /* handle user canceled */ }
        }
    }

    companion object {
        private val localPaymentRequest: LocalPaymentRequest
            get() {
                val address = PostalAddress().apply {
                    streetAddress = "Stadhouderskade 78"
                    countryCodeAlpha2 = "NL"
                    locality = "Amsterdam"
                    postalCode = "1072 AE"
                }

                return LocalPaymentRequest(true).apply {
                    paymentType = "ideal"
                    amount = "1.10"
                    address = address
                    phone = "207215300"
                    email = "android-test-buyer@paypal.com"
                    givenName = "Test"
                    surname = "Buyer"
                    isShippingAddressRequired = true
                    merchantAccountId = "altpay_eur"
                    currencyCode = "EUR"
                }
            }
    }
}
Important

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

Shipping addressesAnchorIcon

If you need a shipping address to ship physical goods, set shippingAddressRequired to true on your LocalPaymentRequest. 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 LocalPaymentRequest 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 LocalPaymentRequest to avoid having your customer provide them again.

If you accept cookies, we’ll use them to improve and customize your experience and enable our partners to show you personalized PayPal ads when you visit other sites. Manage cookies and learn more