killbill-aplcache

Implementation for buildFormDescriptorWithPaymentControl

8/12/2015 9:18:18 PM

Changes

Details

diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
new file mode 100644
index 0000000..cd916d9
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.api;
+
+import java.math.BigDecimal;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+
+public class DefaultApiBase {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultApiBase.class);
+
+    private final PaymentConfig paymentConfig;
+
+    public DefaultApiBase(final PaymentConfig paymentConfig) {
+        this.paymentConfig = paymentConfig;
+    }
+
+    protected void logAPICall(final String transactionType, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey) {
+        if (log.isInfoEnabled()) {
+            final StringBuilder logLine = new StringBuilder();
+            logLine.append("PaymentApi : ")
+                   .append(transactionType)
+                   .append(", account = ")
+                   .append(account.getId());
+            if (paymentMethodId != null) {
+                logLine.append(", paymentMethodId = ")
+                       .append(paymentMethodId);
+            }
+            if (paymentExternalKey != null) {
+                logLine.append(", paymentExternalKey = ")
+                       .append(paymentExternalKey);
+            }
+            if (paymentTransactionExternalKey != null) {
+                logLine.append(", paymentTransactionExternalKey = ")
+                       .append(paymentTransactionExternalKey);
+            }
+            if (paymentId != null) {
+                logLine.append(", paymentId = ")
+                       .append(paymentId);
+            }
+            if (transactionId != null) {
+                logLine.append(", transactionId = ")
+                       .append(transactionId);
+            }
+            if (amount != null) {
+                logLine.append(", amount = ")
+                       .append(amount);
+            }
+            if (currency != null) {
+                logLine.append(", currency = ")
+                       .append(currency);
+            }
+            log.info(logLine.toString());
+        }
+    }
+
+    protected List<String> toPaymentControlPluginNames(final PaymentOptions paymentOptions) {
+        // Special path for JAX-RS InvoicePayment endpoints (see JaxRsResourceBase)
+        if (paymentConfig.getPaymentControlPluginNames() != null &&
+            paymentOptions.getPaymentControlPluginNames() != null &&
+            paymentOptions.getPaymentControlPluginNames().size() == 1 &&
+            InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentOptions.getPaymentControlPluginNames().get(0))) {
+            final List<String> paymentControlPluginNames = new LinkedList<String>(paymentOptions.getPaymentControlPluginNames());
+            paymentControlPluginNames.addAll(paymentConfig.getPaymentControlPluginNames());
+            return paymentControlPluginNames;
+        } else if (paymentOptions.getPaymentControlPluginNames() != null && !paymentOptions.getPaymentControlPluginNames().isEmpty()) {
+            return paymentOptions.getPaymentControlPluginNames();
+        } else if (paymentConfig.getPaymentControlPluginNames() != null && !paymentConfig.getPaymentControlPluginNames().isEmpty()) {
+            return paymentConfig.getPaymentControlPluginNames();
+        } else {
+            return ImmutableList.<String>of();
+        }
+    }
+
+    protected void checkNotNullParameter(final Object parameter, final String parameterName) throws PaymentApiException {
+        if (parameter == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null");
+        }
+    }
+
+    protected void checkPositiveAmount(final BigDecimal amount) throws PaymentApiException {
+        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "amount", "should be greater than 0");
+        }
+    }
+
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
index 1e2d91d..656f830 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
@@ -44,7 +44,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableList;
 
-public class DefaultPaymentApi implements PaymentApi {
+public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
 
     private static final boolean SHOULD_LOCK_ACCOUNT = true;
     private static final boolean IS_API_PAYMENT = true;
@@ -52,7 +52,6 @@ public class DefaultPaymentApi implements PaymentApi {
 
     private static final Logger log = LoggerFactory.getLogger(DefaultPaymentApi.class);
 
-    private final PaymentConfig paymentConfig;
     private final PaymentProcessor paymentProcessor;
     private final PaymentMethodProcessor paymentMethodProcessor;
     private final PluginControlPaymentProcessor pluginControlPaymentProcessor;
@@ -60,7 +59,7 @@ public class DefaultPaymentApi implements PaymentApi {
 
     @Inject
     public DefaultPaymentApi(final PaymentConfig paymentConfig, final PaymentProcessor paymentProcessor, final PaymentMethodProcessor paymentMethodProcessor, final PluginControlPaymentProcessor pluginControlPaymentProcessor, final InternalCallContextFactory internalCallContextFactory) {
-        this.paymentConfig = paymentConfig;
+        super(paymentConfig);
         this.paymentProcessor = paymentProcessor;
         this.paymentMethodProcessor = paymentMethodProcessor;
         this.pluginControlPaymentProcessor = pluginControlPaymentProcessor;
@@ -498,73 +497,4 @@ public class DefaultPaymentApi implements PaymentApi {
 
         return paymentMethods;
     }
-
-    private void logAPICall(final String transactionType, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey) {
-        if (log.isInfoEnabled()) {
-            final StringBuilder logLine = new StringBuilder();
-            logLine.append("PaymentApi : ")
-                   .append(transactionType)
-                   .append(", account = ")
-                   .append(account.getId());
-            if (paymentMethodId != null) {
-                logLine.append(", paymentMethodId = ")
-                       .append(paymentMethodId);
-            }
-            if (paymentExternalKey != null) {
-                logLine.append(", paymentExternalKey = ")
-                       .append(paymentExternalKey);
-            }
-            if (paymentTransactionExternalKey != null) {
-                logLine.append(", paymentTransactionExternalKey = ")
-                       .append(paymentTransactionExternalKey);
-            }
-            if (paymentId != null) {
-                logLine.append(", paymentId = ")
-                       .append(paymentId);
-            }
-            if (transactionId != null) {
-                logLine.append(", transactionId = ")
-                       .append(transactionId);
-            }
-            if (amount != null) {
-                logLine.append(", amount = ")
-                       .append(amount);
-            }
-            if (currency != null) {
-                logLine.append(", currency = ")
-                       .append(currency);
-            }
-            log.info(logLine.toString());
-        }
-    }
-
-    private List<String> toPaymentControlPluginNames(final PaymentOptions paymentOptions) {
-        // Special path for JAX-RS InvoicePayment endpoints (see JaxRsResourceBase)
-        if (paymentConfig.getPaymentControlPluginNames() != null &&
-            paymentOptions.getPaymentControlPluginNames() != null &&
-            paymentOptions.getPaymentControlPluginNames().size() == 1 &&
-            InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentOptions.getPaymentControlPluginNames().get(0))) {
-            final List<String> paymentControlPluginNames = new LinkedList<String>(paymentOptions.getPaymentControlPluginNames());
-            paymentControlPluginNames.addAll(paymentConfig.getPaymentControlPluginNames());
-            return paymentControlPluginNames;
-        } else if (paymentOptions.getPaymentControlPluginNames() != null && !paymentOptions.getPaymentControlPluginNames().isEmpty()) {
-            return paymentOptions.getPaymentControlPluginNames();
-        } else if (paymentConfig.getPaymentControlPluginNames() != null && !paymentConfig.getPaymentControlPluginNames().isEmpty()) {
-            return paymentConfig.getPaymentControlPluginNames();
-        } else {
-            return ImmutableList.<String>of();
-        }
-    }
-
-    private void checkNotNullParameter(final Object parameter, final String parameterName) throws PaymentApiException {
-        if (parameter == null) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null");
-        }
-    }
-
-    private void checkPositiveAmount(final BigDecimal amount) throws PaymentApiException {
-        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "amount", "should be greater than 0");
-        }
-    }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
index c9adbaf..ae10edb 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
@@ -17,6 +17,7 @@
 
 package org.killbill.billing.payment.api;
 
