Client-Side Implementation

Add Local Payment Methods to your website with the Braintree JavaScript SDK v4 client side. You'll render payment buttons, start the popup or redirect flow, and send the resulting nonce to your server.

PrerequisitesAnchorIcon

Before you start, make sure you have:

  • A Braintree client authorization (a client token or tokenization key) to create the client instance.
  • A merchant account ID, if you use a merchant account other than your default one for local payments.
  • A button element in your page for each local payment method you plan to support.
  • If you use the redirect flow, a redirect domain registered in the Braintree Control Panel. See Register your redirect domains.

Browser supportAnchorIcon

Learn more about browser support for v3 of our JavaScript SDK.

Basic configurationAnchorIcon

Determine which merchant account to useAnchorIcon

This determines which PayPal credentials the transaction uses, and it must match the merchant account you specify with any other calls in the transaction lifecycle, for example Transaction.sale. Many merchants use a separate merchant account for local payments. Other merchants use separate merchant accounts for transactions in different currencies. You can't switch merchant accounts between the start and finish of a local payment transaction, so determine the correct one from the start.

  1. Javascript
function merchantAccountId() {
  return("EUR_local");
}

Load the componentAnchorIcon

If you use <script> tags to load files, include at least:

  1. HTML
<script src="https://js.braintreegateway.com/web/3.147.0/js/client.min.js"></script>

<script src="https://js.braintreegateway.com/web/3.147.0/js/local-payment.min.js"></script>

Initialize the componentAnchorIcon

Every integration requires a client. After you create one, pass it to the local payment component to accept your payments.

  1. Async/Await
async function setupLocalPayment() {
  try {
    const clientInstance = await braintree.client.create({
      authorization: 'CLIENT-AUTHORIZATION'
    });

    const localPaymentInstance = await braintree.localPayment.create({
      client: clientInstance,
      merchantAccountId: merchantAccountId()
    });

    // Use the localPaymentInstance to start the payment flow
  } catch (err) {
    // Handle error
  }
}

Render Local Payment Method buttonsAnchorIcon

Render and style a button for each payment method you intend to support.

  1. HTML
<button id="ideal-button">
  <img src="img/ideal.png" class="payment-button" />
</button>
<button id="sofort-button">
  <img src="img/sofort.png" class="payment-button" />
</button>

Use the local payment component to attach a listener to your buttons to begin the payment flow.

  1. Javascript
const idealButton = document.getElementById('ideal-button');
const sofortButton = document.getElementById('sofort-button');

The following is a list of valid paymentType, countryCode, 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 you don't provide a paymentTypeCountryCode, the local payment component uses the countryCode value as the paymentTypeCountryCode.

The following is a list of required parameters for each Local Payment Method, and 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, currencyCodenone
Klarna Pay Now / SOFORTgivenName, surname, currencyCode, countryCode, paymentTypeCountryCode Min: 1.00 EUR (outside the United Kingdom), 1.00 GBP (United Kingdom only)
MyBankgivenName, surname, currencyCodenone
Pay Upon InvoicegivenName, surname, currencyCode, address, email, billingAddress, birthDate, phone, phoneCountryCodeMin: 5.00 EUR
P24givenName, surname, currencyCode, emailMin: 1.00 PLN

Redirect flowAnchorIcon

We've heard about several issues from merchants about blocked popups, which have kept users from completing payments. To improve the user experience and ensure seamless payment processing, we've introduced a redirect flow. This feature lets merchants provide a redirectUrl that redirects users on the current page instead of opening a new popup window. The redirect flow keeps the same functionality as the popup flow, so the transition is seamless for your users.

Use the redirect flow inside an iframeAnchorIcon

To enable the redirect flow to work from inside an iframe, meet a few conditions. Most importantly, on redirect a few URL parameters must pass from the top frame to the inner iframe: token, errorcode, and wasCanceled. All three of these URL parameters must pass to the inner iframe for the flow to work correctly. The simplest way to do this is to parse the parameters from the URL with JavaScript and pass them through the src attribute of the iframe, so it works the same as the non-iframe flow.

Additionally, the allow attribute of the iframe must have a value of payment. If you use the sandbox attribute, it must have both allow-scripts and allow-top-navigation for the flow to work. If you're having problems with the redirect flow inside an iframe, first confirm that all of these criteria are met.

Register your redirect domainsAnchorIcon

To use the redirect flow, register any domains you plan to use as a redirectUrl with PayPal. You can do this through the Braintree Control Panel using the instructions below. You'll have to do this in both your sandbox and production environments.

Rules for domain namesAnchorIcon

