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/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")