Migration
Migrating from v3 to v4
In Android v4, we decoupled the Braintree SDK from Android to offer more
integration flexibility.
Why upgrade to v4?
- First class Kotlin support
- Better support for resource-constrained Android devices
- Simplified Pay with PayPal integration
- Increased modularity
Installation
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.
Browser Switch
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:
- XML
<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>Note
You must also add
android:exported to your Activity with the
intent-filter if your app compile SDK version is API 31 (Android 12) or later.
BraintreeClient constructor that allows you to specify a customUrlScheme:
- Xml
<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>BraintreeClient make sure to pass the appropriate custom
url scheme for each deep link target Activity:
- Kotlin
- Java
// MyPaymentsActivity1.kt
val braintreeClient1 = BraintreeClient(this, "TOKENIZATION_KEY_OR_CLIENT_TOKEN", "custom-url-scheme-1")
val braintreeClient2 = BraintreeClient(this, "TOKENIZATION_KEY_OR_CLIENT_TOKEN", "custom-url-scheme-2")BraintreeFragment
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.
Authorization
A BraintreeClient is needed to construct a feature client for any payment method. When
creating a BraintreeClient, you can provide a tokenization key 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. The
example below shows the initialization with a tokenization key authorization:
- Kotlin
- Java
class MyActivity : AppCompatActivity() {
private lateinit var braintreeClient: BraintreeClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, "<#TOKENIZATION_KEY#>")
}
}Client Token Provider
Below is an example ClientTokenProvider implementation using Retrofit 2.x. This example
assumes that you have a server that supports GEThttps://www.my-api.com/client_token and receives the following json response:
- JSON
{
"value": "<client_token>"
}- Kotlin
- Java
// ClientToken.kt
data class ClientToken(val value: String = "")
// Api.kt
interface Api {
@GET("/client_token")
fun getClientToken(): Call<clienttoken>
}
// ExampleClientTokenProvider.kt
internal class ExampleClientTokenProvider : ClientTokenProvider {
override fun getClientToken(callback: ClientTokenCallback) {
val call = createService().getClientToken()
call.enqueue(object : Callback<clienttoken> {
override fun onResponse(call: Call<clienttoken>?, response: Response<clienttoken>?) {
response?.body()?.value?.let { callback.onSuccess(it) }
}
override fun onFailure(call: Call<clienttoken>?, t: Throwable?) {
callback.onFailure(Exception(t))
}
})
}
companion object {
private val builder = Retrofit.Builder()
.baseUrl("https://my-api.com")
.addConverterFactory(GsonConverterFactory.create())
private val httpClient = OkHttpClient.Builder()
fun createService(): Api {
builder.client(httpClient.build())
val retrofit = builder.build()
return retrofit.create(Api::class.java)
}
}
}BraintreeClient using your ClientTokenProvider:
- Kotlin
- Java
class ExampleActivity : AppCompatActivity() {
private var braintreeClient: BraintreeClient? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, ExampleClientTokenProvider())
}
}Event Handling
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 payment method specific listeners for payment flows that require an
app or browser switch, and callbacks for non-switching methods.
HandlingPaymentMethodNonceResults
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:
- Kotlin
- Java
cardClient.tokenize(card) { cardNonce, error ->
// send cardNonce.string to your server or handle error
}PayPalClient and
PayPalListener:
- Kotlin
- Java
class PayPalActivity : AppCompatActivity(), PayPalListener {
override fun onPayPalSuccess(payPalAccountNonce: PayPalAccountNonce) {
// send nonce to server
}
override fun onPayPalFailure(error: Exception) {
if (error is UserCanceledException) {
// user canceled
} else {
// handle error
}
}
}Handling Cancellation
When the customer cancels out of a payment flow, a
UserCanceledException will be returned in the
onFailure listener method.
Handling Errors
Errors will be returned to the callback of the invoked method or the
onFailure listener method.
Fetching Configuration
If you need to fetch configuration, use
BraintreeClient#getConfiguration(). Previously, this was done via adding a
ConfigurationListener to BraintreeFragment.
Builder Pattern
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:
- Kotlin
- Java
val card = Card()
card.number = "4111111111111111"
card.expirationDate = "12/2022"CardBuilderis nowCard
American Express
The American Express feature is now supported by implementing the following dependencies:
- Groovy
dependencies {
implementation 'com.braintreepayments.api:american-express:4.49.1'
implementation 'com.braintreepayments.api:card:4.49.1'
}AmericanExpressClient:
- Kotlin
- Java
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, "<#CLIENT_AUTHORIZATION#>")
americanExpressClient = AmericanExpressClient(braintreeClient) // you will also need a card client for tokenization in this example
cardClient = CardClient(braintreeClient)
}
private fun tokenizeCard() {
val card = Card()
card.number = "378282246310005"
card.expirationDate = "12/2022"
cardClient.tokenize(card) { cardNonce, error ->
cardNonce?.let { getAmexRewardsBalance(it) }
}
}
private fun getAmexRewardsBalance(cardNonce: CardNonce) {
val nonceString = cardNonce.string
americanExpressClient.getRewardsBalance(nonceString, "USD") { rewardsBalance, error ->
rewardsBalance?.let {
// display rewards amount to user
val rewardsAmount = it.getRewardsAmount()
}
}
}
}Card
The Card feature is now supported in a single dependency:
- Groovy
dependencies {
implementation 'com.braintreepayments.api:card:4.49.1'
}CardClient:
- Kotlin
- Java
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, "<#CLIENT_AUTHORIZATION#>")
cardClient = CardClient(braintreeClient)
}
private fun tokenizeCard() {
val card = Card()
card.number = "4111111111111111"
card.expirationDate = "12/2022"
cardClient.tokenize(card) { cardNonce, error ->
cardNonce?.let { // send it.string to your server }
}
}
}Data Collector
The Data Collector feature is now supported in the following dependency:
- Groovy
dependencies {
implementation 'com.braintreepayments.api:data-collector:4.49.1'
}DataCollector:
- Kotlin
- Java
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, "<#CLIENT_AUTHORIZATION#>")
dataCollector = DataCollector(braintreeClient)
}
private fun collectDeviceData() {
dataCollector.collectDeviceData(this) { deviceData, error ->
// send deviceData to your server
}
}
}Local Payment
The Local Payment feature is now supported in a single dependency:
- Groovy
dependencies {
implementation 'com.braintreepayments.api:local-payment:4.49.1'
}LocalPaymentClient:
- Kotlin
- Java
class MyActivity : AppCompatActivity(), LocalPaymentListener {
private lateinit var braintreeClient: BraintreeClient
private lateinit var localPaymentClient: LocalPaymentClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, ExampleClientTokenProvider())
localPaymentClient = LocalPaymentClient(this, braintreeClient)
localPaymentClient.setListener(this)
}
override fun onNewIntent(newIntent: Intent?) {
super.onNewIntent(newIntent) // required if your activity's launch mode is "singleTop", "singleTask", or "singleInstance"
intent = newIntent
}
private fun startLocalPayment() {
val address = PostalAddress().apply {
streetAddress = "836486 of 22321 Park Lake"
countryCodeAlpha2 = "NL"
locality = "Den Haag"
postalCode = "2585 GJ"
}
val request = LocalPaymentRequest().apply {
paymentType = "ideal"
amount = "1.01"
this.address = address
phone = "639847934"
email = "joe@getbraintree.com"
givenName = "Jon"
surname = "Doe"
shippingAddressRequired = true
currencyCode = "EUR"
}
localPaymentClient.startPayment(request) { localPaymentTransaction, error ->
localPaymentTransaction?.let { transaction ->
// do any pre-processing
transaction.paymentId
localPaymentClient.approvePayment(this@MyActivity, transaction)
}
}
}
override fun onLocalPaymentSuccess(paymentMethod: PaymentMethodNonce) {
// send nonce to server
}
override fun onLocalPaymentFailure(error: Exception) {
if (error is UserCanceledException) {
// user canceled
} else {
// handle error
}
}
}Google Pay
The Google Pay feature is now supported in a single dependency:
- Groovy
dependencies {
implementation 'com.braintreepayments.api:google-pay:4.49.1'
}AndroidManifest.xml:
- XML
undefinedGooglePayClient:
- Kotlin
- Java
class MyActivity : AppCompatActivity(), GooglePayListener {
private lateinit var braintreeClient: BraintreeClient
private lateinit var googlePayClient: GooglePayClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, ExampleClientTokenProvider())
googlePayClient = GooglePayClient(this, braintreeClient)
googlePayClient.setListener(this)
}
private fun checkIfGooglePayIsAvailable() {
googlePayClient.isReadyToPay(this) { isReadyToPay, error ->
if (isReadyToPay) {
// Google Pay is available
}
}
}
private fun makeGooglePayRequest() {
val googlePayRequest = GooglePayRequest().apply {
transactionInfo = TransactionInfo.newBuilder()
.setTotalPrice("1.00")
.setTotalPriceStatus(WalletConstants.TOTAL_PRICE_STATUS_FINAL)
.setCurrencyCode("USD")
.build()
billingAddressRequired = true
}
googlePayClient.requestPayment(this, googlePayRequest)
}
override fun onGooglePaySuccess(paymentMethodNonce: PaymentMethodNonce) {
// send nonce to server
}
override fun onGooglePayFailure(error: Exception) {
if (error is UserCanceledException) {
// user canceled
} else {
// handle error
}
}
}PayPal
The PayPal feature is now supported in a single dependency:
- Groovy
dependencies {
implementation 'com.braintreepayments.api:paypal:4.49.1'
}PayPalClient:
- Kotlin
- Java
class MyActivity : AppCompatActivity(), PayPalListener {
private lateinit var braintreeClient: BraintreeClient
private lateinit var payPalClient: PayPalClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, ExampleClientTokenProvider())
payPalClient = PayPalClient(this, braintreeClient)
payPalClient.setListener(this)
}
override fun onNewIntent(newIntent: Intent?) {
super.onNewIntent(newIntent) // required if your activity's launch mode is "singleTop", "singleTask", or "singleInstance"
intent = newIntent
}
private fun myTokenizePayPalAccountWithCheckoutMethod() {
val request = PayPalCheckoutRequest("1.00").apply {
currencyCode = "USD"
intent = PayPalPaymentIntent.AUTHORIZE
}
payPalClient.tokenizePayPalAccount(this, request)
}
private fun myTokenizePayPalAccountWithVaultMethod() {
val request = PayPalVaultRequest().apply {
billingAgreementDescription = "Your agreement description"
}
payPalClient.tokenizePayPalAccount(this, request);
}
override fun onPayPalSuccess(payPalAccountNonce: PayPalAccountNonce) {
// send nonce to server
}
override fun onPayPalFailure(error: Exception) {
if (error is UserCanceledException) {
// user canceled
} else {
// handle error
}
}
}PayPal Request
v4 introduces two subclasses of PayPalRequest:
PayPalCheckoutRequest, for checkout flowsPayPalVaultRequest, for vault flows
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.
Samsung Pay
Samsung Pay has been deprecated.
Important
This guide has been deprecated. Please use the
pay later offers guide
instead.
Secure Remote Commerce (Visa Checkout)
This feature is not yet supported in v4.
Venmo
The Venmo feature is now supported in a single dependency:
- Groovy
dependencies {
implementation 'com.braintreepayments.api:venmo:4.49.1'
}VenmoClient:
- Kotlin
- Java
class MyActivity : AppCompatActivity(), VenmoListener {
private lateinit var braintreeClient: BraintreeClient
private lateinit var venmoClient: VenmoClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, ExampleClientTokenProvider())
venmoClient = VenmoClient(this, braintreeClient)
venmoClient.setListener(this)
}
// The authorizeAccount() method has been replaced with tokenizeVenmoAccount()
private fun tokenizeVenmoAccount() {
val request = VenmoRequest(VenmoPaymentMethodUsage.MULTI_USE).apply {
profileId = "your-profile-id"
shouldVault = false
}
venmoClient.tokenizeVenmoAccount(this, request);
}
override fun onVenmoSuccess(venmoAccountNonce: VenmoAccountNonce) {
// send nonce to server
}
override fun onVenmoFailure(error: Exception) {
if (error is UserCanceledException) {
// user canceled
} else {
// handle error
}
}
}3D Secure
The 3D Secure feature is now supported in a single dependency:
- Groovy
dependencies {
implementation 'com.braintreepayments.api:three-d-secure:4.49.1'
}- Groovy
repositories {
maven {
url "https://cardinalcommerceprod.jfrog.io/artifactory/android"
credentials {
username 'braintree_team_sdk'
password 'cmVmdGtuOjAxOjIwMzgzMzI5Nzg6Q3U0eUx5Zzl5TDFnZXpQMXpESndSN2tBWHhJ'
}
}
}ThreeDSecureClient:
- Kotlin
- Java
class MyActivity : AppCompatActivity(), ThreeDSecureListener {
private lateinit var braintreeClient: BraintreeClient
private lateinit var threeDSecureClient: ThreeDSecureClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
braintreeClient = BraintreeClient(this, ExampleClientTokenProvider())
threeDSecureClient = ThreeDSecureClient(this, braintreeClient)
threeDSecureClient.setListener(this); // you will also need a card client for tokenization in this example cardClient = CardClient(braintreeClient)
}
override fun onNewIntent(newIntent: Intent?) {
super.onNewIntent(newIntent); // required if your activity's launch mode is "singleTop", "singleTask", or "singleInstance"
intent = newIntent;
}
private fun tokenizeCard() {
val card = Card().apply {
number = "378282246310005"
expirationDate = "12/2022"
}
cardClient.tokenize(card) { cardNonce, error ->
cardNonce?.let { performThreeDSecureVerification(cardNonce) }
}
}
private fun performThreeDSecureVerification(cardNonce: CardNonce) {
val billingAddress = ThreeDSecurePostalAddress().apply {
givenName = "Jill"
surname = "Doe"
phoneNumber = "5551234567"
streetAddress = "555 Smith St"
extendedAddress = "#2"
locality = "Chicago"
region = "IL"
postalCode = "12345"
countryCodeAlpha2 = "US"
}
val additionalInformation = ThreeDSecureAdditionalInformation().apply {
accountId = "account-id"
}
val threeDSecureRequest = ThreeDSecureRequest().apply {
amount = "10"
email = "test@email.com"
billingAddress = billingAddress
nonce = cardNonce.string
shippingMethod = ThreeDSecureShippingMethod.GROUND
additionalInformation = additionalInformation
}
threeDSecureClient.performVerification(this, threeDSecureRequest) { result, error ->
result?.let { ->
// examine lookup response (if necessary), then continue verification
threeDSecureClient.continuePerformVerification(this@MyActivity, threeDSecureRequest, threeDSecureResult)
}
}
}
override fun onThreeDSecureSuccess(threeDSecureResult: ThreeDSecureResult) {
// send this nonce to your server
val nonce = threeDSecureResult.tokenizedCard?.string;
}
override fun onThreeDSecureFailure(error: Exception) {
if (error is UserCanceledException) {
// user canceled
} else {
// handle error
}
}
}3DS1 UI Customization
The ThreeDSecureV1UiCustomization class setters have been updated to remove method
chaining and follow standard Java getter/setter pattern.
3DS2 UI Customization
On ThreeDSecureRequest the uiCustomization property was replaced with
v2UiCustomization of type ThreeDSecureV2UiCustomization. For 3DS2 UI
customization, use the following new classes:
ThreeDSecureV2UiCustomizationThreeDSecureV2ButtonCustomizationThreeDSecureV2LabelCustomizationThreeDSecureV2TextBoxCustomizationThreeDSecureV2ToolbarCustomization
Default 3DS Version
Previously, the versionRequested property on ThreeDSecureRequest defaulted
to VERSION_1. It now defaults to VERSION_2.
Shipping Method
The shippingMethod property on ThreeDSecureRequest is now an enum rather
than a string. Possible values:
SAME_DAYEXPEDITEDPRIORITYGROUNDELECTRONIC_DELIVERYSHIP_TO_STORE