braintree_android icon indicating copy to clipboard operation
braintree_android copied to clipboard

ThreeDSecureClient it never finish instantiation

Open salosoft opened this issue 1 year ago • 11 comments

Braintree SDK Version

three-d-secure:4.26.1

Environment

Sandbox

Android Version & Device

Android API 30 Motorola

Braintree dependencies

implementation 'com.braintreepayments.api:card:4.26.0'
implementation 'com.braintreepayments.api:data-collector:4.26.0'
implementation 'com.braintreepayments.api:paypal:4.26.0'
implementation 'com.braintreepayments.api:local-payment:4.26.0'
implementation 'com.braintreepayments.api:three-d-secure:4.26.1'

Describe the bug

We are using Braintree to perform payment on our platform, using ower own UI to collect card data. We are trying to migrate from v3 to v4 but we are facing some issues on Payment process. We also provide PayPal method as payment which is working just fine, but when I want to pay through Credit Card by using the Braintree I can't make the ThreeDSecureClient instantiate. Here is how I am instantiating the code on my Acticity:

private fun initializeBrainTreeClient(paymentClientToken: String?) {
        brainTreeClient = BraintreeClient(this, paymentClientToken.toString())
        payPalClient = PayPalClient(this, brainTreeClient!!)
        payPalClient?.setListener(this)
        threeDClient = ThreeDSecureClient(this, brainTreeClient!!)
        threeDClient!!.setListener(this)
    }

paymentClientToken is supplied after fetching the data from my backend. But while debugging I realize that when the line threeDClient = ThreeDSecureClient(this, brainTreeClient!!) is executed the follow line threeDClient!!.setListener(this) is never reached. I have no idea how it can happen because my method is not suspended. So I tryied to instantiate ThreeDSecureClient by using the deprecated constructor, which does not require the Activity, this one worked, and executed the setListener but the listener itself is never called when I get back from the confirmation code screen.

Can you help me solve this? Or either how to make the ThreeDSecureClient(this, brainTreeClient) works or get the listener being triggered when I get back from confirmation code screen?

To reproduce

ThreeDSecureClient is never instantiate

Inside an Activity:

  1. Fetch your Token from API
  2. After grabing the Token, Paste this code to initialize the Client and ThreeDSecure:
brainTreeClient = BraintreeClient(this, paymentClientToken.toString())
        payPalClient = PayPalClient(this, brainTreeClient!!)
        payPalClient?.setListener(this)
        threeDClient = ThreeDSecureClient(this, brainTreeClient!!)
        threeDClient!!.setListener(this)
  1. Run the app in debug
  2. Observe the threeDClient!!.setListener(this) is never reached

ThreeDSecureClient is instantiate but onThreeDSecureSuccess or onThreeDSecureFailure never triggered

Inside an Activity:

  1. Fetch your Token from API
  2. After grabing the Token, Paste this code to initialize the Client and ThreeDSecure:
brainTreeClient = BraintreeClient(this, paymentClientToken.toString())
        payPalClient = PayPalClient(this, brainTreeClient!!)
        payPalClient?.setListener(this)
        threeDClient = ThreeDSecureClient(brainTreeClient!!)
        threeDClient!!.setListener(this)
  1. Verify the card nounce and user data AND Perform the continuePerformVerification with the code bellow
val threeDSecureRequest = ThreeDSecureRequest()
        threeDSecureRequest.amount = convertedAmount
        threeDSecureRequest.email = userEmail
        threeDSecureRequest.nonce = creditCardNonce
        threeDSecureRequest.versionRequested = ThreeDSecureRequest.VERSION_2

        threeDClient?.performVerification(
            this,
            threeDSecureRequest
        ) { threeDSecureResult, error ->
            if (threeDSecureResult != null) {
                // examine lookup response (if necessary), then continue verification
                threeDClient?.continuePerformVerification(
                    this@ReviewConfirmOrderActivity,
                    threeDSecureRequest,
                    threeDSecureResult
                )
            } else {
                // handle error
                Log.e("DEBUG", "" + error)
            }
        }
  1. The verification Code will be displayed
  2. Type your code.
  3. Observe the screen will be dismissed but either onThreeDSecureSuccess or onThreeDSecureFailure will be triggered.