+import java.util.List;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -24,20 +25,32 @@ import javax.inject.Inject;
 
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
+import org.killbill.billing.control.plugin.api.HPPType;
+import org.killbill.billing.control.plugin.api.PaymentApiType;
+import org.killbill.billing.control.plugin.api.PaymentControlApiException;
+import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
 import org.killbill.billing.payment.core.PaymentGatewayProcessor;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
 import org.killbill.billing.payment.plugin.api.GatewayNotification;
 import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.PaymentConfig;
 
-public class DefaultPaymentGatewayApi implements PaymentGatewayApi {
+public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentGatewayApi {
 
     private final PaymentGatewayProcessor paymentGatewayProcessor;
+    private final ControlPluginRunner controlPluginRunner;
     private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
-    public DefaultPaymentGatewayApi(final PaymentGatewayProcessor paymentGatewayProcessor, final InternalCallContextFactory internalCallContextFactory) {
+    public DefaultPaymentGatewayApi(final PaymentConfig paymentConfig,
+                                    final PaymentGatewayProcessor paymentGatewayProcessor,
+                                    final ControlPluginRunner controlPluginRunner,
+                                    final InternalCallContextFactory internalCallContextFactory) {
+        super(paymentConfig);
         this.paymentGatewayProcessor = paymentGatewayProcessor;
+        this.controlPluginRunner = controlPluginRunner;
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
@@ -53,8 +66,14 @@ public class DefaultPaymentGatewayApi implements PaymentGatewayApi {
     }
 
     @Override
-    public HostedPaymentPageFormDescriptor buildFormDescriptorWithPaymentControl(final Account account, final UUID uuid, final Iterable<PluginProperty> iterable, final Iterable<PluginProperty> iterable1, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
-        throw new IllegalStateException("Not implemented");
+    public HostedPaymentPageFormDescriptor buildFormDescriptorWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+
+        return executeWithPaymentControl(account, paymentMethodId, properties, paymentOptions, callContext, new WithPaymentControlCallback<HostedPaymentPageFormDescriptor>() {
+            @Override
+            public HostedPaymentPageFormDescriptor doPaymentGatewayApiOperation(final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException {
+                return buildFormDescriptor(account, paymentMethodId, customFields, adjustedPluginProperties, callContext);
+            }
+        });
     }
 
     @Override
@@ -63,7 +82,61 @@ public class DefaultPaymentGatewayApi implements PaymentGatewayApi {
     }
 
     @Override
-    public GatewayNotification processNotificationWithPaymentControl(final String s, final String s1, final Iterable<PluginProperty> iterable, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
-        throw new IllegalStateException("Not implemented");
+    public GatewayNotification processNotificationWithPaymentControl(final String notification, final String pluginName, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+        return executeWithPaymentControl(null, null, properties, paymentOptions, callContext, new WithPaymentControlCallback<GatewayNotification>() {
+            @Override
+            public GatewayNotification doPaymentGatewayApiOperation(final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException {
+                return processNotification(notification, pluginName, adjustedPluginProperties, callContext);
+            }
+        });
+    }
+
+
+    private interface WithPaymentControlCallback<T> {
+        T doPaymentGatewayApiOperation(final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException;
+    }
+
+    private <T> T executeWithPaymentControl(@Nullable final Account account,
+                                            @Nullable final UUID paymentMethodId,
+                                            final Iterable<PluginProperty> properties,
+                                            final PaymentOptions paymentOptions,
+                                            final CallContext callContext,
+                                            final WithPaymentControlCallback<T> callback) throws PaymentApiException {
+
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        if (paymentControlPluginNames.isEmpty()) {
+            return callback.doPaymentGatewayApiOperation(properties);
+        }
+
+        final PriorPaymentControlResult priorCallResult;
+        try {
+            priorCallResult = controlPluginRunner.executePluginPriorCalls(account,
+                                                                          paymentMethodId,
+                                                                          null, null, null, null,
+                                                                          PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
+                                                                          null, null, true, paymentControlPluginNames, properties, callContext);
+
+        } catch (final PaymentControlApiException e) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e);
+        }
+
+        try {
+            final T result = callback.doPaymentGatewayApiOperation(priorCallResult.getAdjustedPluginProperties());
+            controlPluginRunner.executePluginOnSuccessCalls(account,
+                                                            paymentMethodId,
+                                                            null, null, null, null,
+                                                            PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
+                                                            null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext);
+            return result;
+        } catch (final PaymentApiException e) {
+            controlPluginRunner.executePluginOnFailureCalls(account,
+                                                            paymentMethodId,
+                                                            null, null, null, null,
+                                                            PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
+                                                            null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext);
+
+            throw e;
+
+        }
     }
 }
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 9634461..0459ad2 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
@@ -27,8 +27,8 @@ 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 OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    public AuthorizeControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, 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 2d325c4..2b76885 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
@@ -27,8 +27,8 @@ 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 OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    public CaptureControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, 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 3f47fdc..07b5344 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
@@ -28,8 +28,8 @@ 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 OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    public ChargebackControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, 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 d6369e7..6f3e300 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
@@ -19,11 +19,13 @@ 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.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;
@@ -36,8 +38,11 @@ import org.killbill.commons.locker.GlobalLocker;
 //
 public class CompletionControlOperation extends OperationControlCallback {
 
-    public CompletionControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry);
+    public CompletionControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                      final PaymentStateControlContext paymentStateContext,
+                                      final PaymentProcessor paymentProcessor,
+                                      final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
     }
 
     @Override
@@ -54,7 +59,9 @@ public class CompletionControlOperation extends OperationControlCallback {
                                                                                                             paymentStateContext.getPaymentExternalKey(),
                                                                                                             transaction.getId(),
                                                                                                             paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                                            PaymentApiType.PAYMENT_TRANSACTION,
                                                                                                             paymentStateContext.getTransactionType(),
+                                                                                                            null,
                                                                                                             transaction.getAmount(),
                                                                                                             transaction.getCurrency(),
                                                                                                             transaction.getProcessedAmount(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
new file mode 100644
index 0000000..da94cfd
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm.control;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.DefaultCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.control.plugin.api.HPPType;
+import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
+import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult;
+import org.killbill.billing.control.plugin.api.PaymentApiType;
+import org.killbill.billing.control.plugin.api.PaymentControlApiException;
+import org.killbill.billing.control.plugin.api.PaymentControlContext;
+import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.retry.DefaultFailureCallResult;
+import org.killbill.billing.payment.retry.DefaultOnSuccessPaymentControlResult;
+import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ControlPluginRunner {
+
+    private static final Logger log = LoggerFactory.getLogger(ControlPluginRunner.class);
+
+    private final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry;
+
+    @Inject
+    public ControlPluginRunner(final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
+        this.paymentControlPluginRegistry = paymentControlPluginRegistry;
+    }
+
+    public PriorPaymentControlResult executePluginPriorCalls(final Account account,
+                                                             final UUID paymentMethodId,
+                                                             final UUID paymentAttemptId,
+                                                             final UUID paymentId,
+                                                             final String paymentExternalKey,
+                                                             final String paymentTransactionExternalKey,
+                                                             final PaymentApiType paymentApiType,
+                                                             final TransactionType transactionType,
+                                                             final HPPType hppType,
+                                                             final BigDecimal amount,
+                                                             final Currency currency,
+                                                             final boolean  isApiPayment,
+                                                             final List<String> paymentControlPluginNames,
+                                                             final Iterable<PluginProperty> pluginProperties,
+                                                             final CallContext callContext) throws PaymentControlApiException {
+        // Return as soon as the first plugin aborts, or the last result for the last plugin
+        PriorPaymentControlResult prevResult = null;
+
+        // Those values are adjusted prior each call with the result of what previous call to plugin returned
+        Iterable<PluginProperty> inputPluginProperties = pluginProperties;
+        PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account,
+                                                                                            paymentMethodId,
+                                                                                            paymentAttemptId,
+                                                                                            paymentId,
+                                                                                            paymentExternalKey,
+                                                                                            paymentTransactionExternalKey,
+                                                                                            paymentApiType,
+                                                                                            transactionType,
+                                                                                            hppType,
+                                                                                            amount,
+                                                                                            currency,
+                                                                                            isApiPayment,
+                                                                                            callContext);
+
+        for (final String pluginName : paymentControlPluginNames) {
+            final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+            if (plugin == null) {
+                // First call to plugin, we log warn, if plugin is not registered
+                log.warn("Skipping unknown payment control plugin {} when fetching results", pluginName);
+                continue;
+            }
+            prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties);
+            if (prevResult.getAdjustedPluginProperties() != null) {
+                inputPluginProperties = prevResult.getAdjustedPluginProperties();
+            }
+            if (prevResult.isAborted()) {
+                break;
+            }
+            inputPaymentControlContext = new DefaultPaymentControlContext(account,
+                                                                          prevResult.getAdjustedPaymentMethodId() != null ? prevResult.getAdjustedPaymentMethodId() : paymentMethodId,
+                                                                          paymentAttemptId,
+                                                                          paymentId,
+                                                                          paymentExternalKey,
+                                                                          paymentTransactionExternalKey,
+                                                                          paymentApiType,
+                                                                          transactionType,
+                                                                          hppType,
+                                                                          prevResult.getAdjustedAmount() != null ? prevResult.getAdjustedAmount() : amount,
+                                                                          prevResult.getAdjustedCurrency() != null ? prevResult.getAdjustedCurrency() : currency,
+                                                                          isApiPayment,
+                                                                          callContext);
+        }
+        // Rebuild latest result to include inputPluginProperties
+        prevResult = new DefaultPriorPaymentControlResult(prevResult, inputPluginProperties);
+        return prevResult;
+    }
+
+
+    public OnSuccessPaymentControlResult executePluginOnSuccessCalls(final Account account,
+                                            final UUID paymentMethodId,
+                                            final UUID paymentAttemptId,
+                                            final UUID paymentId,
+                                            final String paymentExternalKey,
+                                            final String paymentTransactionExternalKey,
+                                            final PaymentApiType paymentApiType,
+                                            final TransactionType transactionType,
+                                            final HPPType hppType,
+                                            final BigDecimal amount,
+                                            final Currency currency,
+                                            final boolean  isApiPayment,
+                                            final List<String> paymentControlPluginNames,
+                                            final Iterable<PluginProperty> pluginProperties,
+                                            final CallContext callContext) {
+
+        final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account,
+                                                                                                  paymentMethodId,
+                                                                                                  paymentAttemptId,
+                                                                                                  paymentId,
+                                                                                                  paymentExternalKey,
+                                                                                                  paymentTransactionExternalKey,
+                                                                                                  paymentApiType,
+                                                                                                  transactionType,
+                                                                                                  hppType,
+                                                                                                  amount,
+                                                                                                  currency,
+                                                                                                  isApiPayment,
+                                                                                                  callContext);
+        Iterable<PluginProperty> inputPluginProperties = pluginProperties;
+        for (final String pluginName : paymentControlPluginNames) {
+            final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+            if (plugin != null) {
+                try {
+                    final OnSuccessPaymentControlResult result = plugin.onSuccessCall(inputPaymentControlContext, inputPluginProperties);
+                    if (result.getAdjustedPluginProperties() != null) {
+                        inputPluginProperties = result.getAdjustedPluginProperties();
+                    }
+                    // Exceptions from the control plugins are ignored (and logged) because the semantics on what to do are undefined.
+                } catch (final PaymentControlApiException e) {
+                    log.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + inputPaymentControlContext.getPaymentExternalKey(), e);
+                } catch (final RuntimeException e) {
+                    log.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + inputPaymentControlContext.getPaymentExternalKey(), e);
+                }
+            }
+        }
+        return new DefaultOnSuccessPaymentControlResult(inputPluginProperties);
+    }
+
+
+    public OnFailurePaymentControlResult executePluginOnFailureCalls(final Account account,
+                                                                     final UUID paymentMethodId,
+                                                                     final UUID paymentAttemptId,
+                                                                     final UUID paymentId,
+                                                                     final String paymentExternalKey,
+                                                                     final String paymentTransactionExternalKey,
+                                                                     final PaymentApiType paymentApiType,
+                                                                     final TransactionType transactionType,
+                                                                     final HPPType hppType,
+                                                                     final BigDecimal amount,
+                                                                     final Currency currency,
+                                                                     final boolean  isApiPayment,
+                                                                     final List<String> paymentControlPluginNames,
+                                                                     final Iterable<PluginProperty> pluginProperties,
+                                                                     final CallContext callContext) {
+
+        final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account,
+                                                                                                  paymentMethodId,
+                                                                                                  paymentAttemptId,
+                                                                                                  paymentId,
+                                                                                                  paymentExternalKey,
+                                                                                                  paymentTransactionExternalKey,
+                                                                                                  paymentApiType,
+                                                                                                  transactionType,
+                                                                                                  hppType,
+                                                                                                  amount,
+                                                                                                  currency,
+                                                                                                  isApiPayment,
+                                                                                                  callContext);
+
+        DateTime candidate = null;
+        Iterable<PluginProperty> inputPluginProperties = pluginProperties;
+
+        for (final String pluginName : paymentControlPluginNames) {
+            final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+            if (plugin != null) {
+                try {
+                    final OnFailurePaymentControlResult result = plugin.onFailureCall(inputPaymentControlContext, inputPluginProperties);
+                    if (candidate == null) {
+                        candidate = result.getNextRetryDate();
+                    } else if (result.getNextRetryDate() != null) {
+                        candidate = candidate.compareTo(result.getNextRetryDate()) > 0 ? result.getNextRetryDate() : candidate;
+                    }
+
+                    if (result.getAdjustedPluginProperties() != null) {
+                        inputPluginProperties = result.getAdjustedPluginProperties();
+                    }
+
+                } catch (final PaymentControlApiException e) {
+                    log.warn("Plugin " + pluginName + " failed to return next retryDate for payment " + inputPaymentControlContext.getPaymentExternalKey(), e);
+                    return new DefaultFailureCallResult(candidate, inputPluginProperties);
+                }
+            }
+        }
+        return new DefaultFailureCallResult(candidate, inputPluginProperties);
+    }
+
+
+
+
+    public static class DefaultPaymentControlContext extends DefaultCallContext implements PaymentControlContext {
+
+        private final Account account;
+        private final UUID paymentMethodId;
+        private final UUID attemptId;
+        private final UUID paymentId;
+        private final String paymentExternalKey;
+        private final UUID transactionId;
+        private final String transactionExternalKey;
+        private final PaymentApiType paymentApiType;
+        private final HPPType hppType;
+        private final TransactionType transactionType;
+        private final BigDecimal amount;
+        private final Currency currency;
+        private final BigDecimal processedAmount;
+        private final Currency processedCurrency;
+        private final boolean isApiPayment;
+
+        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, final String transactionExternalKey,
+                                            final PaymentApiType paymentApiType, final TransactionType transactionType, final HPPType hppType, final BigDecimal amount, final Currency currency,
+                                            final boolean isApiPayment, final CallContext callContext) {
+            this(account, paymentMethodId, attemptId, paymentId, paymentExternalKey, null, transactionExternalKey, paymentApiType, transactionType, hppType, amount, currency, null, null, isApiPayment, callContext);
+        }
+
+        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, @Nullable final UUID transactionId, final String transactionExternalKey,
+                                            final PaymentApiType paymentApiType, final TransactionType transactionType, final HPPType hppType,
+                                            final BigDecimal amount, final Currency currency, @Nullable final BigDecimal processedAmount, @Nullable final Currency processedCurrency, final boolean isApiPayment, final CallContext callContext) {
+            super(callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(), callContext.getComments(), callContext.getUserToken(), callContext.getCreatedDate(), callContext.getUpdatedDate());
+            this.account = account;
+            this.paymentMethodId = paymentMethodId;
+            this.attemptId = attemptId;
+            this.paymentId = paymentId;
+            this.paymentExternalKey = paymentExternalKey;
+            this.transactionId = transactionId;
+            this.transactionExternalKey = transactionExternalKey;
+            this.paymentApiType = paymentApiType;
+            this.hppType = hppType;
+            this.transactionType = transactionType;
+            this.amount = amount;
+            this.currency = currency;
+            this.processedAmount = processedAmount;
+            this.processedCurrency = processedCurrency;
+            this.isApiPayment = isApiPayment;
+        }
+
+        @Override
+        public UUID getAccountId() {
+            return account.getId();
+        }
+
+        @Override
+        public String getPaymentExternalKey() {
+            return paymentExternalKey;
+        }
+
+        @Override
+        public String getTransactionExternalKey() {
+            return transactionExternalKey;
+        }
+
+        @Override
+        public PaymentApiType getPaymentApiType() {
+            return paymentApiType;
+        }
+
+        @Override
+        public TransactionType getTransactionType() {
+            return transactionType;
+        }
+
+        @Override
+        public HPPType getHPPType() {
+            return hppType;
+        }
+
+        @Override
+        public BigDecimal getAmount() {
+            return amount;
+        }
+
+        @Override
+        public Currency getCurrency() {
+            return currency;
+        }
+
+        @Override
+        public UUID getPaymentMethodId() {
+            return paymentMethodId;
+        }
+
+        @Override
+        public UUID getPaymentId() {
+            return paymentId;
+        }
+
+        @Override
+        public UUID getAttemptPaymentId() {
+            return attemptId;
+        }
+
+        @Override
+        public BigDecimal getProcessedAmount() {
+            return processedAmount;
+        }
+
+        @Override
+        public Currency getProcessedCurrency() {
+            return processedCurrency;
+        }
+
+        @Override
+        public boolean isApiPayment() {
+            return isApiPayment;
+        }
+
+        public UUID getTransactionId() {
+            return transactionId;
+        }
+
+        @Override
+        public String toString() {
+            return "DefaultPaymentControlContext{" +
+                   "account=" + account +
+                   ", paymentMethodId=" + paymentMethodId +
+                   ", attemptId=" + attemptId +
+                   ", paymentId=" + paymentId +
+                   ", paymentExternalKey='" + paymentExternalKey + '\'' +
+                   ", transactionId=" + transactionId +
+                   ", transactionExternalKey='" + transactionExternalKey + '\'' +
+                   ", paymentApiType=" + paymentApiType +
+                   ", hppType=" + hppType +
+                   ", transactionType=" + transactionType +
+                   ", amount=" + amount +
+                   ", currency=" + currency +
+                   ", processedAmount=" + processedAmount +
+                   ", processedCurrency=" + processedCurrency +
+                   ", isApiPayment=" + isApiPayment +
+                   '}';
+        }
+    }
+
+}
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 d0db210..1a071fa 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
@@ -27,8 +27,8 @@ 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 OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    public CreditControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, 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 e1c632f..f2458e8 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
@@ -17,9 +17,7 @@
 
 package org.killbill.billing.payment.core.sm.control;
 
