Client SDK
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 GET
https://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"
CardBuilder
is 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
undefined
GooglePayClient
:
- 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
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:
ThreeDSecureV2UiCustomization
ThreeDSecureV2ButtonCustomization
ThreeDSecureV2LabelCustomization
ThreeDSecureV2TextBoxCustomization
ThreeDSecureV2ToolbarCustomization
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_DAY
EXPEDITED
PRIORITY
GROUND
ELECTRONIC_DELIVERY
SHIP_TO_STORE