A domain name:

  • Must not start with a scheme, for example "https://".
  • Must be between 4 and 255 characters.
  • Can't contain spaces or wildcards (*).
  • Can consist of a top-level domain, a second-level domain, and 0 or more subdomains, and so on. In a.b.example.com, "example" is the top-level domain, "com" is the second-level domain, and "a" and "b" are subdomains.
  • Must have a top-level domain that consists of 2 to 63 letters.
  • Must have a subdomain and a second-level domain that each consist of 1 to 63 alphanumeric characters, hyphens, or both.
  • Can't have a subdomain or a second-level domain that starts or ends with a hyphen.
  • Must separate the top-level domain, second-level domain, and the subdomains with a dot (.).
  • Can end with a trailing dot.

Register your sandbox domainAnchorIcon

Register your sandbox domain name in the Braintree Control Panel:

  1. Log in to your sandbox Control Panel.

  2. Click the gear icon in the top right corner.

  3. Click Account Settings from the drop-down menu.

  4. Scroll to the Payment Methods section.

  5. Next to PayPal, click the Options link.

  6. Click the View Domain Names button.

  7. Enter the domain of your return page in the Specify Your Domain Names section.

  8. Click the Add Domain Names button.

Register your production domainAnchorIcon

Register your production domain name in the Braintree Control Panel:

  1. Log in to your production Control Panel.

  2. Click the gear icon in the top right corner.

  3. Click Account Settings from the drop-down menu.

  4. Scroll to the Payment Methods section.

  5. Next to PayPal, click the Options link.

  6. Click the View Domain Names button.

  7. Enter the domain of your return page in the Specify Your Domain Names section.

  8. Click the Add Domain Names button.

Code examples for redirect flowAnchorIcon

To use the redirect flow, pass the redirectUrl parameter when you call braintree.localPayment.create. This redirectUrl should point to the same page of your merchant website that you redirect the user from, so that when the Braintree SDK redirects the user back, it can finish tokenizing and provide you with a nonce.

  1. Async/Await
async function setupLocalPayment() {
  try {
    const clientInstance = await braintree.client.create({
      authorization: 'CLIENT-AUTHORIZATION'
    });

    const localPaymentInstance = await braintree.localPayment.create({
      client: clientInstance,
      merchantAccountId: "merchant-id",
      // Passing this redirectUrl will initiate the redirect flow
      redirectUrl: "https://www.merchant-site.com/redirect-page.html"
    });

    if (localPaymentInstance.tokenizePayload) {
      // send the nonce to your server
      console.log("Got a nonce: ", localPaymentInstance.tokenizePayload.nonce);
    } else {
      // invoke localPaymentInstance.startPayment({...}) as described below
    }
  } catch (err) {
    // handle component creation error
  }
}

After the SDK redirects the user back from the mandate page, if tokenization was successful, you can access a nonce from localPaymentInstance.tokenizePayload.nonce (see example above).

Handle payment errorsAnchorIcon

If the user cancels out of the payment flow, the redirect URL has the parameters wasCanceled=true and errorcode=payment_error. If the user approves the payment, but there's a problem processing the payment, the redirect URL has the parameters wasCanceled=true and errorcode=processing_error. Check the values of both wasCanceled and errorcode to tell the difference between a user canceling and a payment error. See all the local payment method error codes.

Invoke local payment flowAnchorIcon

Implement the startPayment method for preprocessing and result handling in the popup flow.

  1. Async/Await
function createLocalPaymentClickListener(type) {
  return async function (event) {
    event.preventDefault();

    try {
      const payload = await localPaymentInstance.startPayment({
        paymentType: type,
        amount: '10.67',
        fallback: { // see Fallback section for details on these params
          url: 'https://your-domain.com/page-to-complete-checkout',
          buttonText: 'Complete Payment'
        },
        currencyCode: 'EUR',
        givenName: 'Joe',
        surname: 'Doe',
        address: {
          countryCode: 'NL'
        },
        onPaymentStart: function (data, start) { // this is only required in the popup flow
          // NOTE: It is critical here to store data.paymentId on your server
          //       so it can be mapped to a webhook sent by Braintree once the
          //       buyer completes their payment. See Start the payment
          //       section for details.

          // Call start to initiate the popup
          start();
        }
      });

      // Send the nonce to your server to create a transaction
      console.log(payload.nonce);
    } catch (startPaymentError) {
      if (startPaymentError.code === 'LOCAL_PAYMENT_WINDOW_CLOSED') {
        console.error('Customer closed Local Payment popup.');
      } else {
        console.error('Error!', startPaymentError);
      }
    }
  };
}

idealButton.addEventListener('click', createLocalPaymentClickListener('ideal'));
sofortButton.addEventListener('click', createLocalPaymentClickListener('sofort'));

Start the paymentAnchorIcon

Before you start the payment flow for the buyer, store the paymentId provided in the data object in the onPaymentStart method. This identifier is critical for handling a case where the buyer completes the payment flow with their bank and exits that flow without returning to your checkout page. Braintree recommends that you store the paymentId and map it to a shopping cart identifier or another form of identifier. You'll need this information when Braintree sends you a webhook for a completed payment.

