Skip to content

Commit

Permalink
Merge pull request #15 from processout/apm-token
Browse files Browse the repository at this point in the history
Apm: add support for makeAPMToken method
  • Loading branch information
jlejoux authored Nov 4, 2019
2 parents 84bec91 + b66b393 commit e2a6af0
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.processout.processout_sdk;

import android.net.Uri;

import com.android.volley.Request;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Expand All @@ -8,10 +10,14 @@
import org.json.JSONObject;
import org.junit.Test;

import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;

import androidx.test.core.app.ApplicationProvider;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;


Expand Down Expand Up @@ -49,7 +55,7 @@ public void onSuccess(String token) {
}

@Test
public void listAlternativePaymentGateways() {
public void listPaymentGateways() {
final CountDownLatch signal = new CountDownLatch(1);

Invoice invoice = new Invoice("test", "123.0", "EUR", new Device("android"));
Expand All @@ -63,7 +69,25 @@ public void onError(Exception error) {

@Override
public void onSuccess(JSONObject json) {
signal.countDown();
Invoice invoiceResult = null;
try {
invoiceResult = gson.fromJson(json.getJSONObject("invoice").toString(), Invoice.class);
} catch (JSONException e) {
fail("Unhandled exception");
e.printStackTrace();
return;
}
p.fetchGatewayConfigurations(ProcessOut.GatewaysListingFilter.AlternativePaymentMethodWithTokenization, new FetchGatewaysConfigurationsCallback() {
@Override
public void onSuccess(ArrayList<GatewayConfiguration> gateways) {
signal.countDown();
}

@Override
public void onError(Exception e) {
fail("Error while fetching gateways");
}
});
}
});
} catch (JSONException e) {
Expand All @@ -78,4 +102,32 @@ public void onSuccess(JSONObject json) {
fail("Could not run test");
}
}

@Test
public void testReturnAPMTokenHandling() {
Uri testUri = Uri.parse("https://processout.return?token=test-token&customer_id=test-customer-id&token_id=test-token-id");
APMTokenReturn apmReturn = p.handleAMPURLCallback(testUri);
assertNotNull(apmReturn);
assertEquals(apmReturn.getType(), APMTokenReturn.APMReturnType.TokenCreation);
assertEquals(apmReturn.getCustomerId(), "test-customer-id");
assertEquals(apmReturn.getToken(), "test-token");
assertEquals(apmReturn.getTokenId(), "test-token-id");
assertEquals(apmReturn.getToken(), "test-token");
}

@Test
public void testReturnAPMPaymentHandling() {
Uri testUri = Uri.parse("https://processout.return?token=test-token");
APMTokenReturn apmReturn = p.handleAMPURLCallback(testUri);
assertNotNull(apmReturn);
assertEquals(apmReturn.getType(), APMTokenReturn.APMReturnType.Authorization);
assertEquals(apmReturn.getToken(), "test-token");
}

