Skip to content

Commit

Permalink
Merge pull request #11 from processout/token
Browse files Browse the repository at this point in the history
Add support for makeCardToken method + WebViews challenges
  • Loading branch information
jlejoux authored Oct 2, 2019
2 parents ff7c1cc + 24ebece commit 6d66986
Show file tree
Hide file tree
Showing 18 changed files with 408 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
import android.view.Menu;
import android.view.MenuItem;

import com.processout.processout_sdk.AlternativeGateway;
import com.processout.processout_sdk.Card;
import com.processout.processout_sdk.ListAlternativeMethodsCallback;
import com.processout.processout_sdk.ProcessOut;
import com.processout.processout_sdk.ThreeDSVerificationCallback;
import com.processout.processout_sdk.TokenCallback;
import com.processout.processout_sdk.WebViewReturnAction;

import java.util.ArrayList;


public class MainActivity extends AppCompatActivity {

Expand All @@ -25,46 +29,20 @@ protected void onCreate(Bundle savedInstanceState) {

Intent intent = getIntent();
Uri data = intent.getData();
if (data == null)
if (data == null) {
this.initiatePayment();
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
if (returnAction.isSuccess())
Log.d("PROCESSOUT", returnAction.getValue());
break;
case ThreeDSVerification:
// Value contains the invoice_id
if (returnAction.isSuccess())
Log.d("PROCESSOUT", returnAction.getValue());
break;
case ThreeDSFallbackVerification:
new ProcessOut(this, "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x").continueThreeDSVerification(returnAction.getInvoiceId(), returnAction.getValue(), new ThreeDSVerificationCallback() {
@Override
public void onSuccess(String invoiceId) {
Log.d("PROCESSOUT", invoiceId);
}

@Override
public void onError(Exception error) {
Log.d("PROCESSOUT", error.toString());
}
});
break;
}
}
return;
}

// Check if the activity has been opened from ProcessOut
String gatewayToken = ProcessOut.handleURLCallback(data);
if (gatewayToken != null)
Log.d("PROCESSOUT", gatewayToken); // Send the token to backend
}

public void initiatePayment() {
final ProcessOut p = new ProcessOut(this, "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x");
Card c = new Card("4000000000000259", 10, 20, "737");
Card c = new Card("4000000000003253", 10, 20, "737");
final Activity with = this;
p.tokenize(c, null, new TokenCallback() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;

Expand All @@ -24,14 +23,8 @@ public class AlternativeGateway {
private String projectId;
private Context context;

/**
*
*/
public void redirect() {
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(Network.CHECKOUT_URL + this.projectId + "/" + this.invoiceId + "/redirect/" + this.id));
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(Network.CHECKOUT_URL + "/" + this.projectId + "/" + this.invoiceId + "/redirect/" + this.id));
this.context.startActivity(browserIntent);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ protected enum CustomerActionType {
REDIRECT,
@SerializedName("fingerprint")
FINGERPRINT
};


}

@SerializedName("type")
private CustomerActionType type;
@SerializedName("value")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.processout.processout_sdk;

import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.util.Base64;
import android.view.ViewGroup;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;

import com.google.gson.Gson;
import com.processout.processout_sdk.POWebViews.ProcessOutWebView;
import com.processout.processout_sdk.POWebViews.WebViewCallback;
import com.processout.processout_sdk.ProcessOutExceptions.ProcessOutException;
import com.processout.processout_sdk.ProcessOutExceptions.ProcessOutWebException;

import java.util.HashMap;
import java.util.concurrent.TimeUnit;

class CustomerActionHandler {
private ThreeDSHandler handler;
private Context with;
private CustomerActionCallback callback;
private Gson gson = new Gson();
private ProcessOutWebView processOutWebView;

private final String ThreeDS2ChallengeSuccess = "gway_req_eyJib2R5Ijoie1widHJhbnNTdGF0dXNcIjpcIllcIn0ifQ==";
private final String ThreeDS2ChallengeError = "gway_req_eyJib2R5Ijoie1widHJhbnNTdGF0dXNcIjpcIk5cIn0ifQ==";

public interface CustomerActionCallback {
void shouldContinue(String source);
}

CustomerActionHandler(ThreeDSHandler handler, ProcessOutWebView processOutWebView, Context with, final CustomerActionCallback callback) {
this.handler = handler;
this.with = with;
this.callback = callback;
this.processOutWebView = processOutWebView;
}

void handleCustomerAction(CustomerAction customerAction) {
switch (customerAction.getType()) {
case FINGERPRINT_MOBILE:
DirectoryServerData directoryServerData = gson.fromJson(
new String(Base64.decode(customerAction.getValue().getBytes(), Base64.NO_WRAP)), DirectoryServerData.class);
handler.doFingerprint(directoryServerData, new ThreeDSHandler.DoFingerprintCallback() {
@Override
public void continueCallback(ThreeDSFingerprintResponse request) {
MiscGatewayRequest gwayRequest = new MiscGatewayRequest(gson.toJson(request, ThreeDSFingerprintResponse.class));
String jsonRequest = Base64.encodeToString(gson.toJson(gwayRequest, MiscGatewayRequest.class).getBytes(), Base64.NO_WRAP);
callback.shouldContinue("gway_req_" + jsonRequest);
}
});
break;
case CHALLENGE_MOBILE:
AuthenticationChallengeData authentificationData = gson.fromJson
(new String(Base64.decode(customerAction.getValue().getBytes(), Base64.NO_WRAP)), AuthenticationChallengeData.class);
handler.doChallenge(authentificationData, new ThreeDSHandler.DoChallengeCallback() {
@Override
public void success() {
callback.shouldContinue(ThreeDS2ChallengeSuccess);
}

@Override
public void error() {
callback.shouldContinue(ThreeDS2ChallengeError);
}
});
break;
case REDIRECT:
case URL:
processOutWebView.setCallback(new WebViewCallback() {
@Override
public void onResult(String token) {
callback.shouldContinue(token);
}

@Override
public void onAuthenticationError() {
handler.onError(new ProcessOutWebException("Web authentication failed"));
}
});
handler.doPresentWebView(processOutWebView);
processOutWebView.loadUrl(customerAction.getValue());
break;
case FINGERPRINT:
FrameLayout rootLayout = ((Activity) (with)).findViewById(android.R.id.content);

// Building the possibly needed webView
final WebView FingerprintWebView = new WebView(this.with);
FingerprintWebView.getSettings().setUserAgentString("ProcessOut Android-Webview/" + ProcessOut.SDK_VERSION);
FingerprintWebView.getSettings().setJavaScriptEnabled(true); // enable javascript

// Defining fallback request in case the fingerprint times out or is unavailable
final MiscGatewayRequest fallbackGwayRequest = new MiscGatewayRequest("{\"threeDS2FingerprintTimeout\":true}");
fallbackGwayRequest.setURL(customerAction.getValue());
HashMap<String, String> fallbackHeaders = new HashMap<String, String>();
fallbackHeaders.put("Content-Type", "application/json");
fallbackGwayRequest.setHeaders(fallbackHeaders);

// Setup the timeout handler
final Handler timeOutHandler = new android.os.Handler();
// Configuring the action on timeout
final Runnable fingerprintTimeoutClearer = new Runnable() {
@Override
public void run() {
// Destroying the webview
destroyWebView(FingerprintWebView);
callback.shouldContinue(fallbackGwayRequest.generateToken());
}
};

// Catch webview URL redirects
FingerprintWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
// Check if the current Android version is supporte
String token = null;

// We cancel the timeout handler
timeOutHandler.removeCallbacks(fingerprintTimeoutClearer);

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
token = request.getUrl().getQueryParameter("token");
}
if (token == null) {
// Android version not supported for fingerprinting
token = fallbackGwayRequest.generateToken();
}
callback.shouldContinue(token);
return super.shouldOverrideUrlLoading(view, request);
}
});

