killbill-aplcache

Add Location header and error messages to failed payment responses. Also

5/5/2016 10:34:52 PM

Details

diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 29ad107..45f207f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -25,6 +25,7 @@ import java.net.URI;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -34,7 +35,9 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.StreamingOutput;
 import javax.ws.rs.core.UriInfo;
@@ -52,6 +55,8 @@ import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.killbill.billing.jaxrs.json.BillingExceptionJson;
+import org.killbill.billing.jaxrs.json.BillingExceptionJson.StackTraceElementJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.JsonBase;
 import org.killbill.billing.jaxrs.json.PluginPropertyJson;
@@ -90,6 +95,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
@@ -538,23 +544,58 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         final PaymentTransaction createdTransaction = findCreatedTransaction(payment, transactionType, transactionExternalKey);
         Preconditions.checkNotNull(createdTransaction, "No transaction of type '%s' found", transactionType);
 
+        final ResponseBuilder responseBuilder;
+        final BillingExceptionJson exception;
         switch (createdTransaction.getTransactionStatus()) {
             case PENDING:
             case SUCCESS:
                 return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
             case PAYMENT_FAILURE:
                 // 402 - Payment Required
-                return Response.status(402).build();
+                responseBuilder = Response.status(402);
+                exception = createBillingException(String.format("Payment decline by gateway. Error message: %s", createdTransaction.getGatewayErrorMsg()));
+                break;
             case PAYMENT_SYSTEM_OFF:
+                // 503 - Service Unavailable
+                responseBuilder = Response.status(Status.SERVICE_UNAVAILABLE);
+                exception = createBillingException("Payment system is off.");
+                break;
             case UNKNOWN:
                 // 503 - Service Unavailable
-                return Response.status(Status.SERVICE_UNAVAILABLE).build();
+                responseBuilder = Response.status(Status.SERVICE_UNAVAILABLE);
+                exception = createBillingException("Payment in unknown status, failed to receive gateway response.");
+                break;
             case PLUGIN_FAILURE:
                 // 502 - Bad Gateway
-                return Response.status(502).build();
+                responseBuilder = Response.status(502);
+                exception = createBillingException("Failed to submit payment transaction");
+                break;
+            default:
+                // Should never happen
+                responseBuilder = Response.serverError();
+                exception = createBillingException("This should never have happened!!!");
+        }
+        addExceptionToResponse(responseBuilder, exception);
+        return responseBuilder.location(getPaymentLocation(uriInfo, payment)).build();
+    }
+
+    private void addExceptionToResponse(final ResponseBuilder responseBuilder, final BillingExceptionJson exception) {
+        try {
+            responseBuilder.entity(mapper.writeValueAsString(exception)).type(MediaType.APPLICATION_JSON);
+        } catch (JsonProcessingException e) {
+            log.warn("Unable to serialize exception", exception);
+            responseBuilder.entity(e.toString()).type(MediaType.TEXT_PLAIN_TYPE);
         }
-        // Should never happen
-        return Response.serverError().build();
+    }
+
+    private BillingExceptionJson createBillingException(final String message) {
+        final BillingExceptionJson exception;
+        exception = new BillingExceptionJson(PaymentApiException.class.getName(), null, message, null, null, Collections.<StackTraceElementJson>emptyList());
+        return exception;
+    }
+
+    private URI getPaymentLocation(final UriInfo uriInfo, final Payment payment) {
+        return uriBuilder.buildLocation(uriInfo, PaymentResource.class, "getPayment", payment.getId());
     }
 
     private PaymentTransaction findCreatedTransaction(final Payment payment, final TransactionType transactionType, @Nullable final String transactionExternalKey) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
