adyen-android
adyen-android copied to clipboard
3DS redirect is not returning returnUrl to activity and web browser doesn’t closes.
Description: 3ds redirect component takes the user to the web view to complete the challenge. On pressing the submit button it takes the user to the redirect page and comes back to the credential page again. WebView doesn't close itself and no data is passed to the main activity.
To Reproduce: Steps to reproduce the behavior:
- Press the pay button on the application.
- After landing on the challenge screen in a review, complete the payment challenge.
- Hit "submit" and after being through the redirect page, the credential page will be shown again.
- No data is passed to the parent activity.
Expected behavior: After completing the challenge, webview should be closed and intent data should be passed back to the activity from where it is called.
Android Phones:
- Device: OPPO A91
- OS: Android 11
- Version: 4.3.0
Hi, thanks for reaching out. Are you using the standalone redirect component or drop-in?
Hi I’m using the standalone redirect component.
Can you make sure you are following all the steps in this section https://docs.adyen.com/online-payments/android/components#redirect-component?
The issue could potentially be in your return URL and intent filter, make sure they match otherwise you will not get redirected back to the app.
Yes I followed every step in the documentation and I don’t think returnUrl is issue because IOS side implementation is working fine just android is showing problems.
Hey, are you handling the possible data coming back to the Activity in both onCreate
and onNewIntent
? The Activity might be called in either one depending on the scenario.
My activity is a "singleTask" activity so according to documentation data should be handled in onNewIntent method but for the safe side I'm handling in it both methods.
Here's my activity code, if that helps: MainActivity.kt
@FlowPreview
@ExperimentalCoroutinesApi
class MainActivity : NavActivity(), GoogleMapProvider {
private val viewBinding: ActivityMainBinding by dataBinding(R.layout.activity_main)
private val homeViewModel: HomeViewModel by viewModels { viewModelFactory }
private val bonusViewModel: BonusViewModel by viewModels { viewModelFactory }
private val preAuthViewModel: PapPreAuthViewModel by viewModels { viewModelFactory }
private lateinit var redirectComponent: RedirectComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(null)
viewBinding.executePendingBindings() // inflate views
initNavController()
observeErrorState()
setupRedirect()
handle3DSIntent(intent)
addEventObservers()
}
private fun setupRedirect() {
val configuration = RedirectConfiguration.Builder(applicationContext, BuildConfig.ADYEN_CLIENT_KEY)
.setEnvironment(PayProvider.getCardAdyenEnvironment(BuildConfig.FLAVOR))
.build()
redirectComponent = RedirectComponent.PROVIDER.get(this@MainActivity, application, configuration)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
handle3DSIntent(intent)
handleNewIntent(intent)
}
override fun displayErrorModal(error: SvLogger) {
val navOptions = getNavOptions(error, R.anim.slide_in_up, R.anim.slide_out_down)
val direction = MainGraphDirections.actionMainToErrorFragment(error)
navigate(direction, navOptions)
}
override fun displayErrorDialog(error: SvLogger) {
val navOptions = getNavOptions(error)
val direction = MainGraphDirections.actionMainToErrorDialogFragment(error)
navigate(direction, navOptions)
}
override fun resetGraph() {
// Clear the Intent so to stop the Navigation from relaunching the Authorization/Authentication flow
intent = null
navController.setGraph(R.navigation.main_graph)
}
override fun getMap(): GoogleMap? {
val navHost =
supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment
val homeFragment = navHost.childFragmentManager.fragments[0]
return (homeFragment as? GoogleMapProvider)?.getMap()
}
private fun initNavController() {
navController = findNavController(R.id.main_nav_host_fragment)
}
private fun addEventObservers() {
bonusViewModel.trackerCount.observe(this) {
// keep the lifecycle observer alive to receive reset event
Timber.d("Tracker reset received $it")
}
homeViewModel.singleReservedPreAuth.observe(this){ result ->
val action = preAuthViewModel.get3DSAction(result.data!!.threeDS.toString())
redirectComponent.handleAction(this, action)
}
redirectComponent.observe(this){
Timber.e("Redirect observed $it")
}
redirectComponent.observeErrors(this){
Timber.e("Error received: ${it.errorMessage}")
}
}
private fun getNavOptions(
error: SvLogger,
@AnimRes enterAnim: Int? = null,
@AnimRes popExitAnim: Int? = null
): NavOptions {
// Exit the app when Back is pressed if the error is any of:
// ForceUpgrade, Maintenance, and RootException,
val isBlockingException = error.cause.isBlockingException()
return if (isBlockingException) {
NavOptions.Builder()
.setLaunchSingleTop(isBlockingException)
.setPopUpTo(R.id.main_graph, true)
.build()
} else {
val startDestination = navController.graph.startDestination
NavOptions.Builder().apply {
setPopUpTo(startDestination, false)
if (enterAnim != null) {
setEnterAnim(enterAnim)
}
if (popExitAnim != null) {
setPopExitAnim(popExitAnim)
}
}.build()
}
}
private fun observeErrorState() = homeViewModel.onError.observe(this) {
displayError(it)
}
private fun handleNewIntent(intent: Intent?) {
intent?.let { newIntent ->
when (newIntent.getPredefinedAction()) {
InAppAction.SHOW_TERMS_COND -> with(newIntent) {
val termsCondData: UserTermsCond? = getParcelableExtra(TERMSCOND_DATA)
val sInAppAction: String? = getStringExtra(IN_APP_ACTION)
val inAppAction: InAppAction? = sInAppAction?.let { InAppAction.valueOf(it) }
showTermsCond(termsCondData, inAppAction)
}
else -> homeViewModel.onNewIntent(newIntent)
}
}
}
private fun handle3DSIntent(intent: Intent?) {
val data = intent?.data
if (data != null && data.toString().startsWith(RedirectUtil.REDIRECT_RESULT_SCHEME)) {
redirectComponent.handleIntent(intent)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == GooglePayFragment.REQUEST_CODE) {
homeViewModel.onGooglePayCallbacks.value =
GooglePayActivityResult(requestCode, resultCode, data)
}
}
private fun showTermsCond(termsAndCondition: UserTermsCond?, inAppAction: InAppAction?) {
termsAndCondition?.let {
val navDirection = if (inAppAction != null) {
MainGraphDirections.actionMainToTermsCondFragment(it, inAppAction)
} else {
MainGraphDirections.actionMainToTermsCondFragment(it)
}
navigate(navDirection)
}
}
}
Manifest code:
<activity
android:name=".ui.MainActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="${applicationId}" android:scheme="adyencheckout"/>
</intent-filter>
<nav-graph android:value="@navigation/main_graph" />
</activity>
I suggest trying 2 things:
- Double check your
returnURL
and make sure it starts withadyencheckout://your.package.name
- Create a new blank Activity, without specifying a launch mode or adding any extra code, move your intent filter to that activity instead, then check if
onCreate
gets called
@waqax94 Has your problem been solved? I also encountered the same problem on Huawei (CLT-L29 android 10) mobile phones。
@jreij Hi team I'm also facing the same issue on my end.. Any updates on this ?
I tried providing the return url as specified in the documentation and it did not get redirected but then when i tried giving a return url that is different from the url specified in the manifest and it worked !
One thing I did was checked if the specified return url is correctly working on the android app was by using the adb shell command and it worked.
Sample command:
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'adyencheckout://com.test.app'
I started debugging using chrome dev tools on my end and noticed this.. The return_url that we specify is correctly sent to the adyen server but then when the adyen web app tries the redirect we are getting this error

Devices tested -> Oneplus AC2001 - Android 11, Realme RMX1851 - Android 11, Pixel 4a - Android 12
Hi @shangeethsivan, thanks for reaching out. If you tried adb shell am start
then we can be sure it's not an issue from the SDK or from your redirect component implementation. Can you try to run this command right after the error occurs, using the full URL in the chrome console? Just to make sure the payment completed normally.
On another note, the URL displayed in the console seems weird, there's an &
after com.test.app
then there's a ?
afterwards, usually the first argument has an ?
then the &
comes later for the other arguments. Could it be that the return URL is malformed?
Thanks @jreij it was the URL malformation in our case.. It works fine now.. Thanks for your quick response on this.