Expected behavior

  1. ThreeDSecureRequest is initialized when passing the activity on the constructor
  2. When instantiate ThreeDSecureRequest with deprecated method, the listeners onThreeDSecureSuccess or onThreeDSecureFailure will be called when returning to the app, after adding the Confirmation Code.

Screenshots

No response

salosoft avatar Mar 07 '23 14:03 salosoft

Hi @salosoft thanks for using the Braintree SDK for Android. Can you make sure all libraries have the same version and see if that resolves the issue?

sshropshire avatar Mar 07 '23 15:03 sshropshire

Hi @sshropshire, thanks for the propt response. It was like that before, and I just updated here anyway. Unfortunatly had same behaviour. Any other idea?

salosoft avatar Mar 07 '23 18:03 salosoft

hello @salosoft, Did you mean on #8, neither "onThreeDSecureSuccess or onThreeDSecureFailure will be triggered"?

KunJeongPark avatar Mar 07 '23 21:03 KunJeongPark

Sorry for my english. I ment that when I instantiate the ThreeDSecureClient using the deprecated way, without passing the activity, I am able to ge tin the line that set the listener threeDClient!!.setListener(this). Then I run the app, execute continuePerformVerification during the checkout process, the app display the confirmation code screen, as expected. But after I enter the confirmation code and confirm, the screen dismiss, as expected but the Listeners onThreeDSecureSuccess or onThreeDSecureFailure are never triggered. I mean:

  1. When I enter a correct code it return to the app and doesn't trigger onThreeDSecureSuccess
  2. When I press back button from confirmation code, it doesn't trigger onThreeDSecureFailure

Would that be because I am using the new way of creating the BraintreeClient and passing it into ThreeDSecureClient deprecated constructor?

Please take a look on a representation of how our code is looks like:


class MyActicityX : BaseActivity(), PayPalListener, ThreeDSecureListener {

    private var paymentTypes: String? = ""
    private var brainTreeClient: BraintreeClient? = null
    private var payPalClient: PayPalClient? = null
    private var dataCollector: DataCollector? = null
    private var threeDClient: ThreeDSecureClient? = null
    private var collectiveDataString: String? = ""
    private var amount: String? = null
    private var storeID: String = "32"
    private var userId: String? = null
    private var userEmail: String? = ""
    private var creditCardNonce: String? = ""
    private var sessionManager: SessionManager? = null
    private var currency: String? = null
    private var convertedAmount = ""

    override fun onCreate(arg0: Bundle?) {
        super.onCreate(arg0)
        loadArguments()
        binding.btnProceedReview.setOnClickListener {
            checkPaymentTypes()
        }
        getPaymentCredentials()
        setupFetchConfiguration()
    }

    override fun onNewIntent(newIntent: Intent?) {
        super.onNewIntent(intent)
        intent = newIntent
    }

    private fun loadArguments() {
        creditCardNonce = intent.getStringExtra("CCNonce")
    }

    /**
     * This method will fetch the Braintree credentials
     */
    private fun getPaymentCredentials() {
        ApiUtil.getBraintreeCredentials(mActivity, llReviewOrder, storeID) {
            initializeBrainTreeClient(it.payment_client_token)
        }
    }

    private fun initializeBrainTreeClient(paymentClientToken: String?) {
        brainTreeClient = BraintreeClient(this, paymentClientToken.toString())
        payPalClient = PayPalClient(this, brainTreeClient!!)
        payPalClient?.setListener(this)
        threeDClient = ThreeDSecureClient(brainTreeClient!!)
        threeDClient!!.setListener(this)
    }

    private fun setupFetchConfiguration() {
        if (brainTreeClient != null) {
            dataCollector = DataCollector(brainTreeClient!!)
            brainTreeClient?.getConfiguration { configuration, error ->
                dataCollector?.collectDeviceData(this) { deviceData, error ->
                    collectiveDataString = deviceData
                }
            }

        }
    }

