killbill-memoizeit
Changes
entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java 3(+2 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java 9(+7 -2)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java 6(+2 -4)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/DefaultEntitlementDao.java 10(+8 -2)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/model/EntitlementEventModelDao.java 4(+3 -1)
Details
diff --git a/api/src/main/java/com/ning/billing/beatrix/bus/api/ExtBusEventType.java b/api/src/main/java/com/ning/billing/beatrix/bus/api/ExtBusEventType.java
index ac4fe08..c4a241a 100644
--- a/api/src/main/java/com/ning/billing/beatrix/bus/api/ExtBusEventType.java
+++ b/api/src/main/java/com/ning/billing/beatrix/bus/api/ExtBusEventType.java
@@ -23,8 +23,10 @@ public enum ExtBusEventType {
ACCOUNT_CREATION,
ACCOUNT_CHANGE,
SUBSCRIPTION_CREATION,
+ SUBSCRIPTION_PHASE,
SUBSCRIPTION_CHANGE,
SUBSCRIPTION_CANCEL,
+ SUBSCRIPTION_UNCANCEL,
OVERDUE_CHANGE,
INVOICE_CREATION,
INVOICE_ADJUSTMENT,
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index 427e35b..1c5d83b 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -52,6 +52,17 @@ public interface PaymentApi {
/**
+ *
+ * @param account the account
+ * @param paymentId the payment id
+ * @param context
+ * @return
+ * @throws PaymentApiException
+ */
+ public Payment retryPayment(Account account, UUID paymentId, CallContext context)
+ throws PaymentApiException;
+
+ /**
* Create a refund for a given payment. The associated invoice is not adjusted.
*
* @param account account to refund
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java b/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java
index 39356fb..d716c72 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java
@@ -111,12 +111,18 @@ public class BeatrixListener {
objectType = ObjectType.SUBSCRIPTION;
objectId = realEventST.getSubscriptionId();
if (realEventST.getTransitionType() == SubscriptionTransitionType.CREATE ||
- realEventST.getTransitionType() == SubscriptionTransitionType.RE_CREATE) {
+ realEventST.getTransitionType() == SubscriptionTransitionType.RE_CREATE ||
+ realEventST.getTransitionType() == SubscriptionTransitionType.TRANSFER ||
+ realEventST.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
eventBusType = ExtBusEventType.SUBSCRIPTION_CREATION;
} else if (realEventST.getTransitionType() == SubscriptionTransitionType.CANCEL) {
eventBusType = ExtBusEventType.SUBSCRIPTION_CANCEL;
+ } else if (realEventST.getTransitionType() == SubscriptionTransitionType.PHASE) {
+ eventBusType = ExtBusEventType.SUBSCRIPTION_PHASE;
} else if (realEventST.getTransitionType() == SubscriptionTransitionType.CHANGE) {
eventBusType = ExtBusEventType.SUBSCRIPTION_CHANGE;
+ } else if (realEventST.getTransitionType() == SubscriptionTransitionType.UNCANCEL) {
+ eventBusType = ExtBusEventType.SUBSCRIPTION_UNCANCEL;
}
break;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java
index f8d1f69..5c9a0d1 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java
@@ -21,6 +21,8 @@ import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.Catalog;
@@ -128,7 +130,6 @@ public class DefaultEntitlementTransferApi extends EntitlementApiBase implements
}
break;
case CANCEL:
- case UNCANCEL:
break;
default:
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 5841a98..f29646e 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -383,11 +383,23 @@ public class SubscriptionData extends EntityBase implements Subscription {
if (transitions == null || event == null) {
return null;
}
+ SubscriptionTransitionData prev = null;
for (final SubscriptionTransition cur : transitions) {
- if (((SubscriptionTransitionData) cur).getId().equals(event.getId())) {
- final SubscriptionTransitionData withSeq = new SubscriptionTransitionData((SubscriptionTransitionData)cur, seqId);
+ final SubscriptionTransitionData curData = (SubscriptionTransitionData) cur;
+ if (curData.getId().equals(event.getId())) {
+
+ final SubscriptionTransitionData withSeq = new SubscriptionTransitionData(curData, seqId);
return withSeq;
}
+ if (curData.getTotalOrdering() < event.getTotalOrdering()) {
+ prev = curData;
+ }
+ }
+ // Since UNCANCEL are not part of the transitions, we compute a new 'UNCANCEL' transition based on the event right before that UNCANCEL
+ // This is used to be able to send a bus event for uncancellation
+ if (prev != null && event.getType() == EventType.API_USER && ((ApiEvent) event).getEventType() == ApiEventType.UNCANCEL) {
+ final SubscriptionTransitionData withSeq = new SubscriptionTransitionData((SubscriptionTransitionData)prev, EventType.API_USER, ApiEventType.UNCANCEL, seqId);
+ return withSeq;
}
return null;
}
@@ -587,7 +599,6 @@ public class SubscriptionData extends EntityBase implements Subscription {
nextPhaseName = null;
break;
case UNCANCEL:
- break;
default:
throw new EntitlementError(String.format(
"Unexpected UserEvent type = %s", userEV
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
index 2cadced..d4d44aa 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
@@ -102,12 +102,17 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
}
public SubscriptionTransitionData(final SubscriptionTransitionData input, int remainingEventsForUserOperation) {
+ this(input, input.getEventType(), input.getApiEventType(), remainingEventsForUserOperation);
+ }
+
+ public SubscriptionTransitionData(final SubscriptionTransitionData input, final EventType eventType,
+ final ApiEventType apiEventType, int remainingEventsForUserOperation) {
super();
this.eventId = input.getId();
this.subscriptionId = input.getSubscriptionId();
this.bundleId = input.getBundleId();
- this.eventType = input.getEventType();
- this.apiEventType = input.getApiEventType();
+ this.eventType = eventType;
+ this.apiEventType = apiEventType;
this.requestedTransitionTime = input.getRequestedTransitionTime();
this.effectiveTransitionTime = input.getEffectiveTransitionTime();
this.previousEventId = input.getPreviousEventId();
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
index 9da28ea..e2e4154 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
@@ -98,10 +98,8 @@ public class SubscriptionTransitionDataIterator implements Iterator<Subscription
}
private boolean shouldSkipForBillingEvents(final SubscriptionTransitionData input) {
- // Junction system knows about all events except for MIGRATE_ENTITLEMENT and UNCANCEL-- which is a NO event as it undo
- // something that should have happened in the future.
- return (input.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT ||
- input.getTransitionType() == SubscriptionTransitionType.UNCANCEL);
+ // Junction system knows about all events except for MIGRATE_ENTITLEMENT
+ return input.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT;
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/DefaultEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/DefaultEntitlementDao.java
index f10b36b..8128583 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/DefaultEntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/DefaultEntitlementDao.java
@@ -324,8 +324,14 @@ public class DefaultEntitlementDao implements EntitlementDao {
@Override
public List<EntitlementEvent> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
final List<EntitlementEventModelDao> models = entitySqlDaoWrapperFactory.become(EntitlementEventSqlDao.class).getEventsForSubscription(subscriptionId.toString(), context);
-
- return new ArrayList<EntitlementEvent>(Collections2.transform(models, new Function<EntitlementEventModelDao, EntitlementEvent>() {
+ // Remove UNCANCEL events early on as they are not representative of a state transition but are just markers
+ final Collection<EntitlementEventModelDao> filteredModels = Collections2.filter(models, new Predicate<EntitlementEventModelDao>() {
+ @Override
+ public boolean apply(@Nullable final EntitlementEventModelDao input) {
+ return input.getUserType() != ApiEventType.UNCANCEL;
+ }
+ });
+ return new ArrayList<EntitlementEvent>(Collections2.transform(filteredModels, new Function<EntitlementEventModelDao, EntitlementEvent>() {
@Override
public EntitlementEvent apply(@Nullable final EntitlementEventModelDao input) {
return EntitlementEventModelDao.toEntitlementEvent(input);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/model/EntitlementEventModelDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/model/EntitlementEventModelDao.java
index acc2167..6cc764c 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/model/EntitlementEventModelDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/model/EntitlementEventModelDao.java
@@ -56,7 +56,9 @@ public class EntitlementEventModelDao extends EntityBase implements EntityModelD
private long currentVersion;
private boolean isActive;
- public EntitlementEventModelDao() { /* For the DAO mapper */ }
+ public EntitlementEventModelDao() {
+ /* For the DAO mapper */
+ }
public EntitlementEventModelDao(final UUID id, final long totalOrdering, final EventType eventType, final ApiEventType userType,
final DateTime requestedDate, final DateTime effectiveDate, final UUID subscriptionId,
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java
index 21f7b6b..04e6b3b 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java
@@ -473,4 +473,47 @@ public class TestTransfer extends EntitlementTestSuiteWithEmbeddedDB {
final List<Subscription> subscriptions = entitlementApi.getSubscriptionsForBundle(newBundle.getId(), callContext);
assertEquals(subscriptions.size(), 1);
}
+
+ @Test(groups = "slow")
+ public void testTransferWithUncancel() throws Exception {
+
+ final UUID newAccountId = UUID.randomUUID();
+
+ final String baseProduct = "Shotgun";
+ final BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // CREATE BP
+ Subscription baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList);
+
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDays(30);
+ assertTrue(testListener.isCompleted(3000));
+
+ // SET CTD TO TRIGGER CANCELLATION EOT
+ final DateTime ctd = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+ entitlementInternalApi.setChargedThroughDate(baseSubscription.getId(), ctd, internalCallContext);
+
+
+ // CANCEL BP
+ baseSubscription = entitlementApi.getSubscriptionFromId(baseSubscription.getId(), callContext);
+ baseSubscription.cancel(clock.getUTCNow(), callContext);
+
+ // MOVE CLOCK one day AHEAD AND UNCANCEL BP
+ clock.addDays(1);
+ testListener.pushExpectedEvent(NextEvent.UNCANCEL);
+ baseSubscription.uncancel(callContext);
+ assertTrue(testListener.isCompleted(3000));
+
+ // MOVE CLOCK one day AHEAD AND UNCANCEL BP
+ clock.addDays(1);
+ final DateTime transferRequestedDate = clock.getUTCNow();
+ testListener.pushExpectedEvent(NextEvent.TRANSFER);
+ transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getExternalKey(), transferRequestedDate, true, false, callContext);
+ assertTrue(testListener.isCompleted(3000));
+
+ final SubscriptionBundle newBundle = entitlementApi.getBundleForAccountAndKey(newAccountId, bundle.getExternalKey(), callContext);
+ final List<Subscription> subscriptions = entitlementApi.getSubscriptionsForBundle(newBundle.getId(), callContext);
+ assertEquals(subscriptions.size(), 1);
+ }
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index d0cd6b0..ff67ff3 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -146,11 +146,10 @@ public class TestUserApiAddOn extends EntitlementTestSuiteWithEmbeddedDB {
aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId(), callContext);
aoTransitions = aoSubscription.getAllTransitions();
- assertEquals(aoTransitions.size(), 4);
+ assertEquals(aoTransitions.size(), 3);
assertEquals(aoTransitions.get(0).getTransitionType(), SubscriptionTransitionType.CREATE);
assertEquals(aoTransitions.get(1).getTransitionType(), SubscriptionTransitionType.PHASE);
- assertEquals(aoTransitions.get(2).getTransitionType(), SubscriptionTransitionType.UNCANCEL);
- assertEquals(aoTransitions.get(3).getTransitionType(), SubscriptionTransitionType.CANCEL);
+ assertEquals(aoTransitions.get(2).getTransitionType(), SubscriptionTransitionType.CANCEL);
assertTrue(aoSubscription.getFutureEndDate().compareTo(newBPChargedThroughDate) == 0);
assertListenerStatus();
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
index 4d41e06..e63a21b 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
@@ -30,6 +30,7 @@ import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@@ -136,6 +137,27 @@ public class PaymentResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(paymentJsonSimple).build();
}
+ @PUT
+ @Path("/{paymentId:" + UUID_PATTERN + "}")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response retryFailedPayment(@PathParam(ID_PARAM_NAME) final String paymentIdString,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
+
+ final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+
+ final UUID paymentId = UUID.fromString(paymentIdString);
+ final Payment payment = paymentApi.getPayment(paymentId, false, callContext);
+ final Account account = accountApi.getAccountById(payment.getAccountId(), callContext);
+ final Payment newPayment = paymentApi.retryPayment(account, paymentId, callContext);
+
+ return Response.status(Status.OK).entity(new PaymentJsonSimple(newPayment)).build();
+ }
+
+
@GET
@Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
@Produces(APPLICATION_JSON)
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
index c636c01..ba3f43e 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
@@ -90,6 +90,8 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
case SUBSCRIPTION_CREATION:
case SUBSCRIPTION_CHANGE:
case SUBSCRIPTION_CANCEL:
+ case SUBSCRIPTION_PHASE:
+ case SUBSCRIPTION_UNCANCEL:
handleSubscriptionEvent(killbillEvent, callContext);
break;
case OVERDUE_CHANGE:
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index 92e5d45..346061b 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -29,6 +29,7 @@ import com.ning.billing.payment.core.PaymentMethodProcessor;
import com.ning.billing.payment.core.PaymentProcessor;
import com.ning.billing.payment.core.RefundProcessor;
import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.InternalCallContext;
import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.util.callcontext.TenantContext;
@@ -67,6 +68,13 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
+ public Payment retryPayment(final Account account, final UUID paymentId, final CallContext context) throws PaymentApiException {
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), context);
+ paymentProcessor.retryPaymentFromApi(paymentId, internalCallContext);
+ return getPayment(paymentId, false, context);
+ }
+
+ @Override
public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final TenantContext context) throws PaymentApiException {
final Payment payment = paymentProcessor.getPayment(paymentId, withPluginInfo, internalCallContextFactory.createInternalTenantContext(context));
if (payment == null) {
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
index dc45727..232bfb5 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
@@ -113,8 +113,8 @@ public class PaymentProcessor extends ProcessorBase {
this.autoPayoffRetryService = autoPayoffRetryService;
this.clock = clock;
this.paymentConfig = paymentConfig;
- this.paymentPluginDispatcher = new PluginDispatcher<Payment>(executor);
- this.voidPluginDispatcher = new PluginDispatcher<Void>(executor);
+ this.paymentPluginDispatcher = new PluginDispatcher<Payment>(paymentConfig.getPaymentTimeoutSeconds(), executor);
+ this.voidPluginDispatcher = new PluginDispatcher<Void>(paymentConfig.getPaymentTimeoutSeconds(), executor);
}
public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final InternalTenantContext context) throws PaymentApiException {
@@ -328,6 +328,13 @@ public class PaymentProcessor extends ProcessorBase {
retryFailedPaymentInternal(paymentId, context, PaymentStatus.PAYMENT_FAILURE);
}
+ public void retryPaymentFromApi(final UUID paymentId, final InternalCallContext context) {
+ log.info("Retrying payment " + paymentId + " time = " + clock.getUTCNow());
+ retryFailedPaymentInternal(paymentId, context, PaymentStatus.UNKNOWN,
+ PaymentStatus.AUTO_PAY_OFF,
+ PaymentStatus.PAYMENT_FAILURE,
+ PaymentStatus.PLUGIN_FAILURE);
+ }
private void retryFailedPaymentInternal(final UUID paymentId, final InternalCallContext context, final PaymentStatus... expectedPaymentStates) {
@@ -386,7 +393,7 @@ public class PaymentProcessor extends ProcessorBase {
} catch (AccountApiException e) {
log.error(String.format("Failed to retry payment for paymentId %s", paymentId), e);
} catch (PaymentApiException e) {
- log.info(String.format("Failed to retry payment for paymentId %s", paymentId));
+ log.info(String.format("Failed to retry payment for paymentId %s", paymentId), e);
} catch (TimeoutException e) {
log.warn(String.format("Retry for payment %s timedout", paymentId));
// STEPH we should throw some exception so NotificationQ does not clear status and retries us
diff --git a/payment/src/main/java/com/ning/billing/payment/dispatcher/PluginDispatcher.java b/payment/src/main/java/com/ning/billing/payment/dispatcher/PluginDispatcher.java
index ba0e3fc..d62750c 100644
--- a/payment/src/main/java/com/ning/billing/payment/dispatcher/PluginDispatcher.java
+++ b/payment/src/main/java/com/ning/billing/payment/dispatcher/PluginDispatcher.java
@@ -34,16 +34,18 @@ public class PluginDispatcher<T> {
private final TimeUnit DEEFAULT_PLUGIN_TIMEOUT_UNIT = TimeUnit.SECONDS;
+ private final long timeoutSeconds;
private final ExecutorService executor;
- public PluginDispatcher(final ExecutorService executor) {
+ public PluginDispatcher(final long tiemoutSeconds, final ExecutorService executor) {
+ this.timeoutSeconds = tiemoutSeconds;
this.executor = executor;
}
+
public T dispatchWithAccountLock(final Callable<T> task)
throws PaymentApiException, TimeoutException {
- final long DEFAULT_PLUGIN_TIMEOUT_SEC = 30;
- return dispatchWithAccountLockAndTimeout(task, DEFAULT_PLUGIN_TIMEOUT_SEC, DEEFAULT_PLUGIN_TIMEOUT_UNIT);
+ return dispatchWithAccountLockAndTimeout(task, timeoutSeconds, DEEFAULT_PLUGIN_TIMEOUT_UNIT);
}
public T dispatchWithAccountLockAndTimeout(final Callable<T> task, final long timeout, final TimeUnit unit)
diff --git a/payment/src/test/java/com/ning/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/com/ning/billing/payment/dispatcher/TestPluginDispatcher.java
new file mode 100644
index 0000000..9617ee9
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dispatcher/TestPluginDispatcher.java
@@ -0,0 +1,77 @@
+package com.ning.billing.payment.dispatcher;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.payment.PaymentTestSuiteNoDB;
+import com.ning.billing.payment.api.PaymentApiException;
+
+public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
+
+ private final PluginDispatcher<Void> voidPluginDispatcher = new PluginDispatcher<Void>(10, Executors.newSingleThreadExecutor());
+
+ @Test(groups = "fast")
+ public void testDispatchWithTimeout() throws TimeoutException, PaymentApiException {
+ boolean gotIt = false;
+ try {
+ voidPluginDispatcher.dispatchWithAccountLockAndTimeout(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ Thread.sleep(1000);
+ return null;
+ }
+ }, 100, TimeUnit.MILLISECONDS);
+ Assert.fail("Failed : should have had Timeout exception");
+ } catch (TimeoutException e) {
+ gotIt = true;
+ } catch (PaymentApiException e) {
+ Assert.fail("Failed : should have had Timeout exception");
+ }
+ Assert.assertTrue(gotIt);
+ }
+
+ @Test(groups = "fast")
+ public void testDispatchWithPaymentApiException() throws TimeoutException, PaymentApiException {
+ boolean gotIt = false;
+ try {
+ voidPluginDispatcher.dispatchWithAccountLockAndTimeout(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ throw new PaymentApiException(ErrorCode.PAYMENT_ADD_PAYMENT_METHOD, "foo", "foo");
+ }
+ }, 100, TimeUnit.MILLISECONDS);
+ Assert.fail("Failed : should have had Timeout exception");
+ } catch (TimeoutException e) {
+ Assert.fail("Failed : should have had PaymentApiException exception");
+ } catch (PaymentApiException e) {
+ gotIt = true;
+ }
+ Assert.assertTrue(gotIt);
+ }
+
+ @Test(groups = "fast")
+ public void testDispatchWithRuntimeExceptionWrappedInPaymentApiException() throws TimeoutException, PaymentApiException {
+ boolean gotIt = false;
+ try {
+ voidPluginDispatcher.dispatchWithAccountLockAndTimeout(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ throw new RuntimeException("whatever");
+ }
+ }, 100, TimeUnit.MILLISECONDS);
+ Assert.fail("Failed : should have had Timeout exception");
+ } catch (TimeoutException e) {
+ Assert.fail("Failed : should have had RuntimeException exception");
+ } catch (PaymentApiException e) {
+ gotIt = true;
+ } catch (RuntimeException e) {
+ }
+ Assert.assertTrue(gotIt);
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/config/PaymentConfig.java b/util/src/main/java/com/ning/billing/util/config/PaymentConfig.java
index d4d7ad0..47549a1 100644
--- a/util/src/main/java/com/ning/billing/util/config/PaymentConfig.java
+++ b/util/src/main/java/com/ning/billing/util/config/PaymentConfig.java
@@ -48,6 +48,11 @@ public interface PaymentConfig extends KillbillConfig {
@Description("Maximum number of retries for failed payments")
public int getPluginFailureRetryMaxAttempts();
+ @Config("killbill.payment.timeout.seconds")
+ @Default("90")
+ @Description("Timeout for each payment attempt")
+ public int getPaymentTimeoutSeconds();
+
@Config("killbill.payment.off")
@Default("false")
@Description("Whether the payment subsystem is off")