DropIn UI 3D Secure while add card to vault
General information
- SDK/Library version: com.braintreepayments.api:drop-in:6.5.0, braintree: 4.20.0
- Environment: Production
- Android Version and Device: Not relevant, all
Response from your Support team:
Non-recurring merchant initiated transactions will be processed much the same way that recurring transactions would be. You should request a cardholder challenge to establish SCA when the card is first authorized, establishing a mandate between you and your customer. This can be with a verification, or the first transaction of a recurring billing event. By applying 3D Secure to the first transaction or verification, you signal to the card issuer that you have established a mandate between you and your customer to charge their payment method for subsequent recurring payments as detailed in your terms and conditions. Establishing SCA on verifications will be useful for scenarios where the cardholder will not be present when the charge is issued, and the amount isn’t known when the payment method is stored. For subsequent transactions from that payment method, which would be outside of the scope of PSD2 SCA, use the unscheduled value in the TransactionSource parameter of the Transaction.Sale() API call. Lastly, please note that gateway rejections are not the same as declines. Gateway rejections are blocked by your gateway settings, while declines are blocked by the customer's bank. I hope this helps answer your questions. If you have any additional questions, please let me know.
Issue description
Try to create a payment using a Credit card. We need to store card to Vault manager with 3D Secure validation due to Eurepean regulation and PSD2.
Create a request with 3D Secure for request a cardholder challenge using: isChallengeRequested / isCardAddChallengeRequested is not possible, we try it with:
val threeD = ThreeDSecureRequest().apply {
paymentAmount.let { amount = String.format(Locale.ENGLISH, "%.2f", it) }
email = userInfo.email
billingAddress = userInfo.toThreeDSecurePostalAddress()
mobilePhoneNumber = userPhone
versionRequested = ThreeDSecureRequest.VERSION_2
/**
* Request full 3DS flow to verify card.
* Should be used for avoid [2099 errors](https://developer.paypal.com/braintree/docs/reference/general/processor-responses/authorization-responses#code-2099)
*/
if (isOnlyCardAdd) {
isCardAddChallengeRequested = true
} else {
isChallengeRequested = true
}
additionalInformation = userInfo.additionalInformation.toThreeDSecureAdditionalInfo()
}
val dropInRequest = DropInRequest().apply {
isPayPalDisabled = true
isVaultManagerEnabled = false
isGooglePayDisabled = true
threeDSecureRequest = threeD
isVenmoDisabled = true
}
private fun initDropInClient() {
dropInClient = DropInClient(this) { clientToken ->
viewState.braintreeToken.value?.token?.let { clientToken.onSuccess(it) }.ifNullThen {
clientToken.onFailure(Exception("Braintree token is null"))
}
}
}
private fun startDropIn() {
dropInClient
.apply { setListener(dropInListener) }
.run { launchDropIn(dropInRequest) }
}
The issue is, that while adding card in DropIn UI threeDSecureRequest from dropInRequest is ignored and user address/additional info are not sent with card to card issuer / bank. So the card is not possible to verify in that case.
Request which is send from DropIn UI SDK to Braintree API:
{
"clientSdkMetadata": {
"platform": "android",
"sessionId": "****",
"source": "form",
"integration": "custom"
},
"query": "mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) { tokenizeCreditCard(input: $input) { token creditCard { bin brand expirationMonth expirationYear cardholderName last4 binData { prepaid healthcare debit durbinRegulated commercial payroll issuingBank countryOfIssuance productId } } }}",
"operationName": "TokenizeCreditCard",
"variables": {
"input": {
"options": {
"validate": true
},
"creditCard": {
"number": "*****",
"expirationMonth": "**",
"expirationYear": "**",
"cvv": "***"
}
}
}
}
Missing validation info from threeDSecureRequest, in that case is not applied isChallengeRequested / isCardAddChallengeRequested to request as you can see from request, only card info is sent.
Response to this request is:
{
"errors": [
{
"message": "CVV verification failed",
"locations": [
{
"line": 1,
"column": 66
}
],
"path": [
"tokenizeCreditCard"
],
"extensions": {
"errorClass": "VALIDATION",
"errorType": "user_error",
"inputPath": [
"input",
"creditCard",
"cvv"
],
"legacyCode": "81736"
}
}
],
"data": {
"tokenizeCreditCard": null
},
"extensions": {
"requestId": "***"
}
}
Because Bank Issuer can't validate Credit card without address, etc it return 2099 error. Your API return misunderstood error about wrong CVC. But CVC is correct, same as card number and expiration date.
Current state:
- user click on Pay (or Add card, depends on screen)
- app open DropIn UI with selection of payment method (only
Credit or Debit Cardis available, that's ok) - user click on
Credit or Debit Card - DropIn UI client open screen with
Card details - user fill Card Number and click on Next button
- user fill Expiration date and CVC/CVV and click on ADD CARD button
- DropIn UI show error: CVC is invalid.That's not correct, because card info is ok, but error 2099
Expected state
- user click on Pay (od Add card, depends on screen)
- app open DropIn UI with selection of payment method (only
Credit or Debit Cardis available, that's ok) - user click on
Credit or Debit Card - DropIn UI client open screen with
Card details - user fill Card Number and click on Next button
- user fill Expiration date and CVC/CVV and click on ADD CARD button
- DropIn Client send all info (addtition, address, phone, etc) with card number
- DropIn UI client show 3D Secure screen for add card to Vault manager
second thing: What amount we should fill into 3DS request, when we just want to add card to Braintree, but not process any payment?
The amount looks required, but in cardAddChallengeRequested is mentioned, that amount will be 0. Should we pass in that case amount = "0.00" in ThreeDSecureRequest?
almost same question in stackoverflow without any response: https://stackoverflow.com/q/72950709/4024146
Next respose from your Support team:
Based on your initial description, in order to authenticate with 3DS when saving the card (without creating a transaction), you'll need to:
- Generate a 3DS nonce w/ the Drop-in UI (you can pass 1 for the amount; it shouldn't matter as there isn't going to be charge as the nonce isn't being used to create a transaction -- rather just an authorization for the verification)
- Pass the 3DS nonce into the customer.create() request instead of a transaction.sale() call
I hope this helps further clarify. If you have any additional questions, please let me know.
Just respond, that No, this is not helpful (as all previous communication with your support team). Because when we send amount = 1 result is the same.
Step 1: is FE (android) thing. So yes I've created 3DS request (try with amount set to 1 too), but it's not propagated to you backend from SDK! As I described above, the request contains only Card number, expiration date and CVC code, no info from 3DS, so you have no way how to properly validate 3DS on your side... All 3DS data are ignored, same as isChallengeRequested / isCardAddChallengeRequested)!
Step 2: it's a BE side (in app we are not directly connected to gateway, we can't call customer.create() or transaction.sale() because we have no way for that.
BUT when we don't get NONCE in step 1 from SDK we have no way how to call step 2 on our backend.
@mtrakal thanks for your patience and for using the Braintree SDK for Android. I see your concern. To confirm, if we forward the ThreeDSecureAdditionalInformation#shippingAddress property to the Card tokenization step, that will be enough to resolve the issue?
Also does this occur on iOS as well, or is your app exclusive to the Android platform?
Hi @sshropshire I have no idea, if it will be enough, because I never pass to validate some Credit card on Android app still. So I have no idea what's required by bank / by Braintree to properly validate and add card to vault manager. Same issue on iOS what I remember, but we try to solve with your support team this issue months, so not sure about iOS now, it's too long time from beginning until I started escalate it here.
But if you look on request from first post, there is no info from 3DS, not only shipping address. It send only card data and no 3DS data (email, phone, additional info, etc).
Hi, @sshropshire any news on this issue? Can you escalate it, please? We still have issues with credit cards.
Hi @mtrakal it's difficult to determine the proper API for this scenario. My gut says to forward the billingAddress property from ThreeDSecureRequest when the Card is initially tokenized. Out of curiosity, have you tried adding a card to a vault with our core SDK to see if this works as expected?
Hi @sshropshire it's being bigger issue now, because Czech biggest bank ČSOB implemented 3DS for debit cards too, so it's not now just about credit cards, but for debit cards too. And other banks will be following: according to EU regulations this is required for all banks / cards.
It happens for us on Android, iOS and Web too. For now "just" for this one ČSOB bank, because they are the first who implemented 3DS for vaulting any card. On second side it's a biggest bank with most of our users and it start be a huge issue for us because they are not able pass through payment (not able to use our service, which impacting us as company/our business).
And will start impacting other companies in EU which uses vaulting card.
@mtrakal if it's happening on all platforms we may need you to contact support for additional help. This helps us prioritize and get the right stakeholders involved internally. I'm aware of the issue so I'll keep an eye out, but it sounds like we may need to coordinate a cross platform fix in this case and I want to make sure we get it right the first time.
for internal tracking, issue 4425
The hell, we contacted your support team 5 months ago maybe more! With some stupid responses copied from documentation as I described at beginning of this issue. We escalated it every few weeks with no luck in a solution. After a long time without no luck with your support team I created this issue and hope for some answer here :). Which definetelly work much better than support team 👍 🥇 .
I finally have Debit card from ČSOB (before we got just data from customers) and we had an only Credit card... So I can provide data from API communication (but it looks, that I'm not able to decrypt responses anymore on latest Braintree SDK). So I have just requests (will try on another developer device later with decrypting https etc...).
It looks almost same as for Credit card.
Code DropInRequest created in our code:
this = {DropInRequest@21470}
allowVaultCardOverride = false
cardDisabled = false
cardholderNameStatus = 0
googlePayDisabled = true
googlePayRequest = null
maskCardNumber = false
maskSecurityCode = false
payPalDisabled = true
payPalRequest = null
threeDSecureRequest = {ThreeDSecureRequest@21471}
accountType = null
additionalInformation = {ThreeDSecureAdditionalInformation@21475}
accountAgeIndicator = null
accountChangeDate = null
accountChangeIndicator = null
accountCreateDate = null
accountId = null
accountPurchases = null
accountPwdChangeDate = null
accountPwdChangeIndicator = null
addCardAttempts = null
addressMatch = null
authenticationIndicator = null
deliveryEmail = null
deliveryTimeframe = null
fraudActivity = null
giftCardAmount = null
giftCardCount = null
giftCardCurrencyCode = null
installment = null
ipAddress = null
orderDescription = null
paymentAccountAge = null
paymentAccountIndicator = null
preorderDate = null
preorderIndicator = null
productCode = null
purchaseDate = null
recurringEnd = null
recurringFrequency = null
reorderIndicator = null
sdkMaxTimeout = null
shippingAddress = {ThreeDSecurePostalAddress@21498}
countryCodeAlpha2 = "CZ"
extendedAddress = null
givenName = "Matěj"
line3 = null
locality = "XXX"
phoneNumber = "+420XXXXXXXXX"
postalCode = "XXXXX"
region = null
streetAddress = "XXXXX"
surname = "Trakal"
shadow$_klass_ = {Class@21465} "class com.braintreepayments.api.ThreeDSecurePostalAddress"
shadow$_monitor_ = 0
shippingAddressUsageDate = null
shippingAddressUsageIndicator = null
shippingMethodIndicator = null
shippingNameIndicator = null
taxAmount = null
transactionCountDay = null
transactionCountYear = null
userAgent = null
workPhoneNumber = "+420XXXXXXXX"
shadow$_klass_ = {Class@21467} "class com.braintreepayments.api.ThreeDSecureAdditionalInformation"
shadow$_monitor_ = 0
amount = "1270.00"
billingAddress = {ThreeDSecurePostalAddress@21477}
countryCodeAlpha2 = "CZ"
extendedAddress = null
givenName = "Matej"
line3 = null
locality = "XXXX"
phoneNumber = "+420XXXXXXXX"
postalCode = "XXXXX"
region = null
streetAddress = "XXXXXX"
surname = "Trakal"
shadow$_klass_ = {Class@21465} "class com.braintreepayments.api.ThreeDSecurePostalAddress"
shadow$_monitor_ = 0
cardAddChallengeRequested = null
challengeRequested = true
dataOnlyRequested = false
email = "[email protected]"
exemptionRequested = false
mobilePhoneNumber = "+420XXXXXXXXX"
nonce = null
shippingMethod = 0
v1UiCustomization = null
v2UiCustomization = null
versionRequested = "2"
shadow$_klass_ = {Class@21462} "class com.braintreepayments.api.ThreeDSecureRequest"
shadow$_monitor_ = 0
vaultCardDefaultValue = true
vaultManagerEnabled = false
venmoDisabled = true
venmoRequest = null
shadow$_klass_ = {Class@21460} "class com.braintreepayments.api.DropInRequest"
shadow$_monitor_ = 0
Request send from Braintree SDK to Braintree API:

{
"clientSdkMetadata": {
"platform": "android",
"sessionId": "b5468b4804d941cfa60429344c228831",
"source": "form",
"integration": "custom"
},
"query": "mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) { tokenizeCreditCard(input: $input) { token creditCard { bin brand expirationMonth expirationYear cardholderName last4 binData { prepaid healthcare debit durbinRegulated commercial payroll issuingBank countryOfIssuance productId } } }}",
"operationName": "TokenizeCreditCard",
"variables": {
"input": {
"options": {
"validate": true
},
"creditCard": {
"number": "51683421********",
"expirationMonth": "**",
"expirationYear": "**",
"cvv": "***"
}
}
}
}
Sorry, but your SDK not allow create screenshot even in debug mode 🤷♂️
I'm 100% sure, that provided data are correct, valid and work for online payment on other places / apps / stores. The issue is, that we need to add to Vault before crete payment and it's not allowed because there is not shown 3DS dialog to verify card.

As you can see, all data provided in DropInRequest are forgotten/lost/not provided to API. So API/Bankend is not able to verify 3DS / owner etc. I'm not sure what is the main issue. If we miss something (but I think, that we try almost everything what we can), or because DropInReuqest data not provided to BE or because 3DS dialog is never shown for Valuting card (only for later payment after card is stored) or what else could be the core issue.
It looks, like you maybe create 2 transactions in almost same time (one for amount 0 and one for amount 1).

But both failed with same error code 2099 Authentication Required.
As I wrote before we provide data to SDK in DropInRequest, but it's not propagated to your API for Vaulting card.
@sshropshire Hello guys, we have "workaround" our issue with payment in Braintree Admin tools. I don't have access there, but account owner disable this checkboxes and payment start working. But now there is no card validation. But finally, users with ČSOB can pay in out app!

But still it's a bug and issue, because one switch in admin tool should not break payments for valid cards... Please keep push this issue to solve it and we can again allow card validations. The current state is not where we want to be. So I just post this comment to move your steps and clarify what's causing this issue.
Info from some of our user from his Bank: Payment was not verified due to using deprecated validation/verification of payment card (withdrawal 1 CZK) which is not supported anymore / deprecated - some old/unencpryted protocol or something like that.
@mtrakal thanks for your patience. We're looking into this further and we'll report back when we have more information.
@sshropshire Hello guys, we have "workaround" our issue with payment in Braintree Admin tools. I don't have access there, but account owner disable this checkboxes and payment start working. But now there is no card validation. But finally, users with ČSOB can pay in out app!
But still it's a bug and issue, because one switch in admin tool should not break payments for valid cards... Please keep push this issue to solve it and we can again allow card validations. The current state is not where we want to be. So I just post this comment to move your steps and clarify what's causing this issue.
Info from some of our user from his Bank: Payment was not verified due to using deprecated validation/verification of payment card (withdrawal 1 CZK) which is not supported anymore / deprecated - some old/unencpryted protocol or something like that.
This workaround wouldn't work in case your scenario involves a free trial period, when you as a merchant first try to capture user's payment method for future use, and after the free trial period when customer is not present anymore and cannot pass the 3DS step you try to charge the card automatically. When the cart is not verified upon vaulting, it won't be possible to charge this card when bank requires the 3DS step upon verification. This would probably mean that any new payment with not verified payment instrument will fail because of the bank not willing to accept it.
The same bug presents in the web sdk as well. There is just no way to verify a new credit card before it's stored to a vault :/ We've been having this issue with multiple European banks for some time already, they are giving the 2099 in response of trying to store the unverified card. Customers are just not able to use the cards from those banks.
We are experiencing a similar, if not the same issue.
The issue is that with card verification enabled, the drop-in UI fails to complete its tokenization step with some European bank cards. As a result, the drop-in UI does not return its payload with the 3DS enriched nonce. The drop-in UI never reaches the .verifyCard() step that it should do.
After discussing with Braintree support, the problem appears to be that the Drop-in UI is attempting card verification without 3DS. Even though 3DS is enabled with configs. (Both when the dropin instance is initialized with .create(), and when .requestPaymentMethod() is used)
So, we are not sure how to do 3DS before drop-in UI starts doing its card verification.
Are there any intentions of adding "Card verification with 3DS" feature to the web drop-in UI?