    private fun checkPaymentTypes() {
        if (paymentTypes == "braintree") {
            checkCard()
        } else if (paymentTypes == "braintree_paypal") {
            launchPaypalPayment()
        }
    }


    private fun checkCard() {
        ProgressDialogUtil.showProgressDialog(this)

        val amountString = amount?.contains(",")
        if (amountString!!) {
            convertedAmount = amount.toString().replace(",", ".", false)
        } else {
            convertedAmount = amount.toString()
        }

        if (!userId.isNullOrEmpty()) {
            userEmail = sessionManager?.userDetails?.get(ApiParam.EMAIL)
        }


        val threeDSecureRequest = ThreeDSecureRequest()
        threeDSecureRequest.amount = convertedAmount
        threeDSecureRequest.email = userEmail
        threeDSecureRequest.nonce = creditCardNonce
        threeDSecureRequest.versionRequested = ThreeDSecureRequest.VERSION_2

        threeDClient?.performVerification(
            this,
            threeDSecureRequest
        ) { threeDSecureResult, error ->
            if (threeDSecureResult != null) {
                // examine lookup response (if necessary), then continue verification
                threeDClient?.continuePerformVerification(
                    this@MyActicityX,
                    threeDSecureRequest,
                    threeDSecureResult
                )
            } else {
                // handle error
                ...
            }
        }
    }

    private fun launchPaypalPayment() {
        ProgressDialogUtil.showProgressDialog(this)

        val amountString = "10.0"

        val request = PayPalCheckoutRequest(convertedAmount)
        request.currencyCode = currency
        request.intent = PayPalPaymentIntent.AUTHORIZE

        payPalClient?.tokenizePayPalAccount(this, request)
    }

    override fun onPayPalSuccess(payPalAccountNonce: PayPalAccountNonce) {
        TODO("Proceed with checkout")
    }

    override fun onPayPalFailure(error: java.lang.Exception) {
        TODO("Display error")
    }

    override fun onThreeDSecureSuccess(threeDSecureResult: ThreeDSecureResult) {
        TODO("Proceed with checkout")
    }

    override fun onThreeDSecureFailure(error: java.lang.Exception) {
        TODO("Display error")
    }
}

Like I said, PayPal payment is working just fine. The issue is that on credit card after this screen I don't receive the callback above.

Screenshot 2023-03-08 at 07 53 36

salosoft avatar Mar 10 '23 18:03 salosoft

Hi @KunJeongPark,

I wanted to let you know that I have found some important information regarding the issue with the ThreeDSecureClient initialization. It seems that when passing "this", as activity, in the constructor it does not work, and this may be related to the fact that my activity inherits from a BaseActivity. However, when I remove the BaseActivity and make it inherit from AppCompatActivity, the constructor and listener work as expected.

salosoft avatar Mar 14 '23 11:03 salosoft

Hi @salosoft does your BaseActivity inherit from AppCompatActivity?

sshropshire avatar Mar 14 '23 21:03 sshropshire

Hi @sshropshire It inherit from my class BaseActivity that only then inherit from AppCompatActivity

salosoft avatar Mar 15 '23 00:03 salosoft

@salosoft

If your BaseActivity extends AppCompatActivity then it should work properly, unless there's some customization down in BaseActivity that prevents AppCompatActivity from functioning as it normally should.

Usually a super.onCreate() call could be missing or something that breaks the inheritance chain could cause unpredictable behavior from AppCompatActivity.

sshropshire avatar Mar 21 '23 14:03 sshropshire

Hey @salosoft any update on this?

sshropshire avatar Apr 06 '23 14:04 sshropshire

Hey @salosoft! We just released a beta for the next major version, v5.

In this new version, we updated the SDK callback interface. We'd love for you to try out the new version and provide feedback.

v5 Migration Guide: https://github.com/braintree/braintree_android/blob/main/v5_MIGRATION_GUIDE.md v5 Release: https://github.com/braintree/braintree_android/releases/tag/5.0.0-beta1

tdchow avatar Jul 24 '24 16:07 tdchow