-import java.math.BigDecimal;
 import java.util.List;
-import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 
@@ -29,30 +27,24 @@ import org.joda.time.DateTime;
 import org.killbill.automaton.Operation.OperationCallback;
 import org.killbill.automaton.OperationException;
 import org.killbill.automaton.OperationResult;
-import org.killbill.billing.account.api.Account;
-import org.killbill.billing.callcontext.DefaultCallContext;
-import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
 import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult;
+import org.killbill.billing.control.plugin.api.PaymentApiType;
 import org.killbill.billing.control.plugin.api.PaymentControlApiException;
 import org.killbill.billing.control.plugin.api.PaymentControlContext;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
-import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
-import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
 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.payment.retry.DefaultPriorPaymentControlResult;
-import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.LockFailedException;
 import org.slf4j.Logger;
@@ -62,15 +54,20 @@ import com.google.common.base.MoreObjects;
 
 public abstract class OperationControlCallback extends OperationCallbackBase<Payment, PaymentApiException> implements OperationCallback {
 
+    private static final Logger logger = LoggerFactory.getLogger(OperationControlCallback.class);
+
     protected final PaymentProcessor paymentProcessor;
     protected final PaymentStateControlContext paymentStateControlContext;
-    private final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry;
-    private final Logger logger = LoggerFactory.getLogger(OperationControlCallback.class);
+    private final ControlPluginRunner controlPluginRunner;
 
-    protected OperationControlCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry) {
+    protected OperationControlCallback(final GlobalLocker locker,
+                                       final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                       final PaymentStateControlContext paymentStateContext,
+                                       final PaymentProcessor paymentProcessor,
+                                       final ControlPluginRunner controlPluginRunner) {
         super(locker, paymentPluginDispatcher, paymentStateContext);
         this.paymentProcessor = paymentProcessor;
-        this.paymentControlPluginRegistry = retryPluginRegistry;
+        this.controlPluginRunner = controlPluginRunner;
         this.paymentStateControlContext = paymentStateContext;
     }
 
@@ -91,7 +88,9 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
                                                                                                      paymentStateContext.getPaymentId(),
                                                                                                      paymentStateContext.getPaymentExternalKey(),
                                                                                                      paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                                     PaymentApiType.PAYMENT_TRANSACTION,
                                                                                                      paymentStateContext.getTransactionType(),
+                                                                                                     null,
                                                                                                      paymentStateContext.getAmount(),
                                                                                                      paymentStateContext.getCurrency(),
                                                                                                      paymentStateControlContext.isApiPayment(),
@@ -124,7 +123,9 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
                                                                                                                     result.getExternalKey(),
                                                                                                                     transaction.getId(),
                                                                                                                     paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                                                    PaymentApiType.PAYMENT_TRANSACTION,
                                                                                                                     paymentStateContext.getTransactionType(),
+                                                                                                                    null,
                                                                                                                     transaction.getAmount(),
                                                                                                                     transaction.getCurrency(),
                                                                                                                     transaction.getProcessedAmount(),
@@ -177,67 +178,45 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
     }
 
     private PriorPaymentControlResult executePluginPriorCalls(final List<String> paymentControlPluginNames, final PaymentControlContext paymentControlContextArg) throws PaymentControlApiException {
-        // Return as soon as the first plugin aborts, or the last result for the last plugin
-        PriorPaymentControlResult prevResult = null;
-
-        // Those values are adjusted prior each call with the result of what previous call to plugin returned
-        PaymentControlContext inputPaymentControlContext = paymentControlContextArg;
-        Iterable<PluginProperty> inputPluginProperties = paymentStateContext.getProperties();
-
-        for (final String pluginName : paymentControlPluginNames) {
-            final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
-            if (plugin == null) {
-                // First call to plugin, we log warn, if plugin is not registered
-                logger.warn("Skipping unknown payment control plugin {} when fetching results", pluginName);
-                continue;
-            }
-            prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties);
-            if (prevResult.getAdjustedPluginProperties() != null) {
-                inputPluginProperties = prevResult.getAdjustedPluginProperties();
-            }
-            if (prevResult.isAborted()) {
-                break;
-            }
-            inputPaymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(),
-                                                                          prevResult.getAdjustedPaymentMethodId() != null ? prevResult.getAdjustedPaymentMethodId() : inputPaymentControlContext.getPaymentMethodId(),
-                                                                          paymentStateControlContext.getAttemptId(),
-                                                                          paymentStateContext.getPaymentId(),
-                                                                          paymentStateContext.getPaymentExternalKey(),
-                                                                          paymentStateContext.getPaymentTransactionExternalKey(),
-                                                                          paymentStateContext.getTransactionType(),
-                                                                          prevResult.getAdjustedAmount() != null ? prevResult.getAdjustedAmount() : inputPaymentControlContext.getAmount(),
-                                                                          prevResult.getAdjustedCurrency() != null ? prevResult.getAdjustedCurrency() : inputPaymentControlContext.getCurrency(),
-                                                                          paymentStateControlContext.isApiPayment(),
-                                                                          paymentStateContext.getCallContext());
 
