From ce76046e52a16f8fea0b70aa2587bfcd250ee526 Mon Sep 17 00:00:00 2001 From: Jeremy Lejoux Date: Mon, 22 Jul 2019 18:41:59 +0200 Subject: [PATCH 1/4] threeds: improve the way return url are handled --- .../processoutexample/MainActivity.java | 28 ++++++++--- .../processout/processout_sdk/Network.java | 3 ++ .../processout/processout_sdk/ProcessOut.java | 46 +++++++++++++++++-- .../processout_sdk/WebViewReturnAction.java | 31 +++++++++++++ 4 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 processout-sdk/src/main/java/com/processout/processout_sdk/WebViewReturnAction.java diff --git a/app/src/main/java/com/processout/processoutexample/MainActivity.java b/app/src/main/java/com/processout/processoutexample/MainActivity.java index bbe4e916..07e54476 100644 --- a/app/src/main/java/com/processout/processoutexample/MainActivity.java +++ b/app/src/main/java/com/processout/processoutexample/MainActivity.java @@ -11,7 +11,7 @@ import com.processout.processout_sdk.Card; import com.processout.processout_sdk.ProcessOut; import com.processout.processout_sdk.TokenCallback; - +import com.processout.processout_sdk.WebViewReturnAction; public class MainActivity extends AppCompatActivity { @@ -25,14 +25,30 @@ protected void onCreate(Bundle savedInstanceState) { Uri data = intent.getData(); if (data == null) this.initiatePayment(); - else - Log.d("PROCESSOUT", "TOKEN=" + data.getQueryParameter("token")); + else { + // Check if the activity has been opened from ProcessOut + WebViewReturnAction returnAction = ProcessOut.handleProcessOutReturn(data); + if (returnAction == null) { + // Opening URI is not from ProcessOut + } else { + switch (returnAction.getType()) { + case APMAuthorization: + // Value contains the APM token + Log.d("PROCESSOUT", returnAction.getValue()); + break; + case ThreeDSVerification: + // Value contains the invoice_id + Log.d("PROCESSOUT", returnAction.getValue()); + break; + } + } + } } public void initiatePayment() { final ProcessOut p = new ProcessOut(this, "test-proj_WijDbvE1oEkS67ikx2cfu25Nr5Qx4emX"); - Card c = new Card("4000000000003063", 10, 20, "737"); - p.tokenize(c,null, new TokenCallback() { + Card c = new Card("4000000000003246", 10, 20, "737"); + p.tokenize(c, null, new TokenCallback() { @Override public void onError(Exception error) { Log.e("PROCESSOUT", error.toString()); @@ -43,7 +59,7 @@ public void onSuccess(String token) { p.makeCardPayment("invoice-id", token, ProcessOut.createDefaultTestHandler(MainActivity.this, new ProcessOut.ThreeDSHandlerTestCallback() { @Override public void onSuccess(String invoiceId) { - Log.d("PROCESSOUT", "invocie: " + invoiceId); + Log.d("PROCESSOUT", "invoice: " + invoiceId); } @Override diff --git a/processout-sdk/src/main/java/com/processout/processout_sdk/Network.java b/processout-sdk/src/main/java/com/processout/processout_sdk/Network.java index c3e550f3..ddbc2d4b 100644 --- a/processout-sdk/src/main/java/com/processout/processout_sdk/Network.java +++ b/processout-sdk/src/main/java/com/processout/processout_sdk/Network.java @@ -5,6 +5,7 @@ import android.util.Log; import com.android.volley.AuthFailureError; +import com.android.volley.DefaultRetryPolicy; import com.android.volley.NetworkError; import com.android.volley.NoConnectionError; import com.android.volley.ParseError; @@ -77,6 +78,8 @@ public void onErrorResponse(VolleyError error) { } catch (UnsupportedEncodingException e) { callback.onError(e); } + } else { + callback.onError(new ProcessOutNetworkException("Could not receive server data.")); } } else if (error instanceof NetworkError) { callback.onError(new ProcessOutNetworkException("Could not connect to server")); diff --git a/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java b/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java index 23ff5d75..03140e22 100644 --- a/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java +++ b/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java @@ -3,12 +3,18 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; import android.util.Base64; +import android.util.Log; +import android.webkit.WebSettings; +import android.webkit.WebView; import com.android.volley.Request; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; +import com.processout.processout_sdk.ProcessOutExceptions.ProcessOutAuthException; import org.json.JSONArray; import org.json.JSONException; @@ -205,7 +211,6 @@ private void handleAuthorizationResult( } // Customer action required - Type mapType = new TypeToken>() {}.getType(); switch (cA.getType()) { case FINGERPRINT_MOBILE: DirectoryServerData directoryServerData = gson.fromJson(new String(Base64.decode(cA.getValue().getBytes(), Base64.NO_WRAP)), DirectoryServerData.class); @@ -232,18 +237,53 @@ public void error() { } }); break; + case URL: + WebView theWebPage = new WebView(this.context); + theWebPage.getSettings().setJavaScriptEnabled(true); + theWebPage.getSettings().setPluginState(WebSettings.PluginState.ON); + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(cA.getValue())); + this.context.startActivity(browserIntent); + break; default: - //TODO: handle FINGERPRINT, URL and REDIRECT for mobiles that don't support 3ds2 challenges - handler.onError(null); + handler.onError(new ProcessOutAuthException("Unhandled three D S action:" + cA.getType().name())); break; } } + /** + * Checks if the opening URL is from ProcessOut and returns the corresponding value if so. Returns null otherwise + * + * @param intentData intentData from the Activity + * @return A WebViewReturnAction containing the return type, value and success or null if not from ProcessOut + */ + public static WebViewReturnAction handleProcessOutReturn(Uri intentData) { + if (!intentData.getHost().contains("processout")) + return null; + + String token = intentData.getQueryParameter("token"); + if (token != null) + return new WebViewReturnAction(true, WebViewReturnAction.WebViewReturnType.APMAuthorization, token); + + String threeDSStatus = intentData.getQueryParameter("three_d_s_status"); + if (threeDSStatus != null) { + switch (threeDSStatus) { + case "success": + return new WebViewReturnAction(true, WebViewReturnAction.WebViewReturnType.ThreeDSVerification, intentData.getQueryParameter("invoice_id")); + default: + return new WebViewReturnAction(false, WebViewReturnAction.WebViewReturnType.ThreeDSVerification, null); + } + } + + return null; + } + public interface ThreeDSHandlerTestCallback { void onSuccess(String invoiceId); + void onError(Exception error); } + /** * Generate a test ThreeDSHandler for 3DS2 challenges * diff --git a/processout-sdk/src/main/java/com/processout/processout_sdk/WebViewReturnAction.java b/processout-sdk/src/main/java/com/processout/processout_sdk/WebViewReturnAction.java new file mode 100644 index 00000000..9799f8f9 --- /dev/null +++ b/processout-sdk/src/main/java/com/processout/processout_sdk/WebViewReturnAction.java @@ -0,0 +1,31 @@ +package com.processout.processout_sdk; + +public class WebViewReturnAction { + + public enum WebViewReturnType { + APMAuthorization, + ThreeDSVerification, + } + + private boolean success; + private WebViewReturnType type; + private String value; + + public WebViewReturnAction(boolean success, WebViewReturnType type, String value) { + this.success = success; + this.type = type; + this.value = value; + } + + public boolean isSuccess() { + return success; + } + + public WebViewReturnType getType() { + return type; + } + + public String getValue() { + return value; + } +} From 13eaa4ed8d5064a7dcf81b9017b7be8f813e0b94 Mon Sep 17 00:00:00 2001 From: Jeremy Lejoux Date: Mon, 22 Jul 2019 18:42:49 +0200 Subject: [PATCH 2/4] threeds: add success check on return --- .../java/com/processout/processoutexample/MainActivity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/processout/processoutexample/MainActivity.java b/app/src/main/java/com/processout/processoutexample/MainActivity.java index 07e54476..3ed6c5f8 100644 --- a/app/src/main/java/com/processout/processoutexample/MainActivity.java +++ b/app/src/main/java/com/processout/processoutexample/MainActivity.java @@ -34,11 +34,13 @@ protected void onCreate(Bundle savedInstanceState) { switch (returnAction.getType()) { case APMAuthorization: // Value contains the APM token - Log.d("PROCESSOUT", returnAction.getValue()); + if (returnAction.isSuccess()) + Log.d("PROCESSOUT", returnAction.getValue()); break; case ThreeDSVerification: // Value contains the invoice_id - Log.d("PROCESSOUT", returnAction.getValue()); + if (returnAction.isSuccess()) + Log.d("PROCESSOUT", returnAction.getValue()); break; } } From 5f530d4169e1038fa62da14f9de1dbc03eef75d4 Mon Sep 17 00:00:00 2001 From: Jeremy Lejoux Date: Mon, 22 Jul 2019 18:43:33 +0200 Subject: [PATCH 3/4] threeds: optimize imports --- .../com/processout/processout_sdk/AuthorizationResult.java | 1 - .../com/processout/processout_sdk/MiscGatewayRequest.java | 2 -- .../src/main/java/com/processout/processout_sdk/Network.java | 4 ---- .../main/java/com/processout/processout_sdk/ProcessOut.java | 4 ---- 4 files changed, 11 deletions(-) diff --git a/processout-sdk/src/main/java/com/processout/processout_sdk/AuthorizationResult.java b/processout-sdk/src/main/java/com/processout/processout_sdk/AuthorizationResult.java index 847d494c..71649c96 100644 --- a/processout-sdk/src/main/java/com/processout/processout_sdk/AuthorizationResult.java +++ b/processout-sdk/src/main/java/com/processout/processout_sdk/AuthorizationResult.java @@ -1,7 +1,6 @@ package com.processout.processout_sdk; import com.google.gson.annotations.SerializedName; -import com.processout.processout_sdk.CustomerAction; class AuthorizationResult { @SerializedName("customer_action") diff --git a/processout-sdk/src/main/java/com/processout/processout_sdk/MiscGatewayRequest.java b/processout-sdk/src/main/java/com/processout/processout_sdk/MiscGatewayRequest.java index 961bb21e..4df9c8e9 100644 --- a/processout-sdk/src/main/java/com/processout/processout_sdk/MiscGatewayRequest.java +++ b/processout-sdk/src/main/java/com/processout/processout_sdk/MiscGatewayRequest.java @@ -1,7 +1,5 @@ package com.processout.processout_sdk; -import android.util.Base64; - import com.google.gson.annotations.SerializedName; import java.util.Map; diff --git a/processout-sdk/src/main/java/com/processout/processout_sdk/Network.java b/processout-sdk/src/main/java/com/processout/processout_sdk/Network.java index ddbc2d4b..6087f9a2 100644 --- a/processout-sdk/src/main/java/com/processout/processout_sdk/Network.java +++ b/processout-sdk/src/main/java/com/processout/processout_sdk/Network.java @@ -2,14 +2,11 @@ import android.content.Context; import android.util.Base64; -import android.util.Log; import com.android.volley.AuthFailureError; -import com.android.volley.DefaultRetryPolicy; import com.android.volley.NetworkError; import com.android.volley.NoConnectionError; import com.android.volley.ParseError; -import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android.volley.ServerError; @@ -25,7 +22,6 @@ import com.processout.processout_sdk.ProcessOutExceptions.ProcessOutException; import com.processout.processout_sdk.ProcessOutExceptions.ProcessOutNetworkException; -import org.json.JSONException; import org.json.JSONObject; import java.io.UnsupportedEncodingException; diff --git a/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java b/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java index 03140e22..b7e682ea 100644 --- a/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java +++ b/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java @@ -6,23 +6,19 @@ import android.content.Intent; import android.net.Uri; import android.util.Base64; -import android.util.Log; import android.webkit.WebSettings; import android.webkit.WebView; import com.android.volley.Request; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; import com.processout.processout_sdk.ProcessOutExceptions.ProcessOutAuthException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Map; /** * Created by jeremylejoux on 17/01/2018. From 2323c3e8e8c72091a427de503091bf954f9d4fee Mon Sep 17 00:00:00 2001 From: Jeremy Lejoux Date: Tue, 23 Jul 2019 10:16:33 +0200 Subject: [PATCH 4/4] threeds: improve host comparison on return url --- .../src/main/java/com/processout/processout_sdk/ProcessOut.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java b/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java index b7e682ea..39b7de2f 100644 --- a/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java +++ b/processout-sdk/src/main/java/com/processout/processout_sdk/ProcessOut.java @@ -254,7 +254,7 @@ public void error() { * @return A WebViewReturnAction containing the return type, value and success or null if not from ProcessOut */ public static WebViewReturnAction handleProcessOutReturn(Uri intentData) { - if (!intentData.getHost().contains("processout")) + if (intentData.getHost().compareToIgnoreCase("processout.return") != 0) return null; String token = intentData.getQueryParameter("token");