Integrate 3DS 1.0 Flow in Android App 2.2.3
- Brian Brotherton
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).
.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.
{ "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:Â
- ThreeDSVersion - If the value is '1.x.x', the transaction is 1.0
- ACSUrl - If the value is not empty, the transaction is a challenge.
- 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)
- Ensure the request is URL_Encoded
- Implement the method in the code snippet below to your ActivityWebView 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
- In this step we will intercept a GET request from the TermURL and grab the payload to use in step 3
- Implement the method in the code snippet below to your ActivityWebView 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.
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.