index ee1d165..a40529f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -31,12 +31,16 @@ import org.killbill.billing.jaxrs.resources.JaxrsResource;
 public class JaxrsUriBuilder {
 
     public Response buildResponse(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass, final String getMethodName, final Object objectId) {
+        final URI location = buildLocation(uriInfo, theClass, getMethodName, objectId);
+        return Response.created(location).build();
+    }
+
+    public URI buildLocation(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass, final String getMethodName, final Object objectId) {
         final UriBuilder uriBuilder = getUriBuilder(theClass, getMethodName).scheme(uriInfo.getAbsolutePath().getScheme())
                                                                             .host(uriInfo.getAbsolutePath().getHost())
                                                                             .port(uriInfo.getAbsolutePath().getPort());
 
-        final URI location = objectId != null ? uriBuilder.build(objectId) : uriBuilder.build();
-        return Response.created(location).build();
+        return objectId != null ? uriBuilder.build(objectId) : uriBuilder.build();
     }
 
     public URI nextPage(final Class<? extends JaxrsResource> theClass, final String getMethodName, final Long nextOffset, final Long limit, final Map<String, String> params) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index f1c4a17..aadbcea 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -106,7 +106,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         mockPaymentProviderPlugin.clear();
         account = testHelper.createTestAccount("bobo@gmail.com", true);
 
-        mockPaymentControlProviderPlugin = new MockPaymentControlProviderPlugin();;
+        mockPaymentControlProviderPlugin = new MockPaymentControlProviderPlugin();
         controlPluginRegistry.registerService(new OSGIServiceDescriptor() {
             @Override
             public String getPluginSymbolicName() {
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
index b46915f..20478a2 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
@@ -18,6 +18,7 @@
 package org.killbill.billing.jaxrs;
 
 import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.UUID;
 
@@ -32,11 +33,14 @@ import org.killbill.billing.client.model.PaymentMethodPluginDetail;
 import org.killbill.billing.client.model.PaymentTransaction;
 import org.killbill.billing.client.model.Payments;
 import org.killbill.billing.client.model.PluginProperty;
+import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.provider.MockPaymentControlProviderPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
@@ -54,14 +58,35 @@ public class TestPayment extends TestJaxrsBase {
 
     @Inject
     protected OSGIServiceRegistration<PaymentPluginApi> registry;
+    @Inject
+    private OSGIServiceRegistration<PaymentControlPluginApi> controlPluginRegistry;
 
     private MockPaymentProviderPlugin mockPaymentProviderPlugin;
+    private MockPaymentControlProviderPlugin mockPaymentControlProviderPlugin;
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
         mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(PLUGIN_NAME);
         mockPaymentProviderPlugin.clear();
+
+        mockPaymentControlProviderPlugin = new MockPaymentControlProviderPlugin();
+        controlPluginRegistry.registerService(new OSGIServiceDescriptor() {
+            @Override
+            public String getPluginSymbolicName() {
+                return null;
+            }
+
+            @Override
+            public String getPluginName() {
+                return MockPaymentControlProviderPlugin.PLUGIN_NAME;
+            }
+
+            @Override
+            public String getRegistrationName() {
+                return MockPaymentControlProviderPlugin.PLUGIN_NAME;
+            }
+        }, mockPaymentControlProviderPlugin);
     }
 
     @Test(groups = "slow")
@@ -74,9 +99,48 @@ public class TestPayment extends TestJaxrsBase {
         authTransaction.setAmount(BigDecimal.ONE);
         authTransaction.setCurrency(account.getCurrency());
         authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
-        final Payment payment = killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
-        assertEquals(payment.getTransactions().get(0).getGatewayErrorCode(), MockPaymentProviderPlugin.GATEWAY_ERROR_CODE);
-        assertEquals(payment.getTransactions().get(0).getGatewayErrorMsg(), MockPaymentProviderPlugin.GATEWAY_ERROR);
+        try {
+            killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+            fail();
+        } catch (KillBillClientException e) {
+            assertEquals(402, e.getResponse().getStatusCode());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testWithCanceledPayment() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+
+        mockPaymentProviderPlugin.makeNextPaymentFailWithCancellation();
+
+        final PaymentTransaction authTransaction = new PaymentTransaction();
+        authTransaction.setAmount(BigDecimal.ONE);
+        authTransaction.setCurrency(account.getCurrency());
+        authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
+        try {
+            killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+            fail();
+        } catch (KillBillClientException e) {
+            assertEquals(502, e.getResponse().getStatusCode());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testWithTimeoutPayment() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+
+        mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds(10000);
+
+        final PaymentTransaction authTransaction = new PaymentTransaction();
+        authTransaction.setAmount(BigDecimal.ONE);
+        authTransaction.setCurrency(account.getCurrency());
+        authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
+        try {
+            killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+            fail();
+        } catch (KillBillClientException e) {
+            assertEquals(504, e.getResponse().getStatusCode());
+        }
     }
 
     @Test(groups = "slow")
@@ -363,14 +427,43 @@ public class TestPayment extends TestJaxrsBase {
     public void testComboAuthorization() throws Exception {
         final Account accountJson = getAccount();
         accountJson.setAccountId(null);
+        final String paymentExternalKey = UUID.randomUUID().toString();
+
+        final ComboPaymentTransaction comboPaymentTransaction = createComboPaymentTransaction(accountJson, paymentExternalKey);
 
+        final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+        verifyComboPayment(payment, paymentExternalKey, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+
+        // Void payment using externalKey
+        final String voidTransactionExternalKey = UUID.randomUUID().toString();
+        final Payment voidPayment = killBillClient.voidPayment(null, paymentExternalKey, voidTransactionExternalKey, null, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+        verifyPaymentTransaction(accountJson, voidPayment.getPaymentId(), paymentExternalKey, voidPayment.getTransactions().get(1),
+                                 voidTransactionExternalKey, null, "VOID", "SUCCESS");
+    }
+
+    @Test(groups = "slow")
+    public void testComboAuthorizationControlPluginException() throws Exception {
+        final Account accountJson = getAccount();
+        accountJson.setAccountId(null);
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final ComboPaymentTransaction comboPaymentTransaction = createComboPaymentTransaction(accountJson, paymentExternalKey);
+
+        mockPaymentControlProviderPlugin.throwsException(new IllegalStateException());
+        try {
+            killBillClient.createPayment(comboPaymentTransaction, Arrays.asList(MockPaymentControlProviderPlugin.PLUGIN_NAME), ImmutableMap.<String, String>of(), createdBy, reason, comment);
+            fail();
+        } catch (KillBillClientException e) {
+            assertEquals(e.getResponse().getStatusCode(), 500);
+        }
+    }
+
+    private ComboPaymentTransaction createComboPaymentTransaction(final Account accountJson, final String paymentExternalKey) {
         final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
         info.setProperties(null);
 
         final String paymentMethodExternalKey = UUID.randomUUID().toString();
         final PaymentMethod paymentMethodJson = new PaymentMethod(null, paymentMethodExternalKey, null, true, PLUGIN_NAME, info);
 
-        final String paymentExternalKey = UUID.randomUUID().toString();
         final String authTransactionExternalKey = UUID.randomUUID().toString();
         final PaymentTransaction authTransactionJson = new PaymentTransaction();
         authTransactionJson.setAmount(BigDecimal.TEN);
@@ -379,16 +472,7 @@ public class TestPayment extends TestJaxrsBase {
         authTransactionJson.setTransactionExternalKey(authTransactionExternalKey);
         authTransactionJson.setTransactionType("AUTHORIZE");
 
-        final ComboPaymentTransaction comboPaymentTransaction = new ComboPaymentTransaction(accountJson, paymentMethodJson, authTransactionJson, ImmutableList.<PluginProperty>of(), ImmutableList.<PluginProperty>of());
-
-        final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
-        verifyComboPayment(payment, paymentExternalKey, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
-
-        // Void payment using externalKey
-        final String voidTransactionExternalKey = UUID.randomUUID().toString();
-        final Payment voidPayment = killBillClient.voidPayment(null, paymentExternalKey, voidTransactionExternalKey, null, ImmutableMap.<String, String>of(), createdBy, reason, comment);
-        verifyPaymentTransaction(accountJson, voidPayment.getPaymentId(), paymentExternalKey, voidPayment.getTransactions().get(1),
-                                 voidTransactionExternalKey, null, "VOID", "SUCCESS");
+        return new ComboPaymentTransaction(accountJson, paymentMethodJson, authTransactionJson, ImmutableList.<PluginProperty>of(), ImmutableList.<PluginProperty>of());
     }
 
     @Test(groups = "slow")
diff --git a/profiles/killbill/src/test/resources/killbill.properties b/profiles/killbill/src/test/resources/killbill.properties
index 08ec536..427e790 100644
--- a/profiles/killbill/src/test/resources/killbill.properties
+++ b/profiles/killbill/src/test/resources/killbill.properties
@@ -21,6 +21,8 @@ org.killbill.overdue.uri=overdue.xml
 
 org.killbill.payment.retry.days=8,8,8
 
+org.killbill.payment.plugin.timeout=8s
+
 # Local DB
 #org.killbill.billing.dbi.test.useLocalDb=true