killbill-memoizeit
Changes
payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java 30(+6 -24)
payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java 40(+11 -29)
payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java 2(+2 -0)
Details
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
index 35cd19f..c97bf29 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
@@ -145,7 +145,8 @@ public abstract class ExceptionMapperBase {
}
protected Response buildPluginTimeoutResponse(final Exception e, final UriInfo uriInfo) {
- final Response.ResponseBuilder responseBuilder = Response.status(Status.ACCEPTED);
+ // 504 - Gateway Timeout
+ final Response.ResponseBuilder responseBuilder = Response.status(504);
serializeException(e, uriInfo, responseBuilder);
return new LoggingResponse(e, responseBuilder.build());
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index e0c27ce..e4a59f3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -961,11 +961,7 @@ public class AccountResource extends JaxRsResourceBase {
default:
return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " is not allowed for an account").build();
}
- // Aborted payment?
- if (result == null) {
- return Response.noContent().build();
- }
- return uriBuilder.buildResponse(PaymentResource.class, "getPayment", result.getId(), uriInfo.getBaseUri().toString());
+ return createPaymentResponse(uriInfo, result, transactionType, json.getTransactionExternalKey());
}
/*
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 ca71075..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;
@@ -98,6 +104,7 @@ import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
public abstract class JaxRsResourceBase implements JaxrsResource {
@@ -533,4 +540,83 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
Preconditions.checkArgument(actual == expected, errorMessage);
}
+ protected Response createPaymentResponse(final UriInfo uriInfo, final Payment payment, final TransactionType transactionType, @Nullable final String transactionExternalKey) {
+ 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
+ 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
+ responseBuilder = Response.status(Status.SERVICE_UNAVAILABLE);
+ exception = createBillingException("Payment in unknown status, failed to receive gateway response.");
+ break;
+ case PLUGIN_FAILURE:
+ // 502 - Bad Gateway
+ 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);
+ }
+ }
+
+ 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) {
+ // Make sure we start looking from the latest transaction created
+ final List<PaymentTransaction> reversedTransactions = Lists.reverse(payment.getTransactions());
+ final Iterable<PaymentTransaction> matchingTransactions = Iterables.filter(reversedTransactions, new Predicate<PaymentTransaction>() {
+ @Override
+ public boolean apply(final PaymentTransaction input) {
+ return input.getTransactionType() == transactionType;
+ }
+ });
+
+ if (transactionExternalKey != null) {
+ for (final PaymentTransaction transaction : matchingTransactions) {
+ if (transactionExternalKey.equals(transaction.getExternalKey())) {
+ return transaction;
+ }
+ }
+ }
+
+ // If nothing is found, return the latest transaction of given type
+ return Iterables.getFirst(matchingTransactions, null);
+ }
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
index 2463abe..82db576 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
@@ -291,35 +291,36 @@ public class PaymentResource extends ComboPaymentResource {
json != null ? json.getTransactionExternalKey() : null,
json != null ? json.getTransactionType() : null);
- final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
- switch (pendingTransaction.getTransactionType()) {
+ final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+ final Payment result;
+ switch (pendingTransaction.getTransactionType()) {
case AUTHORIZE:
- paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
+ result = paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case CAPTURE:
- paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, pendingTransaction.getExternalKey(),
+ result = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, pendingTransaction.getExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case PURCHASE:
- paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
+ result = paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case CREDIT:
- paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
+ result = paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case REFUND:
- paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency,
+ result = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency,
pendingTransaction.getExternalKey(), pluginProperties, paymentOptions, callContext);
break;
default:
return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + pendingTransaction.getTransactionType() + " cannot be completed").build();
}
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", initialPayment.getId());
+ return createPaymentResponse(uriInfo, result, pendingTransaction.getTransactionType(), pendingTransaction.getExternalKey());
}
@TimedResource(name = "captureAuthorization")
@@ -383,7 +384,7 @@ public class PaymentResource extends ComboPaymentResource {
final Payment payment = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
json.getTransactionExternalKey(), pluginProperties, paymentOptions, callContext);
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+ return createPaymentResponse(uriInfo, payment, TransactionType.CAPTURE, json.getTransactionExternalKey());
}
@TimedResource(name = "refundPayment")
@@ -449,8 +450,8 @@ public class PaymentResource extends ComboPaymentResource {
final Payment payment = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
json.getTransactionExternalKey(), pluginProperties, paymentOptions, callContext);
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+ return createPaymentResponse(uriInfo, payment, TransactionType.REFUND, json.getTransactionExternalKey());
}
@TimedResource(name = "voidPayment")
@@ -510,7 +511,7 @@ public class PaymentResource extends ComboPaymentResource {
final Payment payment = paymentApi.createVoidWithPaymentControl(account, initialPayment.getId(), transactionExternalKey,
pluginProperties, paymentOptions, callContext);
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+ return createPaymentResponse(uriInfo, payment, TransactionType.VOID, json.getTransactionExternalKey());
}
@TimedResource(name = "chargebackPayment")
@@ -574,7 +575,7 @@ public class PaymentResource extends ComboPaymentResource {
final Payment payment = paymentApi.createChargebackWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
json.getTransactionExternalKey(), paymentOptions, callContext);
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+ return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey());
}
@TimedResource
@@ -628,7 +629,7 @@ public class PaymentResource extends ComboPaymentResource {
default:
return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " is not allowed for an account").build();
}
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", result.getId());
+ return createPaymentResponse(uriInfo, result, transactionType, paymentTransactionJson.getTransactionExternalKey());
}
@Override
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/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
index 22b0d54..8c64a2d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
@@ -18,8 +18,6 @@
package org.killbill.billing.payment.core.sm.control;
import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
@@ -47,12 +45,10 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher;
import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
import org.killbill.billing.util.config.PaymentConfig;
import org.killbill.commons.locker.GlobalLocker;
-import org.killbill.commons.locker.LockFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
-import com.google.common.base.MoreObjects;
public abstract class OperationControlCallback extends OperationCallbackBase<Payment, PaymentApiException> implements OperationCallback {
@@ -149,33 +145,19 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(paymentControlContext));
} catch (final RuntimeException e) {
// Attempts to set the retry date in context if needed.
- executePluginOnFailureCallsAndSetRetryDate(paymentControlContext);
- throw new OperationException(e, OperationResult.EXCEPTION);
+ throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(paymentControlContext));
}
}
});
}
@Override
- protected OperationException unwrapExceptionFromDispatchedTask(final Exception e) {
- // If this is an ExecutionException we attempt to extract the cause first
- final Throwable originalExceptionOrCausePossiblyOperationException = e instanceof ExecutionException ? MoreObjects.firstNonNull(e.getCause(), e) : e;
-
- // Unwrap OperationException too (doOperationCallback wraps exceptions in OperationException)
- final Throwable originalExceptionOrCause = originalExceptionOrCausePossiblyOperationException instanceof OperationException ? MoreObjects.firstNonNull(originalExceptionOrCausePossiblyOperationException.getCause(), originalExceptionOrCausePossiblyOperationException) : originalExceptionOrCausePossiblyOperationException;
-
- if (originalExceptionOrCause instanceof OperationException) {
- return (OperationException) originalExceptionOrCause;
- } else if (originalExceptionOrCause instanceof LockFailedException) {
- logger.warn("Failed to lock accountId='{}'", paymentStateContext.getAccount().getId());
- } else if (originalExceptionOrCause instanceof TimeoutException) {
- logger.warn("Call TIMEOUT for accountId='{}'", paymentStateContext.getAccount().getId());
- } else if (originalExceptionOrCause instanceof InterruptedException) {
- logger.warn("Call was interrupted for accountId='{}'", paymentStateContext.getAccount().getId());
- } else {
- logger.warn("Operation failed for accountId='{}'", paymentStateContext.getAccount().getId(), e);
+ protected OperationException unwrapExceptionFromDispatchedTask(final PaymentApiException e) {
+ if (e.getCause() instanceof OperationException) {
+ return (OperationException) e.getCause();
}
- return new OperationException(originalExceptionOrCause, getOperationResultOnException(paymentStateContext));
+ logger.warn("Operation failed for accountId='{}' accountExternalKey='{}' error='{}'", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+ return new OperationException(e, getOperationResultOnException(paymentStateContext));
}
private OperationResult getOperationResultOnException(final PaymentStateContext paymentStateContext) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
index 1f52802..7eb4821 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
@@ -18,14 +18,14 @@
package org.killbill.billing.payment.core.sm;
import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
import org.killbill.automaton.OperationException;
import org.killbill.automaton.OperationResult;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.core.ProcessorBase.CallableWithAccountLock;
import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback;
+import org.killbill.billing.payment.dispatcher.PaymentPluginDispatcher;
import org.killbill.billing.payment.dispatcher.PluginDispatcher;
import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
import org.killbill.billing.util.config.PaymentConfig;
@@ -67,18 +67,9 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
account.getExternalKey(),
paymentConfig,
callback);
- logger.debug("Calling plugin(s) {}", pluginNames);
- final OperationResult operationResult = paymentPluginDispatcher.dispatchWithTimeout(task);
- logger.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, account.getExternalKey(), operationResult);
+ final OperationResult operationResult = PaymentPluginDispatcher.dispatchWithExceptionHandling(account, pluginNames, task, paymentPluginDispatcher);
return operationResult;
- } catch (final ExecutionException e) {
- throw unwrapExceptionFromDispatchedTask(e);
- } catch (final TimeoutException e) {
- logger.warn("TimeoutException while executing the plugin(s) {}", pluginNames);
- throw unwrapExceptionFromDispatchedTask(e);
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- logger.warn("InterruptedException while executing the following plugin(s): {}", pluginNames);
+ } catch (final PaymentApiException e) {
throw unwrapExceptionFromDispatchedTask(e);
}
}
@@ -91,5 +82,5 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
//
protected abstract CallbackOperationResult doCallSpecificOperationCallback() throws CallbackOperationException;
- protected abstract OperationException unwrapExceptionFromDispatchedTask(final Exception e);
+ protected abstract OperationException unwrapExceptionFromDispatchedTask(final PaymentApiException e);
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
index 4587b28..2010258 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -222,8 +222,6 @@ public class PaymentAutomatonRunner {
throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
} else if (e.getCause() instanceof PaymentApiException) {
throw (PaymentApiException) e.getCause();
- } else if (e.getCause() instanceof TimeoutException) {
- throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, Objects.firstNonNull(e.getMessage(), ""));
} else {
throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
index e269932..bfb1c21 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
@@ -19,8 +19,6 @@ package org.killbill.billing.payment.core.sm.payments;
import java.math.BigDecimal;
import java.util.Iterator;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
import org.killbill.automaton.Operation.OperationCallback;
import org.killbill.automaton.OperationException;
@@ -44,11 +42,9 @@ import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
import org.killbill.billing.util.config.PaymentConfig;
import org.killbill.commons.locker.GlobalLocker;
-import org.killbill.commons.locker.LockFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -86,35 +82,15 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
} else {
try {
return doSimpleOperationCallback();
- } catch (final Exception e) {
- // We need to unwrap OperationException (see doSimpleOperationCallback below)
- throw unwrapExceptionFromDispatchedTask(e);
+ } catch (final OperationException e) {
+ throw convertToUnknownTransactionStatusAndErroredPaymentState(e);
}
}
}
@Override
- protected OperationException unwrapExceptionFromDispatchedTask(final Exception e) {
- // If this is an ExecutionException we attempt to extract the cause first
- final Throwable originalExceptionOrCausePossiblyOperationException = e instanceof ExecutionException ? MoreObjects.firstNonNull(e.getCause(), e) : e;
-
- // Unwrap OperationException too (doOperationCallback wraps exceptions in OperationException)
- final Throwable originalExceptionOrCause = originalExceptionOrCausePossiblyOperationException instanceof OperationException ? MoreObjects.firstNonNull(originalExceptionOrCausePossiblyOperationException.getCause(), originalExceptionOrCausePossiblyOperationException) : originalExceptionOrCausePossiblyOperationException;
-
- //
- // Any case of exception (checked or runtime) should lead to a TransactionStatus.UNKNOWN (and a XXX_ERRORED payment state).
- // In order to reach that state we create PaymentTransactionInfoPlugin with an PaymentPluginStatus.UNDEFINED status (and an OperationResult.EXCEPTION).
- //
- if (originalExceptionOrCause instanceof LockFailedException) {
- logger.warn("Failed to lock accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey());
- } else if (originalExceptionOrCause instanceof TimeoutException) {
- logger.warn("Plugin call TIMEOUT for accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey());
- } else if (originalExceptionOrCause instanceof InterruptedException) {
- logger.warn("Plugin call was interrupted for accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey());
- } else {
- logger.warn("Payment plugin call threw an exception for accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey(), originalExceptionOrCause);
- }
- return convertToUnknownTransactionStatusAndErroredPaymentState(originalExceptionOrCause);
+ protected OperationException unwrapExceptionFromDispatchedTask(final PaymentApiException e) {
+ return convertToUnknownTransactionStatusAndErroredPaymentState(e);
}
//
@@ -123,7 +99,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
// - Construct a PaymentTransactionInfoPlugin whose PaymentPluginStatus = UNDEFINED to end up with a paymentTransactionStatus = UNKNOWN and have a chance to
// be fixed by Janitor.
//
- private OperationException convertToUnknownTransactionStatusAndErroredPaymentState(final Throwable e) {
+ private OperationException convertToUnknownTransactionStatusAndErroredPaymentState(final Exception e) {
final PaymentTransactionInfoPlugin paymentInfoPlugin = new DefaultNoOpPaymentInfoPlugin(paymentStateContext.getPaymentId(),
paymentStateContext.getTransactionId(),
@@ -136,6 +112,12 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
null,
null);
paymentStateContext.setPaymentTransactionInfoPlugin(paymentInfoPlugin);
+ if (e.getCause() instanceof OperationException) {
+ return (OperationException) e.getCause();
+ }
+ if (e instanceof OperationException) {
+ return (OperationException) e;
+ }
return new OperationException(e, OperationResult.EXCEPTION);
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
index fec3c2e..a84a036 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
@@ -45,15 +45,15 @@ public class PaymentPluginDispatcher {
try {
log.debug("Calling plugin(s) {}", pluginNames);
final ReturnType result = pluginDispatcher.dispatchWithTimeout(callable);
- log.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, accountId, result);
+ log.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, accountExternalKey, result);
return result;
} catch (final TimeoutException e) {
- final String errorMessage = String.format("TimeoutException while executing plugin='%s'", pluginNames);
+ final String errorMessage = String.format("Call TIMEOUT for accountId='%s' accountExternalKey='%s' plugin='%s'", accountId, accountExternalKey, pluginNames);
log.warn(errorMessage);
throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, errorMessage);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
- final String errorMessage = String.format("InterruptedException while executing plugin='%s'", pluginNames);
+ final String errorMessage = String.format("Call was interrupted for accountId='%s' accountExternalKey='%s' plugin='%s'", accountId, accountExternalKey, pluginNames);
log.warn(errorMessage, e);
throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), errorMessage));
} catch (final ExecutionException e) {
@@ -64,7 +64,8 @@ public class PaymentPluginDispatcher {
log.warn(format);
throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, format);
} else {
- throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
+ // Unwraps the ExecutionException (e.getCause()), since it's a dispatch implementation detail
+ throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
}
}
}
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 bb8c247..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
@@ -20,16 +20,14 @@ package org.killbill.billing.payment.api;
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
import org.joda.time.LocalDate;
-import org.killbill.automaton.OperationException;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.catalog.api.Currency;
@@ -37,15 +35,16 @@ import org.killbill.billing.control.plugin.api.PaymentControlApiException;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
import org.killbill.billing.payment.MockRecurringInvoiceItem;
import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
-import org.killbill.billing.payment.core.sm.OperationCallbackBase;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
import org.killbill.billing.payment.dao.PaymentSqlDao;
import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
-import org.killbill.billing.payment.logging.SpyLogger;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
+import org.killbill.billing.payment.provider.MockPaymentControlProviderPlugin;
import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.bus.api.PersistentBus.EventBusException;
@@ -54,20 +53,21 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
-import static org.killbill.billing.payment.logging.TestLoggingHelper.withSpyLogger;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
private MockPaymentProviderPlugin mockPaymentProviderPlugin;
+ private MockPaymentControlProviderPlugin mockPaymentControlProviderPlugin;
+
final PaymentOptions INVOICE_PAYMENT = new PaymentOptions() {
@Override
public boolean isExternalPayment() {
@@ -80,6 +80,18 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
}
};
+ final PaymentOptions CONTROL_PLUGIN_OPTIONS = new PaymentOptions() {
+ @Override
+ public boolean isExternalPayment() {
+ return true;
+ }
+
+ @Override
+ public List<String> getPaymentControlPluginNames() {
+ return Arrays.asList(MockPaymentControlProviderPlugin.PLUGIN_NAME);
+ }
+ };
+
private Account account;
@BeforeClass(groups = "slow")
@@ -93,6 +105,24 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
super.beforeMethod();
mockPaymentProviderPlugin.clear();
account = testHelper.createTestAccount("bobo@gmail.com", true);
+
+ 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")
@@ -255,6 +285,55 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
}
+ @Test(groups = "slow")
+ public void testCreatePurchasePaymentPluginException() {
+ mockPaymentProviderPlugin.makeNextPaymentFailWithException();
+
+ final BigDecimal requestedAmount = BigDecimal.TEN;
+ final String paymentExternalKey = "pay external key";
+ final String transactionExternalKey = "txn external key";
+ try {
+ paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+ paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
+ fail();
+ } catch (PaymentApiException e) {
+ assertTrue(e.getCause() instanceof PaymentPluginApiException);
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testCreatePurchaseWithControlPaymentPluginException() throws Exception {
+ mockPaymentProviderPlugin.makeNextPaymentFailWithException();
+
+ final BigDecimal requestedAmount = BigDecimal.TEN;
+ final String paymentExternalKey = "pay controle external key";;
+ final String transactionExternalKey = "txn control external key";
+ try {
+ paymentApi.createPurchaseWithPaymentControl(
+ account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+ paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
+ fail();
+ } catch (PaymentApiException e) {
+ assertTrue(e.getCause() instanceof PaymentPluginApiException);
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testCreatePurchaseWithControlPluginException() throws Exception {
+ mockPaymentControlProviderPlugin.throwsException(new IllegalStateException());
+
+ final BigDecimal requestedAmount = BigDecimal.TEN;
+ final String paymentExternalKey = "pay controle external key";;
+ final String transactionExternalKey = "txn control external key";
+ try {
+ paymentApi.createPurchaseWithPaymentControl(
+ account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+ paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
+ fail();
+ } catch (PaymentApiException e) {
+ assertTrue(e.getCause() instanceof IllegalStateException);
+ }
+ }
@Test(groups = "slow")
public void testCreateSuccessAuthVoid() throws PaymentApiException {
@@ -1183,38 +1262,33 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String paymentExternalKey = "ohhhh";
final String transactionExternalKey = "naaahhh";
- final String pluginName = mockPaymentProviderPlugin.PLUGIN_NAME;
-
mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds((int) (paymentConfig.getPaymentPluginTimeout().getMillis() + 100));
+ try {
+ paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+ paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
+ fail();
+ } catch (PaymentApiException e) {
+ assertEquals(e.getCode(), ErrorCode.PAYMENT_PLUGIN_TIMEOUT.getCode());
+ }
+ }
- SpyLogger spyLogger = withSpyLogger(OperationCallbackBase.class, new Callable<Void>() {
-
- @Override
- public Void call() throws Exception {
- PaymentApiException thrownException = null;
-
- try {
- final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
- ImmutableList.<PluginProperty>of(), callContext);
- } catch (PaymentApiException e) {
- thrownException = e;
- }
-
- assertNotNull(thrownException);
-
- Throwable timeoutException = thrownException.getCause();
- assertNotNull(timeoutException);
- assertTrue(timeoutException instanceof TimeoutException);
-
- return null;
- }
- });
+ @Test(groups = "slow")
+ public void testCreatePurchaseWithControlTimeout() throws Exception {
+ final BigDecimal requestedAmount = BigDecimal.ONE;
+ final String paymentExternalKey = "111111";
+ final String transactionExternalKey = "11111";
- assertTrue(spyLogger.contains("Calling plugin.*" + pluginName, Optional.of(SpyLogger.LOG_LEVEL_DEBUG)));
- assertTrue(spyLogger.contains("TimeoutException.*" + pluginName, Optional.of(SpyLogger.LOG_LEVEL_WARN)));
+ mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds((int) (paymentConfig.getPaymentPluginTimeout().getMillis() + 100));
+ try {
+ paymentApi.createPurchaseWithPaymentControl(
+ account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey,
+ transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
+ fail();
+ } catch (PaymentApiException e) {
+ assertEquals(e.getCode(), ErrorCode.PAYMENT_PLUGIN_TIMEOUT.getCode());
+ }
}
-
@Test(groups = "slow")
public void testSanityAcrossTransactionTypes() throws PaymentApiException {
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
index 2a3a677..07f878b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
@@ -65,6 +65,8 @@ public class MockRetryAuthorizeOperationCallback extends AuthorizeControlOperati
if (exception != null) {
if (exception instanceof PaymentApiException) {
throw (PaymentApiException) exception;
+ } else if (exception instanceof RuntimeException) {
+ throw (RuntimeException) exception;
} else {
throw new RuntimeException(exception);
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
index 41155e8..a1a514f 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
@@ -57,7 +57,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
private static final String PLUGIN_NAME_PLACEHOLDER = "pluginName";
- private static final int TIMEOUT = 5;
+ private static final int TIMEOUT = 10;
private final GlobalLocker locker = new MemoryGlobalLocker();
private final Account account = Mockito.mock(Account.class);
@@ -97,7 +97,8 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
Assert.fail();
} catch (final OperationException e) {
Assert.assertEquals(e.getOperationResult(), OperationResult.EXCEPTION);
- Assert.assertTrue(e.getCause() instanceof NullPointerException);
+ Assert.assertTrue(e.getCause() instanceof PaymentApiException);
+ Assert.assertTrue(e.getCause().getCause() instanceof NullPointerException);
}
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
index dc7b36f..786dae5 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
@@ -20,6 +20,7 @@ package org.killbill.billing.payment;
import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PaymentApi;
@@ -59,6 +60,8 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
@Inject
protected OSGIServiceRegistration<PaymentPluginApi> registry;
@Inject
+ protected OSGIServiceRegistration<PaymentControlPluginApi> controlPluginRegistry;
+ @Inject
protected PersistentBus eventBus;
@Inject
protected PaymentApi paymentApi;
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
index 0327309..c150594 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
@@ -35,6 +35,7 @@ public class MockPaymentControlProviderPlugin implements PaymentControlPluginApi
private boolean isAborted;
private DateTime nextRetryDate;
+ private Exception exception;
public MockPaymentControlProviderPlugin setAborted(final boolean isAborted) {
this.isAborted = isAborted;
@@ -46,8 +47,23 @@ public class MockPaymentControlProviderPlugin implements PaymentControlPluginApi
return this;
}
+ public MockPaymentControlProviderPlugin throwsException(PaymentControlApiException exception) {
+ this.exception = exception;
+ return this;
+ }
+
+ public MockPaymentControlProviderPlugin throwsException(RuntimeException exception) {
+ this.exception = exception;
+ return this;
+ }
+
@Override
public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+ if (exception instanceof PaymentControlApiException) {
+ throw (PaymentControlApiException) exception;
+ } else if (exception instanceof RuntimeException) {
+ throw (RuntimeException) exception;
+ }
return new DefaultPriorPaymentControlResult(isAborted);
}
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