@Test
public void testReturnAPMWrongURI() {
Uri testUri = Uri.parse("https://processout.wrong?token=test-token&customer_id=test-customer-id&token_id=test-token-id");
APMTokenReturn apmReturn = p.handleAMPURLCallback(testUri);
assertNull(apmReturn);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;

import androidx.test.filters.LargeTest;
Expand Down Expand Up @@ -67,7 +68,6 @@ public void onSuccess(final String token) {
fail("Could not encode body");
return;
}

Network.getTestInstance(withActivity, projectId, privateKey).CallProcessOut("/invoices", Request.Method.POST, body, new Network.NetworkResult() {
@Override
public void onError(Exception error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.processout.processout_sdk;

import android.support.annotation.NonNull;

import com.processout.processout_sdk.ProcessOutExceptions.ProcessOutException;

public class APMTokenReturn {

public enum APMReturnType {
Authorization,
TokenCreation
}

private String token;
private String tokenId;
private String customerId;
private APMReturnType type;
private ProcessOutException error;

public APMTokenReturn(@NonNull String token, @NonNull String customerId, @NonNull String tokenId) {
this.token = token;
this.tokenId = tokenId;
this.customerId = customerId;
this.type = APMReturnType.TokenCreation;
}

public APMTokenReturn(@NonNull String token) {
this.token = token;
this.type = APMReturnType.Authorization;
}

public APMTokenReturn(ProcessOutException error) {
this.error = error;
}

public ProcessOutException getError() {
return error;
}

public String getToken() {
return token;
}

public String getTokenId() {
return tokenId;
}

public String getCustomerId() {
return customerId;
}

public APMReturnType getType() {
return type;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.processout.processout_sdk;

import java.util.ArrayList;

public interface FetchGatewaysConfigurationsCallback {
void onSuccess(ArrayList<GatewayConfiguration> gateways);

void onError(Exception e);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import com.google.gson.annotations.SerializedName;

public class AlternativeGateway {
public class GatewayConfiguration {
@SerializedName("id")
private String id;
@SerializedName("name")
Expand All @@ -19,15 +19,6 @@ public class AlternativeGateway {
@SerializedName("gateway")
private Gateway gateway;

private String invoiceId;
private String projectId;
private Context context;

public void redirect() {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(Network.CHECKOUT_URL + "/" + this.projectId + "/" + this.invoiceId + "/redirect/" + this.id));
this.context.startActivity(browserIntent);
}

public String getId() {
return id;
}
Expand All @@ -47,16 +38,4 @@ public String getDefaultCurrency() {
public Gateway getGateway() {
return gateway;
}

public void setInvoiceId(String invoiceId) {
this.invoiceId = invoiceId;
}

public void setProjectId(String projectId) {
this.projectId = projectId;
}

public void setContext(Context context) {
this.context = context;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.FrameLayout;

Expand All @@ -16,6 +16,7 @@
import com.processout.processout_sdk.POWebViews.CardTokenWebView;
import com.processout.processout_sdk.POWebViews.PaymentWebView;
import com.processout.processout_sdk.POWebViews.ProcessOutWebView;
import com.processout.processout_sdk.ProcessOutExceptions.ProcessOutException;

import org.json.JSONArray;
import org.json.JSONException;
Expand All @@ -29,7 +30,7 @@

public class ProcessOut {

public static final String SDK_VERSION = "v2.10.0";
public static final String SDK_VERSION = "v2.11.0";

private String projectId;
private Context context;
Expand Down Expand Up @@ -121,21 +122,35 @@ public void onSuccess(JSONObject json) {
}
}


public enum GatewaysListingFilter {
All,
AlternativePaymentMethods,
AlternativePaymentMethodWithTokenization
}

/**
* Retrieves the list of active alternative payment methods
* Retrieves the list of gateway configurations
*
* @param invoiceId Invoice ID that should be used for charging (this needs to be generated on your backend).
* Keep in mind that you should set the return_url to "your_app://processout.return".
* Check https://www.docs.processsout.com for more details
* @param callback Callback for listing alternative payment methods
* @param filter Filter for gateway configurations
* @param callback Callback for listing gateway configurations
*/
public void listAlternativeMethods(@NonNull final String invoiceId, @NonNull final ListAlternativeMethodsCallback callback) {
final Context context = this.context;
final String projectId = this.projectId;

public void fetchGatewayConfigurations(@NonNull GatewaysListingFilter filter, @NonNull final FetchGatewaysConfigurationsCallback callback) {

String filterValue;
switch (filter) {
case AlternativePaymentMethods:
filterValue = "alternative-payment-methods";
break;
case AlternativePaymentMethodWithTokenization:
filterValue = "alternative-payment-methods-with-tokenization";
break;
default:
filterValue = "";
}

Network.getInstance(this.context, this.projectId).CallProcessOut(
"/gateway-configurations?filter=alternative-payment-methods&expand_merchant_accounts=true",
"/gateway-configurations?filter=" + filterValue + "&expand_merchant_accounts=true",
Request.Method.GET, null, new Network.NetworkResult() {
@Override
public void onError(Exception error) {
Expand All @@ -147,13 +162,10 @@ public void onSuccess(JSONObject json) {
try {
JSONArray configs = json.getJSONArray("gateway_configurations");

ArrayList<AlternativeGateway> gways = new ArrayList<>();
ArrayList<GatewayConfiguration> gways = new ArrayList<>();
for (int i = 0; i < configs.length(); i++) {
AlternativeGateway g = gson.fromJson(
configs.getJSONObject(i).toString(), AlternativeGateway.class);
g.setContext(context);
g.setProjectId(projectId);
g.setInvoiceId(invoiceId);
GatewayConfiguration g = gson.fromJson(
configs.getJSONObject(i).toString(), GatewayConfiguration.class);
gways.add(g);
}

Expand All @@ -166,6 +178,30 @@ public void onSuccess(JSONObject json) {
});
}

/**
* Redirects the user to an alternative payment method payment page to authorize a token
*
* @param apm Gateway previously retrieved
* @param customerId Customer ID created on your backend
* @param tokenId Customer token ID created on your backend with empty source
*/
public void makeAPMToken(@NonNull GatewayConfiguration apm, @NonNull String customerId, @NonNull String tokenId) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(Network.CHECKOUT_URL + "/" + this.projectId + "/" + customerId + "/" + tokenId + "/redirect/" + apm.getId()));
this.context.startActivity(browserIntent);
}

/**
* Redirects the user to an alternative payment method payment page to complete a payment
*
* @param apm Gateway previously retrieved
* @param invoiceId Invoice created on your backend
*/
public void makeAPMPayment(@NonNull GatewayConfiguration apm, @NonNull String invoiceId) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(Network.CHECKOUT_URL + "/" + this.projectId + "/" + invoiceId + "/redirect/" + apm.getId()));
this.context.startActivity(browserIntent);
}

/**
* Allow card payments authorization (with 3DS2 support)
*
Expand Down Expand Up @@ -267,6 +303,7 @@ public void shouldContinue(String source) {
*
* @param uri Uri from a deep-link app opening
* @return The gateway token if available, null otherwise
* @deprecated Use handleAPMURLCallback instead
*/
public static String handleURLCallback(@NonNull Uri uri) {
if (uri.getHost().matches("processout.return"))
Expand All @@ -275,6 +312,39 @@ public static String handleURLCallback(@NonNull Uri uri) {
return null;
}

/**
* Parses an intent uri. Either for an APM payment return or after an makeAPMToken call
*
* @param uri Uri from a deep-link app opening
* @return Null if the URL is not a correct processout url.
* An APMTokenReturn object containing the customerId, tokenId and new token source
* to update the customer token from your backend otherwise
*/
public static APMTokenReturn handleAMPURLCallback(@NonNull Uri uri) {
if (!uri.getHost().matches("processout.return")) {
return null;
}

String token = uri.getQueryParameter("token");
String customerId = uri.getQueryParameter("customer_id");
String tokenId = uri.getQueryParameter("token_id");

// if not check if we have a token
if (token == null || token.isEmpty()) {
// No parameter token is available
return new APMTokenReturn(new ProcessOutException("Missing APM token in return paramaters"));
}

// Check if we have a customer id and token id
if (customerId != null && tokenId != null && !customerId.isEmpty() && !tokenId.isEmpty()) {
// Case of token creation
return new APMTokenReturn(token, customerId, tokenId);
}

// Case of simple APM authorization
return new APMTokenReturn(token);
}

public interface ThreeDSHandlerTestCallback {
void onSuccess(String invoiceId);

Expand Down

0 comments on commit e2a6af0

Please sign in to comment.