Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DropIn UI 3D Secure while add card to vault #376

Open
mtrakal opened this issue Dec 8, 2022 · 15 comments
Open

DropIn UI 3D Secure while add card to vault #376

mtrakal opened this issue Dec 8, 2022 · 15 comments
Labels

Comments

@mtrakal
Copy link

mtrakal commented Dec 8, 2022

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 Card is 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 Card is 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
@mtrakal
Copy link
Author

mtrakal commented Dec 8, 2022

second thing: What amount we should fill into 3DS request, when we just want to add card to Braintree, but not process any payment?

image

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

@mtrakal
Copy link
Author

mtrakal commented Dec 15, 2022

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:

  1. 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)
  2. 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.

@sshropshire
Copy link
Contributor

@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?

@mtrakal
Copy link
Author

mtrakal commented Dec 23, 2022

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).

@mtrakal
Copy link
Author

mtrakal commented Feb 8, 2023

Hi, @sshropshire any news on this issue? Can you escalate it, please? We still have issues with credit cards.

@sshropshire
Copy link
Contributor

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?

@mtrakal
Copy link
Author

mtrakal commented Feb 14, 2023

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.

@sshropshire
Copy link
Contributor

@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.

@hollabaq86
Copy link
Contributor

for internal tracking, issue 4425

@mtrakal
Copy link
Author

mtrakal commented Feb 14, 2023

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:
image

{
  "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": "***"
      }
    }
  }
}

image

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.
WIN_20230214_22_55_05_Pro

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.

@mtrakal
Copy link
Author

mtrakal commented Feb 15, 2023

It looks, like you maybe create 2 transactions in almost same time (one for amount 0 and one for amount 1).

image (2)
image

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.

@mtrakal
Copy link
Author

mtrakal commented Feb 22, 2023

@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!

image

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.

@sshropshire
Copy link
Contributor

@mtrakal thanks for your patience. We're looking into this further and we'll report back when we have more information.

@usrenmae
Copy link

usrenmae commented Mar 2, 2023

@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!

image

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.

@jpeldes
Copy link

jpeldes commented Jul 26, 2023

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants