See the CHANGELOG for a complete list of changes. This migration guide outlines the basics for updating your Braintree integration from v3 to v4.
Documentation for v4 is available at https://developer.paypal.com/braintree/docs/start/hello-client/android/v4.
- Gradle
- Browser Switch
- BraintreeFragment
- Client Token Provider
- Event Handling
- Builder Pattern
- Fragment Architecture
- American Express
- Card
- PayPal Data Collector
- Local Payment
- Google Pay
- PayPal
- Samsung Pay
- Visa Checkout
- Union Pay
- Venmo
- 3D Secure
- Integrating Multiple Payment Methods
- Manual Browser Switching for Browser-Based Flows
The features of the Braintree SDK are now organized into modules and can each be imported as dependencies in your build.gradle
file. You must remove the com.braintreepayments.api:braintree:3.x.x
dependency when migrating to v4.
The examples below show the required dependencies for each feature.
In v3, com.braintreepayments.api.BraintreeBrowserSwitchActivity
was the designated deep link destination activity maintained by the Braintree SDK. In v4, we've removed BraintreeBrowserSwitchActivity
to give apps more control over their deep link configuration.
In the AndroidManifest.xml
, migrate the intent-filter
from your v3 integration into an activity you own:
Note:
android:exported
is required if your app compile SDK version is API 31 (Android 12) or later.
<activity android:name="com.company.app.MyPaymentsActivity"
android:exported="true">
...
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="${applicationId}.braintree"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
Additionally, apps that use both Drop-in and BraintreeClient should specify a custom url scheme, since DropInActivity
already uses the ${applicationId}.braintree
url intent filter.
If your app has multiple browser switch targets, you can specify multiple intent filters and use the BraintreeClient
constructor that allows you to specify a customUrlScheme
:
<activity android:name="com.company.app.MyPaymentsActivity1"
android:exported="true">
...
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="custom-url-scheme-1"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
<activity android:name="com.company.app.MyPaymentsActivity2"
android:exported="true">
...
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="custom-url-scheme-2"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
Then when constructing your BraintreeClient
make sure to pass the appropriate custom url scheme for each deep link target Activity:
// MyPaymentsActivity1.java
BraintreeClient braintreeClient =
new BraintreeClient(this, "TOKENIZATION_KEY_OR_CLIENT_TOKEN", "custom-url-scheme-1");
// MyPaymentsActivity2.java
BraintreeClient braintreeClient =
new BraintreeClient(this, "TOKENIZATION_KEY_OR_CLIENT_TOKEN", "custom-url-scheme-2");
In v4, we decoupled the Braintree SDK from Android to offer more integration flexibility.
BraintreeFragment
has been replaced by a Client
for each respective payment feature.
See the below payment method sections for examples of instantiating and using the feature clients.
When creating a BraintreeClient
, you can provide a tokenization key, client token, or a ClientTokenProvider
. When given a ClientTokenProvider
, the SDK will fetch a client token on your behalf when it is needed. This makes it possible to construct a BraintreeClient
instance using client token authorization in onCreate
.
Assuming you have a server that supports GET https://www.my-api.com/client_token
and receives the following json response:
{
"value": "<CLIENT_TOKEN>"
}
Here is an example ClientTokenProvider
implementation using Retrofit 2.x:
// ClientToken.java
public class ClientToken {
@SerializedName
private String value;
public String getValue() {
return value;
}
}
// Api.java
public interface Api {
@GET("/client_token")
Call<ClientToken> getClientToken();
}
// ExampleClientTokenProvider.java
class ExampleClientTokenProvider implements ClientTokenProvider {
private static final Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("https://my-api.com")
.addConverterFactory(GsonConverterFactory.create());
private static final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
public static Api createService() {
builder.client(httpClient.build());
Retrofit retrofit = builder.build();
return retrofit.create(Api.class);
}
void getClientToken(@NonNull ClientTokenCallback callback) {
Call<ClientToken> call = createService().getClientToken();
call.enqueue(new Callback<ClientToken>() {
@Override
public void onResponse(Call<ClientToken> call, Response<ClientToken> response) {
callback.onSuccess(response.body().getValue());
}
@Override
public void onFailure(Call<ClientToken> call, Throwable t) {
callback.onFailure(new Exception(t));
}
});
}
}
Then in an Activity or Fragment, create an instance of BraintreeClient
:
// ExampleActivity.java
public class ExampleActivity extends AppCompatActivity {
private BraintreeClient braintreeClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, new ExampleClientTokenProvider());
}
}
In v3, there were several interfaces that would be called when events occurred: PaymentMethodNonceCreatedListener
, ConfigurationListener
, BraintreeCancelListener
, and BraintreeErrorListener
.
In v4, these listeners have been replaced by a callback pattern for flows that do not require an app or browser switch, and new listeners for payment flows that do require an app or browser switch.
For payment methods that do not require leaving the application, the result will be returned via the callback passed into the tokenization method.
For example, using the CardClient
:
cardClient.tokenize(card, (cardNonce, error) -> {
// send cardNonce.getString() to your server or handle error
});
For payment methods that require a browser switch or app switch, the result will be returned via the payment method listener (ex: PayPalListener
).
For example, using the PayPalClient
:
public class MerchantActivity extends AppCompatActivity implements PayPalListener {
...
@Override
public void onPayPalSuccess(PayPalAccountNonce payPalAccountNonce) {
// send nonce to server and create a transaction
}
@Override
public void onPayPalFailure(Exception error) {
// handle error
}
}
Full implementation examples can be found in the payment method feature sections below.
When the customer cancels out of a payment flow, a UserCanceledException
will be returned in the callback of the invoked method.
Errors will be returned to the callback of the invoked method for methods that do no invoke an app or browser switch or to the payment method listener for methods that do invoke an app or browser switch.
If you need to fetch configuration, use BraintreeClient#getConfiguration()
.
Previously, this was done via BraintreeFragment
:
braintreeFragment.addListener(new ConfigurationListener() {
void onConfigurationFeetched(Configuration configuration) {
}
});
The builder pattern has been removed in v4 to allow for consistent object creation across Java and Kotlin.
Classes have been renamed without the Builder
postfix, method chaining has been removed, and setters have been renamed with the set
prefix.
For example, CardBuilder
in v3 becomes Card
in v4:
Card card = new Card();
card.setNumber("4111111111111111");
card.setExpirationDate("12/2022");
Builder classes that have been renamed:
CardBuilder
is nowCard
UnionPayCardBuilder
is nowUnionPayCard
In v3
, the PaymentMethod
class could be used to retrieve a current list of PaymentMethodNonce
objects for the current customer. In v4
, that functionality will be moved to drop-in
.
The code snippets for the example payment method flows show how to integrate each payment method into an Activity
. These payment methods can also be integrated into a Fragment
.
To integrate via Fragment
, update your fragment to implement the applicable <PaymentMethod>Listener
. Construct the BraintreeClient
and <PaymentMethod>Client
within the fragment, and set the fragment as the listener on the <PaymentMethod>Client
.
There are certain methods that require a FragmentActivity
as a parameter. This is necessary for handling app or browser switches within Android. To invoke these methods from within a fragment, pass requireActivity()
as the parameter.
The American Express feature is now supported by implementing the following dependencies:
dependencies {
implementation 'com.braintreepayments.api:american-express:4.47.0'
implementation 'com.braintreepayments.api:card:4.47.0'
}
To use the feature, instantiate an AmericanExpressClient
:
package com.my.app;
public class AmericanExpressActivity extends AppCompatActivity {
private BraintreeClient braintreeClient;
private AmericanExpressClient americanExpressClient;
private CardClient cardClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
americanExpressClient = new AmericanExpressClient(braintreeClient);
// you will also need a card client for tokenization in this example
cardClient = new CardClient(braintreeClient);
}
private void tokenizeCard() {
Card card = new Card();
card.setNumber("378282246310005");
card.setExpirationDate("12/2022");
cardClient.tokenize(card, (cardNonce, error) -> {
if (cardNonce != null) {
getAmexRewardsBalance(cardNonce);
} else {
// handle error
}
});
}
private void getAmexRewardsBalance(CardNonce cardNonce) {
String nonceString = cardNonce.getString();
americanExpressClient.getRewardsBalance(nonceString, "USD", (rewardsBalance, error) -> {
if (rewardsBalance != null) {
// display rewards amount to user
String rewardsAmount = rewardsBalance.getRewardsAmount();
} else {
// handle error
}
});
}
}
The Card feature is now supported in a single dependency:
dependencies {
implementation 'com.braintreepayments.api:card:4.47.0'
}
To use the feature, instantiate a CardClient
:
package com.my.app;
public class CardActivity extends AppCompatActivity {
private BraintreeClient braintreeClient;
private CardClient cardClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
cardClient = new CardClient(braintreeClient);
}
private void tokenizeCard() {
Card card = new Card();
card.setNumber("4111111111111111");
card.setExpirationDate("12/2022");
cardClient.tokenize(card, (cardNonce, error) -> {
if (cardNonce != null) {
// send this nonce to your server
String nonce = cardNonce.getString();
} else {
// handle error
}
});
}
}
The validate
property on Card
has been renamed to shouldValidate
.
The PayPal Data Collector feature is now supported in the following dependency:
dependencies {
implementation 'com.braintreepayments.api:paypal-data-collector:4.47.0'
}
To use the feature, instantiate a PayPalDataCollector
:
package com.my.app;
public class PaymentsActivity extends AppCompatActivity {
private BraintreeClient braintreeClient;
private PayPalDataCollector dataCollector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
dataCollector = new PayPalDataCollector(braintreeClient);
}
private void collectDeviceData() {
dataCollector.collectDeviceData(this, (deviceData, error) -> {
// send deviceData to your server
});
}
}
The Local Payment feature is now supported in a single dependency:
dependencies {
implementation 'com.braintreepayments.api:local-payment:4.47.0'
}
To use the feature, instantiate a LocalPaymentClient
:
package com.my.app;
public class LocalPaymentActivity extends AppCompatActivity implements LocalPaymentListener {
private BraintreeClient braintreeClient;
private LocalPaymentClient localPaymentClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
localPaymentClient = new LocalPaymentClient(this, braintreeClient);
localPaymentClient.setListener(this);
}
@Override
protected void onNewIntent(Intent newIntent) {
super.onNewIntent(newIntent);
// required if your activity's launch mode is "singleTop", "singleTask", or "singleInstance"
setIntent(newIntent);
}
private void startLocalPayment() {
PostalAddress address = new PostalAddress();
address.setStreetAddress("836486 of 22321 Park Lake");
address.setCountryCodeAlpha2("NL");
address.setLocality("Den Haag");
address.setPostalCode("2585 GJ");
LocalPaymentRequest request = new LocalPaymentRequest();
request.setPaymentType("ideal");
request.setAmount("1.01");
request.setAddress(address);
request.setPhone("639847934");
request.setEmail("[email protected]");
request.setGivenName("Jon");
request.setSurname("Doe");
request.setShippingAddressRequired(true);
request.setCurrencyCode("EUR");
localPaymentClient.startPayment(request, (localPaymentTransaction, error) -> {
if (localPaymentTransaction != null) {
// do any pre-processing transaction.getPaymentId()
localPaymentClient.approvePayment(MyLocalPaymentActivity.this, transaction);
} else {
// handle error
}
});
}
@Override
public void onLocalPaymentSuccess(@NonNull LocalPaymentNonce localPaymentNonce) {
// send nonce to server
}
@Override
public void onLocalPaymentFailure(@NonNull Exception error) {
// handle error
}
}
The Google Pay feature is now supported in a single dependency:
dependencies {
implementation 'com.braintreepayments.api:google-pay:4.47.0'
}
Note: The following wallet enabled metadata tag is now included by the SDK and is no longer required in your AndroidManifest.xml
:
<meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true"/>
To use the feature, instantiate an GooglePayClient
:
package com.my.app;
public class GooglePayActivity extends AppCompatActivity implements GooglePayListener {
private BraintreeClient braintreeClient;
private GooglePayClient googlePayClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
googlePayClient = new GooglePayClient(this, braintreeClient);
googlePayClient.setListener(this);
}
private void checkIfGooglePayIsAvailable() {
googlePayClient.isReadyToPay(this, (isReadyToPay, error) -> {
if (isReadyToPay) {
// Google Pay is available
} else {
// handle error
}
});
}
private void makeGooglePayRequest() {
GooglePayRequest googlePayRequest = new GooglePayRequest();
googlePayRequest.setTransactionInfo(TransactionInfo.newBuilder()
.setTotalPrice("1.00")
.setTotalPriceStatus(WalletConstants.TOTAL_PRICE_STATUS_FINAL)
.setCurrencyCode("USD")
.build());
googlePayRequest.setBillingAddressRequired(true);
googlePayRequest.setGoogleMerchantId("merchant-id-from-google");
googlePayClient.requestPayment(this, googlePayRequest);
}
@Override
public void onGooglePaySuccess(@NonNull PaymentMethodNonce paymentMethodNonce) {
// send nonce to server
}
@Override
public void onGooglePayFailure(@NonNull Exception error) {
// handle error
}
}
The PayPal feature is now supported in a single dependency:
dependencies {
implementation 'com.braintreepayments.api:paypal:4.47.0'
}
To use the feature, instantiate a PayPalClient
:
package com.my.app;
public class PayPalActivity extends AppCompatActivity implements PayPalListener {
private BraintreeClient braintreeClient;
private PayPalClient payPalClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
payPalClient = new PayPalClient(this, braintreeClient);
payPalClient.setListener(this);
}
@Override
protected void onNewIntent(Intent newIntent) {
super.onNewIntent(newIntent);
// required if your activity's launch mode is "singleTop", "singleTask", or "singleInstance"
setIntent(newIntent);
}
private void myTokenizePayPalAccountWithCheckoutMethod() {
PayPalCheckoutRequest request = new PayPalCheckoutRequest("1.00");
request.setCurrencyCode("USD");
request.setIntent(PayPalPaymentIntent.AUTHORIZE);
payPalClient.tokenizePayPalAccount(this, request);
}
private void myTokenizePayPalAccountWithVaultMethod() {
PayPalVaultRequest request = new PayPalVaultRequest();
request.setBillingAgreementDescription("Your agreement description");
payPalClient.tokenizePayPalAccount(this, request);
}
@Override
public void onPayPalSuccess(@NonNull PayPalAccountNonce payPalAccountNonce) {
// send nonce to server
}
@Override
public void onPayPalFailure(@NonNull Exception error) {
// handle error
}
}
v4 introduces two subclasses of PayPalRequest
:
PayPalCheckoutRequest
, for checkout flowsPayPalVaultRequest
, for vault flows
The setters on the request classes have been updated to remove method chaining.
The requestOneTimePayment
and requestBillingAgreement
methods on PayPalClient
have been updated to expect instances of PayPalCheckoutRequest
and PayPalVaultRequest
, respectively.
However, requestOneTimePayment
and requestBillingAgreement
have been deprecated in favor of tokenizePayPalAccount
.
The SamsungPay feature is now supported in a single dependency:
dependencies {
implementation 'com.braintreepayments.api:samsung-pay:4.47.0'
}
To use the feature, instantiate a SamsungPayClient
:
package com.my.app;
public class SamsungPayActivity extends AppCompatActivity implements SamsungPayListener {
private BraintreeClient braintreeClient;
private SamsungPayClient samsungPayClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
samsungPayClient = new SamsungPayClient(braintreeClient);
}
private void checkIfSamsungPayIsAvailable() {
samsungPayClient.isReadyToPay((isReadyToPay, error) -> {
if (isReadyToPay) {
// Samsung Pay is available
} else {
// handle error
}
});
}
private void launchSamsungPay() {
samsungPayClient.buildCustomSheetPaymentInfo((builder, error) -> {
if (builder != null) {
CustomSheetPaymentInfo paymentInfo = builder
.setAddressInPaymentSheet(CustomSheetPaymentInfo.AddressInPaymentSheet.NEED_BILLING_AND_SHIPPING)
.setCustomSheet(getCustomSheet())
.setOrderNumber("order-number")
.build();
samsungPayClient.startSamsungPay(paymentInfo, SamsungPayActivity.this);
} else {
// handle error
}
});
}
private CustomSheet getCustomSheet() {
CustomSheet sheet = new CustomSheet();
final AddressControl billingAddressControl = new AddressControl("billingAddressId", SheetItemType.BILLING_ADDRESS);
billingAddressControl.setAddressTitle("Billing Address");
billingAddressControl.setSheetUpdatedListener((controlId, customSheet) -> {
samsungPayClient.updateCustomSheet(customSheet);
});
sheet.addControl(billingAddressControl);
final AddressControl shippingAddressControl = new AddressControl("shippingAddressId", SheetItemType.SHIPPING_ADDRESS);
shippingAddressControl.setAddressTitle("Shipping Address");
shippingAddressControl.setSheetUpdatedListener((controlId, customSheet) -> {
samsungPayClient.updateCustomSheet(customSheet);
});
sheet.addControl(shippingAddressControl);
AmountBoxControl amountBoxControl = new AmountBoxControl("amountID", "USD");
amountBoxControl.setAmountTotal(1.0, AmountConstants.FORMAT_TOTAL_PRICE_ONLY);
sheet.addControl(amountBoxControl);
return sheet;
}
@Override
public void onSamsungPayStartError(@NonNull Exception error) {
// handle samsung pay start error
}
@Override
public void onSamsungPayStartSuccess(@NonNull SamsungPayNonce samsungPayNonce, @NonNull CustomSheetPaymentInfo paymentInfo) {
// send samsungPayNonce.getString() to server to create a transaction
}
@Override
public void onSamsungPayCardInfoUpdated(@NonNull CardInfo cardInfo, @NonNull CustomSheet customSheet) {
// make adjustments to custom sheet as needed
samsungPayClient.updateCustomSheet(customSheet);
}
}
Visa Checkout is not yet supported in v4.
The Union Pay feature is now supported by implementing the following dependencies:
dependencies {
implementation 'com.braintreepayments.api:union-pay:4.47.0'
}
To use the feature, instantiate a UnionPayClient
:
package com.my.app;
public class UnionPayActivity extends AppCompatActivity {
private BraintreeClient braintreeClient;
private UnionPayClient unionPayClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
unionPayClient = new UnionPayClient(braintreeClient);
}
private void fetchUnionPayCapabilities() {
unionPayClient.fetchCapabilities("4111111111111111", (capabilities, error) -> {
if (capabilities != null) {
// inspect Union Pay capabilities
} else {
// handle error
}
});
}
private void enrollUnionPay() {
UnionPayCard unionPayCard = new UnionPayCard();
unionPayCard.setNumber("4111111111111111");
unionPayCard.setExpirationMonth("12");
unionPayCard.setExpirationYear("22");
unionPayCard.setCvv("123");
unionPayCard.setPostalCode("12345");
unionPayCard.setMobileCountryCode("1");
unionPayCard.setMobilePhoneNumber("1234567890");
unionPayClient.enroll(unionPayCard, (enrollment, error) -> {
unionPayCard.setSmsCode("1234");
unionPayCard.setEnrollmentId(enrollment.getId());
tokenizeUnionPay(unionPayCard);
});
}
private tokenizeUnionPay(UnionPayCard unionPayCard) {
unionPayClient.tokenize(unionPayCard, (cardNonce, error) -> {
if (cardNonce != null) {
// send this nonce to your server
String nonce = cardNonce.getString();
} else {
// handle error
}
});
}
}
The Venmo feature is now supported in a single dependency:
dependencies {
implementation 'com.braintreepayments.api:venmo:4.47.0'
}
To use the feature, instantiate a VenmoClient
:
package com.my.app;
public class VenmoActivity extends AppCompatActivity implements VenmoListener {
private BraintreeClient braintreeClient;
private VenmoClient venmoClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
venmoClient = new VenmoClient(this, braintreeClient);
venmoClient.setListener(this);
}
// The authorizeAccount() method has been replaced with tokenizeVenmoAccount()
private void tokenizeVenmoAccount() {
VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.MULTI_USE);
request.setProfileId("your-profile-id");
request.setShouldVault(false);
venmoClient.tokenizeVenmoAccount(this, request);
}
@Override
public void onVenmoSuccess(@NonNull VenmoAccountNonce venmoAccountNonce) {
// send nonce to server
}
@Override
public void onVenmoFailure(@NonNull Exception error) {
// handle error
}
}
The 3D Secure feature is now supported in a single dependency:
dependencies {
implementation 'com.braintreepayments.api:three-d-secure:4.47.0'
}
Additionally, add the following Maven repository and (non-sensitive) credentials to your app-level gradle:
repositories {
maven {
url "https://cardinalcommerceprod.jfrog.io/artifactory/android"
credentials {
username 'braintree_team_sdk'
password 'cmVmdGtuOjAxOjIwMzgzMzI5Nzg6Q3U0eUx5Zzl5TDFnZXpQMXpESndSN2tBWHhJ'
}
}
}
To use the feature, instantiate a ThreeDSecureClient
:
package com.my.app;
public class ThreeDSecureActivity extends AppCompatActivity implements ThreeDSecureListener {
private BraintreeClient braintreeClient;
private ThreeDSecureClient threeDSecureClient;
private CardClient cardClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
braintreeClient = new BraintreeClient(this, MerchantClientTokenProvider());
threeDSecureClient = new ThreeDSecureClient(braintreeClient);
threeDSecureClient.setListner(this);
// you will also need a card client for tokenization in this example
cardClient = new CardClient(braintreeClient);
}
@Override
protected void onNewIntent(Intent newIntent) {
super.onNewIntent(newIntent);
// required if your activity's launch mode is "singleTop", "singleTask", or "singleInstance"
setIntent(newIntent);
}
private void tokenizeCard() {
Card card = new Card();
card.setNumber("378282246310005");
card.setExpirationDate("12/2022");
cardClient.tokenize(card, (cardNonce, error) -> {
if (cardNonce != null) {
performThreeDSecureVerification(cardNonce);
} else {
// handle error
}
});
}
private void performThreeDSecureVerification(CardNonce cardNonce) {
ThreeDSecurePostalAddress billingAddress = new ThreeDSecurePostalAddress();
billingAddress.setGivenName("Jill");
billingAddress.setSurname("Doe");
billingAddress.setPhoneNumber("5551234567");
billingAddress.setStreetAddress("555 Smith St");
billingAddress.setExtendedAddress("#2");
billingAddress.setLocality("Chicago");
billingAddress.setRegion("IL");
billingAddress.setPostalCode("12345");
billingAddress.setCountryCodeAlpha2("US");
ThreeDSecureAdditionalInformation additionalInformation = new ThreeDSecureAdditionalInformation();
additionalInformation.accountId("account-id");
ThreeDSecureRequest threeDSecureRequest = new ThreeDSecureRequest();
threeDSecureRequest.setAmount("10");
threeDSecureRequest.setEmail("[email protected]");
threeDSecureRequest.setBillingAddress(billingAddress);
threeDSecureRequest.setNonce(cardNonce.getString());
threeDSecureRequest.setShippingMethod(ThreeDSecureShippingMethod.GROUND);
threeDSecureRequest.setAdditionalInformation(additionalInformation);
threeDSecureClient.performVerification(this, threeDSecureRequest, (threeDSecureResult, error) -> {
if (threeDSecureResult != null) {
// examine lookup response (if necessary), then continue verification
threeDSecureClient.continuePerformVerification(ThreeDSecureActivity.this, threeDSecureRequest, threeDSecureResult);
} else {
// handle error
}
});
}
@Override
public void onThreeDSecureSuccess(@NonNull ThreeDSecureResult threeDSecureResult) {
// send this nonce to your server
String nonce = threeDSecureResult.getTokenizedCard().getString();
}
@Override
public void onThreeDSecureFailure(@NonNull Exception error) {
// handle error
}
}
The ThreeDSecureV1UiCustomization
class setters have been updated to remove method chaining and follow standard Java getter/setter pattern.
On ThreeDSecureRequest
the uiCustomization
property was replaced with v2UiCustomization
of type ThreeDSecureV2UiCustomization
.
For 3DS2 UI customization, use the following new classes:
ThreeDSecureV2UiCustomization
ThreeDSecureV2ButtonCustomization
ThreeDSecureV2LabelCustomization
ThreeDSecureV2TextBoxCustomization
ThreeDSecureV2ToolbarCustomization
Previously, the versionRequested
property on ThreeDSecureRequest
defaulted to VERSION_1
. It now defaults to VERSION_2
.
The shippingMethod
property on ThreeDSecureRequest
is now an enum rather than a string. Possible values:
SAME_DAY
EXPEDITED
PRIORITY
GROUND
ELECTRONIC_DELIVERY
SHIP_TO_STORE
Several features of the SDK require handling a browser switch result or an activity result. These can be handled in the same or different Activity/Fragment. If handled together, the results of each flow will be returned to the respective listener:
package com.my.app;
public class PaymentsActivity extends AppCompatActivity implements PayPalListener, VenmoListener {
...
@Override
protected void onPayPalSuccess(PayPalAccountNonce payPalAccountNonce) {
// send nonce to your server
}
@Override
protected void onPayPalFailure(Exception error) {
// handle PayPal error
}
@Override
protected void onVenmoSuccess(VenmoAccountNonce venmoAccountNonce) {
// send nonce to your server
}
@Override
protected void onVenmoFailure(Exception error) {
// handle Venmo error
}
}
For more control over browser-based payment flows, consider the manual browser switch integration pattern.
Here is an example of manual browser switching using PayPalClient
:
class MainActivity : AppCompatActivity() {
private lateinit var payPalClient: PayPalClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val authorization = "<TOKENIZATION_KEY_OR_CLIENT_TOKEN>"
val braintreeClient = BraintreeClient(this, authorization, "my-custom-url-scheme")
payPalClient = PayPalClient(braintreeClient)
val browserSwitchResult = payPalClient.parseBrowserSwitchResult(this, intent)
if (browserSwitchResult != null) {
// process kill scenario
handleBrowserSwitchResult(browserSwitchResult)
} else {
// initiate tokenization
val payPalRequest = PayPalCheckoutRequest("1.00")
payPalClient.tokenizePayPalAccount(payPalRequest)
}
}
override fun onResume() {
super.onResume()
payPalClient.parseBrowserSwitchResult(this, intent)?.let {
handleBrowserSwitchResult(it)
}
}
override fun onNewIntent(newIntent: Intent?) {
super.onNewIntent(newIntent)
// required if your activity's launch mode is "singleTop", "singleTask", or "singleInstance"
payPalClient.parseBrowserSwitchResult(this, newIntent)?.let {
handleBrowserSwitchResult(it)
}
}
private fun handleBrowserSwitchResult(result: BrowserSwitchResult) {
payPalClient.onBrowserSwitchResult(result) { payPalAccountNonce, error ->
payPalAccountNonce?.let {
// forward nonce to server
} ?: error?.let {
// handle error
}
}
// clear pending request to guard against additional browser switch result invocations
payPalClient.clearActiveBrowserSwitchRequests(this)
}
}