-        }
-        // Rebuild latest result to include inputPluginProperties
-        prevResult = new DefaultPriorPaymentControlResult(prevResult, inputPluginProperties);
-        // Adjust context with all values if necessary
-        adjustStateContextForPriorCall(paymentStateContext, prevResult);
-        return prevResult;
+        final PriorPaymentControlResult result = controlPluginRunner.executePluginPriorCalls(paymentStateContext.getAccount(),
+                                                                                             paymentControlContextArg.getPaymentMethodId(),
+                                                                                             paymentStateControlContext.getAttemptId(),
+                                                                                             paymentStateContext.getPaymentId(),
+                                                                                             paymentStateContext.getPaymentExternalKey(),
+                                                                                             paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                             PaymentApiType.PAYMENT_TRANSACTION,
+                                                                                             paymentStateContext.getTransactionType(),
+                                                                                             null,
+                                                                                             paymentControlContextArg.getAmount(),
+                                                                                             paymentControlContextArg.getCurrency(),
+                                                                                             paymentStateControlContext.isApiPayment(),
+                                                                                             paymentControlPluginNames,
+                                                                                             paymentStateContext.getProperties(),
+                                                                                             paymentStateContext.getCallContext());
+
+        adjustStateContextForPriorCall(paymentStateContext, result);
+        return result;
     }
 
     protected void executePluginOnSuccessCalls(final List<String> paymentControlPluginNames, final PaymentControlContext paymentControlContext) {
 
-        Iterable<PluginProperty> inputPluginProperties = paymentStateContext.getProperties();
-        for (final String pluginName : paymentControlPluginNames) {
-            final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
-            if (plugin != null) {
-                try {
-                    final OnSuccessPaymentControlResult result = plugin.onSuccessCall(paymentControlContext, inputPluginProperties);
-                    if (result.getAdjustedPluginProperties() != null) {
-                        inputPluginProperties = result.getAdjustedPluginProperties();
-                    }
-                    // Exceptions from the control plugins are ignored (and logged) because the semantics on what to do are undefined.
-                } catch (final PaymentControlApiException e) {
-                    logger.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + paymentControlContext.getPaymentExternalKey(), e);
-                } catch (final RuntimeException e) {
-                    logger.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + paymentControlContext.getPaymentExternalKey(), e);
-                }
-            }
-        }
-        adjustStateContextPluginProperties(paymentStateContext, inputPluginProperties);
+        final OnSuccessPaymentControlResult result = controlPluginRunner.executePluginOnSuccessCalls(paymentStateContext.getAccount(),
+                                                                                                     paymentControlContext.getPaymentMethodId(),
+                                                                                                     paymentStateControlContext.getAttemptId(),
+                                                                                                     paymentStateContext.getPaymentId(),
+                                                                                                     paymentStateContext.getPaymentExternalKey(),
+                                                                                                     paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                                     PaymentApiType.PAYMENT_TRANSACTION,
+                                                                                                     paymentStateContext.getTransactionType(),
+                                                                                                     null,
+                                                                                                     paymentControlContext.getAmount(),
+                                                                                                     paymentControlContext.getCurrency(),
+                                                                                                     paymentStateControlContext.isApiPayment(),
+                                                                                                     paymentControlPluginNames,
+                                                                                                     paymentStateContext.getProperties(),
+                                                                                                     paymentStateContext.getCallContext());
+        adjustStateContextPluginProperties(paymentStateContext, result.getAdjustedPluginProperties());
     }
 
     private OperationResult executePluginOnFailureCallsAndSetRetryDate(final PaymentStateControlContext paymentStateControlContext, final PaymentControlContext paymentControlContext) {
@@ -250,32 +229,23 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
 
     private DateTime executePluginOnFailureCalls(final List<String> paymentControlPluginNames, final PaymentControlContext paymentControlContext) {
 
-        DateTime candidate = null;
-        Iterable<PluginProperty> inputPluginProperties = paymentStateContext.getProperties();
-
-        for (final String pluginName : paymentControlPluginNames) {
-            final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
-            if (plugin != null) {
-                try {
-                    final OnFailurePaymentControlResult result = plugin.onFailureCall(paymentControlContext, inputPluginProperties);
-                    if (candidate == null) {
-                        candidate = result.getNextRetryDate();
-                    } else if (result.getNextRetryDate() != null) {
-                        candidate = candidate.compareTo(result.getNextRetryDate()) > 0 ? result.getNextRetryDate() : candidate;
-                    }
-
-                    if (result.getAdjustedPluginProperties() != null) {
-                        inputPluginProperties = result.getAdjustedPluginProperties();
-                    }
-
-                } catch (final PaymentControlApiException e) {
-                    logger.warn("Plugin " + pluginName + " failed to return next retryDate for payment " + paymentControlContext.getPaymentExternalKey(), e);
-                    return candidate;
-                }
-            }
-        }
-        adjustStateContextPluginProperties(paymentStateContext, inputPluginProperties);
-        return candidate;
+        final OnFailurePaymentControlResult result = controlPluginRunner.executePluginOnFailureCalls(paymentStateContext.getAccount(),
+                                                                                                     paymentControlContext.getPaymentMethodId(),
+                                                                                                     paymentStateControlContext.getAttemptId(),
+                                                                                                     paymentStateContext.getPaymentId(),
+                                                                                                     paymentStateContext.getPaymentExternalKey(),
+                                                                                                     paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                                     PaymentApiType.PAYMENT_TRANSACTION,
+                                                                                                     paymentStateContext.getTransactionType(),
+                                                                                                     null,
+                                                                                                     paymentControlContext.getAmount(),
+                                                                                                     paymentControlContext.getCurrency(),
+                                                                                                     paymentStateControlContext.isApiPayment(),
+                                                                                                     paymentControlPluginNames,
+                                                                                                     paymentStateContext.getProperties(),
+                                                                                                     paymentStateContext.getCallContext());
+        adjustStateContextPluginProperties(paymentStateContext, result.getAdjustedPluginProperties());
+        return result.getNextRetryDate();
     }
 
     private void adjustStateContextForPriorCall(final PaymentStateContext inputContext, @Nullable final PriorPaymentControlResult pluginResult) {
@@ -303,127 +273,4 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
         final PaymentStateControlContext input = (PaymentStateControlContext) inputContext;
         input.setProperties(pluginProperties);
     }
-
-    public static class DefaultPaymentControlContext extends DefaultCallContext implements PaymentControlContext {
-
-        private final Account account;
-        private final UUID paymentMethodId;
-        private final UUID attemptId;
-        private final UUID paymentId;
-        private final String paymentExternalKey;
-        private final UUID transactionId;
-        private final String transactionExternalKey;
-        private final TransactionType transactionType;
-        private final BigDecimal amount;
-        private final Currency currency;
-        private final BigDecimal processedAmount;
-        private final Currency processedCurrency;
-        private final boolean isApiPayment;
-
-        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, final String transactionExternalKey, final TransactionType transactionType, final BigDecimal amount, final Currency currency,
-                                            final boolean isApiPayment, final CallContext callContext) {
-            this(account, paymentMethodId, attemptId, paymentId, paymentExternalKey, null, transactionExternalKey, transactionType, amount, currency, null, null, isApiPayment, callContext);
-        }
-
-        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, @Nullable final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType,
-                                            final BigDecimal amount, final Currency currency, @Nullable final BigDecimal processedAmount, @Nullable final Currency processedCurrency, final boolean isApiPayment, final CallContext callContext) {
-            super(callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(), callContext.getComments(), callContext.getUserToken(), callContext.getCreatedDate(), callContext.getUpdatedDate());
-            this.account = account;
-            this.paymentMethodId = paymentMethodId;
-            this.attemptId = attemptId;
-            this.paymentId = paymentId;
-            this.paymentExternalKey = paymentExternalKey;
-            this.transactionId = transactionId;
-            this.transactionExternalKey = transactionExternalKey;
-            this.transactionType = transactionType;
-            this.amount = amount;
-            this.currency = currency;
-            this.processedAmount = processedAmount;
-            this.processedCurrency = processedCurrency;
-            this.isApiPayment = isApiPayment;
-        }
-
-        @Override
-        public UUID getAccountId() {
-            return account.getId();
-        }
-
-        @Override
-        public String getPaymentExternalKey() {
-            return paymentExternalKey;
-        }
-
-        @Override
-        public String getTransactionExternalKey() {
-            return transactionExternalKey;
-        }
-
-        @Override
-        public TransactionType getTransactionType() {
-            return transactionType;
-        }
-
-        @Override
-        public BigDecimal getAmount() {
-            return amount;
-        }
-
-        @Override
-        public Currency getCurrency() {
-            return currency;
-        }
-
-        @Override
-        public UUID getPaymentMethodId() {
-            return paymentMethodId;
-        }
-
-        @Override
-        public UUID getPaymentId() {
-            return paymentId;
-        }
-
-        @Override
-        public UUID getAttemptPaymentId() {
-            return attemptId;
-        }
-
-        @Override
-        public BigDecimal getProcessedAmount() {
-            return processedAmount;
-        }
-
-        @Override
-        public Currency getProcessedCurrency() {
-            return processedCurrency;
-        }
-
-        @Override
-        public boolean isApiPayment() {
-            return isApiPayment;
-        }
-
-        public UUID getTransactionId() {
-            return transactionId;
-        }
-
-        @Override
-        public String toString() {
-            return "DefaultPaymentControlContext{" +
-                   "account=" + account +
-                   ", paymentMethodId=" + paymentMethodId +
-                   ", attemptId=" + attemptId +
-                   ", paymentId=" + paymentId +
-                   ", paymentExternalKey='" + paymentExternalKey + '\'' +
-                   ", transactionId=" + transactionId +
-                   ", transactionExternalKey='" + transactionExternalKey + '\'' +
-                   ", transactionType=" + transactionType +
-                   ", amount=" + amount +
-                   ", currency=" + currency +
-                   ", processedAmount=" + processedAmount +
-                   ", processedCurrency=" + processedCurrency +
-                   ", isApiPayment=" + isApiPayment +
-                   '}';
-        }
-    }
 }
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 602231e..7ad848f 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
@@ -27,8 +27,8 @@ 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 OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry);
+    public PurchaseControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, 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 d9152f0..5f4d7cd 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
@@ -27,8 +27,8 @@ 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 OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    public RefundControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, 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 67f4d9b..00200d1 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
@@ -27,8 +27,8 @@ 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 OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    public VoidControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
     }
 
     @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 9c1dbc7..f3ca0ca 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
