LivotovLabs/3DSView

Chrome for Android Update 75

rcpassos opened this issue ยท 17 comments

I can't complete the 3D Secure process after the new Chrome for Android update since June 19, 2019.

Any suggestion?
Thanks.

any update about this issue?

Hello! I have a similar problem. I've debugged our payment process and found out that after a user had submitted his 3ds code, the onPageFinished() method was not invoked.

public void onPageStarted(WebView view, String url, Bitmap icon) {
final boolean stackedMode = !TextUtils.isEmpty(stackedModePostbackUrl);

            if (!urlReturned && !postbackHandled.get()) {
                if ((!stackedMode && url.toLowerCase().startsWith(postbackUrl.toLowerCase())) || (stackedMode && url.toLowerCase().startsWith(stackedModePostbackUrl.toLowerCase()))) {
                    if (!TextUtils.isEmpty(stackedModePostbackUrl)) {
                        if (postbackHandled.compareAndSet(false, true)) {
                            authorizationListener.onAuthorizationCompletedInStackedMode(url);
                        }
                    } else {
                        view.loadUrl(String.format("javascript:window.%s.processHTML(document.getElementsByTagName('html')[0].innerHTML);", JavaScriptNS));
                    }
                    urlReturned = true;
                } else {
                   // super.onPageStarted(view, url, icon);
                    onPageFinished(view, url);
                }
            }
        }

In some cases I was able to circumvent the problem by force invoking onPageFinished(). However, it does not fix the problem for all our users and is a hack IMO.

When I try to Debug-run the app on those devices that don't budge to this hack, everything works just fine as I step over each line. It seems like that Debug mode provides some necessary delays, that make code work correctly every the time. Maybe there's a way to add those delays manually in proper places, but I wasn't able to figure out where exactly. I appreciate any help you can provide!

@bobvse Any update? Thanks

@livotov Any update?

Hope this helps someone. I have managed to work around this problem, but the solution is very hacky.

First of all, as I have moved my codebase to AndroidX, I now use WebViewClientCompat instead of WebViewClient. There I've extended onPageCommitVisible() method.

private AtomicBoolean isFirstStart = new AtomicBoolean(true); //first run

setWebViewClient(new WebViewClientCompat() {
/.../

       @Override
        public void onPageCommitVisible(WebView view, String url) {
            Log.d("Threads", "on_page_commit_visible " + url);
            if (!url.equals(postbackUrl) && !isFirstStart.get()) {
                view.loadUrl(String.format("javascript:window.%s.processHTML(document.getElementsByTagName('html')[0].innerHTML);", JavaScriptNS));
            }

            isFirstStart.set(false);
            super.onPageCommitVisible(view, url);
        }

       
      @Override
        public void onPageStarted(WebView view, String url, Bitmap icon) {
            Log.d("Threads", "on_page_started " + url);

            if (url.equals(postbackUrl)) {
                Log.d("Threads", "geteaway_from_onStarted " + url);
                return;
            }

            view.loadUrl(String.format("javascript:window.%s.processHTML(document.getElementsByTagName('html')[0].innerHTML);", JavaScriptNS));
            super.onPageStarted(view, url, icon);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            Log.d("Threads", "on_page_finished " + url);

            if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.LOLLIPOP_MR1 && !url.equals(postbackUrl)) {
                view.loadUrl(String.format("javascript:window.%s.processHTML(document.getElementsByTagName('html')[0].innerHTML);", JavaScriptNS));
            }

            super.onPageFinished(view, url);
        }

}

Processing is done in a separate thread now, using Handler. This may not be ideal, but it works for me. I have had to change onPageStarted and onPageFinished methods as well.

This is the gist of my solution. Though far from ideal, as I check for Md and PaRes each time a page is loaded, at least this way I have it in a separate thread and able to finish payment process the moment I get the params. I'm glad if this helps, but a proper solution is still needed.

private void completeAuthorizationIfPossible(String html) {
Log.d("Threads", "get_data_to_activity");
if (authorizationListener != null) {
authorizationListener.onGetResultString(html);
}
}

In Activity code:

webView.setAuthorizationListener(new D3SSViewAuthorizationListener() {

        @Override
        public synchronized void onGetResultString(String result) {
            Runnable runnable = new Runnable() {
                public void run() {
                    Log.d("Threads", "start_processing_thread");
                    Message msg = handler.obtainMessage();
                    Bundle bundle = new Bundle();
                    ConcurrentHashMap<String, String> map;
                    synchronized (this) {
                        map = completeAuthorizationIfPossible(result);
                    }
                    bundle.putSerializable("Map", map);
                    msg.setData(bundle);
                    handler.sendMessage(msg);
                }
            };
            Thread thread = new Thread(runnable);
            thread.start();
        }

        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Bundle bundle = msg.getData();
                ConcurrentHashMap<String, String> map = (ConcurrentHashMap<String, String>) bundle.getSerializable("Map");
                if (map != null && !paymentProcessed.get()) {
                    paymentProcessed.set(true);
                    Log.d("Threads", "map is ready");
                   
                      webView.stopLoading();
                    /*** show you progress and hide webview ***/

                    // go to another step payment
                } else {
                    Log.d("Threads", "map is null or pament is porocessed");
                }
            }
        };

Thanks!

@bobvse I don't have the possibility to move my code base to AndroidX and I don't have the onGetResultString method in D3SSViewAuthorizationListener.

Thanks!

I have tried the code from @bobvse and it works but I think not all of the code is required for me just adding onPageCommitVisible and isFirstStart works -- in test environment at-least.


private AtomicBoolean isFirstStart = new AtomicBoolean(true);

            @Override
            public void onPageCommitVisible(WebView view, String url) {
                Log.d("Threads", "onPageCommitVisible " + url);

                if (!url.equals(postbackUrl) && !isFirstStart.get()) {
                    view.loadUrl(String.format("javascript:window.%s.processHTML(document.getElementsByTagName('html')[0].innerHTML);", JavaScriptNS));
                }

                isFirstStart.set(false);
                super.onPageCommitVisible(view, url);
            }

isFirstStart - flag to prevent the page from being processed the first time. You can not use it if you do not want.
Using it will only slightly improve performance.

These are the logs that I get on v72.0 where it works and v75.0 where it's failing.

on chrome v72.0

onPageStarted https://test.adyen.com/hpp/3d/validate.shtml
onPageFinished https://test.adyen.com/hpp/3d/validate.shtml
onPageStarted https://test.adyen.com/hpp/3d/authenticate.shtml;jsessionid=asdf.test103e
onPageFinished https://test.adyen.com/hpp/3d/authenticate.shtml;jsessionid=asdf.test103e
onPageStarted https://test.adyen.com/hpp/3d/www.google.com
onPageFinished https://test.adyen.com/hpp/3d/www.google.com

on chrome v75.0

onPageStarted https://test.adyen.com/hpp/3d/validate.shtml
onPageFinished https://test.adyen.com/hpp/3d/validate.shtml
onPageStarted https://test.adyen.com/hpp/3d/authenticate.shtml
onPageStarted https://test.adyen.com/hpp/3d/www.google.com
onPageFinished https://test.adyen.com/hpp/3d/www.google.com

I have opened a bug on the Chrome bug tracker for this as well.
https://bugs.chromium.org/p/chromium/issues/detail?id=984741

@bobvse

isFirstStart - flag to prevent the page from being processed the first time. You can not use it if you do not want.
Using it will only slightly improve performance.

Be careful with that one ... I something similar and had to back it out here https://github.com/LivotovLabs/3DSView/pull/24/files

Basically, it broke processing for some providers that just provide a PARes on first page load, which the major PSP test environments don't simulate.

It sounds like you've done a fair amount of work here ... any chance of raising a PR?


I'm coming to the conclusion that I need a fallback solution, as this sort of thing has happened twice now (#20 and here).

The fallback would be to receive the PARes server side, and notify the app in some way, so at least the user wouldn't get the Google error page. This library is otherwise good, and provides a bit more of a slicker solution than merely doing this server side.

Ok - I've knocked this together:
#26

I got some inspiration from @bobvse, however I didn't like his/her solution for the following reasons:

  1. It had an option to skip processing on first start, which breaks things for certain providers
  2. I didn't like the amount of code that needed to go into the app - clearly that shouldn't be necessary. However I liked the threading idea, so the library creates threads for HTML processing and it does improve performance. ๐Ÿ‘

@sandyscoffable is it works on Android 9?

Yes it works on Android 9

@sandyscoffable Using these changes, it fixed the issue in the test environment, but still causing issues in production, much more than before this fix was applied.

@sandyscoffable Using these changes, it fixed the issue in the test environment, but still causing issues in production, much more than before this fix was applied.

Can you describe the issues you are having?

Just FYI - @sandyscoffable 's PR is merged now