See the Local Payment Method webhooks reference for details on the exact content of the Local Payment Method completed notification.

Configure a fallback for mobile app switchesAnchorIcon

If a customer uses a mobile device and has a mobile app to process their payment, the window automatically redirects to the mobile app. The app then opens a new window that can't communicate with the original page that started the flow. Because of this, configure a fallback URL and button text, which the SDK uses to create a button in the window the mobile app opens, to redirect back to your website.

The URL you configure automatically gets query parameters added to it that complete the payment. On the page you configure, set up the localPaymentInstance and call tokenize:

  1. Async/Await
async function setupLocalPayment() {
  try {
    const clientInstance = await braintree.client.create({
      authorization: 'CLIENT-AUTHORIZATION'
    });

    const localPaymentInstance = await braintree.localPayment.create({
      client: clientInstance
    });

    if (localPaymentInstance.hasTokenizationParams()) {
      try {
        const payload = await localPaymentInstance.tokenize();
        // send payload.nonce to your server
      } catch (tokenizeError) {
        // handle tokenization error
      }
    } else {
      // if this page should only be reached when
      // recovering from a mobile app switch,
      // display an error for not having the
      // correct params in the query string
    }
  } catch (err) {
    // Handle error
  }
}

Collect shipping addressesAnchorIcon

If you need a shipping address to ship physical goods, set shippingAddressRequired to true. This setting prompts your customer to provide shipping details. If you already collected these details from your customer, pass the shipping details within the startPayment method so your customer doesn't have to provide them again.

  1. Async/Await
function createLocalPaymentClickListener(type) {
  return async function (event) {
    event.preventDefault();

    try {
      const payload = await localPaymentInstance.startPayment({
        paymentType: type,
        amount: '10.67',
        fallback: { // see Fallback section for details on these params
          url: 'https://your-domain.com/page-to-complete-checkout',
          buttonText: 'Complete Payment'
        },
        currencyCode: 'EUR',
        shippingAddressRequired: true,
        email: '[email protected]',
        phone: '5101231234',
        givenName: 'Joe',
        surname: 'Doe',
        address: {
          streetAddress: 'Oosterdoksstraat 110',
          extendedAddress: 'Apt. B',
          locality: 'DK Amsterdam',
          postalCode: '1011',
          region: 'NH',
          countryCode: 'NL'
        },
        onPaymentStart: function (data, start) {
          // NOTE: It is critical here to store data.paymentId on your server
          //       so it can be mapped to a webhook sent by Braintree once the
          //       buyer completes their payment. See Start the payment
          //       section for details.

          // Call start to initiate the popup
          start();
        }
      });

      // Send the nonce to your server to create a transaction
      console.log(payload.nonce);
    } catch (startPaymentError) {
      if (startPaymentError.code === 'LOCAL_PAYMENT_WINDOW_CLOSED') {
        console.error('Customer closed Local Payment popup.');
      } else {
        console.error('Error!', startPaymentError);
      }
    }
  };
}

idealButton.addEventListener('click', createLocalPaymentClickListener('ideal'));
sofortButton.addEventListener('click', createLocalPaymentClickListener('sofort'));

If you aren't shipping physical goods, set shippingAddressRequired to false. This setting prompts your customer to provide just the basic information, such as givenName, surname, phone, and email. If you already collected these details from your customer, pass them within the startPayment method so your customer doesn't have to provide them again.

  1. Async/Await
function createLocalPaymentClickListener(type) {
  return async function (event) {
    event.preventDefault();

    try {
      const payload = await localPaymentInstance.startPayment({
        paymentType: type,
        amount: '10.67',
        fallback: { // see Fallback section for details on these params
          url: 'https://your-domain.com/page-to-complete-checkout',
          buttonText: 'Complete Payment'
        },
        currencyCode: 'EUR',
        shippingAddressRequired: false,
        email: '[email protected]',
        phone: '5101231234',
        givenName: 'Joe',
        surname: 'Doe',
        address: {
          countryCode: 'NL'
        },
        onPaymentStart: function (data, start) {
          // NOTE: It is critical here to store data.paymentId on your server
          //       so it can be mapped to a webhook sent by Braintree once the
          //       buyer completes their payment. See Start the payment
          //       section for details.

          // Call start to initiate the popup
          start();
        }
      });

      // Send the nonce to your server to create a transaction
      console.log(payload.nonce);
    } catch (err) {
      if (err.code === 'LOCAL_PAYMENT_WINDOW_CLOSED') {
        console.error('Customer closed Local Payment popup.');
      } else {
        console.error('Error!', err);
      }
    }
  };
}

idealButton.addEventListener('click', createLocalPaymentClickListener('ideal'));
sofortButton.addEventListener('click', createLocalPaymentClickListener('sofort'));