@@ -47,6 +47,7 @@ import org.killbill.billing.payment.core.sm.control.AuthorizeControlOperation;
 import org.killbill.billing.payment.core.sm.control.CaptureControlOperation;
 import org.killbill.billing.payment.core.sm.control.ChargebackControlOperation;
 import org.killbill.billing.payment.core.sm.control.CompletionControlOperation;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
 import org.killbill.billing.payment.core.sm.control.CreditControlOperation;
 import org.killbill.billing.payment.core.sm.control.DefaultControlCompleted;
 import org.killbill.billing.payment.core.sm.control.DefaultControlInitiated;
@@ -78,16 +79,19 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner 
     private final PaymentProcessor paymentProcessor;
     private final RetryServiceScheduler retryServiceScheduler;
     private final PaymentControlStateMachineHelper paymentControlStateMachineHelper;
+    private final ControlPluginRunner controlPluginRunner;
 
     @Inject
     public PluginControlPaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
                                                final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry, final Clock clock, final PaymentProcessor paymentProcessor, @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler,
-                                               final PaymentConfig paymentConfig, @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper, final PersistentBus eventBus) {
+                                               final PaymentConfig paymentConfig, @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper,
+                                               final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) {
         super(stateMachineConfig, paymentConfig, paymentDao, locker, pluginRegistry, clock, executor, eventBus, paymentSMHelper);
         this.paymentProcessor = paymentProcessor;
         this.paymentControlPluginRegistry = paymentControlPluginRegistry;
         this.retryServiceScheduler = retryServiceScheduler;
         this.paymentControlStateMachineHelper = paymentControlStateMachineHelper;
+        this.controlPluginRunner = controlPluginRunner;
     }
 
     public Payment run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
