killbill-memoizeit

Changes

account/pom.xml 2(+1 -1)

api/pom.xml 2(+1 -1)

beatrix/pom.xml 2(+1 -1)

bin/db-helper 15(+9 -6)

catalog/pom.xml 2(+1 -1)

currency/pom.xml 2(+1 -1)

invoice/pom.xml 2(+1 -1)

jaxrs/pom.xml 2(+1 -1)

junction/pom.xml 2(+1 -1)

NEWS 3(+3 -0)

overdue/pom.xml 2(+1 -1)

payment/pom.xml 2(+1 -1)

pom.xml 4(+2 -2)

profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java 321(+0 -321)

profiles/pom.xml 2(+1 -1)

tenant/pom.xml 2(+1 -1)

usage/pom.xml 2(+1 -1)

util/pom.xml 2(+1 -1)

Details

account/pom.xml 2(+1 -1)

diff --git a/account/pom.xml b/account/pom.xml
index e331e15..44b911a 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>

api/pom.xml 2(+1 -1)

diff --git a/api/pom.xml b/api/pom.xml
index f26b6fc..5b8aad5 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-internal-api</artifactId>

beatrix/pom.xml 2(+1 -1)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 8613028..c6bdbda 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>

bin/db-helper 15(+9 -6)

diff --git a/bin/db-helper b/bin/db-helper
index 929c59a..95761e6 100755
--- a/bin/db-helper
+++ b/bin/db-helper
@@ -19,7 +19,7 @@
 #                                                                                 #
 ###################################################################################
 
-#set -x
+# set -x
 
 HERE=`cd \`dirname $0\`; pwd`
 TOP=$HERE/..
@@ -27,6 +27,7 @@ TOP=$HERE/..
 POM="$TOP/pom.xml"
 
 ACTION=
+HOST="localhost"
 DATABASE="killbill"
 USER="root"
 PWD="root"
@@ -42,6 +43,7 @@ SKIP="(server)"
 function usage() {
     echo -n "./db_helper "
     echo -n " -a <create|clean|dump>"
+    echo -n " -H MySQL host (default = localhost)"
     echo -n " -d database_name (default = killbill)"
     echo -n " -u user_name (default = root)"
     echo -n " -p password (default = root)"
@@ -106,7 +108,7 @@ function create_ddl_file() {
 
     local tmp="/tmp/ddl-$DATABASE.$$"
     touch $tmp
-    echo "/*! use $DATABASE; */" >> $tmp
+    echo "use $DATABASE;" >> $tmp
     echo "" >> $tmp
     for d in $ddls; do
         cat $d >> $tmp
@@ -120,10 +122,11 @@ function cleanup() {
 }
 
 
-while getopts ":a:d:u:p:f:t" options; do
+while getopts ":a:d:H:u:p:f:t" options; do
   case $options in
     a ) ACTION=$OPTARG;;
 	d ) DATABASE=$OPTARG;;
+	H ) HOST=$OPTARG;;
 	u ) USER=$OPTARG;;
 	p ) PWD=$OPTARG;;
 	t ) TEST_ALSO=1;;
@@ -152,15 +155,15 @@ fi
 
 if [ $ACTION == "create" ]; then
     DDL_FILE=`create_ddl_file`
-    echo "Applying new schema $tmp to database $DATABASE"
-    mysql -u $USER --password=$PWD < $DDL_FILE
+    echo "Applying new schema to database $DATABASE"
+    mysql -h $HOST -u $USER --password=$PWD < $DDL_FILE
 fi
 
 if [ $ACTION == "clean" ]; then
     DDL_FILE=`create_ddl_file`
     CLEAN_FILE=`create_clean_file $DDL_FILE`
     echo "Cleaning db tables on database $DATABASE"
-    mysql -u $USER --password=$PWD < $DDL_FILE
+    mysql -h $HOST -u $USER --password=$PWD < $DDL_FILE
 fi
 
 cleanup

catalog/pom.xml 2(+1 -1)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index abdc1b9..e6ad7c8 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>

currency/pom.xml 2(+1 -1)

diff --git a/currency/pom.xml b/currency/pom.xml
index 62ea8e3..55e978a 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-currency</artifactId>
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index ea853a2..7895360 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>

invoice/pom.xml 2(+1 -1)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 6b48d16..b1460fe 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>

jaxrs/pom.xml 2(+1 -1)

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 5a8de80..fbb674e 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-jaxrs</artifactId>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
index 7f50aa1..62b723e 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
@@ -44,6 +44,7 @@ import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PaymentGatewayApi;
+import org.killbill.billing.payment.api.PaymentOptions;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.plugin.api.GatewayNotification;
 import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
