October 22, 2024
Chicago 12, Melborne City, USA
Android

Mollie checkout URL not showing in Android Webview


In my Android App built using Jetpack Compose and Kotlin, I am using Android Webview to load Mollie checkout url for payments. The URL (https://www.mollie.com/checkout/select-method/SOMECODESPECIFICTOPAYMENT) shows content like this
enter image description here

Initially everything was working and Android Webview was loading Mollie URLs properly, but now the users on live app are facing issue of empty page display, So the Webvie loads the URL but the content is not displayed.

Below is the BookingPaymentScreen which loads the URL

@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun BookingPaymentScreen(
    navController: NavController,
    sharedViewModel: SharedViewModel,
    addBookingViewModel: AddBookingViewModel,
) {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    val result = navController.previousBackStackEntry?.savedStateHandle?.get<Boolean>("isFromRetry")
    val resultUrl = navController.previousBackStackEntry?.savedStateHandle?.get<String>("url")
    val errorToastState = remember { mutableStateOf(false) }
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartActivityForResult(),
        onResult = { activityResult ->
            addBookingViewModel.retryCallMade = false
            val bookingDetail = BookingDetail(
                bookingId = addBookingViewModel.bookingFinalUiState.bookingId,
                ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken,
                url = addBookingViewModel.bookingFinalUiState.paymentUrl
            )
            navController.currentBackStackEntry?.savedStateHandle?.set(
                key = "bookingDetail", value = bookingDetail
            )
            navController.navigate(BOOKING_DETAIL_ROUTE)
        })


    BackHandler {
        goBackBookingPaymentScreen(addBookingViewModel, navController)
    }
    // Ensure that retry logic is only executed once and after the Composable is composed
    LaunchedEffect(result) {
        if (result == true && !addBookingViewModel.retryCallMade) {
            addBookingViewModel.onEvent(AddBookingUiEvent.onRetryPayment)
            addBookingViewModel.retryCallMade = true
            sharedViewModel.logAnalyticsEvent(AnalyticsEvents.mlBookingsRetryPaymentBtnClicked)
        }
    }
    Surface(
        modifier = Modifier
            .background(
                color = PallaAppTheme.colors.primary
            )
            .fillMaxHeight()
            .statusBarsPadding()
    ) {
        if (errorToastState.value) {
            Toast.makeText(context, "App not installed or setup", Toast.LENGTH_LONG).show()
            errorToastState.value = false
        }

        if (addBookingViewModel.checkoutLoading) {
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(AppVariables.AppPading)
                    .background(PallaAppTheme.AppBackground), contentAlignment = Alignment.Center
            ) {
                CircularProgressIndicator(
                    modifier = Modifier.size(20.dp),
                    color = PallaAppTheme.colors.secondary,
                    strokeWidth = 2.dp
                )
            }
        } else {
            AndroidView(modifier = Modifier
                .fillMaxSize()
                .statusBarsPadding()
                .navigationBarsPadding(),
                factory = { context ->
                    WebView(context).apply {
                        settings.javaScriptEnabled = true
                        settings.domStorageEnabled = true
                        webViewClient = object : WebViewClient() {
                            override fun onPageCommitVisible(view: WebView?, url: String?) {
                                super.onPageCommitVisible(view, url)
                            }

                            override fun onPageStarted(
                                view: WebView?, url: String?, favicon: Bitmap?
                            ) {
                                super.onPageStarted(view, url, favicon)
                            }

                            override fun onPageFinished(view: WebView?, url: String?) {
                                super.onPageFinished(view, url)
                            }

                            override fun shouldOverrideUrlLoading(
                                view: WebView?, request: WebResourceRequest?
                            ): Boolean {
                                Log.d("shouldOverrideUrlLoading", request?.url.toString())
                                return handleOverrideUrlLoading(webView = view,
                                    url = request?.url.toString(),
                                    addBookingViewModel,
                                    navController,
                                    context,
                                    launcher,
                                    onBankingAppNotFound = {
                                        errorToastState.value = true
                                    })
                            }

                            @Deprecated("Deprecated in Java")
                            override fun shouldOverrideUrlLoading(
                                view: WebView?, url: String?
                            ): Boolean {
                                Log.d("shouldOverrideUrlLoading", "$url")
                                return handleOverrideUrlLoading(webView = view,
                                    url = url,
                                    addBookingViewModel,
                                    navController,
                                    context,
                                    launcher,
                                    onBankingAppNotFound = {
                                        errorToastState.value = true
                                    })
                            }

                            override fun onReceivedError(
                                view: WebView?,
                                request: WebResourceRequest?,
                                error: WebResourceError?
                            ) {
                                Log.d("onReceivedError", error.toString())
                                val error1 = error
                                val abc = "fddg"
                                super.onReceivedError(view, request, error)
                            }

                            override fun onReceivedSslError(
                                view: WebView?,
                                handler: SslErrorHandler?,
                                error: SslError?
                            ) {
                                Log.d("onReceivedSslError", error.toString())

                                // ignore ssl error
                                if (handler != null) {
                                    handler.proceed()
                                } else {
                                    super.onReceivedSslError(view, null, error)
                                }
                            }
                        }
                        // WebChromeClient for JavaScript console logs
                        webChromeClient = object : WebChromeClient() {
                            override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
                                Log.d("WebView Console", "${consoleMessage.message()} -- from line " +
                                        "${consoleMessage.lineNumber()} of ${consoleMessage.sourceId()}")
                                return super.onConsoleMessage(consoleMessage)
                            }
                        }
                        var url = addBookingViewModel.bookingFinalUiState.paymentUrl
                        if (url.isEmpty()) {
                            url = resultUrl ?: ""
                        }
                        loadUrl(url)
                    }
                },
                update = { webView ->
                    // Update the WebView if needed
                    //CookieManager.getInstance()?.setAcceptThirdPartyCookies(webView, true)
                })
        }

    }
}