@@ -132,7 +136,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner 
 
     public Payment completeRun(final PaymentStateControlContext paymentStateContext) throws PaymentApiException {
         try {
-            final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+            final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
             final LeavingStateCallback leavingStateCallback = new NoopControlInitiated();
             final EnteringStateCallback enteringStateCallback = new DefaultControlCompleted(this, paymentStateContext, paymentControlStateMachineHelper.getRetriedState(), retryServiceScheduler);
 
@@ -165,25 +169,25 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner 
         final OperationCallback callback;
         switch (transactionType) {
             case AUTHORIZE:
-                callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case CAPTURE:
-                callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case PURCHASE:
-                callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case VOID:
-                callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case CREDIT:
-                callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case REFUND:
-                callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             case CHARGEBACK:
-                callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
                 break;
             default:
                 throw new IllegalStateException("Unsupported transaction type " + transactionType);
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
index fb3c06b..0c0b0a7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
@@ -47,6 +47,7 @@ import org.killbill.billing.payment.core.janitor.Janitor;
 import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper;
 import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
 import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
 import org.killbill.billing.payment.dao.DefaultPaymentDao;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.invoice.PaymentTagHandler;
@@ -125,6 +126,8 @@ public class PaymentModule extends KillBillModule {
         bind(StateMachineProvider.class).annotatedWith(Names.named(STATE_MACHINE_PAYMENT)).toInstance(new StateMachineProvider(DEFAULT_STATE_MACHINE_PAYMENT_XML));
         bind(StateMachineConfig.class).annotatedWith(Names.named(STATE_MACHINE_PAYMENT)).toProvider(Key.get(StateMachineProvider.class, Names.named(STATE_MACHINE_PAYMENT)));
         bind(PaymentStateMachineHelper.class).asEagerSingleton();
+
+        bind(ControlPluginRunner.class).asEagerSingleton();
     }
 
     protected void installAutomatonRunner() {
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java
index 91408b7..d1bb59d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java
@@ -22,10 +22,20 @@ import org.killbill.billing.payment.api.PluginProperty;
 
 public class DefaultFailureCallResult implements OnFailurePaymentControlResult {
 
+    private final Iterable<PluginProperty> adjustedPluginProperties;
     private final DateTime nextRetryDate;
 
+    public DefaultFailureCallResult() {
+        this(null, null);
+    }
+
     public DefaultFailureCallResult(final DateTime nextRetryDate) {
+        this(nextRetryDate, null);
+    }
+
+    public DefaultFailureCallResult(final DateTime nextRetryDate, final Iterable<PluginProperty> adjustedPluginProperties) {
         this.nextRetryDate = nextRetryDate;
+        this.adjustedPluginProperties = adjustedPluginProperties;
     }
 
     @Override
@@ -35,6 +45,6 @@ public class DefaultFailureCallResult implements OnFailurePaymentControlResult {
 
     @Override
     public Iterable<PluginProperty> getAdjustedPluginProperties() {
-        return null;
+        return adjustedPluginProperties;
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java
index 0060036..a97dc58 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java
@@ -22,8 +22,18 @@ import org.killbill.billing.payment.api.PluginProperty;
 
 public class DefaultOnSuccessPaymentControlResult implements OnSuccessPaymentControlResult {
 
+    private final Iterable<PluginProperty> adjustedPluginProperties;
+
+    public DefaultOnSuccessPaymentControlResult() {
+        this(null);
+    }
+
+    public DefaultOnSuccessPaymentControlResult(final Iterable<PluginProperty> adjustedPluginProperties) {
+        this.adjustedPluginProperties = adjustedPluginProperties;
+    }
+
     @Override
     public Iterable<PluginProperty> getAdjustedPluginProperties() {
-        return null;
+        return adjustedPluginProperties;
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java
new file mode 100644
index 0000000..a53830f
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApi.java
@@ -0,0 +1,4 @@
+package org.killbill.billing.payment.api;
+
+public class TestPaymentGatewayApi {
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
index 7a69891..14f6758 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
@@ -37,6 +37,7 @@ 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.PaymentProcessor;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
 import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
@@ -62,8 +63,8 @@ public class MockRetryablePaymentAutomatonRunner extends PluginControlPaymentAut
     @Inject
     public MockRetryablePaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, @Named(PaymentModule.STATE_MACHINE_RETRY) final StateMachineConfig retryStateMachine, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry, final Clock clock, final TagInternalApi tagApi, final PaymentProcessor paymentProcessor,
                                                @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, final PaymentConfig paymentConfig, @com.google.inject.name.Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
-                                               final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper retrySMHelper, final PersistentBus eventBus) {
-        super(stateMachineConfig, paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executor, paymentSMHelper, retrySMHelper, eventBus);
+                                               final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper retrySMHelper, final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) {
+        super(stateMachineConfig, paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executor, paymentSMHelper, retrySMHelper, controlPluginRunner, eventBus);
     }
 
     @Override
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 f7007c0..3dd00a4 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
@@ -28,6 +28,7 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.sm.control.AuthorizeControlOperation;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
 import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
@@ -45,8 +46,8 @@ 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 OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry, final PaymentDao paymentDao, final Clock clock) {
-        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry);
+    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);
         this.paymentDao = paymentDao;
         this.clock = clock;
     }
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 b016e7a..433806e 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
@@ -41,6 +41,7 @@ import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.PluginControlPaymentProcessor;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
 import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext;
 import org.killbill.billing.payment.dao.MockPaymentDao;
 import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
@@ -109,6 +110,8 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
     @Inject
     private PaymentControlStateMachineHelper retrySMHelper;
     @Inject
+    private ControlPluginRunner controlPluginRunner;
+    @Inject
     private InternalCallContextFactory internalCallContextFactory;
 
     private Account account;
@@ -169,6 +172,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                 executor,
                 paymentSMHelper,
                 retrySMHelper,
+                controlPluginRunner,
                 eventBus);
 
         paymentStateContext =
@@ -191,7 +195,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                                                         runner.getPaymentPluginDispatcher(),
                                                         paymentStateContext,
                                                         null,
-                                                        runner.getRetryPluginRegistry(),
+                                                        controlPluginRunner,
                                                         paymentDao,
                                                         clock);