killbill-memoizeit

Changes

Details

diff --git a/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java b/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java
index bab9f06..40eee4f 100644
--- a/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java
+++ b/api/src/main/java/org/killbill/billing/payment/plugin/api/NoOpPaymentPluginApi.java
@@ -16,13 +16,18 @@
 
 package org.killbill.billing.payment.plugin.api;
 
+import java.util.List;
+import java.util.UUID;
+
 public interface NoOpPaymentPluginApi extends PaymentPluginApi {
 
-    public void clear();
+    void clear();
+
+    void makeNextPaymentFailWithError();
 
-    public void makeNextPaymentFailWithError();
+    void makeNextPaymentFailWithException();
 
-    public void makeNextPaymentFailWithException();
+    void makeAllInvoicesFailWithError(boolean failure);
 
-    public void makeAllInvoicesFailWithError(boolean failure);
+    void updatePaymentTransactions(UUID paymentId, List<PaymentTransactionInfoPlugin> newTransactions);
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
index b56af28..30b968e 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
@@ -39,6 +39,7 @@ import org.killbill.billing.invoice.model.ItemAdjInvoiceItem;
 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.config.InvoiceConfig;
 import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.commons.locker.GlobalLock;
 import org.killbill.commons.locker.GlobalLocker;
@@ -57,26 +58,25 @@ public class InvoiceApiHelper {
 
     private static final Logger log = LoggerFactory.getLogger(InvoiceApiHelper.class);
 
-    // 50 * 100ms = 5sec
-    private static final int NB_LOCK_TRY = 50;
-
     private final InvoicePluginDispatcher invoicePluginDispatcher;
     private final InvoiceDao dao;
     private final GlobalLocker locker;
     private final InternalCallContextFactory internalCallContextFactory;
+    private final InvoiceConfig invoiceConfig;
 
     @Inject
-    public InvoiceApiHelper(final InvoicePluginDispatcher invoicePluginDispatcher, final InvoiceDao dao, final GlobalLocker locker, final InternalCallContextFactory internalCallContextFactory) {
+    public InvoiceApiHelper(final InvoicePluginDispatcher invoicePluginDispatcher, final InvoiceDao dao, final GlobalLocker locker, final InvoiceConfig invoiceConfig, final InternalCallContextFactory internalCallContextFactory) {
         this.invoicePluginDispatcher = invoicePluginDispatcher;
         this.dao = dao;
         this.locker = locker;
+        this.invoiceConfig = invoiceConfig;
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
     public List<InvoiceItem> dispatchToInvoicePluginsAndInsertItems(final UUID accountId, final WithAccountLock withAccountLock, final CallContext context) throws InvoiceApiException {
         GlobalLock lock = null;
         try {
-            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), NB_LOCK_TRY);
+            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), invoiceConfig.getMaxGlobalLockRetries());
 
             final Iterable<Invoice> invoicesForPlugins = withAccountLock.prepareInvoices();
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index 859d106..4460632 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -113,9 +113,6 @@ public class InvoiceDispatcher {
     private static final Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class);
 
 
-    // 50 * 100ms = 5sec
-    private static final int NB_LOCK_TRY = 50;
-
     private static final Ordering<DateTime> UPCOMING_NOTIFICATION_DATE_ORDERING = Ordering.natural();
     private final static Joiner JOINER_COMMA = Joiner.on(",");
     private static final NullDryRunArguments NULL_DRY_RUN_ARGUMENTS = new NullDryRunArguments();
@@ -208,7 +205,7 @@ public class InvoiceDispatcher {
                                   @Nullable final DryRunArguments dryRunArguments, final InternalCallContext context) throws InvoiceApiException {
         GlobalLock lock = null;
         try {
-            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), NB_LOCK_TRY);
+            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), invoiceConfig.getMaxGlobalLockRetries());
 
             return processAccountWithLock(accountId, targetDate, dryRunArguments, context);
         } catch (final LockFailedException e) {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index cadda9d..9c5ec23 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -127,6 +127,11 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
             public int getMaxRawUsagePreviousPeriod() {
                 return -1;
             }
+
+            @Override
+            public int getMaxGlobalLockRetries() {
+                return 10;
+            }
         };
         this.generator = new DefaultInvoiceGenerator(clock, invoiceConfig, null);
         this.account = new MockAccountBuilder().name(UUID.randomUUID().toString().substring(1, 8))
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
index 60281b4..2d4a157 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
@@ -124,7 +124,7 @@ abstract class CompletionTaskBase<T> implements Runnable {
         GlobalLock lock = null;
         try {
             final Account account = accountInternalApi.getAccountByRecordId(internalTenantContext.getAccountRecordId(), internalTenantContext);
-            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), account.getExternalKey(), ProcessorBase.NB_LOCK_TRY);
+            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), account.getExternalKey(), paymentConfig.getMaxGlobalLockRetries());
             return callback.doIteration();
         } catch (AccountApiException e) {
             log.warn(String.format("Janitor failed to retrieve account with recordId %s", internalTenantContext.getAccountRecordId()), e);
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
index 8c013d4..94d318b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
@@ -142,7 +142,7 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
         if (!TRANSACTION_STATUSES_TO_CONSIDER.contains(event.getStatus())) {
             return;
         }
-        insertNewNotificationForUnresolvedTransactionIfNeeded(event.getPaymentTransactionId(), 1, event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
+        insertNewNotificationForUnresolvedTransactionIfNeeded(event.getPaymentTransactionId(), 0, event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
     }
 
     public boolean updatePaymentAndTransactionIfNeededWithAccountLock(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin, final InternalTenantContext internalTenantContext) {
@@ -200,6 +200,14 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
                 return false;
         }
 
+        // Our status did not change, so we just insert a new notification (attemptNumber will be incremented)
+        if (transactionStatus == paymentTransaction.getTransactionStatus()) {
+            log.debug("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}",
+                      payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
+            insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransaction.getId(), attemptNumber, userToken, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId());
+            return false;
+        }
+
         // Recompute new lastSuccessPaymentState. This is important to be able to allow new operations on the state machine (for e.g an AUTH_SUCCESS would now allow a CAPTURE operation)
         final String lastSuccessPaymentState = paymentStateMachineHelper.isSuccessState(newPaymentState) ? newPaymentState : null;
 
@@ -213,13 +221,9 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
         final String gatewayErrorCode = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayErrorCode() : paymentTransaction.getGatewayErrorCode();
         final String gatewayError = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayError() : paymentTransaction.getGatewayErrorMsg();
 
-        if (transactionStatus == paymentTransaction.getTransactionStatus()) {
-            log.debug("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}",
-                      payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
-        } else {
-            log.info("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}",
-                     payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
-        }
+
+        log.info("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}",
+                 payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(payment.getAccountId(), callContext);
         paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(), payment.getId(), paymentTransaction.getTransactionType(), newPaymentState, lastSuccessPaymentState,
@@ -248,10 +252,10 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
     }
 
     @VisibleForTesting
-    DateTime getNextNotificationTime(@Nullable final Integer attemptNumber) {
+    DateTime getNextNotificationTime(final Integer attemptNumber) {
 
         final List<TimeSpan> retries = paymentConfig.getIncompleteTransactionsRetries();
-        if (attemptNumber == null || attemptNumber > retries.size()) {
+        if (attemptNumber > retries.size()) {
             return null;
         }
         final TimeSpan nextDelay = retries.get(attemptNumber - 1);
@@ -259,8 +263,15 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
     }
 
     private void insertNewNotificationForUnresolvedTransactionIfNeeded(final UUID paymentTransactionId, @Nullable final Integer attemptNumber, @Nullable final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
-        final NotificationEvent key = new JanitorNotificationKey(paymentTransactionId, IncompletePaymentTransactionTask.class.toString(), attemptNumber);
-        final DateTime notificationTime = getNextNotificationTime(attemptNumber);
+        // When we come from a GET path, we don't want to insert a new notification
+        if (attemptNumber == null) {
+            return;
+        }
+
+        // Increment value before we insert
+        final Integer newAttemptNumber = attemptNumber.intValue() + 1;
+        final NotificationEvent key = new JanitorNotificationKey(paymentTransactionId, IncompletePaymentTransactionTask.class.toString(), newAttemptNumber);
+        final DateTime notificationTime = getNextNotificationTime(newAttemptNumber);
         // Will be null in the GET path or when we run out opf attempts..
         if (notificationTime != null) {
             try {
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 102c3d2..6368518 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
@@ -79,6 +79,8 @@ public class PaymentMethodProcessor extends ProcessorBase {
 
     private final PluginDispatcher<UUID> uuidPluginNotificationDispatcher;
 
+    private final PaymentConfig paymentConfig;
+
     @Inject
     public PaymentMethodProcessor(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
                                   final AccountInternalApi accountInternalApi,
@@ -92,6 +94,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
                                   final Clock clock) {
         super(pluginRegistry, accountInternalApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock);
         final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
+        this.paymentConfig = paymentConfig;
         this.uuidPluginNotificationDispatcher = new PluginDispatcher<UUID>(paymentPluginTimeoutSec, executors);
     }
 
@@ -102,7 +105,8 @@ public class PaymentMethodProcessor extends ProcessorBase {
         return dispatchWithExceptionHandling(account,
                                              new CallableWithAccountLock<UUID, PaymentApiException>(locker,
                                                                                                     account.getExternalKey(),
-                                                                                                    new WithAccountLockCallback<PluginDispatcherReturnType<UUID>, PaymentApiException>() {
+                                                                                                    paymentConfig,
+                                                                                                    new DispatcherCallback<PluginDispatcherReturnType<UUID>, PaymentApiException>() {
 
                                                                                                         @Override
                                                                                                         public PluginDispatcherReturnType<UUID> doOperation() throws PaymentApiException {
@@ -331,7 +335,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
                                      final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context)
             throws PaymentApiException {
         try {
-            new WithAccountLock<Void, PaymentApiException>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<PluginDispatcherReturnType<Void>, PaymentApiException>() {
+            new WithAccountLock<Void, PaymentApiException>(paymentConfig).processAccountWithLock(locker, account.getExternalKey(), new DispatcherCallback<PluginDispatcherReturnType<Void>, PaymentApiException>() {
 
                 @Override
                 public PluginDispatcherReturnType<Void> doOperation() throws PaymentApiException {
@@ -374,7 +378,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
     public void setDefaultPaymentMethod(final Account account, final UUID paymentMethodId, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context)
             throws PaymentApiException {
         try {
-            new WithAccountLock<Void, PaymentApiException>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<PluginDispatcherReturnType<Void>, PaymentApiException>() {
+            new WithAccountLock<Void, PaymentApiException>(paymentConfig).processAccountWithLock(locker, account.getExternalKey(), new DispatcherCallback<PluginDispatcherReturnType<Void>, PaymentApiException>() {
 
                 @Override
                 public PluginDispatcherReturnType<Void> doOperation() throws PaymentApiException {
@@ -438,7 +442,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
         }
 
         try {
-            final PluginDispatcherReturnType<List<PaymentMethod>> result = new WithAccountLock<List<PaymentMethod>, PaymentApiException>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<PluginDispatcherReturnType<List<PaymentMethod>>, PaymentApiException>() {
+            final PluginDispatcherReturnType<List<PaymentMethod>> result = new WithAccountLock<List<PaymentMethod>, PaymentApiException>(paymentConfig).processAccountWithLock(locker, account.getExternalKey(), new DispatcherCallback<PluginDispatcherReturnType<List<PaymentMethod>>, PaymentApiException>() {
                 @Override
                 public PluginDispatcherReturnType<List<PaymentMethod>> doOperation() throws PaymentApiException {
 
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 3b76d67..1e8be54 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
@@ -23,7 +23,6 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeoutException;
 
 import javax.annotation.Nullable;
@@ -47,6 +46,7 @@ import org.killbill.billing.util.api.TagApiException;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
@@ -63,9 +63,6 @@ import com.google.common.collect.Collections2;
 
 public abstract class ProcessorBase {
 
-    // 50 * 100ms = 5sec
-    public static final int NB_LOCK_TRY = 50;
-
     protected final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
     protected final AccountInternalApi accountInternalApi;
     protected final GlobalLocker locker;
@@ -158,9 +155,7 @@ public abstract class ProcessorBase {
         return internalCallContextFactory.createCallContext(context);
     }
 
-    // TODO Rename - there is no lock!
-    public interface WithAccountLockCallback<PluginDispatcherReturnType, ExceptionType extends Exception> {
-
+    public interface DispatcherCallback<PluginDispatcherReturnType, ExceptionType extends Exception> {
         public PluginDispatcherReturnType doOperation() throws ExceptionType;
     }
 
@@ -168,29 +163,38 @@ public abstract class ProcessorBase {
 
         private final GlobalLocker locker;
         private final String accountExternalKey;
-        private final WithAccountLockCallback<PluginDispatcherReturnType<ReturnType>, ExceptionType> callback;
+        private final DispatcherCallback<PluginDispatcherReturnType<ReturnType>, ExceptionType> callback;
+        private final PaymentConfig paymentConfig;
 
         public CallableWithAccountLock(final GlobalLocker locker,
                                        final String accountExternalKey,
-                                       final WithAccountLockCallback<PluginDispatcherReturnType<ReturnType>, ExceptionType> callback) {
+                                       final PaymentConfig paymentConfig,
+                                       final DispatcherCallback<PluginDispatcherReturnType<ReturnType>, ExceptionType> callback) {
             this.locker = locker;
             this.accountExternalKey = accountExternalKey;
             this.callback = callback;
+            this.paymentConfig = paymentConfig;
         }
 
         @Override
         public PluginDispatcherReturnType<ReturnType> call() throws ExceptionType, LockFailedException {
-            return new WithAccountLock<ReturnType, ExceptionType>().processAccountWithLock(locker, accountExternalKey, callback);
+            return new WithAccountLock<ReturnType, ExceptionType>(paymentConfig).processAccountWithLock(locker, accountExternalKey, callback);
         }
     }
 
     public static class WithAccountLock<ReturnType, ExceptionType extends Exception> {
 
-        public PluginDispatcherReturnType<ReturnType> processAccountWithLock(final GlobalLocker locker, final String accountExternalKey, final WithAccountLockCallback<PluginDispatcherReturnType<ReturnType>, ExceptionType> callback)
+        private final PaymentConfig paymentConfig;
+
+        public WithAccountLock(final PaymentConfig paymentConfig) {
+            this.paymentConfig = paymentConfig;
+        }
+
+        public PluginDispatcherReturnType<ReturnType> processAccountWithLock(final GlobalLocker locker, final String accountExternalKey, final DispatcherCallback<PluginDispatcherReturnType<ReturnType>, ExceptionType> callback)
                 throws ExceptionType, LockFailedException {
             GlobalLock lock = null;
             try {
-                lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountExternalKey, NB_LOCK_TRY);
+                lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountExternalKey, paymentConfig.getMaxGlobalLockRetries());
                 return callback.doOperation();
             } finally {
                 if (lock != null) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
index 0459ad2..4f05f5e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
@@ -23,12 +23,18 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class AuthorizeControlOperation extends OperationControlCallback {
 
-    public AuthorizeControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+    public AuthorizeControlOperation(final GlobalLocker locker,
+                                     final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                     final PaymentConfig paymentConfig,
+                                     final PaymentStateControlContext paymentStateContext,
+                                     final PaymentProcessor paymentProcessor,
+                                     final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
index 2b76885..54f133f 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
@@ -23,12 +23,18 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class CaptureControlOperation extends OperationControlCallback {
 
-    public CaptureControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+    public CaptureControlOperation(final GlobalLocker locker,
+                                   final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                   final PaymentConfig paymentConfig,
+                                   final PaymentStateControlContext paymentStateContext,
+                                   final PaymentProcessor paymentProcessor,
+                                   final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
index 07b5344..4836a2b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
@@ -24,12 +24,18 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class ChargebackControlOperation extends OperationControlCallback {
 
-    public ChargebackControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+    public ChargebackControlOperation(final GlobalLocker locker,
+                                      final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                      final PaymentConfig paymentConfig,
+                                      final PaymentStateControlContext paymentStateContext,
+                                      final PaymentProcessor paymentProcessor,
+                                      final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
index 6f3e300..d7cdac9 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
@@ -20,17 +20,16 @@ package org.killbill.billing.payment.core.sm.control;
 import org.killbill.automaton.OperationException;
 import org.killbill.automaton.OperationResult;
 import org.killbill.billing.control.plugin.api.PaymentApiType;
-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.core.PaymentProcessor;
-import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback;
 import org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext;
 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.control.plugin.api.PaymentControlContext;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 //
@@ -38,17 +37,19 @@ import org.killbill.commons.locker.GlobalLocker;
 //
 public class CompletionControlOperation extends OperationControlCallback {
 
-    public CompletionControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+    public CompletionControlOperation(final GlobalLocker locker,
+                                      final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                      final PaymentConfig paymentConfig,
                                       final PaymentStateControlContext paymentStateContext,
                                       final PaymentProcessor paymentProcessor,
                                       final ControlPluginRunner controlPluginRunner) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner);
     }
 
     @Override
     public OperationResult doOperationCallback() throws OperationException {
 
-        return dispatchWithAccountLockAndTimeout(new WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
+        return dispatchWithAccountLockAndTimeout(new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
             @Override
             public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
                 final PaymentTransactionModelDao transaction = paymentStateContext.getPaymentTransactionModelDao();
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
index 1a071fa..5d2c643 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
@@ -23,12 +23,18 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class CreditControlOperation extends OperationControlCallback {
 
-    public CreditControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+    public CreditControlOperation(final GlobalLocker locker,
+                                  final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                  final PaymentConfig paymentConfig,
+                                  final PaymentStateControlContext paymentStateContext,
+                                  final PaymentProcessor paymentProcessor,
+                                  final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
index a1e8928..add2adf 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
@@ -39,12 +39,13 @@ import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.core.PaymentProcessor;
-import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback;
 import org.killbill.billing.payment.core.sm.OperationCallbackBase;
 import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.LockFailedException;
 import org.slf4j.Logger;
@@ -64,8 +65,9 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
                                        final PluginDispatcher<OperationResult> paymentPluginDispatcher,
                                        final PaymentStateControlContext paymentStateContext,
                                        final PaymentProcessor paymentProcessor,
+                                       final PaymentConfig paymentConfig,
                                        final ControlPluginRunner controlPluginRunner) {
-        super(locker, paymentPluginDispatcher, paymentStateContext);
+        super(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
         this.paymentProcessor = paymentProcessor;
         this.controlPluginRunner = controlPluginRunner;
         this.paymentStateControlContext = paymentStateContext;
@@ -77,7 +79,7 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
     @Override
     public OperationResult doOperationCallback() throws OperationException {
 
-        return dispatchWithAccountLockAndTimeout(new WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
+        return dispatchWithAccountLockAndTimeout(new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
 
             @Override
             public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
index 7ad848f..4a766c7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
@@ -23,12 +23,18 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class PurchaseControlOperation extends OperationControlCallback {
 
-    public PurchaseControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+    public PurchaseControlOperation(final GlobalLocker locker,
+                                    final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                    final PaymentConfig paymentConfig,
+                                    final PaymentStateControlContext paymentStateContext,
+                                    final PaymentProcessor paymentProcessor,
+                                    final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
index 5f4d7cd..4c60dd3 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
@@ -23,12 +23,18 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class RefundControlOperation extends OperationControlCallback {
 
-    public RefundControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+    public RefundControlOperation(final GlobalLocker locker,
+                                  final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                  final PaymentConfig paymentConfig,
+                                  final PaymentStateControlContext paymentStateContext,
+                                  final PaymentProcessor paymentProcessor,
+                                  final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
index 00200d1..db74fa9 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
@@ -23,12 +23,18 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class VoidControlOperation extends OperationControlCallback {
 
-    public VoidControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+    public VoidControlOperation(final GlobalLocker locker,
+                                final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                final PaymentConfig paymentConfig,
+                                final PaymentStateControlContext paymentStateContext,
+                                final PaymentProcessor paymentProcessor,
+                                final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentConfig, controlPluginRunner);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
index 8ab58ff..34c8c1b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
@@ -25,9 +25,10 @@ import org.killbill.automaton.OperationException;
 import org.killbill.automaton.OperationResult;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.payment.core.ProcessorBase.CallableWithAccountLock;
-import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,15 +39,18 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
 
     private final GlobalLocker locker;
     private final PluginDispatcher<OperationResult> paymentPluginDispatcher;
+    private final PaymentConfig paymentConfig;
 
     protected final PaymentStateContext paymentStateContext;
 
     protected OperationCallbackBase(final GlobalLocker locker,
                                     final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                    final PaymentConfig paymentConfig,
                                     final PaymentStateContext paymentStateContext) {
         this.locker = locker;
         this.paymentPluginDispatcher = paymentPluginDispatcher;
         this.paymentStateContext = paymentStateContext;
+        this.paymentConfig = paymentConfig;
     }
 
     //
@@ -54,13 +58,14 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
     // The dispatcher may throw a TimeoutException, ExecutionException, or InterruptedException; those will be handled in specific
     // callback to eventually throw a OperationException, that will be used to drive the state machine in the right direction.
     //
-    protected <ExceptionType extends Exception> OperationResult dispatchWithAccountLockAndTimeout(final WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, ExceptionType> callback) throws OperationException {
+    protected <ExceptionType extends Exception> OperationResult dispatchWithAccountLockAndTimeout(final DispatcherCallback<PluginDispatcherReturnType<OperationResult>, ExceptionType> callback) throws OperationException {
         final Account account = paymentStateContext.getAccount();
         logger.debug("Dispatching plugin call for account {}", account.getExternalKey());
 
         try {
             final Callable<PluginDispatcherReturnType<OperationResult>> task = new CallableWithAccountLock<OperationResult, ExceptionType>(locker,
                                                                                                                                            account.getExternalKey(),
+                                                                                                                                           paymentConfig,
                                                                                                                                            callback);
             final OperationResult operationResult = paymentPluginDispatcher.dispatchWithTimeout(task);
             logger.debug("Successful plugin call for account {} with result {}", account.getExternalKey(), operationResult);
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 d0f56a0..49702f5 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
@@ -90,6 +90,7 @@ public class PaymentAutomatonRunner {
     protected final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
     protected final Clock clock;
     private final PersistentBus eventBus;
+    private final PaymentConfig paymentConfig;
 
     @Inject
     public PaymentAutomatonRunner(final PaymentConfig paymentConfig,
@@ -106,7 +107,7 @@ public class PaymentAutomatonRunner {
         this.pluginRegistry = pluginRegistry;
         this.clock = clock;
         this.eventBus = eventBus;
-
+        this.paymentConfig = paymentConfig;
         final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
         this.paymentPluginDispatcher = new PluginDispatcher<OperationResult>(paymentPluginTimeoutSec, executors);
 
@@ -151,37 +152,37 @@ public class PaymentAutomatonRunner {
         final EnteringStateCallback enteringStateCallback;
         switch (transactionType) {
             case PURCHASE:
-                operationCallback = new PurchaseOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                operationCallback = new PurchaseOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
                 leavingStateCallback = new PurchaseInitiated(daoHelper, paymentStateContext);
                 enteringStateCallback = new PurchaseCompleted(daoHelper, paymentStateContext);
                 break;
             case AUTHORIZE:
-                operationCallback = new AuthorizeOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                operationCallback = new AuthorizeOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
                 leavingStateCallback = new AuthorizeInitiated(daoHelper, paymentStateContext);
                 enteringStateCallback = new AuthorizeCompleted(daoHelper, paymentStateContext);
                 break;
             case CAPTURE:
-                operationCallback = new CaptureOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                operationCallback = new CaptureOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
                 leavingStateCallback = new CaptureInitiated(daoHelper, paymentStateContext);
                 enteringStateCallback = new CaptureCompleted(daoHelper, paymentStateContext);
                 break;
             case VOID:
-                operationCallback = new VoidOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                operationCallback = new VoidOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
                 leavingStateCallback = new VoidInitiated(daoHelper, paymentStateContext);
                 enteringStateCallback = new VoidCompleted(daoHelper, paymentStateContext);
                 break;
             case REFUND:
-                operationCallback = new RefundOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                operationCallback = new RefundOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
                 leavingStateCallback = new RefundInitiated(daoHelper, paymentStateContext);
                 enteringStateCallback = new RefundCompleted(daoHelper, paymentStateContext);
                 break;
             case CREDIT:
-                operationCallback = new CreditOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                operationCallback = new CreditOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
                 leavingStateCallback = new CreditInitiated(daoHelper, paymentStateContext);
                 enteringStateCallback = new CreditCompleted(daoHelper, paymentStateContext);
                 break;
             case CHARGEBACK:
-                operationCallback = new ChargebackOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                operationCallback = new ChargebackOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
                 leavingStateCallback = new ChargebackInitiated(daoHelper, paymentStateContext);
                 enteringStateCallback = new ChargebackCompleted(daoHelper, paymentStateContext);
                 break;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java
index 2919089..d8eb405 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java
@@ -24,6 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,9 +34,11 @@ public class AuthorizeOperation extends PaymentOperation {
     private final Logger logger = LoggerFactory.getLogger(AuthorizeOperation.class);
 
     public AuthorizeOperation(final PaymentAutomatonDAOHelper daoHelper,
-                              final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                              final GlobalLocker locker,
+                              final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                              final PaymentConfig paymentConfig,
                               final PaymentStateContext paymentStateContext) throws PaymentApiException {
-        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+        super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java
index 43903b8..e6b67c5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java
@@ -24,6 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,9 +34,11 @@ public class CaptureOperation extends PaymentOperation {
     private final Logger logger = LoggerFactory.getLogger(CaptureOperation.class);
 
     public CaptureOperation(final PaymentAutomatonDAOHelper daoHelper,
-                            final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                            final GlobalLocker locker,
+                            final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                            final PaymentConfig paymentConfig,
                             final PaymentStateContext paymentStateContext) throws PaymentApiException {
-        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+        super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java
index 0585c0f..7193ab1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java
@@ -30,6 +30,7 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,9 +40,11 @@ public class ChargebackOperation extends PaymentOperation {
     private final Logger logger = LoggerFactory.getLogger(ChargebackOperation.class);
 
     public ChargebackOperation(final PaymentAutomatonDAOHelper daoHelper,
-                               final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                               final GlobalLocker locker,
+                               final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                               final PaymentConfig paymentConfig,
                                final PaymentStateContext paymentStateContext) throws PaymentApiException {
-        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+        super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java
index c206625..53b5634 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java
@@ -24,6 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,9 +34,11 @@ public class CreditOperation extends PaymentOperation {
     private final Logger logger = LoggerFactory.getLogger(CreditOperation.class);
 
     public CreditOperation(final PaymentAutomatonDAOHelper daoHelper,
-                           final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                           final GlobalLocker locker,
+                           final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                           final PaymentConfig paymentConfig,
                            final PaymentStateContext paymentStateContext) throws PaymentApiException {
-        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+        super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
index ba6e48d..4896409 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
@@ -30,7 +30,7 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.PaymentTransactionInfoPluginConverter;
-import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback;
 import org.killbill.billing.payment.core.sm.OperationCallbackBase;
 import org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper;
 import org.killbill.billing.payment.core.sm.PaymentStateContext;
@@ -42,6 +42,7 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.LockFailedException;
 
@@ -59,8 +60,9 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
     protected PaymentOperation(final GlobalLocker locker,
                                final PaymentAutomatonDAOHelper daoHelper,
                                final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                               final PaymentConfig paymentConfig,
                                final PaymentStateContext paymentStateContext) {
-        super(locker, paymentPluginDispatcher, paymentStateContext);
+        super(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
         this.daoHelper = daoHelper;
     }
 
@@ -154,7 +156,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
     }
 
     private OperationResult doOperationCallbackWithDispatchAndAccountLock() throws OperationException {
-        return dispatchWithAccountLockAndTimeout(new WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
+        return dispatchWithAccountLockAndTimeout(new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
             @Override
             public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
                 final OperationResult result = doSimpleOperationCallback();
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java
index 7c9aabb..1ddf4b1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java
@@ -24,6 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,9 +34,11 @@ public class PurchaseOperation extends PaymentOperation {
     private final Logger logger = LoggerFactory.getLogger(PurchaseOperation.class);
 
     public PurchaseOperation(final PaymentAutomatonDAOHelper daoHelper,
-                             final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                             final GlobalLocker locker,
+                             final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                             final PaymentConfig paymentConfig,
                              final PaymentStateContext paymentStateContext) throws PaymentApiException {
-        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+        super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java
index 77534c5..22d9239 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java
@@ -24,6 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,9 +34,11 @@ public class RefundOperation extends PaymentOperation {
     private final Logger logger = LoggerFactory.getLogger(RefundOperation.class);
 
     public RefundOperation(final PaymentAutomatonDAOHelper daoHelper,
-                           final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                           final GlobalLocker locker,
+                           final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                           final PaymentConfig paymentConfig,
                            final PaymentStateContext paymentStateContext) throws PaymentApiException {
-        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+        super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java
index a188a43..0d776b6 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java
@@ -24,6 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,9 +34,11 @@ public class VoidOperation extends PaymentOperation {
     private final Logger logger = LoggerFactory.getLogger(VoidOperation.class);
 
     public VoidOperation(final PaymentAutomatonDAOHelper daoHelper,
-                         final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                         final GlobalLocker locker,
+                         final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                         final PaymentConfig paymentConfig,
                          final PaymentStateContext paymentStateContext) throws PaymentApiException {
-        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+        super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext);
     }
 
     @Override
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 4f58817..b15dcdb 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
@@ -77,6 +77,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner 
     private final RetryServiceScheduler retryServiceScheduler;
     private final PaymentControlStateMachineHelper paymentControlStateMachineHelper;
     private final ControlPluginRunner controlPluginRunner;
+    private final PaymentConfig paymentConfig;
 
     @Inject
     public PluginControlPaymentAutomatonRunner(final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
@@ -89,6 +90,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner 
         this.retryServiceScheduler = retryServiceScheduler;
         this.paymentControlStateMachineHelper = paymentControlStateMachineHelper;
         this.controlPluginRunner = controlPluginRunner;
+        this.paymentConfig = paymentConfig;
     }
 
     public Payment run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
@@ -133,7 +135,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner 
 
     public Payment completeRun(final PaymentStateControlContext paymentStateContext) throws PaymentApiException {
         try {
-            final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+            final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
             final LeavingStateCallback leavingStateCallback = new NoopControlInitiated();
             final EnteringStateCallback enteringStateCallback = new DefaultControlCompleted(this, paymentStateContext, paymentControlStateMachineHelper.getRetriedState(), retryServiceScheduler);
 
@@ -166,25 +168,25 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner 
         final OperationCallback callback;
         switch (transactionType) {
             case AUTHORIZE:
-                callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+                callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case CAPTURE:
-                callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+                callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case PURCHASE:
-                callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+                callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case VOID:
-                callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+                callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case CREDIT:
-                callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+                callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case REFUND:
-                callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+                callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case CHARGEBACK:
-                callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+                callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             default:
                 throw new IllegalStateException("Unsupported transaction type " + transactionType);
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
index 2908799..a194300 100644
--- a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
@@ -93,6 +93,11 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
+    public void updatePaymentTransactions(final UUID paymentId, final List<PaymentTransactionInfoPlugin> newTransactions) {
+        throw new IllegalStateException("Not implemented");
+    }
+
+    @Override
     public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentPluginApiException {
         return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency);
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java
index 569070f..9e81218 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java
@@ -35,7 +35,6 @@ public class TestIncompletePaymentTransactionTask extends PaymentTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testGetNextNotificationTime() {
-        assertNull(incompletePaymentTransactionTask.getNextNotificationTime(null));
         final DateTime initTime = clock.getUTCNow();
 
         // Based on config "15s,1m,3m,1h,1d,1d,1d,1d,1d"
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
index 3dd00a4..1a8f812 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
@@ -35,6 +35,7 @@ import org.killbill.billing.payment.dao.PaymentModelDao;
 import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 
@@ -46,8 +47,15 @@ public class MockRetryAuthorizeOperationCallback extends AuthorizeControlOperati
     private Exception exception;
     private OperationResult result;
 
-    public MockRetryAuthorizeOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner, final PaymentDao paymentDao, final Clock clock) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
+    public MockRetryAuthorizeOperationCallback(final GlobalLocker locker,
+                                               final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                               final PaymentConfig paymentConfig,
+                                               final PaymentStateControlContext paymentStateContext,
+                                               final PaymentProcessor paymentProcessor,
+                                               final ControlPluginRunner controlPluginRunner,
+                                               final PaymentDao paymentDao,
+                                               final Clock clock) {
+        super(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
         this.paymentDao = paymentDao;
         this.clock = clock;
     }
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 7e4e1b6..1765bef 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
@@ -38,6 +38,7 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.memory.MemoryGlobalLocker;
 import org.mockito.Mockito;
@@ -125,7 +126,7 @@ public class TestPaymentOperation extends PaymentTestSuiteNoDB {
         Mockito.when(paymentDao.getPaymentMethodIncludedDeleted(paymentStateContext.getPaymentMethodId(), internalCallContext)).thenReturn(paymentMethodModelDao);
 
         final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, clock.getUTCNow(), paymentDao, registry, internalCallContext, eventBus, paymentSMHelper);
-        paymentOperation = new PaymentOperationTest(paymentPluginStatus, daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+        paymentOperation = new PaymentOperationTest(paymentPluginStatus, daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
     }
 
     private static final class PaymentOperationTest extends PaymentOperation {
@@ -134,8 +135,10 @@ public class TestPaymentOperation extends PaymentTestSuiteNoDB {
 
         public PaymentOperationTest(@Nullable final PaymentPluginStatus paymentPluginStatus,
                                     final PaymentAutomatonDAOHelper daoHelper, final GlobalLocker locker,
-                                    final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateContext paymentStateContext) throws PaymentApiException {
-            super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+                                    final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                    final PaymentConfig paymentConfig,
+                                    final PaymentStateContext paymentStateContext) throws PaymentApiException {
+            super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext);
             this.paymentInfoPlugin = (paymentPluginStatus == null ? null : getPaymentInfoPlugin(paymentPluginStatus));
         }
 
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 9c73489..75e39c3 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
@@ -20,7 +20,6 @@ package org.killbill.billing.payment.core.sm;
 import java.math.BigDecimal;
 import java.util.UUID;
 import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -36,12 +35,13 @@ import org.killbill.billing.payment.PaymentTestSuiteNoDB;
 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.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback;
 import org.killbill.billing.payment.core.sm.payments.PaymentOperation;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.memory.MemoryGlobalLocker;
 import org.mockito.Mockito;
@@ -202,10 +202,10 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
 
         final PaymentAutomatonDAOHelper daoHelper = Mockito.mock(PaymentAutomatonDAOHelper.class);
         Mockito.when(daoHelper.getPaymentProviderPlugin()).thenReturn(null);
-        return new PluginOperationTest(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+        return new PluginOperationTest(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
     }
 
-    private static final class CallbackTest implements WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, PaymentApiException> {
+    private static final class CallbackTest implements DispatcherCallback<PluginDispatcherReturnType<OperationResult>, PaymentApiException> {
 
         private final AtomicInteger runCount = new AtomicInteger(0);
 
@@ -274,8 +274,8 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
 
     private static final class PluginOperationTest extends PaymentOperation {
 
-        protected PluginOperationTest(final PaymentAutomatonDAOHelper daoHelper, final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateContext paymentStateContext) throws PaymentApiException {
-            super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+        protected PluginOperationTest(final PaymentAutomatonDAOHelper daoHelper, final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentConfig paymentConfig, final PaymentStateContext paymentStateContext) throws PaymentApiException {
+            super(locker, daoHelper, paymentPluginDispatcher, paymentConfig, paymentStateContext);
         }
 
         @Override
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 b796607..a64c15a 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
@@ -189,6 +189,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
         mockRetryAuthorizeOperationCallback =
                 new MockRetryAuthorizeOperationCallback(locker,
                                                         runner.getPaymentPluginDispatcher(),
+                                                        paymentConfig,
                                                         paymentStateContext,
                                                         null,
                                                         controlPluginRunner,
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
index 9e8243d..f3b7088 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -209,6 +209,13 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
+    public void updatePaymentTransactions(final UUID paymentId, final List<PaymentTransactionInfoPlugin> newTransactions) {
+        if (paymentTransactions.containsKey(paymentId.toString())) {
+            paymentTransactions.put (paymentId.toString(), newTransactions);
+        }
+    }
+
+    @Override
     public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentPluginApiException {
         return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency, properties);
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 1753af8..e99ccaa 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -47,6 +47,10 @@ import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.glue.DefaultPaymentService;
 import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -55,6 +59,7 @@ import org.killbill.notificationq.api.NotificationEvent;
 import org.killbill.notificationq.api.NotificationEventWithMetadata;
 import org.killbill.notificationq.api.NotificationQueueService;
 import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.skife.config.TimeSpan;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.tweak.HandleCallback;
 import org.testng.Assert;
@@ -393,6 +398,58 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
         Assert.assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
     }
 
+    // The test will check that when a PENDING entry stays PENDING, we go through all our retries and evebtually give up (no infinite loop of retries)
+    @Test(groups = "slow")
+    public void testPendingEntriesThatDontMove() throws PaymentApiException, EventBusException, NoSuchNotificationQueue, PaymentPluginApiException, InterruptedException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final String paymentExternalKey = "haha";
+        final String transactionExternalKey = "hoho!";
+
+        testListener.pushExpectedEvent(NextEvent.PAYMENT);
+        final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), paymentExternalKey,
+                                                               transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
+        testListener.assertListenerStatus();
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+
+        // Artificially move the transaction status to PENDING AND update state on the plugin as well
+        final List<PaymentTransactionInfoPlugin> paymentTransactions = mockPaymentProviderPlugin.getPaymentInfo(account.getId(), payment.getId(), ImmutableList.<PluginProperty>of(), callContext);
+        final PaymentTransactionInfoPlugin oTx = paymentTransactions.remove(0);
+        final PaymentTransactionInfoPlugin updatePaymentTransaction = new DefaultNoOpPaymentInfoPlugin(oTx.getKbPaymentId(), oTx.getKbTransactionPaymentId(), oTx.getTransactionType(), oTx.getAmount(), oTx.getCurrency(), oTx.getCreatedDate(), oTx.getCreatedDate(), PaymentPluginStatus.PENDING, null);
+        paymentTransactions.add(updatePaymentTransaction);
+        mockPaymentProviderPlugin.updatePaymentTransactions(payment.getId(), paymentTransactions);
+
+        final String paymentStateName = paymentSMHelper.getPendingStateForTransaction(TransactionType.AUTHORIZE).toString();
+
+
+        testListener.pushExpectedEvent(NextEvent.PAYMENT);
+        paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), payment.getId(), TransactionType.AUTHORIZE, paymentStateName, paymentStateName,
+                                                           payment.getTransactions().get(0).getId(), TransactionStatus.PENDING, requestedAmount, account.getCurrency(),
+                                                           "loup", "chat", internalCallContext);
+        testListener.assertListenerStatus();
+
+        // 15s,1m,3m,1h,1d,1d,1d,1d,1d
+        for (TimeSpan cur : paymentConfig.getIncompleteTransactionsRetries()) {
+            // Verify there is a notification to retry updating the value
+            assertEquals(getPendingNotificationCnt(internalCallContext), 1);
+
+            clock.addDeltaFromReality(cur.getMillis() + 1);
+
+            // We add a sleep here to make sure the notification gets processed. Note that calling assertNotificationsCompleted would not work
+            // because there is a point in time where the notification  queue is empty (showing notification was processed), but the processing of the notification
+            // will itself enter a new notification, and so the synchronization is difficult without writing *too much code*.
+            Thread.sleep(1000);
+
+            //assertNotificationsCompleted(internalCallContext, 5);
+            final Payment updatedPayment = paymentApi.getPayment(payment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+            Assert.assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
+        }
+
+        assertEquals(getPendingNotificationCnt(internalCallContext), 0);
+    }
+
+
     private List<PluginProperty> createPropertiesForInvoice(final Invoice invoice) {
         final List<PluginProperty> result = new ArrayList<PluginProperty>();
         result.add(new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false));
@@ -441,5 +498,15 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
             fail("Test failed ", e);
         }
     }
+
+    private int getPendingNotificationCnt(final InternalCallContext internalCallContext) {
+        try {
+            return notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()).size();
+        } catch (final Exception e) {
+            fail("Test failed ", e);
+        }
+        // Not reached..
+        return -1;
+    }
 }
 
diff --git a/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java b/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java
index f42a635..19b3368 100644
--- a/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java
@@ -26,22 +26,27 @@ public interface InvoiceConfig extends KillbillConfig {
     @Config("org.killbill.invoice.maxNumberOfMonthsInFuture")
     @Default("36")
     @Description("Maximum target date to consider when generating an invoice")
-    public int getNumberOfMonthsInFuture();
+    int getNumberOfMonthsInFuture();
 
     @Config("org.killbill.invoice.emailNotificationsEnabled")
     @Default("false")
     @Description("Whether to send email notifications on invoice creation (for configured accounts)")
-    public boolean isEmailNotificationsEnabled();
+    boolean isEmailNotificationsEnabled();
 
     @Config("org.killbill.invoice.dryRunNotificationSchedule")
     @Default("0s")
     @Description("DryRun invoice notification time before targetDate (ignored if set to 0s)")
-    public TimeSpan getDryRunNotificationSchedule();
+    TimeSpan getDryRunNotificationSchedule();
 
 
     @Config("org.killbill.invoice.readMaxRawUsagePreviousPeriod")
     @Default("2")
     @Description("Maximum number of past billing periods we use to fetch raw usage data (usage optimization)")
-    public int getMaxRawUsagePreviousPeriod();
+    int getMaxRawUsagePreviousPeriod();
 
+
+    @Config("org.killbill.invoice.globalLock.retries")
+    @Default("50")
+    @Description("Maximum number of times the system will retry to grab global lock (with a 100ms wait each time)")
+    int getMaxGlobalLockRetries();
 }
diff --git a/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java b/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java
index 7b630c6..9eadf60 100644
--- a/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java
@@ -29,60 +29,65 @@ public interface PaymentConfig extends KillbillConfig {
     // See ExternalPaymentProviderPlugin.PLUGIN_NAME
     @Default("__external_payment__")
     @Description("Default payment provider to use")
-    public String getDefaultPaymentProvider();
+    String getDefaultPaymentProvider();
 
     @Config("org.killbill.payment.retry.days")
     @Default("8,8,8")
     @Description("Specify the number of payment retries along with the interval in days between payment retries when payment failures occur")
-    public List<Integer> getPaymentFailureRetryDays();
+    List<Integer> getPaymentFailureRetryDays();
 
     @Config("org.killbill.payment.failure.retry.start.sec")
     @Default("300")
     @Description("Specify the interval of time in seconds before retrying a payment that failed due to a plugin failure (gateway is down, transient error, ...")
-    public int getPluginFailureInitialRetryInSec();
+    int getPluginFailureInitialRetryInSec();
 
     @Config("org.killbill.payment.failure.retry.multiplier")
     @Default("2")
     @Description("Specify the multiplier to apply between in retry before retrying a payment that failed due to a plugin failure (gateway is down, transient error, ...")
-    public int getPluginFailureRetryMultiplier();
+    int getPluginFailureRetryMultiplier();
 
     @Config("org.killbill.payment.failure.retry.max.attempts")
     @Default("8")
     @Description("Specify the max number of attempts before retrying a payment that failed due to a plugin failure (gateway is down, transient error, ...\"")
-    public int getPluginFailureRetryMaxAttempts();
+    int getPluginFailureRetryMaxAttempts();
 
     @Config("org.killbill.payment.plugin.timeout")
     @Default("30s")
     @Description("Timeout for each payment attempt")
-    public TimeSpan getPaymentPluginTimeout();
+    TimeSpan getPaymentPluginTimeout();
 
     @Config("org.killbill.payment.plugin.threads.nb")
     @Default("10")
     @Description("Number of threads for plugin executor dispatcher")
-    public int getPaymentPluginThreadNb();
+    int getPaymentPluginThreadNb();
 
     @Config("org.killbill.payment.janitor.attempts.delay")
     @Default("12h")
     @Description("Delay before which unresolved attempt should be retried")
-    public TimeSpan getIncompleteAttemptsTimeSpanDelay();
+    TimeSpan getIncompleteAttemptsTimeSpanDelay();
 
     @Config("org.killbill.payment.janitor.transactions.retries")
     @Default("15s,1m,3m,1h,1d,1d,1d,1d,1d")
     @Description("Delay before which unresolved transactions should be retried")
-    public List<TimeSpan> getIncompleteTransactionsRetries();
+    List<TimeSpan> getIncompleteTransactionsRetries();
 
     @Config("org.killbill.payment.janitor.rate")
     @Default("1h")
     @Description("Rate at which janitor tasks are scheduled")
-    public TimeSpan getJanitorRunningRate();
+    TimeSpan getJanitorRunningRate();
 
     @Config("org.killbill.payment.invoice.plugin")
     @Default("")
     @Description("Default payment control plugin names")
-    public List<String> getPaymentControlPluginNames();
+    List<String> getPaymentControlPluginNames();
+
+    @Config("org.killbill.payment.globalLock.retries")
+    @Default("50")
+    @Description("Maximum number of times the system will retry to grab global lock (with a 100ms wait each time)")
+    int getMaxGlobalLockRetries();
 
     @Config("org.killbill.payment.off")
     @Default("false")
     @Description("Whether the payment subsystem is off")
-    public boolean isPaymentOff();
+    boolean isPaymentOff();
 }