fun goBackBookingPaymentScreen(
    addBookingViewModel: AddBookingViewModel, navController: NavController
) {
    addBookingViewModel.retryCallMade = false
    val bookingDetail = BookingDetail(
        bookingId = addBookingViewModel.bookingFinalUiState.bookingId,
        ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken
    )
    navController.currentBackStackEntry?.savedStateHandle?.set(
        key = "bookingDetail", value = bookingDetail
    )
    navController.navigate(BOOKING_DETAIL_ROUTE)
}

fun isDeepLink(url: String?): Boolean {
    if ((url?.startsWith("nl") == true && url.contains("payloadUri"))) {
        return true
    }
    if (url?.startsWith("http") == false && !url.startsWith("https") && url.contains("://")) {
        return true
    }
    return false
}

//OLD FUNCTION WITH DEEP LINK

private fun handleOverrideUrlLoading(
    webView: WebView?,
    url: String?,
    addBookingViewModel: AddBookingViewModel,
    navController: NavController,
    context: Context,
    launcher: ManagedActivityResultLauncher<Intent, ActivityResult>,
    onBankingAppNotFound: () -> Unit
): Boolean {
    if (url != null && url.startsWith(AppDeepLinks.COURT_BOOKING)) {
        addBookingViewModel.retryCallMade = false
        val bookingDetail = BookingDetail(
            bookingId = addBookingViewModel.bookingFinalUiState.bookingId,
            ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken,
            url = addBookingViewModel.bookingFinalUiState.paymentUrl
        )
        navController.currentBackStackEntry?.savedStateHandle?.set(
            key = "bookingDetail", value = bookingDetail
        )
        navController.navigate(BOOKING_DETAIL_ROUTE)
        return true
    }
    if (isIntentLink(url)) {
        handleBankingAppIntent(url ?: "", context, launcher, onBankingAppNotFound = {
            onBankingAppNotFound()
        })
        return true
    }
    if (isDeepLink(url)) {
        try {
            val intent = Intent(ACTION_VIEW, Uri.parse(url))
            //context.startActivity(intent)
            launcher.launch(intent)
        } catch (e: ActivityNotFoundException) {
            // Only browser apps are available, or a browser is the default.
            // So you can open the URL directly in your app, for example in a
            e.printStackTrace()
            onBankingAppNotFound()
        }
        return true
    }
    url?.let {
        webView?.loadUrl(it)
        return true
    }
    return true
}

fun isIntentLink(url: String?): Boolean {
    if ((url?.startsWith("intent://") == true)) {
        return true
    }
    return false
}


/*// In some cases Mollie creates wrong Android Chrome Intent (lower cased)
// https://developer.chrome.com/multidevice/android/intents
private fun String.fixedIntentName(): String = replace("#intent;", "#Intent;")*/
private fun handleBankingAppIntent(
    url: String, context: Context,
    launcher: ManagedActivityResultLauncher<Intent, ActivityResult>,
    onBankingAppNotFound: () -> Unit
) {
    try {
        val intent = Intent.parseUri(
            url, Intent.URI_INTENT_SCHEME
        )
        try {
            // context.startActivity(intent)
            launcher.launch(intent)
        } catch (e: ActivityNotFoundException) {
            // Only browser apps are available, or a browser is the default.
            // So you can open the URL directly in your app, for example in a
            e.printStackTrace()
            onBankingAppNotFound()
        }
    } catch (e: URISyntaxException) {
        e.printStackTrace()
        onBankingAppNotFound()
    }
}

I tried Webview settings like initial scale, zoom, scroll, static height/width and different user Agent Strings but nothing seems to be working. The same Mollie payment URLs works on the iOS App.

settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
CookieManager.getInstance().setAcceptThirdPartyCookies(this, true)
settings.userAgentString="Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36"

Any help with this will be appreciated.



You need to sign in to view this answers

Leave feedback about this

  • Quality
  • Price
  • Service

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video