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.
Prerequisites
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 support
Learn more about browser support for v3 of our JavaScript SDK.
Basic configuration
Determine which merchant account to use
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.
- Javascript
function merchantAccountId() {
return("EUR_local");
}Load the component
If you use <script> tags to load files, include
at least:
- 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 component
Every integration requires a client. After you create one,
pass it to the local payment component to accept your payments.
- 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 buttons
Render and style a button for each payment method you intend to support.
- 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.
- 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 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 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 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 | none |
| Klarna Pay Now / SOFORT | givenName, surname,
currencyCode, countryCode,
paymentTypeCountryCode | Min: 1.00 EUR (outside the United Kingdom), 1.00 GBP (United Kingdom only) |
| MyBank | givenName, surname,
currencyCode | none |
| Pay Upon Invoice | givenName, surname,
currencyCode, address, email,
billingAddress, birthDate,
phone, phoneCountryCode | Min: 5.00 EUR |
| P24 | givenName, surname,
currencyCode, email | Min: 1.00 PLN |
Redirect flow
Local Payment Method redirect flow is available in the Braintree JS SDK starting in version 3.111.0.
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 iframe
Local Payment Method redirect flow from inside an iframe is available in the Braintree JS SDK starting in version 3.115.0.
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 domains
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 names
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 domain
Register your sandbox domain name in the Braintree Control Panel:
Log in to your sandbox Control Panel.
Click the gear icon in the top right corner.
Click Account Settings from the drop-down menu.
Scroll to the Payment Methods section.
Next to PayPal, click the Options link.
Click the View Domain Names button.
Enter the domain of your return page in the Specify Your Domain Names section.
Click the Add Domain Names button.
Register your production domain
Register your production domain name in the Braintree Control Panel:
Log in to your production Control Panel.
Click the gear icon in the top right corner.
Click Account Settings from the drop-down menu.
Scroll to the Payment Methods section.
Next to PayPal, click the Options link.
Click the View Domain Names button.
Enter the domain of your return page in the Specify Your Domain Names section.
Click the Add Domain Names button.
Code examples for redirect flow
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.
- 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 errors
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 flow
Implement the
startPayment
method for preprocessing and result handling in the popup flow.
- 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'));Implement Braintree webhooks to accept Local Payment Methods. See Server-side for details.
Start the payment
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 switches
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:
- 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 addresses
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.
- 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.
- 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'));