@@ -94,6 +95,7 @@ public class PaymentGatewayResource extends ComboPaymentResource {
     @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid data for Account or PaymentMethod")})
     public Response buildComboFormDescriptor(final ComboHostedPaymentPageJson json,
                                              @PathParam("accountId") final String accountIdString,
+                                             @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                              @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                              @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                              @HeaderParam(HDR_REASON) final String reason,
@@ -103,6 +105,7 @@ public class PaymentGatewayResource extends ComboPaymentResource {
         verifyNonNullOrEmpty(json, "ComboHostedPaymentPageJson body should be specified");
 
         final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
 
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         final Account account = getOrCreateAccount(json.getAccount(), callContext);
@@ -113,7 +116,7 @@ public class PaymentGatewayResource extends ComboPaymentResource {
         final HostedPaymentPageFieldsJson hostedPaymentPageFields = json.getHostedPaymentPageFieldsJson();
         final Iterable<PluginProperty> customFields = extractPluginProperties(hostedPaymentPageFields != null ? hostedPaymentPageFields.getCustomFields() : null);
 
-        final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptor(account, paymentMethodId, customFields, pluginProperties, callContext);
+        final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptorWithPaymentControl(account, paymentMethodId, customFields, pluginProperties, paymentOptions, callContext);
         final HostedPaymentPageFormDescriptorJson result = new HostedPaymentPageFormDescriptorJson(descriptor);
 
         return Response.status(Response.Status.OK).entity(result).build();
@@ -130,6 +133,7 @@ public class PaymentGatewayResource extends ComboPaymentResource {
     public Response buildFormDescriptor(final HostedPaymentPageFieldsJson json,
                                         @PathParam("accountId") final String accountIdString,
                                         @QueryParam(QUERY_PAYMENT_METHOD_ID) final String paymentMethodIdStr,
+                                        @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                         @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                         @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                         @HeaderParam(HDR_REASON) final String reason,
@@ -137,6 +141,7 @@ public class PaymentGatewayResource extends ComboPaymentResource {
                                         @javax.ws.rs.core.Context final UriInfo uriInfo,
                                         @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
         final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         final UUID accountId = UUID.fromString(accountIdString);
         final Account account = accountUserApi.getAccountById(accountId, callContext);
@@ -146,7 +151,7 @@ public class PaymentGatewayResource extends ComboPaymentResource {
 
         final Iterable<PluginProperty> customFields = extractPluginProperties(json.getCustomFields());
 
-        final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptor(account, paymentMethodId, customFields, pluginProperties, callContext);
+        final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptorWithPaymentControl(account, paymentMethodId, customFields, pluginProperties, paymentOptions, callContext);
         final HostedPaymentPageFormDescriptorJson result = new HostedPaymentPageFormDescriptorJson(descriptor);
 
         return Response.status(Response.Status.OK).entity(result).build();
@@ -161,6 +166,7 @@ public class PaymentGatewayResource extends ComboPaymentResource {
     @ApiResponses(value = {})
     public Response processNotification(final String body,
                                         @PathParam(QUERY_PAYMENT_PLUGIN_NAME) final String pluginName,
+                                        @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                         @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                         @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                         @HeaderParam(HDR_REASON) final String reason,
@@ -168,6 +174,7 @@ public class PaymentGatewayResource extends ComboPaymentResource {
                                         @javax.ws.rs.core.Context final UriInfo uriInfo,
                                         @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
         final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
         final String notificationPayload;
@@ -178,7 +185,7 @@ public class PaymentGatewayResource extends ComboPaymentResource {
         }
 
         // Note: the body is opaque here, as it comes from the gateway. The associated payment plugin will know how to deserialize it though
-        final GatewayNotification notification = paymentGatewayApi.processNotification(notificationPayload, pluginName, pluginProperties, callContext);
+        final GatewayNotification notification = paymentGatewayApi.processNotificationWithPaymentControl(notificationPayload, pluginName, pluginProperties, paymentOptions, callContext);
         final GatewayNotificationJson result = new GatewayNotificationJson(notification);
 
         // The plugin told us how to build the response
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 e69e85c..804063d 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
@@ -250,7 +250,6 @@ public class PaymentResource extends ComboPaymentResource {
 
     @Timed
     @PUT
-    @Path("/")
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Complete an existing transaction")

junction/pom.xml 2(+1 -1)

diff --git a/junction/pom.xml b/junction/pom.xml
index 67b286a..2f403da 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-junction</artifactId>

NEWS 3(+3 -0)

diff --git a/NEWS b/NEWS
index c5b5ee6..4118402 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+0.15.3
+    Release for milestone 0.15.3: https://github.com/killbill/killbill/issues?q=is%3Aissue+milestone%3ARelease-0.15.3+is%3Aclosed
+
 0.15.2
     See https://github.com/killbill/killbill/issues?q=milestone%3ARelease-0.15.2+is%3Aclosed
 

overdue/pom.xml 2(+1 -1)

diff --git a/overdue/pom.xml b/overdue/pom.xml
index 8c77086..03b322c 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-overdue</artifactId>

payment/pom.xml 2(+1 -1)

diff --git a/payment/pom.xml b/payment/pom.xml
index 83e453a..ef45525 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
index e7f2318..9851afb 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
@@ -70,11 +70,16 @@ public class IncompletePaymentAttemptTask extends CompletionTaskBase<PaymentAtte
     private final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner;
 
     @Inject
-    public IncompletePaymentAttemptTask(final InternalCallContextFactory internalCallContextFactory, final PaymentConfig paymentConfig,
-                                        final PaymentDao paymentDao, final Clock clock, final PaymentStateMachineHelper paymentStateMachineHelper,
-                                        final PaymentControlStateMachineHelper retrySMHelper, final AccountInternalApi accountInternalApi,
+    public IncompletePaymentAttemptTask(final InternalCallContextFactory internalCallContextFactory,
+                                        final PaymentConfig paymentConfig,
+                                        final PaymentDao paymentDao,
+                                        final Clock clock,
+                                        final PaymentStateMachineHelper paymentStateMachineHelper,
+                                        final PaymentControlStateMachineHelper retrySMHelper,
+                                        final AccountInternalApi accountInternalApi,
                                         final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner,
-                                        final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry, final GlobalLocker locker) {
+                                        final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
+                                        final GlobalLocker locker) {
         super(internalCallContextFactory, paymentConfig, paymentDao, clock, paymentStateMachineHelper, retrySMHelper, accountInternalApi, pluginRegistry, locker);
         this.pluginControlledPaymentAutomatonRunner = pluginControlledPaymentAutomatonRunner;
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java
index 3b06de1..fbac8e0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java
@@ -22,13 +22,22 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 import org.joda.time.DateTime;
+import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.events.PaymentInternalEvent;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.core.PaymentExecutors;
+import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner;
+import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.glue.DefaultPaymentService;
-import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.notificationq.api.NotificationEvent;
 import org.killbill.notificationq.api.NotificationQueue;
 import org.killbill.notificationq.api.NotificationQueueService;
@@ -49,29 +58,76 @@ public class Janitor {
     public static final String QUEUE_NAME = "janitor";
 
     private final NotificationQueueService notificationQueueService;
-    private final ScheduledExecutorService janitorExecutor;
     private final PaymentConfig paymentConfig;
-    private final IncompletePaymentAttemptTask incompletePaymentAttemptTask;
-    private final IncompletePaymentTransactionTask incompletePaymentTransactionTask;
+    private final PaymentExecutors paymentExecutors;
+    private final Clock clock;
+    private final PaymentDao paymentDao;
+    private final InternalCallContextFactory internalCallContextFactory;
+    private final PaymentStateMachineHelper paymentStateMachineHelper;
+    private final PaymentControlStateMachineHelper retrySMHelper;
+    private final AccountInternalApi accountInternalApi;
+    private final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
+    private final GlobalLocker locker;
+    private final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner;
 
+
+
+
+    private IncompletePaymentAttemptTask incompletePaymentAttemptTask;
+    private IncompletePaymentTransactionTask incompletePaymentTransactionTask;
     private NotificationQueue janitorQueue;
+    private ScheduledExecutorService janitorExecutor;
 
     private volatile boolean isStopped;
 
     @Inject
-    public Janitor(final PaymentConfig paymentConfig,
+    public Janitor(final InternalCallContextFactory internalCallContextFactory,
+                   final PaymentDao paymentDao,
+                   final Clock clock,
+                   final PaymentStateMachineHelper paymentStateMachineHelper,
+                   final PaymentControlStateMachineHelper retrySMHelper,
+                   final AccountInternalApi accountInternalApi,
+                   final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner,
+                   final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
+                   final GlobalLocker locker,
+                   final PaymentConfig paymentConfig,
                    final NotificationQueueService notificationQueueService,
-                   @Named(PaymentModule.JANITOR_EXECUTOR_NAMED) final ScheduledExecutorService janitorExecutor,
-                   final IncompletePaymentAttemptTask incompletePaymentAttemptTask,
-                   final IncompletePaymentTransactionTask incompletePaymentTransactionTask) {
+                   final PaymentExecutors paymentExecutors) {
         this.notificationQueueService = notificationQueueService;
-        this.janitorExecutor = janitorExecutor;
+        this.paymentExecutors = paymentExecutors;
         this.paymentConfig = paymentConfig;
-        this.incompletePaymentAttemptTask = incompletePaymentAttemptTask;
-        this.incompletePaymentTransactionTask = incompletePaymentTransactionTask;
-        this.isStopped = false;
+        this.internalCallContextFactory = internalCallContextFactory;
+        this.paymentDao = paymentDao;
+        this.clock = clock;
+        this.pluginControlledPaymentAutomatonRunner = pluginControlledPaymentAutomatonRunner;
+        this.paymentStateMachineHelper = paymentStateMachineHelper;
+        this.retrySMHelper = retrySMHelper;
+        this.accountInternalApi = accountInternalApi;
+        this.pluginRegistry = pluginRegistry;
+        this.locker = locker;
+
     }
 
+    /*
+        public IncompletePaymentAttemptTask(final InternalCallContextFactory internalCallContextFactory,
+                                        final PaymentConfig paymentConfig,
+                                        final PaymentDao paymentDao,
+                                        final Clock clock,
+                                        final PaymentStateMachineHelper paymentStateMachineHelper,
+                                        final PaymentControlStateMachineHelper retrySMHelper,
+                                        final AccountInternalApi accountInternalApi,
+                                        final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner,
+                                        final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
+                                        final GlobalLocker locker) {
+
+
+    public IncompletePaymentTransactionTask(final InternalCallContextFactory internalCallContextFactory, final PaymentConfig paymentConfig,
+                                            final PaymentDao paymentDao, final Clock clock,
+                                            final PaymentStateMachineHelper paymentStateMachineHelper, final PaymentControlStateMachineHelper retrySMHelper, final AccountInternalApi accountInternalApi,
+                                            final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry, final GlobalLocker locker) {
+
+     */
+
     public void initialize() throws NotificationQueueAlreadyExists {
         janitorQueue = notificationQueueService.createNotificationQueue(DefaultPaymentService.SERVICE_NAME,
                                                                         QUEUE_NAME,
@@ -90,15 +146,38 @@ public class Janitor {
                                                                             }
                                                                         }
                                                                        );
+
+        this.incompletePaymentAttemptTask = new IncompletePaymentAttemptTask(internalCallContextFactory,
+                                                                             paymentConfig,
+                                                                             paymentDao,
+                                                                             clock,
+                                                                             paymentStateMachineHelper,
+                                                                             retrySMHelper,
+                                                                             accountInternalApi,
+                                                                             pluginControlledPaymentAutomatonRunner,
+                                                                             pluginRegistry,
+                                                                             locker);
+
+        this.incompletePaymentTransactionTask = new IncompletePaymentTransactionTask(internalCallContextFactory,
+                                                                                     paymentConfig,
+                                                                                     paymentDao,
+                                                                                     clock,
+                                                                                     paymentStateMachineHelper,
+                                                                                     retrySMHelper,
+                                                                                     accountInternalApi,
+                                                                                     pluginRegistry,
+                                                                                     locker);
+
+
         incompletePaymentTransactionTask.attachJanitorQueue(janitorQueue);
         incompletePaymentAttemptTask.attachJanitorQueue(janitorQueue);
     }
 
     public void start() {
-        if (isStopped) {
-            log.warn("Janitor is not a restartable service, and was already started, aborting");
-            return;
-        }
+
+        this.isStopped = false;
+
+        janitorExecutor = paymentExecutors.getJanitorExecutorService();
 
         janitorQueue.startQueue();
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java
new file mode 100644
index 0000000..bd4ca08
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.commons.concurrent.Executors;
+import org.killbill.commons.concurrent.WithProfilingThreadPoolExecutor;
+
+public class PaymentExecutors {
+
+    private static final long TIMEOUT_EXECUTOR_SEC = 3L;
+
+    private static final String PLUGIN_THREAD_PREFIX = "Plugin-th-";
+    private static final String PAYMENT_PLUGIN_TH_GROUP_NAME = "pay-plugin-grp";
+
+    public static final String JANITOR_EXECUTOR_NAMED = "JanitorExecutor";
+    public static final String PLUGIN_EXECUTOR_NAMED = "PluginExecutor";
+
+    private final PaymentConfig paymentConfig;
+
+    private volatile ExecutorService pluginExecutorService;
+    private volatile ScheduledExecutorService janitorExecutorService;
+
+    @Inject
+    public PaymentExecutors(PaymentConfig paymentConfig) {
+        this.paymentConfig = paymentConfig;
+
+    }
+
+    public void initialize() {
+        this.pluginExecutorService = createPluginExecutorService();
+        this.janitorExecutorService = createJanitorExecutorService();
+    }
+
+
+    public void stop() throws InterruptedException {
+        pluginExecutorService.shutdownNow();
+        janitorExecutorService.shutdownNow();
+
+        pluginExecutorService.awaitTermination(TIMEOUT_EXECUTOR_SEC, TimeUnit.SECONDS);
+        pluginExecutorService = null;
+
+        janitorExecutorService.awaitTermination(TIMEOUT_EXECUTOR_SEC, TimeUnit.SECONDS);
+        janitorExecutorService = null;
+    }
+
+    public ExecutorService getPluginExecutorService() {
+        return pluginExecutorService;
+    }
+
+    public ScheduledExecutorService getJanitorExecutorService() {
+        return janitorExecutorService;
+    }
+
+    private ExecutorService createPluginExecutorService() {
+        return new WithProfilingThreadPoolExecutor(paymentConfig.getPaymentPluginThreadNb(),
+                                                   paymentConfig.getPaymentPluginThreadNb(),
+                                                   0L,
+                                                   TimeUnit.MILLISECONDS,
+                                                   new LinkedBlockingQueue<Runnable>(),
+                                                   new ThreadFactory() {
+
+                                                       @Override
+                                                       public Thread newThread(final Runnable r) {
+                                                           final Thread th = new Thread(new ThreadGroup(PAYMENT_PLUGIN_TH_GROUP_NAME), r);
+                                                           th.setName(PLUGIN_THREAD_PREFIX + th.getId());
+                                                           return th;
+                                                       }
+                                                   });
+
+    }
+
+    private ScheduledExecutorService createJanitorExecutorService() {
+        return Executors.newSingleThreadScheduledExecutor("PaymentJanitor");
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
index dbc7541..cf9e41f 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
@@ -19,7 +19,6 @@ package org.killbill.billing.payment.core;
 
 import java.util.UUID;
 import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
@@ -47,13 +46,8 @@ import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Objects;
-import com.google.inject.name.Named;
-
-import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 
 // We don't take any lock here because the call needs to be re-entrant
 // from the plugin: for example, the BitPay plugin will create the payment during the
@@ -65,8 +59,6 @@ public class PaymentGatewayProcessor extends ProcessorBase {
     private final PluginDispatcher<HostedPaymentPageFormDescriptor> paymentPluginFormDispatcher;
     private final PluginDispatcher<GatewayNotification> paymentPluginNotificationDispatcher;
 
-    private static final Logger log = LoggerFactory.getLogger(PaymentGatewayProcessor.class);
-
     @Inject
     public PaymentGatewayProcessor(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
                                    final AccountInternalApi accountUserApi,
@@ -75,13 +67,13 @@ public class PaymentGatewayProcessor extends ProcessorBase {
                                    final PaymentDao paymentDao,
                                    final GlobalLocker locker,
                                    final PaymentConfig paymentConfig,
-                                   @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                                   final PaymentExecutors executors,
                                    final InternalCallContextFactory internalCallContextFactory,
                                    final Clock clock) {
-        super(pluginRegistry, accountUserApi, paymentDao, tagUserApi, locker, executor, internalCallContextFactory, invoiceApi, clock);
+        super(pluginRegistry, accountUserApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock);
         final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
-        this.paymentPluginFormDispatcher = new PluginDispatcher<HostedPaymentPageFormDescriptor>(paymentPluginTimeoutSec, executor);
-        this.paymentPluginNotificationDispatcher = new PluginDispatcher<GatewayNotification>(paymentPluginTimeoutSec, executor);
+        this.paymentPluginFormDispatcher = new PluginDispatcher<HostedPaymentPageFormDescriptor>(paymentPluginTimeoutSec, executors);
+        this.paymentPluginNotificationDispatcher = new PluginDispatcher<GatewayNotification>(paymentPluginTimeoutSec, executors);
     }
 
     public GatewayNotification processNotification(final String notification, final String pluginName, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
index 4babea1..102c3d2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
@@ -23,7 +23,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Nullable;
@@ -52,6 +51,7 @@ import org.killbill.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
 import org.killbill.billing.payment.provider.DefaultPaymentMethodInfoPlugin;
 import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
 import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
@@ -69,10 +69,7 @@ import com.google.common.base.Objects;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
-import com.google.inject.name.Named;
 
-import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
-import org.killbill.billing.util.UUIDs;
 import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPagination;
 import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationFromPlugins;
 
@@ -90,12 +87,12 @@ public class PaymentMethodProcessor extends ProcessorBase {
                                   final TagInternalApi tagUserApi,
                                   final GlobalLocker locker,
                                   final PaymentConfig paymentConfig,
-                                  @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                                  final PaymentExecutors executors,
                                   final InternalCallContextFactory internalCallContextFactory,
                                   final Clock clock) {
-        super(pluginRegistry, accountInternalApi, paymentDao, tagUserApi, locker, executor, internalCallContextFactory, invoiceApi, clock);
+        super(pluginRegistry, accountInternalApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock);
         final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
-        this.uuidPluginNotificationDispatcher = new PluginDispatcher<UUID>(paymentPluginTimeoutSec, executor);
+        this.uuidPluginNotificationDispatcher = new PluginDispatcher<UUID>(paymentPluginTimeoutSec, executors);
     }
 
     public UUID addPaymentMethod(final String paymentMethodExternalKey, final String paymentPluginServiceName, final Account account,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index 3326bd3..7b39cf1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
@@ -27,7 +27,6 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
-import java.util.concurrent.ExecutorService;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
@@ -75,9 +74,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
-import com.google.inject.name.Named;
 
-import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPagination;
 import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationFromPlugins;
 
@@ -98,11 +95,10 @@ public class PaymentProcessor extends ProcessorBase {
                             final PaymentDao paymentDao,
                             final InternalCallContextFactory internalCallContextFactory,
                             final GlobalLocker locker,
-                            @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
                             final PaymentAutomatonRunner paymentAutomatonRunner,
                             final IncompletePaymentTransactionTask incompletePaymentTransactionTask,
                             final Clock clock) {
-        super(pluginRegistry, accountUserApi, paymentDao, tagUserApi, locker, executor, internalCallContextFactory, invoiceApi, clock);
+        super(pluginRegistry, accountUserApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock);
         this.paymentAutomatonRunner = paymentAutomatonRunner;
         this.incompletePaymentTransactionTask = incompletePaymentTransactionTask;
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
index fd66fe9..96a6eda 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
@@ -21,7 +21,6 @@ import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.ExecutorService;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
@@ -55,9 +54,6 @@ import org.killbill.commons.locker.GlobalLocker;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
-import com.google.inject.name.Named;
-
-import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 
 public class PluginControlPaymentProcessor extends ProcessorBase {
 
@@ -73,12 +69,11 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
                                          final TagInternalApi tagUserApi,
                                          final PaymentDao paymentDao,
                                          final GlobalLocker locker,
-                                         @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
                                          final InternalCallContextFactory internalCallContextFactory,
                                          final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner,
                                          final PaymentControlStateMachineHelper paymentControlStateMachineHelper,
                                          final Clock clock) {
-        super(pluginRegistry, accountInternalApi, paymentDao, tagUserApi, locker, executor, internalCallContextFactory, invoiceApi, clock);
+        super(pluginRegistry, accountInternalApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock);
         this.paymentControlStateMachineHelper = paymentControlStateMachineHelper;
         this.pluginControlledPaymentAutomatonRunner = pluginControlledPaymentAutomatonRunner;
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
index f8c2d67..e657269 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
@@ -37,10 +37,8 @@ import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentMethodModelDao;
-import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
@@ -61,9 +59,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
-import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
-import com.google.common.collect.Iterables;
 
 public abstract class ProcessorBase {
 
@@ -72,7 +68,6 @@ public abstract class ProcessorBase {
     protected final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
     protected final AccountInternalApi accountInternalApi;
     protected final GlobalLocker locker;
-    protected final ExecutorService executor;
     protected final PaymentDao paymentDao;
     protected final InternalCallContextFactory internalCallContextFactory;
     protected final TagInternalApi tagInternalApi;
@@ -86,7 +81,6 @@ public abstract class ProcessorBase {
                          final PaymentDao paymentDao,
                          final TagInternalApi tagInternalApi,
                          final GlobalLocker locker,
-                         final ExecutorService executor,
                          final InternalCallContextFactory internalCallContextFactory,
                          final InvoiceInternalApi invoiceApi,
                          final Clock clock) {
@@ -94,7 +88,6 @@ public abstract class ProcessorBase {
         this.accountInternalApi = accountInternalApi;
         this.paymentDao = paymentDao;
         this.locker = locker;
-        this.executor = executor;
         this.tagInternalApi = tagInternalApi;
         this.internalCallContextFactory = internalCallContextFactory;
         this.invoiceApi = invoiceApi;
@@ -166,6 +159,7 @@ public abstract class ProcessorBase {
 
     // TODO Rename - there is no lock!
     public interface WithAccountLockCallback<PluginDispatcherReturnType, ExceptionType extends Exception> {
+
         public PluginDispatcherReturnType doOperation() throws ExceptionType;
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
index 41c37a2..9d037f9 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
@@ -73,7 +73,7 @@ public class ControlPluginRunner {
                                                              final Iterable<PluginProperty> pluginProperties,
                                                              final CallContext callContext) throws PaymentControlApiException {
         // Return as soon as the first plugin aborts, or the last result for the last plugin
-        PriorPaymentControlResult prevResult = null;
+        PriorPaymentControlResult prevResult = new DefaultPriorPaymentControlResult(false, amount, currency, paymentMethodId, pluginProperties);
 
         // Those values are adjusted prior each call with the result of what previous call to plugin returned
         Iterable<PluginProperty> inputPluginProperties = pluginProperties;
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 4353d9e..d0f56a0 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
@@ -19,7 +19,6 @@ package org.killbill.billing.payment.core.sm;
 
 import java.math.BigDecimal;
 import java.util.UUID;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -36,7 +35,6 @@ import org.killbill.automaton.State;
 import org.killbill.automaton.State.EnteringStateCallback;
 import org.killbill.automaton.State.LeavingStateCallback;
 import org.killbill.automaton.StateMachine;
-import org.killbill.automaton.StateMachineConfig;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalCallContext;
@@ -45,6 +43,7 @@ import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.sm.payments.AuthorizeCompleted;
 import org.killbill.billing.payment.core.sm.payments.AuthorizeInitiated;
 import org.killbill.billing.payment.core.sm.payments.AuthorizeOperation;
@@ -69,7 +68,6 @@ import org.killbill.billing.payment.core.sm.payments.VoidOperation;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.payment.glue.PaymentModule;
 import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.util.callcontext.CallContext;
@@ -82,9 +80,6 @@ import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
-import com.google.inject.name.Named;
-
-import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 
 public class PaymentAutomatonRunner {
 
@@ -97,13 +92,12 @@ public class PaymentAutomatonRunner {
     private final PersistentBus eventBus;
 
     @Inject
-    public PaymentAutomatonRunner(@javax.inject.Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig,
-                                  final PaymentConfig paymentConfig,
+    public PaymentAutomatonRunner(final PaymentConfig paymentConfig,
                                   final PaymentDao paymentDao,
                                   final GlobalLocker locker,
                                   final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
                                   final Clock clock,
-                                  @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                                  final PaymentExecutors executors,
                                   final PersistentBus eventBus,
                                   final PaymentStateMachineHelper paymentSMHelper) {
         this.paymentSMHelper = paymentSMHelper;
@@ -114,7 +108,7 @@ public class PaymentAutomatonRunner {
         this.eventBus = eventBus;
 
         final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
-        this.paymentPluginDispatcher = new PluginDispatcher<OperationResult>(paymentPluginTimeoutSec, executor);
+        this.paymentPluginDispatcher = new PluginDispatcher<OperationResult>(paymentPluginTimeoutSec, executors);
 
     }
 
@@ -125,20 +119,23 @@ public class PaymentAutomatonRunner {
                     final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         final DateTime utcNow = clock.getUTCNow();
 
-        final PaymentStateContext paymentStateContext = new PaymentStateContext(isApiPayment, paymentId, transactionId, attemptId, paymentExternalKey, paymentTransactionExternalKey, transactionType,
+        // Retrieve the payment id from the payment external key if needed
+        final UUID effectivePaymentId = paymentId != null ? paymentId : retrievePaymentId(paymentExternalKey, internalCallContext);
+
+        final PaymentStateContext paymentStateContext = new PaymentStateContext(isApiPayment, effectivePaymentId, transactionId, attemptId, paymentExternalKey, paymentTransactionExternalKey, transactionType,
                                                                                 account, paymentMethodId, amount, currency, shouldLockAccount, overridePluginOperationResult, properties, internalCallContext, callContext);
 
         final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, utcNow, paymentDao, pluginRegistry, internalCallContext, eventBus, paymentSMHelper);
 
         final UUID effectivePaymentMethodId;
         final String currentStateName;
-        if (paymentId != null) {
+        if (effectivePaymentId != null) {
             final PaymentModelDao paymentModelDao = daoHelper.getPayment();
             effectivePaymentMethodId = paymentModelDao.getPaymentMethodId();
             currentStateName = paymentModelDao.getLastSuccessStateName() != null ? paymentModelDao.getLastSuccessStateName() : paymentSMHelper.getInitStateNameForTransaction();
 
             // Check for illegal states (should never happen)
-            Preconditions.checkState(currentStateName != null, "State name cannot be null for payment " + paymentId);
+            Preconditions.checkState(currentStateName != null, "State name cannot be null for payment " + effectivePaymentId);
             Preconditions.checkState(paymentMethodId == null || effectivePaymentMethodId.equals(paymentMethodId), "Specified payment method id " + paymentMethodId + " doesn't match the one on the payment " + effectivePaymentMethodId);
         } else {
             // If the payment method is not specified, retrieve the default one on the account; it could still be null, in which case
@@ -242,4 +239,13 @@ public class PaymentAutomatonRunner {
 
         return invoiceProperty == null || invoiceProperty.getValue() == null ? null : invoiceProperty.getValue().toString();
     }
+
+    private UUID retrievePaymentId(@Nullable final String paymentExternalKey, final InternalCallContext internalCallContext) {
+        if (paymentExternalKey == null) {
+            return null;
+        }
+
+        final PaymentModelDao payment = paymentDao.getPaymentByExternalKey(paymentExternalKey, internalCallContext);
+        return payment == null ? null : payment.getId();
+    }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java
index 6fb17d7..edc6f70 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java
@@ -71,21 +71,12 @@ public abstract class PaymentLeavingStateCallback implements LeavingStateCallbac
                 existingPaymentTransactions = ImmutableList.of();
             }
 
+            // Validate the payment transactions belong to the right payment
+            validatePaymentId(existingPaymentTransactions);
+
             // Validate some constraints on the unicity of that paymentTransactionExternalKey
             validateUniqueTransactionExternalKey(existingPaymentTransactions);
 
-            // Handle UNKNOWN cases, where we skip the whole state machine and let the getPayment (through Janitor) logic refresh the state.
-            final PaymentTransactionModelDao unknownPaymentTransaction = getUnknownPaymentTransaction(existingPaymentTransactions);
-            if (unknownPaymentTransaction != null) {
-                // Reset the attemptId on the existing paymentTransaction row since it is not accurate
-                unknownPaymentTransaction.setAttemptId(paymentStateContext.getAttemptId());
-                // Set the current paymentTransaction in the context (needed for the state machine logic)
-                paymentStateContext.setPaymentTransactionModelDao(unknownPaymentTransaction);
-                // Set special flag to bypass the state machine altogether (plugin will not be called, state will not be updated, no event will be sent unless state is fixed)
-                paymentStateContext.setSkipOperationForUnknownTransaction(true);
-                return;
-            }
-
             // Handle PENDING cases, where we want to re-use the same transaction
             final PaymentTransactionModelDao pendingPaymentTransaction = getPendingPaymentTransaction(existingPaymentTransactions);
             if (pendingPaymentTransaction != null) {
@@ -94,7 +85,7 @@ public abstract class PaymentLeavingStateCallback implements LeavingStateCallbac
                 return;
             }
 
-            // At this point we are left with PAYMENT_FAILURE, PLUGIN_FAILURE or nothing, and we validated the uniquess of the paymentTransactionExternalKey so we will create a new row
+            // At this point we are left with PAYMENT_FAILURE, PLUGIN_FAILURE or nothing, and we validated the uniqueness of the paymentTransactionExternalKey so we will create a new row
             daoHelper.createNewPaymentTransaction();
 
         } catch (PaymentApiException e) {
@@ -141,4 +132,13 @@ public abstract class PaymentLeavingStateCallback implements LeavingStateCallbac
             throw new PaymentApiException(ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS, paymentStateContext.getPaymentTransactionExternalKey());
         }
     }
+
+    // At this point, the payment id should have been populated for follow-up transactions (see PaymentAutomationRunner#run)
+    protected void validatePaymentId(final List<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
+        for (final PaymentTransactionModelDao paymentTransactionModelDao : existingPaymentTransactions) {
+            if (!paymentTransactionModelDao.getPaymentId().equals(paymentStateContext.getPaymentId())) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, paymentTransactionModelDao.getId(), "does not belong to payment " + paymentStateContext.getPaymentId());
+            }
+        }
+    }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
index f3ca0ca..4f58817 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
@@ -20,7 +20,6 @@ package org.killbill.billing.payment.core.sm;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.ExecutorService;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
@@ -32,16 +31,17 @@ import org.killbill.automaton.OperationException;
 import org.killbill.automaton.State;
 import org.killbill.automaton.State.EnteringStateCallback;
 import org.killbill.automaton.State.LeavingStateCallback;
-import org.killbill.automaton.StateMachineConfig;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.sm.control.AuthorizeControlOperation;
 import org.killbill.billing.payment.core.sm.control.CaptureControlOperation;
@@ -57,10 +57,8 @@ import org.killbill.billing.payment.core.sm.control.PurchaseControlOperation;
 import org.killbill.billing.payment.core.sm.control.RefundControlOperation;
 import org.killbill.billing.payment.core.sm.control.VoidControlOperation;
 import org.killbill.billing.payment.dao.PaymentDao;
-import org.killbill.billing.payment.glue.PaymentModule;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.bus.api.PersistentBus;
@@ -70,7 +68,6 @@ import org.killbill.commons.locker.GlobalLocker;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
 
-import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED;
 
 public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner {
@@ -82,11 +79,11 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner 
     private final ControlPluginRunner controlPluginRunner;
 
     @Inject
-    public PluginControlPaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
+    public PluginControlPaymentAutomatonRunner(final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
                                                final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry, final Clock clock, final PaymentProcessor paymentProcessor, @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler,
-                                               final PaymentConfig paymentConfig, @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper,
+                                               final PaymentConfig paymentConfig, final PaymentExecutors executors, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper,
                                                final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) {
-        super(stateMachineConfig, paymentConfig, paymentDao, locker, pluginRegistry, clock, executor, eventBus, paymentSMHelper);
+        super(paymentConfig, paymentDao, locker, pluginRegistry, clock, executors, eventBus, paymentSMHelper);
         this.paymentProcessor = paymentProcessor;
         this.paymentControlPluginRegistry = paymentControlPluginRegistry;
         this.retryServiceScheduler = retryServiceScheduler;
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
new file mode 100644
index 0000000..e78d8ad
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.dispatcher;
+
+import java.util.concurrent.Callable;
+
+import org.killbill.commons.request.Request;
+import org.killbill.commons.request.RequestData;
+
+public class CallableWithRequestData<T> implements Callable<T> {
+
+    private final RequestData requestData;
+    private final Callable<T> delegate;
+
+    public CallableWithRequestData(final RequestData requestData, final Callable<T> delegate) {
+        this.requestData = requestData;
+        this.delegate = delegate;
+    }
+
+    @Override
+    public T call() throws Exception {
+        try {
+            Request.setPerThreadRequestData(requestData);
+            return delegate.call();
+        } finally {
+            Request.resetPerThreadRequestData();
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
index a60eff6..bda52e0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
@@ -20,24 +20,27 @@ package org.killbill.billing.payment.dispatcher;
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.commons.profiling.Profiling;
 import org.killbill.commons.profiling.ProfilingData;
+import org.killbill.commons.request.Request;
 
 public class PluginDispatcher<ReturnType> {
 
     private final TimeUnit DEEFAULT_PLUGIN_TIMEOUT_UNIT = TimeUnit.SECONDS;
 
     private final long timeoutSeconds;
-    private final ExecutorService executor;
+    private final PaymentExecutors paymentExecutors;
 
-    public PluginDispatcher(final long timeoutSeconds, final ExecutorService executor) {
+    public PluginDispatcher(final long timeoutSeconds, final PaymentExecutors paymentExecutors) {
         this.timeoutSeconds = timeoutSeconds;
-        this.executor = executor;
+        this.paymentExecutors = paymentExecutors;
     }
 
     // TODO Once we switch fully to automata, should this throw PaymentPluginApiException instead?
@@ -48,7 +51,12 @@ public class PluginDispatcher<ReturnType> {
     public ReturnType dispatchWithTimeout(final Callable<PluginDispatcherReturnType<ReturnType>> task, final long timeout, final TimeUnit unit)
             throws TimeoutException, ExecutionException, InterruptedException {
 
-        final Future<PluginDispatcherReturnType<ReturnType>> future = executor.submit(task);
+        final ExecutorService pluginExecutor = paymentExecutors.getPluginExecutorService();
+
+        // Wrap existing callable to keep the original requestId
+        final Callable<PluginDispatcherReturnType<ReturnType>> callableWithRequestData = new CallableWithRequestData(Request.getPerThreadRequestData(), task);
+
+        final Future<PluginDispatcherReturnType<ReturnType>> future = pluginExecutor.submit(callableWithRequestData);
         final PluginDispatcherReturnType<ReturnType> pluginDispatcherResult = future.get(timeout, unit);
 
         if (pluginDispatcherResult instanceof WithProfilingPluginDispatcherReturnType) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
index d003f74..93603b1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
@@ -21,6 +21,7 @@ package org.killbill.billing.payment.glue;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentService;
 import org.killbill.billing.payment.bus.PaymentBusEventHandler;
+import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.invoice.PaymentTagHandler;
 import org.killbill.billing.payment.core.janitor.Janitor;
 import org.killbill.billing.payment.retry.DefaultRetryService;
@@ -46,6 +47,7 @@ public class DefaultPaymentService implements PaymentService {
     private final PaymentApi api;
     private final DefaultRetryService retryService;
     private final Janitor janitor;
+    private final PaymentExecutors paymentExecutors;
 
     @Inject
     public DefaultPaymentService(final PaymentBusEventHandler paymentBusEventHandler,
@@ -53,13 +55,15 @@ public class DefaultPaymentService implements PaymentService {
                                  final PaymentApi api,
                                  final DefaultRetryService retryService,
                                  final PersistentBus eventBus,
-                                 final Janitor janitor) {
+                                 final Janitor janitor,
+                                 final PaymentExecutors paymentExecutors) {
         this.paymentBusEventHandler = paymentBusEventHandler;
         this.tagHandler = tagHandler;
         this.eventBus = eventBus;
         this.api = api;
         this.retryService = retryService;
         this.janitor = janitor;
+        this.paymentExecutors = paymentExecutors;
     }
 
     @Override
@@ -75,6 +79,7 @@ public class DefaultPaymentService implements PaymentService {
         } catch (final PersistentBus.EventBusException e) {
             log.error("Unable to register with the EventBus!", e);
         }
+        paymentExecutors.initialize();
         retryService.initialize();
         janitor.initialize();
     }
@@ -95,6 +100,12 @@ public class DefaultPaymentService implements PaymentService {
         }
         retryService.stop();
         janitor.stop();
+        try {
+            paymentExecutors.stop();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("PaymentService got interrupted", e);
+        }
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
index 0c0b0a7..c9a0c74 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
@@ -18,16 +18,11 @@
 
 package org.killbill.billing.payment.glue;
 
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-
 import javax.inject.Provider;
 
 import org.killbill.automaton.DefaultStateMachineConfig;
 import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.AdminPaymentApi;
 import org.killbill.billing.payment.api.DefaultAdminPaymentApi;
@@ -37,6 +32,7 @@ import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentGatewayApi;
 import org.killbill.billing.payment.api.PaymentService;
 import org.killbill.billing.payment.bus.PaymentBusEventHandler;
+import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.PaymentGatewayProcessor;
 import org.killbill.billing.payment.core.PaymentMethodProcessor;
 import org.killbill.billing.payment.core.PaymentProcessor;
@@ -58,10 +54,8 @@ import org.killbill.billing.payment.retry.DefaultRetryService;
 import org.killbill.billing.payment.retry.DefaultRetryService.DefaultRetryServiceScheduler;
 import org.killbill.billing.payment.retry.RetryService;
 import org.killbill.billing.platform.api.KillbillConfigSource;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.billing.util.glue.KillBillModule;
-import org.killbill.commons.concurrent.WithProfilingThreadPoolExecutor;
 import org.killbill.xmlloader.XMLLoader;
 import org.skife.config.ConfigurationObjectFactory;
 
@@ -73,10 +67,7 @@ import com.google.inject.name.Names;
 
 public class PaymentModule extends KillBillModule {
 
-    private static final String PLUGIN_THREAD_PREFIX = "Plugin-th-";
 
-    public static final String JANITOR_EXECUTOR_NAMED = "JanitorExecutor";
-    public static final String PLUGIN_EXECUTOR_NAMED = "PluginExecutor";
     public static final String RETRYABLE_NAMED = "Retryable";
 
     public static final String STATE_MACHINE_RETRY = "RetryStateMachine";
@@ -101,11 +92,6 @@ public class PaymentModule extends KillBillModule {
     }
 
     protected void installJanitor() {
-        final ScheduledExecutorService janitorExecutor = org.killbill.commons.concurrent.Executors.newSingleThreadScheduledExecutor("PaymentJanitor");
-        bind(ScheduledExecutorService.class).annotatedWith(Names.named(JANITOR_EXECUTOR_NAMED)).toInstance(janitorExecutor);
-
-        bind(IncompletePaymentTransactionTask.class).asEagerSingleton();
-        bind(IncompletePaymentAttemptTask.class).asEagerSingleton();
         bind(Janitor.class).asEagerSingleton();
     }
 
@@ -135,20 +121,6 @@ public class PaymentModule extends KillBillModule {
     }
 
     protected void installProcessors(final PaymentConfig paymentConfig) {
-
-        final ExecutorService pluginExecutorService = new WithProfilingThreadPoolExecutor(paymentConfig.getPaymentPluginThreadNb(), paymentConfig.getPaymentPluginThreadNb(),
-                                                                                          0L, TimeUnit.MILLISECONDS,
-                                                                                          new LinkedBlockingQueue<Runnable>(),
-                                                                                          new ThreadFactory() {
-
-                                                                                              @Override
-                                                                                              public Thread newThread(final Runnable r) {
-                                                                                                  final Thread th = new Thread(r);
-                                                                                                  th.setName(PLUGIN_THREAD_PREFIX + th.getId());
-                                                                                                  return th;
-                                                                                              }
-                                                                                          });
-        bind(ExecutorService.class).annotatedWith(Names.named(PLUGIN_EXECUTOR_NAMED)).toInstance(pluginExecutorService);
         bind(PaymentProcessor.class).asEagerSingleton();
         bind(PluginControlPaymentProcessor.class).asEagerSingleton();
         bind(PaymentGatewayProcessor.class).asEagerSingleton();
@@ -170,6 +142,7 @@ public class PaymentModule extends KillBillModule {
         bind(PaymentBusEventHandler.class).asEagerSingleton();
         bind(PaymentTagHandler.class).asEagerSingleton();
         bind(PaymentService.class).to(DefaultPaymentService.class).asEagerSingleton();
+        bind(PaymentExecutors.class).asEagerSingleton();
         installPaymentProviderPlugins(paymentConfig);
         installPaymentDao();
         installProcessors(paymentConfig);
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 19c04f6..a5b2a5a 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
@@ -582,34 +582,74 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         }
     }
 
-    @Test(groups = "slow")
-    public void testApiRetryWithUnknownPaymentTransaction() throws Exception {
+    @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/371")
+    public void testApiWithDuplicatePendingPaymentTransaction() throws Exception {
         final BigDecimal requestedAmount = BigDecimal.TEN;
 
-        final String paymentExternalKey = UUID.randomUUID().toString();
-        final String paymentTransactionExternalKey = UUID.randomUUID().toString();
-
-        final Payment badPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(),
-                                                                  paymentExternalKey, paymentTransactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
-
-        final String paymentStateName = paymentSMHelper.getErroredStateForTransaction(TransactionType.AUTHORIZE).toString();
-        paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), badPayment.getId(), TransactionType.AUTHORIZE, paymentStateName, paymentStateName,
-                                                           badPayment.getTransactions().get(0).getId(), TransactionStatus.UNKNOWN, requestedAmount, account.getCurrency(),
-                                                           "eroor 64", "bad something happened", internalCallContext);
-
-        final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(),
-                                                               paymentExternalKey, paymentTransactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
-
-        Assert.assertEquals(payment.getId(), badPayment.getId());
-        Assert.assertEquals(payment.getExternalKey(), paymentExternalKey);
-        Assert.assertEquals(payment.getExternalKey(), paymentExternalKey);
+        for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
+            final String payment1ExternalKey = UUID.randomUUID().toString();
+            final String payment1TransactionExternalKey = UUID.randomUUID().toString();
+            final String payment2ExternalKey = UUID.randomUUID().toString();
+            final String payment2TransactionExternalKey = UUID.randomUUID().toString();
+            final String payment3TransactionExternalKey = UUID.randomUUID().toString();
+
+            final Payment pendingPayment1 = createPayment(transactionType, null, payment1ExternalKey, payment1TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
+            Assert.assertNotNull(pendingPayment1);
+            Assert.assertEquals(pendingPayment1.getExternalKey(), payment1ExternalKey);
+            Assert.assertEquals(pendingPayment1.getTransactions().size(), 1);
+            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
+            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
+            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getCurrency(), account.getCurrency());
+            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getExternalKey(), payment1TransactionExternalKey);
+            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
+
+            // Attempt to create a second transaction for the same payment, but with a different transaction external key
+            final Payment pendingPayment2 = createPayment(transactionType, null, payment1ExternalKey, payment2TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
+            Assert.assertNotNull(pendingPayment2);
+            Assert.assertEquals(pendingPayment2.getId(), pendingPayment1.getId());
+            Assert.assertEquals(pendingPayment2.getExternalKey(), payment1ExternalKey);
+            Assert.assertEquals(pendingPayment2.getTransactions().size(), 2);
+            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
+            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
+            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getCurrency(), account.getCurrency());
+            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getExternalKey(), payment1TransactionExternalKey);
+            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
+            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getAmount().compareTo(requestedAmount), 0);
+            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getProcessedAmount().compareTo(requestedAmount), 0);
+            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getCurrency(), account.getCurrency());
+            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getExternalKey(), payment2TransactionExternalKey);
+            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.PENDING);
+
+            try {
+                // Verify we cannot use the same transaction external key on a different payment if the payment id isn't specified
+                createPayment(transactionType, null, payment2ExternalKey, payment1TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
+                Assert.fail();
+            } catch (final PaymentApiException e) {
+                Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
+            }
 
-        Assert.assertEquals(payment.getTransactions().size(), 1);
-        Assert.assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
-        Assert.assertEquals(payment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey);
+            try {
+                // Verify we cannot use the same transaction external key on a different payment if the payment id isn't specified
+                createPayment(transactionType, null, payment2ExternalKey, payment2TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
+                Assert.fail();
+            } catch (final PaymentApiException e) {
+                Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
+            }
+
+            // Attempt to create a second transaction for a different payment
+            final Payment pendingPayment3 = createPayment(transactionType, null, payment2ExternalKey, payment3TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
+            Assert.assertNotNull(pendingPayment3);
+            Assert.assertNotEquals(pendingPayment3.getId(), pendingPayment1.getId());
+            Assert.assertEquals(pendingPayment3.getExternalKey(), payment2ExternalKey);
+            Assert.assertEquals(pendingPayment3.getTransactions().size(), 1);
+            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
+            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
+            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getCurrency(), account.getCurrency());
+            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getExternalKey(), payment3TransactionExternalKey);
+            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
+        }
     }
 
-    // Example of a 3D secure payment for instance
     @Test(groups = "slow")
     public void testApiWithPendingPaymentTransaction() throws Exception {
         for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java
new file mode 100644
index 0000000..e48f3b4
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm.control;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.control.plugin.api.PaymentApiType;
+import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
+import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.provider.DefaultPaymentControlProviderPluginRegistry;
+import org.killbill.billing.util.UUIDs;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestControlPluginRunner extends PaymentTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testPriorCallWithUnknownPlugin() throws Exception {
+        final Account account = Mockito.mock(Account.class);
+        final UUID paymentMethodId = UUIDs.randomUUID();
+        final UUID paymentId = UUIDs.randomUUID();
+        final String paymentExternalKey = UUIDs.randomUUID().toString();
+        final String paymentTransactionExternalKey = UUIDs.randomUUID().toString();
+        final BigDecimal amount = BigDecimal.ONE;
+        final Currency currency = Currency.USD;
+        final ImmutableList<String> paymentControlPluginNames = ImmutableList.<String>of("not-registered");
+        final ImmutableList<PluginProperty> pluginProperties = ImmutableList.<PluginProperty>of();
+
+        final ControlPluginRunner controlPluginRunner = new ControlPluginRunner(new DefaultPaymentControlProviderPluginRegistry());
+        final PriorPaymentControlResult paymentControlResult = controlPluginRunner.executePluginPriorCalls(account,
+                                                                                                           paymentMethodId,
+                                                                                                           null,
+                                                                                                           paymentId,
+                                                                                                           paymentExternalKey,
+                                                                                                           paymentTransactionExternalKey,
+                                                                                                           PaymentApiType.PAYMENT_TRANSACTION,
+                                                                                                           TransactionType.AUTHORIZE,
+                                                                                                           null,
+                                                                                                           amount,
+                                                                                                           currency,
+                                                                                                           true,
+                                                                                                           paymentControlPluginNames,
+                                                                                                           pluginProperties,
+                                                                                                           callContext);
+        Assert.assertEquals(paymentControlResult.getAdjustedAmount(), amount);
+        Assert.assertEquals(paymentControlResult.getAdjustedCurrency(), currency);
+        Assert.assertEquals(paymentControlResult.getAdjustedPaymentMethodId(), paymentMethodId);
+        Assert.assertEquals(paymentControlResult.getAdjustedPluginProperties(), pluginProperties);
+        Assert.assertFalse(paymentControlResult.isAborted());
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
index 14f6758..07c8031 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
@@ -20,7 +20,6 @@ package org.killbill.billing.payment.core.sm;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.ExecutorService;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
@@ -28,23 +27,22 @@ import javax.inject.Named;
 
 import org.killbill.automaton.Operation.OperationCallback;
 import org.killbill.automaton.OperationResult;
-import org.killbill.automaton.StateMachineConfig;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
 import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.payment.glue.PaymentModule;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.config.PaymentConfig;
@@ -52,7 +50,6 @@ import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 
-import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED;
 
 public class MockRetryablePaymentAutomatonRunner extends PluginControlPaymentAutomatonRunner {
@@ -61,10 +58,10 @@ public class MockRetryablePaymentAutomatonRunner extends PluginControlPaymentAut
     private PaymentStateControlContext context;
 
     @Inject
-    public MockRetryablePaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, @Named(PaymentModule.STATE_MACHINE_RETRY) final StateMachineConfig retryStateMachine, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry, final Clock clock, final TagInternalApi tagApi, final PaymentProcessor paymentProcessor,
-                                               @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, final PaymentConfig paymentConfig, @com.google.inject.name.Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+    public MockRetryablePaymentAutomatonRunner(final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry, final Clock clock, final TagInternalApi tagApi, final PaymentProcessor paymentProcessor,
+                                               @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, final PaymentConfig paymentConfig, final PaymentExecutors executors,
                                                final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper retrySMHelper, final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) {
-        super(stateMachineConfig, paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executor, paymentSMHelper, retrySMHelper, controlPluginRunner, eventBus);
+        super(paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executors, paymentSMHelper, retrySMHelper, controlPluginRunner, eventBus);
     }
 
     @Override
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
index 586e759..7e4e1b6 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
@@ -19,7 +19,6 @@ package org.killbill.billing.payment.core.sm;
 
 import java.math.BigDecimal;
 import java.util.UUID;
-import java.util.concurrent.Executors;
 
 import javax.annotation.Nullable;
 
@@ -104,7 +103,7 @@ public class TestPaymentOperation extends PaymentTestSuiteNoDB {
 
     private void setUp(final PaymentPluginStatus paymentPluginStatus) throws Exception {
         final GlobalLocker locker = new MemoryGlobalLocker();
-        final PluginDispatcher<OperationResult> paymentPluginDispatcher = new PluginDispatcher<OperationResult>(1, Executors.newCachedThreadPool());
+        final PluginDispatcher<OperationResult> paymentPluginDispatcher = new PluginDispatcher<OperationResult>(1, paymentExecutors);
         paymentStateContext = new PaymentStateContext(true,
                                                       UUID.randomUUID(),
                                                       null, null,
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 f004f02..9c73489 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
@@ -184,7 +184,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
     }
 
     private PaymentOperation getPluginOperation(final boolean shouldLockAccount, final int timeoutSeconds) throws PaymentApiException {
-        final PluginDispatcher<OperationResult> paymentPluginDispatcher = new PluginDispatcher<OperationResult>(timeoutSeconds, Executors.newCachedThreadPool());
+        final PluginDispatcher<OperationResult> paymentPluginDispatcher = new PluginDispatcher<OperationResult>(timeoutSeconds, paymentExecutors);
 
         final PaymentStateContext paymentStateContext = new PaymentStateContext(true, UUID.randomUUID(),
                                                                                 null, null,
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
index 433806e..b796607 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
@@ -20,7 +20,6 @@ package org.killbill.billing.payment.core.sm;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.ExecutorService;
 
 import javax.inject.Named;
 
@@ -32,6 +31,7 @@ import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
+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.PaymentTestSuiteNoDB;
@@ -39,6 +39,7 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.PluginControlPaymentProcessor;
 import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
@@ -53,7 +54,6 @@ import org.killbill.billing.payment.glue.PaymentModule;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.provider.MockPaymentControlProviderPlugin;
 import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.dao.NonEntityDao;
@@ -72,7 +72,6 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
-import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
@@ -103,8 +102,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
     @Named(RETRYABLE_NAMED)
     private RetryServiceScheduler retryServiceScheduler;
     @Inject
-    @Named(PLUGIN_EXECUTOR_NAMED)
-    private ExecutorService executor;
+    private PaymentExecutors executors;
     @Inject
     private PaymentStateMachineHelper paymentSMHelper;
     @Inject
@@ -158,8 +156,6 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
         this.utcNow = clock.getUTCNow();
 
         runner = new MockRetryablePaymentAutomatonRunner(
-                stateMachineConfig,
-                retryStateMachineConfig,
                 paymentDao,
                 locker,
                 pluginRegistry,
@@ -169,7 +165,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                 paymentProcessor,
                 retryServiceScheduler,
                 paymentConfig,
-                executor,
+                paymentExecutors,
                 paymentSMHelper,
                 retrySMHelper,
                 controlPluginRunner,
@@ -177,18 +173,18 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
 
         paymentStateContext =
                 new PaymentStateControlContext(ImmutableList.<String>of(MockPaymentControlProviderPlugin.PLUGIN_NAME),
-                                                 true,
-                                                 null,
-                                                 paymentExternalKey,
-                                                 paymentTransactionExternalKey,
-                                                 TransactionType.AUTHORIZE,
-                                                 account,
-                                                 paymentMethodId,
-                                                 amount,
-                                                 currency,
-                                                 emptyProperties,
-                                                 internalCallContext,
-                                                 callContext);
+                                               true,
+                                               null,
+                                               paymentExternalKey,
+                                               paymentTransactionExternalKey,
+                                               TransactionType.AUTHORIZE,
+                                               account,
+                                               paymentMethodId,
+                                               amount,
+                                               currency,
+                                               emptyProperties,
+                                               internalCallContext,
+                                               callContext);
 
         mockRetryAuthorizeOperationCallback =
                 new MockRetryAuthorizeOperationCallback(locker,
@@ -205,7 +201,6 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                                                       tagApi,
                                                       paymentDao,
                                                       locker,
-                                                      executor,
                                                       internalCallContextFactory,
                                                       runner,
                                                       retrySMHelper,
diff --git a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
index d226d73..835df39 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
@@ -26,12 +26,27 @@ import org.killbill.billing.ErrorCode;
 import org.killbill.billing.payment.PaymentTestSuiteNoDB;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.commons.profiling.Profiling;
+import org.killbill.commons.request.Request;
+import org.killbill.commons.request.RequestData;
 import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
 
-    private final PluginDispatcher<Void> voidPluginDispatcher = new PluginDispatcher<Void>(10, Executors.newSingleThreadExecutor());
+    private PluginDispatcher<Void> voidPluginDispatcher;
+
+    private PluginDispatcher<String> stringPluginDispatcher;
+
+    @BeforeMethod(groups = "fast")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        eventBus.start();
+        voidPluginDispatcher = new PluginDispatcher<Void>(10, paymentExecutors);
+        stringPluginDispatcher = new PluginDispatcher<String>(1, paymentExecutors);
+    }
+
 
     @Test(groups = "fast")
     public void testDispatchWithTimeout() throws TimeoutException, PaymentApiException {
@@ -106,4 +121,25 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
         }
         Assert.assertTrue(gotIt);
     }
+
+
+    @Test(groups = "fast")
+    public void testDispatchWithRequestData() throws TimeoutException, PaymentApiException, ExecutionException, InterruptedException {
+
+        final String requestId = "vive la vie et les coquillettes";
+
+        final Callable<PluginDispatcherReturnType<String>> delegate = new Callable<PluginDispatcherReturnType<String>>() {
+            @Override
+            public PluginDispatcherReturnType<String> call() throws Exception {
+                return PluginDispatcher.<String>createPluginDispatcherReturnType(Request.getPerThreadRequestData().getRequestId());
+            }
+        };
+
+        final CallableWithRequestData<PluginDispatcherReturnType<String>> callable = new CallableWithRequestData<PluginDispatcherReturnType<String>>(new RequestData(requestId),
+                                                                                                                                                        delegate);
+
+        final String actualRequestId = stringPluginDispatcher.dispatchWithTimeout(callable, 100, TimeUnit.MILLISECONDS);
+        Assert.assertEquals(actualRequestId, requestId);
+    }
+
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
index 9c19560..9af2ee5 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
@@ -24,6 +24,7 @@ import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentGatewayApi;
+import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.PaymentMethodProcessor;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.PluginControlPaymentProcessor;
@@ -82,6 +83,8 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
     protected DefaultRetryService retryService;
     @Inject
     protected CacheControllerDispatcher cacheControllerDispatcher;
+    @Inject
+    protected PaymentExecutors paymentExecutors;
 
     @Override
     protected KillbillConfigSource getConfigSource() {
@@ -100,11 +103,13 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
         eventBus.start();
+        paymentExecutors.initialize();
         Profiling.resetPerThreadProfilingData();
     }
 
     @AfterMethod(groups = "fast")
     public void afterMethod() throws Exception {
+        paymentExecutors.stop();
         eventBus.stop();
     }
 }
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 1ab3f64..14072c3 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
@@ -24,6 +24,7 @@ import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentGatewayApi;
+import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.PaymentMethodProcessor;
 import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
@@ -70,6 +71,8 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     protected PaymentDao paymentDao;
     @Inject
     protected TestPaymentHelper testHelper;
+    @Inject
+    protected PaymentExecutors paymentExecutors;
 
     @Override
     protected KillbillConfigSource getConfigSource() {
@@ -87,6 +90,7 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
+        paymentExecutors.initialize();
         eventBus.start();
         Profiling.resetPerThreadProfilingData();
         clock.resetDeltaFromReality();
@@ -96,5 +100,6 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
         eventBus.stop();
+        paymentExecutors.stop();
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index 7ed69f5..1753af8 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -114,18 +114,17 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
     protected void beforeClass() throws Exception {
         super.beforeClass();
         mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME);
-        janitor.initialize();
-        janitor.start();
     }
 
     @AfterClass(groups = "slow")
     protected void afterClass() throws Exception {
-        janitor.stop();
     }
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
+        janitor.initialize();
+        janitor.start();
         eventBus.register(handler);
         testListener.reset();
         eventBus.register(testListener);
@@ -135,6 +134,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
 
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        janitor.stop();
         eventBus.unregister(handler);
         eventBus.unregister(testListener);
         super.afterMethod();

pom.xml 4(+2 -2)

diff --git a/pom.xml b/pom.xml
index 6b6b458..2ca99c5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,10 +21,10 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.37</version>
+        <version>0.38</version>
     </parent>
     <artifactId>killbill</artifactId>
-    <version>0.15.3-SNAPSHOT</version>
+    <version>0.15.4-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>
diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index 47ce217..78f963d 100644
--- a/profiles/killbill/pom.xml
+++ b/profiles/killbill/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-profiles</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-profiles-killbill</artifactId>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java
new file mode 100644
index 0000000..a32a507
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.filters;
+
+import java.util.List;
+
+import org.killbill.billing.util.UUIDs;
+import org.killbill.commons.request.Request;
+import org.killbill.commons.request.RequestData;
+
+import com.google.inject.Singleton;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponse;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+
+@Singleton
+public class RequestDataFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+
+    private static final String REQUEST_ID_HEADER_REQ = "X-Killbill-Request-Id-Req";
+
+    @Override
+    public ContainerRequest filter(final ContainerRequest request) {
+        final List<String> requestIdHeaderRequests = request.getRequestHeader(REQUEST_ID_HEADER_REQ);
+        final String requestId = (requestIdHeaderRequests == null || requestIdHeaderRequests.isEmpty()) ? UUIDs.randomUUID().toString() : requestIdHeaderRequests.get(0);
+        Request.setPerThreadRequestData(new RequestData(requestId));
+        return request;
+    }
+
+    @Override
+    public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response) {
+        Request.resetPerThreadRequestData();
+        return response;
+    }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
index 6e506da..0b16384 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
@@ -28,6 +28,7 @@ import org.killbill.billing.jaxrs.util.KillbillEventHandler;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.platform.config.DefaultKillbillConfigSource;
 import org.killbill.billing.server.filters.ProfilingContainerResponseFilter;
+import org.killbill.billing.server.filters.RequestDataFilter;
 import org.killbill.billing.server.filters.ResponseCorsFilter;
 import org.killbill.billing.server.modules.KillbillServerModule;
 import org.killbill.billing.server.security.TenantFilter;
@@ -73,6 +74,7 @@ public class KillbillGuiceListener extends KillbillPlatformGuiceListener {
         // The logging filter is still incompatible with the GZIP filter
         //builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName());
         builder.addJerseyFilter(ProfilingContainerResponseFilter.class.getName());
+        builder.addJerseyFilter(RequestDataFilter.class.getName());
 
         // Broader, to support the "Try it out!" feature
         //builder.addFilter("/" + SWAGGER_PATH + "*", ResponseCorsFilter.class);
diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml
index 4544cc3..231f343 100644
--- a/profiles/killpay/pom.xml
+++ b/profiles/killpay/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>killbill-profiles</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-profiles-killpay</artifactId>

profiles/pom.xml 2(+1 -1)

diff --git a/profiles/pom.xml b/profiles/pom.xml
index 6f18124..88e8cd0 100644
--- a/profiles/pom.xml
+++ b/profiles/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-profiles</artifactId>
diff --git a/subscription/pom.xml b/subscription/pom.xml
index efcb4d4..cd54166 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-subscription</artifactId>

tenant/pom.xml 2(+1 -1)

diff --git a/tenant/pom.xml b/tenant/pom.xml
index aef172b..a8d2a80 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-tenant</artifactId>
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java
index 776e6b1..6b3a2bb 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java
@@ -42,6 +42,7 @@ import org.killbill.billing.tenant.glue.DefaultTenantModule;
 import org.killbill.billing.util.config.TenantConfig;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.commons.concurrent.Executors;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -67,22 +68,21 @@ public class TenantCacheInvalidation {
 
     private final Map<TenantKey, CacheInvalidationCallback> cache;
     private final TenantBroadcastDao broadcastDao;
-    private final ScheduledExecutorService tenantExecutor;
     private final TenantConfig tenantConfig;
     private final PersistentBus eventBus;
     private final TenantDao tenantDao;
     private AtomicLong latestRecordIdProcessed;
     private volatile boolean isStopped;
 
+    private ScheduledExecutorService tenantExecutor;
+
     @Inject
     public TenantCacheInvalidation(@Named(DefaultTenantModule.NO_CACHING_TENANT) final TenantBroadcastDao broadcastDao,
-                                   @Named(DefaultTenantModule.TENANT_EXECUTOR_NAMED) final ScheduledExecutorService tenantExecutor,
                                    @Named(DefaultTenantModule.NO_CACHING_TENANT) final TenantDao tenantDao,
                                    final PersistentBus eventBus,
                                    final TenantConfig tenantConfig) {
         this.cache = new HashMap<TenantKey, CacheInvalidationCallback>();
         this.broadcastDao = broadcastDao;
-        this.tenantExecutor = tenantExecutor;
         this.tenantConfig = tenantConfig;
         this.tenantDao = tenantDao;
         this.eventBus = eventBus;
@@ -92,14 +92,11 @@ public class TenantCacheInvalidation {
     public void initialize() {
         final TenantBroadcastModelDao entry = broadcastDao.getLatestEntry();
         this.latestRecordIdProcessed = entry != null ? new AtomicLong(entry.getRecordId()) : new AtomicLong(0L);
-
+        this.tenantExecutor = Executors.newSingleThreadScheduledExecutor("TenantExecutor");
+        this.isStopped = false;
     }
 
     public void start() {
-        if (isStopped) {
-            logger.warn("TenantExecutor is in a stopped state, abort start sequence");
-            return;
-        }
         final TimeUnit pendingRateUnit = tenantConfig.getTenantBroadcastServiceRunningRate().getUnit();
         final long pendingPeriod = tenantConfig.getTenantBroadcastServiceRunningRate().getPeriod();
         tenantExecutor.scheduleAtFixedRate(new TenantCacheInvalidationRunnable(this, broadcastDao, tenantDao), pendingPeriod, pendingPeriod, pendingRateUnit);
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java b/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java
index 6770c67..af1c9f7 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java
@@ -18,8 +18,6 @@
 
 package org.killbill.billing.tenant.glue;
 
-import java.util.concurrent.ScheduledExecutorService;
-
 import org.killbill.billing.glue.TenantModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.tenant.api.DefaultTenantInternalApi;
@@ -46,8 +44,6 @@ public class DefaultTenantModule extends KillBillModule implements TenantModule 
 
     public static final String NO_CACHING_TENANT = "NoCachingTenant";
 
-    public static final String TENANT_EXECUTOR_NAMED = "TenantExecutor";
-
     public DefaultTenantModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
@@ -79,11 +75,6 @@ public class DefaultTenantModule extends KillBillModule implements TenantModule 
         bind(TenantCacheInvalidation.class).asEagerSingleton();
     }
 
-    protected void installExecutor() {
-        final ScheduledExecutorService tenantExecutor = org.killbill.commons.concurrent.Executors.newSingleThreadScheduledExecutor("TenantExecutor");
-        bind(ScheduledExecutorService.class).annotatedWith(Names.named(TENANT_EXECUTOR_NAMED)).toInstance(tenantExecutor);
-    }
-
     @Override
     protected void configure() {
         installConfig();
@@ -91,6 +82,5 @@ public class DefaultTenantModule extends KillBillModule implements TenantModule 
         installTenantService();
         installTenantUserApi();
         installTenantCacheInvalidation();
-        installExecutor();
     }
 }

usage/pom.xml 2(+1 -1)

diff --git a/usage/pom.xml b/usage/pom.xml
index 4cd57d8..16205bd 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-usage</artifactId>

util/pom.xml 2(+1 -1)

diff --git a/util/pom.xml b/util/pom.xml
index 0a88a19..bfc65ce 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15.3-SNAPSHOT</version>
+        <version>0.15.4-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>