Integrate 3DS 1.0 Flow in Android App 2.2.3

This article will be useful for merchants who want to Integrate a 1.0 flow natively to their Android apps.

Overview



Prerequisite | Setup TermURL

In this step, we will set up your backend server to listen for POST calls from the ACS with PaRes. The code below consumes incoming POST request to your_url/oneDotOhRequest and takes the 'PaRes' value from the request and redirects to a URL, along with the 'PaRes' value that the app will intercept later (Step 2).


Node.js Sample
 .post("/oneDotOhRequest", function(req, res) {
    var reqObj = req.body
    log.info("1.0 Payload Received");
    var payload = reqObj.PaRes;
    return res.redirect("PRE_DEFINED_TERM_URL" + "/" + payload);
  })


Once you have set up this in your backend server, note the endpoint - you will be using this as your TermUrl later in the flow.

Step 1 | Determine if a transaction is 1.0 Challenge

Below is an example of cmpi_lookup response from Centinel.

cmpi_lookup sample response
{  
   "AuthenticationPath":"value_here",
   "CardBin":"value_here",
   "ThreeDSVersion":"1.0.2",
   "ErrorDesc":"value_here",
   "ACSUrl":"value_here",
   "ErrorNo":"0",
   "Payload":"value_here",
   "EciFlag":"value_here",
   "Enrolled":"Y",
   "TransactionId":"value",
   "OrderId":"value_here",
   "CardBrand":"VISA"
}

There are three fields in the response that are important to help you determine if a transaction is 1.0 and if the transaction requires a challenge to be performed: 

  1. ThreeDSVersion - If the value is '1.x.x', the transaction is 1.0
  2. ACSUrl - If the value is not empty, the transaction is a challenge.
  3. Payload - If the value is not empty, the transaction is a challenge.

Step 2 | Setup your WebView

There are two parts that will need to be set up in your Activity to perform a 1.0 Challenge Transaction.

Step 2a | POST request to ACSUrl with 'PaReq' using a WebView (this will present the challenge screen in the WebView)

  1. Ensure the request is URL_Encoded
  2. Implement the method in the code snippet below to your Activity
    WebView post sample
    private void loadUrl(WebView webView) throws UnsupportedEncodingException {
    		String payload = PAYLOAD_VALUE_HERE; 
            String termUrl = TERM_URL_VALUE_HERE; 
            String acsUrl = ACS_URL_VALUE_HERE; 
    
            String postData = "&PaReq=" + URLEncoder.encode(payload, "UTF-8") + "&TermUrl=" + URLEncoder.encode(termUrl, "UTF-8");
            webView.postUrl(acsUrl, postData.getBytes());
        }

Step 2b | Intercept a GET Request from your TermURL

  1. In this step we will intercept a GET request from the TermURL and grab the payload to use in step 3
  2. Implement the method in the code snippet below to your Activity
    WebView Intercept method sample
        private void initWebView() {
            this.webView.setWebChromeClient(new WebChromeClient());
            WebSettings webSettings = webView.getSettings();
            webSettings.setJavaScriptEnabled(true);
            webSettings.setSupportMultipleWindows(true);
            webSettings.setDefaultTextEncodingName("utf-8");
    
            webView.setWebViewClient(new WebViewClient() {
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
    
                    if (url.contains("example")) {
                        String payload = url.replace("PREDEFINED_REDIRECT_URL", "");
                        try {
                            if (!payload.isEmpty()) {
                                //TODO: add logic here for valid payload (cmpi_authenticate) and close webview using finish() or open another Activity
                                
                            } else {
                                //TODO: handle invalid/empty payload
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    return false;
                }
            });
        }

Below is an example of an Activity that implements the WebView that you can use as a guide to implementing the flow in your app.

Sample 1.0 Activity
import android.annotation.SuppressLint;
import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.cardinal.cardinalcommercemarketappVendorTesting.R;
import com.cardinal.cardinalcommercemarketappVendorTesting.model.PaymentResponse;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Objects;


public class oneDotOhWebView extends AppCompatActivity implements Serializable {

    private WebView webView = findViewById(R.id.oneDotOhWebview);
    private String uri;
    private PaymentResponse paymentResponse;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_one_dot_oh_web_view);

        paymentResponse = (PaymentResponse) Objects.requireNonNull(getIntent().getExtras()).getSerializable("response");
        if (paymentResponse != null) {
            uri = paymentResponse.getRawACSUrl();
        }
        initWebView();
        try {
            loadUrl(this.webview);
        } catch (UnsupportedEncodingException e) {
            Log.e("EncodingException", "Failed to encode POST request" + e.getLocalizedMessage());
        }
    }

    /*
        STEP 2a
     */
    private void loadUrl(WebView webView) throws UnsupportedEncodingException {
		String payload = ""; //PAYLOAD_VALUE_HERE
        String termUrl = ""; //TERM_URL_VALUE_HERE
        String acsUrl = ""; //ACS_URL_VALUE_HERE

        String postData = "&PaReq=" + URLEncoder.encode(payload, "UTF-8") + "&TermUrl=" + URLEncoder.encode(termUrl, "UTF-8");
        webView.postUrl(acsUrl, postData.getBytes());
    }

    /*
        STEP 2b
     */
    private void initWebView() {
        this.webView.setWebChromeClient(new WebChromeClient());
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setSupportMultipleWindows(true);
        webSettings.setDefaultTextEncodingName("utf-8");

        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {

                if (url.contains("example")) {
                    String payload = url.replace("PREDEFINED_REDIRECT_URL", "");
                    try {
                        if (!payload.isEmpty()) {
                            //TODO: add logic here for valid payload (cmpi_authenticate) and close webview using finish() or open another Activity
                            
                        } else {
                            //TODO: handle invalid/empty payload
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return false;
            }
        });
    }
}

Step 3 | cmpi_authenticate

After the authentication session is complete, you will need to make the second API call - called the cmpi_authenticate request.  This API request will allow you to pull back the results of authentication, as well as the authentication values required to be passed in the authorization.

Please follow the Authenticate Request/Response sections for completing your backend integration.

Authenticate Request/Response