// Start the timeout
timeOutHandler.postDelayed(fingerprintTimeoutClearer, TimeUnit.SECONDS.toMillis(10));

// Load the fingerprint URL
FingerprintWebView.loadUrl(customerAction.getValue());

// Add the webview to content
if (with instanceof Activity) {
// We perform the fingerprint by displaying the hidden webview
rootLayout.addView(FingerprintWebView);
} else {
// We can't instantiate the webview so fallback to default fingerprinting value
timeOutHandler.removeCallbacks(fingerprintTimeoutClearer);
callback.shouldContinue(fallbackGwayRequest.generateToken());
}
break;
default:
handler.onError(new ProcessOutException("Unhandled threeDS action:" + customerAction.getType().name()));
break;
}
}

private void destroyWebView(WebView webView) {
// Preparing the webview removal
((ViewGroup) webView.getParent()).removeView(webView);
webView.removeAllViews();
webView.clearHistory();
webView.clearCache(true);
webView.onPause();
webView.destroy();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@

public interface CvcUpdateCallback {
public void onSuccess();

public void onError(Exception error);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

public interface ListAlternativeMethodsCallback {
void onSuccess(ArrayList<AlternativeGateway> gateways);

void onError(Exception e);
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ class Network {
private static String projectId;
private static String privateKey = "";

private static final String API_URL = "https://api.processout.com";
private static final int REQUEST_DEFAULT_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(15);
private static final int REQUEST_MAXIMUM_RETRIES = 2;

protected static final String API_URL = "https://api.processout.com";
protected static final String CHECKOUT_URL = "https://checkout.processout.com";

private Network() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.processout.processout_sdk.POWebViews;

import android.content.Context;
import android.net.Uri;

public class CardTokenWebView extends ProcessOutWebView {

public CardTokenWebView(Context context) {
super(context);
}

@Override
void onRedirect(Uri uri) {
// Configuring the webview
String token = uri.getQueryParameter("token");
if (token != null) {
// Destroying the webview
callback.onResult(token);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.processout.processout_sdk.POWebViews;

import android.content.Context;
import android.net.Uri;

public class PaymentWebView extends ProcessOutWebView {

public PaymentWebView(Context context) {
super(context);
}

@Override
void onRedirect(Uri uri) {
// Configuring the webview
String token = uri.getQueryParameter("token");

if (token != null) {
// Destroying the webview
callback.onResult(token);
return;
}

callback.onAuthenticationError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.processout.processout_sdk.POWebViews;

import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.processout.processout_sdk.ProcessOut;

public abstract class ProcessOutWebView extends WebView {

final private String REDIRECT_URL_PATTERN = "https:\\/\\/checkout\\.processout\\.(ninja|com)\\/helpers\\/mobile-processout-webview-landing.*";
protected WebViewCallback callback;

public ProcessOutWebView(Context context) {
super(context);
this.getSettings().setUserAgentString("ProcessOut Android-Webview/" + ProcessOut.SDK_VERSION);
this.getSettings().setJavaScriptEnabled(true); // enable javascript

// Catch URL loading
final ProcessOutWebView instance = this;
setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (url.matches(REDIRECT_URL_PATTERN))
instance.onRedirect(Uri.parse(url));
super.onPageStarted(view, url, favicon);
}
});
}

public void setCallback(WebViewCallback callback) {
this.callback = callback;
}

abstract void onRedirect(Uri uri);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.processout.processout_sdk.POWebViews;

public interface WebViewCallback {
void onResult(String token);

void onAuthenticationError();
}
Loading

0 comments on commit 6d66986

Please sign in to comment.