braintree / braintree_android

Braintree SDK for Android
https://developer.paypal.com/braintree/docs/start/hello-client/android/v4
MIT License
407 stars 233 forks source link

Users getting UserCanceledException on tokenizeVenmoAccount #1041

Closed juanfactor88 closed 3 months ago

juanfactor88 commented 3 months ago

Braintree SDK Version

4.47.0

Environment

Sandbox

Android Version & Device

Google Pixel 4 android 10, Motorola Edge 20 Pro Android 13

Braintree dependencies

com.braintreepayments.api.BraintreeClient com.braintreepayments.api.VenmoAccountNonce com.braintreepayments.api.VenmoClient com.braintreepayments.api.VenmoListener com.braintreepayments.api.VenmoPaymentMethodUsage com.braintreepayments.api.VenmoRequest

Describe the bug

Whenever the tokenizeVenmoAccount is called for the VenmoClient, the response is an "User cancelled" Error, even if the checkout Process finish successfully.

To reproduce

  1. Create Braintree client.
  2. Create VenmoCliente.
  3. Create Venmo Request.
  4. Call tokenizeVenmoAccount method.

Expected behavior

tokenizeVenmoAccount should return a successful response with the VenmoAccountNonce if the user finish the process successfully. If the user cancels or experience an error in the process the result should be the appropriate error.

CODE

internal class VenmoWebCheckoutActivity : AppCompatActivity(), VenmoListener {

    private lateinit var venmoClient: VenmoClient
    private lateinit var braintreeClient: BraintreeClient
    private lateinit var sessionToken: String
    private lateinit var clientToken: String
    private lateinit var customUrlScheme: String

    private val observer = object : DefaultLifecycleObserver {
        override fun onResume(owner: LifecycleOwner) {
            super.onResume(owner)
            handleLifecycleObserverOnResume()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_venmo_web_checkout)
        handleOnCreate()
        launchVenmo()
    }

    private fun handleOnCreate() {
        onBackPressedDispatcher.addCallback(this, provideOnBackPressedCallback())

        sessionToken = intent.getStringExtra("SESSION_TOKEN") ?: ""
        clientToken = intent.getStringExtra("CLIENT_TOKEN") ?: ""
        customUrlScheme = intent.getStringExtra("CUSTOM_URL_SCHEME") ?: ""

        lifecycle.addObserver(observer)
    }

    internal fun handleLifecycleObserverOnResume() {
        removeLifecycleObserver()
    }

    private fun launchVenmo() {
        val authorization = clientToken.takeUnless { it.isEmpty() } ?: Consts.VENMO_AUTHORIZATION_KEY

        braintreeClient =
            BraintreeClient(context = this, authorization = authorization, customUrlScheme)
        venmoClient = VenmoClient(this, braintreeClient)
        venmoClient.setListener(this)

        val request = VenmoRequest(VenmoPaymentMethodUsage.MULTI_USE)

        request.collectCustomerBillingAddress = true
        request.collectCustomerShippingAddress = true
        request.profileId = Consts.PROFILE_ID
        request.shouldVault = false

        if (venmoClient.isVenmoAppSwitchAvailable(this)) {
            venmoClient.tokenizeVenmoAccount(this, request)
        } else if (!isAppInstalled(this)) {
            venmoClient.showVenmoInGooglePlayStore(this)
        }
    }

    override fun onVenmoSuccess(venmoAccountNonce: VenmoAccountNonce) {
        Log.d("VenmoWebCheckoutActivity", "Venmo account nonce: ${venmoAccountNonce.notify()}")
        val resultIntent = Intent().apply {
            putExtra("VENMO_ACCOUNT_NONCE", venmoAccountNonce.toString())
            putExtra("JWT_SESSION_TOKEN", sessionToken)
        }
        setResult(VenmoConstants.RESULT_SUCCESS, resultIntent)
        finish()
    }

    override fun onVenmoFailure(error: Exception) {
        finishActivityWithResult(VenmoConstants.RESULT_FAILED)
        val resultIntent = Intent().apply {
            putExtra("ERROR_MESSAGE", error.message)
            putExtra("JWT_SESSION_TOKEN", sessionToken)
        }
        setResult(VenmoConstants.RESULT_FAILED, resultIntent)
        finish()
    }

    private fun removeLifecycleObserver() {
        lifecycle.removeObserver(this@VenmoWebCheckoutActivity.observer)
    }

    private fun provideOnBackPressedCallback(): OnBackPressedCallback =
        object : OnBackPressedCallback(true) {

            override fun handleOnBackPressed() {
                finishActivityWithResult(VenmoConstants.RESULT_CANCELED)
            }

        }

    internal fun finishActivityWithResult(result: Int) {
        setResult(result)
        finish()
    }

    private fun isAppInstalled(context: Context): Boolean {
        val packageManager = context.packageManager
        return try {
            packageManager.getPackageInfo(Consts.VENMO_PACKAGE, PackageManager.GET_ACTIVITIES)
            true
        } catch (e: PackageManager.NameNotFoundException) {
            false
        }
    }
}
sshropshire commented 3 months ago

Hey @juanfactor88 thanks for using the Braintree SDK for Android. Can you set a breakpoint here in VenmoLifecycleObserver.java to evaluate what occurs after Venmo app switches back into your app?

Also if possible, see if you can reproduce with version 4.41.0. We recently added support for deep linking back into the merchant apps using App Links. I have a feeling that's why we're seeing this behavior.

It doesn't look like you're using app links, so I'd expect this line does not execute. If it does we may have a bug.

juanfactor88 commented 3 months ago

@sshropshire, thanks for your response. Upon further testing I realized the issue was caused by the channel configured in the sandbox Venmo app:

image

In order to receive the success response the app should have the App Switch channel selected in the dev settings of the app.

This solved my issue.