3D Secure
Step by Step Integration
Table of Contents
3DS Client side flow
Generate a client token
If you do not decommission your app versions that include the older SDK versions or force upgrade your app with the updated certificates by the expiration date, 100% of your customer traffic will fail.
Before you can initialize a ThreeDSecureClient
, you will need to set up the SDK and initialize it with a client token generated on your server.
If you would like to use a merchant account ID other than your default, specify the merchant_account_id
when generating the client token. The merchant account ID used to create the client token must match the merchant account ID used to create the subsequent transaction or verification.
Verify a card using 3DS
To make sure your app can complete a 3D Secure verification, declare a URL scheme in your AndroidManifest. This will allow Android to return to your app from a browser-based verification flow.
Get the SDK
Add the following Maven repository and (non-sensitive) credentials to your app-level gradle.
- Groovy
repositories {
maven {
url "https://cardinalcommerceprod.jfrog.io/artifactory/android"
credentials {
username = 'braintree_team_sdk'
password = 'AKCp8jQcoDy2hxSWhDAUQKXLDPDx6NYRkqrgFLRc3qDrayg6rrCbJpsKKyMwaykVL8FWusJpp'
}
}
}
Add the following to your app-level build.gradle
file:
- Kotlin
- Groovy
dependencies {
implementation("com.braintreepayments.api:three-d-secure:5.8.0")
}
Initialization
Create a ThreeDSecureLauncher
inside of your Activity's onCreate()
. Then, create a ThreeDSecureClient
with a Client Token.
- Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
threeDSecureLauncher = ThreeDSecureLauncher(this) { paymentAuthResult ->
// Handle ThreeDSecurePaymentAuthResult
}
threeDSecureClient = ThreeDSecureClient(
context = this,
authorization = "[CLIENT_TOKEN]"
)
}
Create a request
To use 3DS, you will need to create a ThreeDSecureRequest
object with relevant customer and transaction data in order to minimize the need for issuing banks to present authentication challenges to customers.
The ThreeDSecureRequest object must contain the following fields:
amount
nonce
The object should contain as many of the following fields as possible. The full list of fields is described in the SDK documentation.
- Kotlin
val address = ThreeDSecurePostalAddress(
givenName = "Jill", // ASCII-printable characters required, else will throw a validation error
surname = "Doe", // ASCII-printable characters required, else will throw a validation error
phoneNumber = "5551234567",
streetAddress = "555 Smith St",
extendedAddress = "#2",
locality = "Chicago",
region = "IL", // ISO-3166-2 code
postalCode = "12345",
countryCodeAlpha2 = "US",
)
// For best results, provide as many additional elements as possible.
val additionalInformation = ThreeDSecureAdditionalInformation(
shippingAddress = address
)
val cardNonce = paymentMethodNonce as CardNonce
val threeDSecureRequest = ThreeDSecureRequest(
amount = "10.00",
nonce = cardNonce.string,
email = "test@email.com",
billingAddress = address,
additionalInformation = additionalInformation,
)
To verify vaulted cards, you would need to first generate the nonce on the server and then pass it in the ThreeDSecureRequest
.
Invoke the 3DS Flow
To start the 3DS flow call ThreeDSecureClient.createPaymentAuthRequest()
passing in the ThreeDSecureRequest
. Handle the ThreeDSecurePaymentAuthRequest
returned in the callback.
If a challenge is required, ReadyToLaunch
will be returned. Invoke ThreeDSecureLauncher.launch()
to start the flow.
When no additional authentication challenge is required, LaunchNotRequired
is returned. The payment flow can continue with the returned nonce.
If Failure
is returned, handle the error.
- Kotlin
threeDSecureClient.createPaymentAuthRequest(
context = this,
request = threeDSecureRequest
) { paymentAuthRequest ->
when (paymentAuthRequest) {
is ThreeDSecurePaymentAuthRequest.ReadyToLaunch -> {
threeDSecureLauncher.launch(paymentAuthRequest)
}
is ThreeDSecurePaymentAuthRequest.LaunchNotRequired -> {
// Continue payment flow with paymentAuthRequest.nonce
}
is ThreeDSecurePaymentAuthRequest.Failure -> {
// Handle error
}
}
}
Handle the 3DS Result
Invoke ThreeDSecureClient.tokenize()
inside of the ThreeDSecureLauncherCallback
and handle the ThreeDSecureResult
.
When Success
is returned, the 3DS challenge was completed and the original payment flow can be continued.
If Failure
is returned, the challenge failed and the error should be handled.
If Cancel
is returned, the user canceled the challenge.
- Kotlin
threeDSecureLauncher = ThreeDSecureLauncher(this) { paymentAuthResult ->
threeDSecureClient.tokenize(paymentAuthResult) { threeDSecureResult ->
when (threeDSecureResult) {
is ThreeDSecureResult.Success -> {
// 3DS challenge completed - continue payment flow with threeDSecureResult.nonce
}
is ThreeDSecureResult.Failure -> {
// Handle error
}
is ThreeDSecureResult.Cancel -> {
// Handle cancel flow
}
}
}
}
Complete Integration
- Kotlin
class DemoActivity : AppCompatActivity() {
private lateinit var threeDSecureLauncher: ThreeDSecureLauncher
private lateinit var threeDSecureClient: ThreeDSecureClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
threeDSecureLauncher = ThreeDSecureLauncher(this) { paymentAuthResult ->
threeDSecureClient.tokenize(paymentAuthResult) { threeDSecureResult ->
when (threeDSecureResult) {
is ThreeDSecureResult.Success -> {
// 3DS challenge completed - continue payment flow with threeDSecureResult.nonce
}
is ThreeDSecureResult.Failure -> {
// Handle error
}
is ThreeDSecureResult.Cancel -> {
// Handle cancel flow
}
}
}
}
threeDSecureClient = ThreeDSecureClient(
context = this,
authorization = "[CLIENT_TOKEN]"
)
}
private fun startThreeDsFlow(cardNonce: String) {
val address = ThreeDSecurePostalAddress(
givenName = "Jill", // ASCII-printable characters required, else will throw a validation error
surname = "Doe", // ASCII-printable characters required, else will throw a validation error
phoneNumber = "5551234567",
streetAddress = "555 Smith St",
extendedAddress = "#2",
locality = "Chicago",
region = "IL", // ISO-3166-2 code
postalCode = "12345",
countryCodeAlpha2 = "US",
)
// For best results, provide as many additional elements as possible.
val additionalInformation = ThreeDSecureAdditionalInformation(
shippingAddress = address
)
val threeDSecureRequest = ThreeDSecureRequest(
amount = "10.00",
nonce = cardNonce,
email = "test@email.com",
billingAddress = address,
additionalInformation = additionalInformation,
)
threeDSecureClient.createPaymentAuthRequest(
context = this,
request = threeDSecureRequest
) { paymentAuthRequest ->
when (paymentAuthRequest) {
is ThreeDSecurePaymentAuthRequest.ReadyToLaunch -> {
threeDSecureLauncher.launch(paymentAuthRequest)
}
is ThreeDSecurePaymentAuthRequest.LaunchNotRequired -> {
// Continue payment flow with paymentAuthRequest.nonce
}
is ThreeDSecurePaymentAuthRequest.Failure -> {
// Handle error
}
}
}
}
}
A validation error from Braintree will be returned if a field does not follow Cardinal's documentation.
3DS Server side flow
An alternative way of performing 3DS is to make the 3DS call from the server instead of from the client machine.
To meet the device data collection requirements for 3DS, merchants must either make a prerequisite prepareLookup
call in the client SDK or pass in the browser fields directly as input parameters.
Prepare the 3DS lookup
The prepareLookup
call will return the data needed to perform a 3D Secure lookup call. This call is triggered from the client's device. The payload returned by this call should be passed on to the server so a 3DS lookup can be done from there.
- Kotlin
threeDSecureClient.prepareLookup(
context = this,
request = ThreeDSecureRequest(),
callback = { prepareLookupResult ->
if (prepareLookupResult is ThreeDSecurePrepareLookupResult.Success) {
// Send payload to the server to do server side lookup
} else {
// Handle error
}
}
)
Make the 3DS lookup call
Use the GraphQL mutation performThreeDSecureLookup
to attempt to perform 3D Secure Authentication on credit card payment method. This may consume the payment method and return a new single-use payment method.
Including device data is mandatory. This can be done either by passing along the dfReferenceId
or browserInformation
.
- Mutation
mutation PerformThreeDSecureLookup($input: PerformThreeDSecureLookupInput!) {
performThreeDSecureLookup(input: $input) {
clientMutationId
threeDSecureLookupData {
authenticationId
}
paymentMethod {
id
details {
... on CreditCardDetails {
bin
threeDSecure {
authentication {
cavv
eciFlag
liabilityShifted
liabilityShiftPossible
}
}
}
}
}
}
}
- Variables
{
input: {
paymentMethodId: single_use_payment_method_id,
amount: "10.00",
transactionInformation: {
browserInformation:
{
javaEnabled: false,
acceptHeader: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
language: "en-GB",
colorDepth: 24,
screenHeight: 720,
screenWidth: 1280,
timeZone: "0",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safar i/537.36",
javascriptEnabled: true,
}
ipAddress: "82.34.105.112",
deviceChannel: "BROWSER",
},
}
}
Trigger 3DS challenge
This call will launch the iframe challenge using a 3D Secure lookup response from a server side lookup. This call is only necessary if a challenge is required.
- Kotlin
threeDSecureClient.initializeChallengeWithLookupResponse(
lookupResponse = "[Lookup Response]",
) { paymentAuthRequest ->
when(paymentAuthRequest) {
is ThreeDSecurePaymentAuthRequest.ReadyToLaunch -> {
// Invoke threeDSecureLauncher.launch() to start the challenge
}
is ThreeDSecurePaymentAuthRequest.LaunchNotRequired -> {
// Challenge is not required, continue with payment flow
}
is ThreeDSecurePaymentAuthRequest.Failure -> {
// Handle error
}
}
}
See Invoke the 3DS Flow for launching the challenge.