killbill-memoizeit

Changes

pom.xml 2(+1 -1)

Details

diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index ce1496b..e66f187 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -83,6 +83,7 @@ import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PaymentMethod;
+import org.killbill.billing.payment.api.PaymentOptions;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.api.AuditLevel;
@@ -710,6 +711,7 @@ public class AccountResource extends JaxRsResourceBase {
     public Response processPayment(final PaymentTransactionJson json,
                                    @PathParam("accountId") final String accountIdStr,
                                    @QueryParam("paymentMethodId") final String paymentMethodIdStr,
+                                   @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                    @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                    @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                    @HeaderParam(HDR_REASON) final String reason,
@@ -729,22 +731,23 @@ public class AccountResource extends JaxRsResourceBase {
         final UUID paymentId = json.getPaymentId() == null ? null : UUID.fromString(json.getPaymentId());
 
         final TransactionType transactionType = TransactionType.valueOf(json.getTransactionType());
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
         final Payment result;
         switch (transactionType) {
             case AUTHORIZE:
-                result = paymentApi.createAuthorization(account, paymentMethodId, paymentId, json.getAmount(), currency,
-                                                        json.getPaymentExternalKey(), json.getTransactionExternalKey(),
-                                                        pluginProperties, callContext);
+                result = paymentApi.createAuthorizationWithPaymentControl(account, paymentMethodId, paymentId, json.getAmount(), currency,
+                                                                          json.getPaymentExternalKey(), json.getTransactionExternalKey(),
+                                                                          pluginProperties, paymentOptions, callContext);
                 break;
             case PURCHASE:
-                result = paymentApi.createPurchase(account, paymentMethodId, paymentId, json.getAmount(), currency,
-                                                   json.getPaymentExternalKey(), json.getTransactionExternalKey(),
-                                                   pluginProperties, callContext);
+                result = paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, paymentId, json.getAmount(), currency,
+                                                                     json.getPaymentExternalKey(), json.getTransactionExternalKey(),
+                                                                     pluginProperties, paymentOptions, callContext);
                 break;
             case CREDIT:
-                result = paymentApi.createCredit(account, paymentMethodId, paymentId, json.getAmount(), currency,
-                                                 json.getPaymentExternalKey(), json.getTransactionExternalKey(),
-                                                 pluginProperties, callContext);
+                result = paymentApi.createCreditWithPaymentControl(account, paymentMethodId, paymentId, json.getAmount(), currency,
+                                                                   json.getPaymentExternalKey(), json.getTransactionExternalKey(),
+                                                                   pluginProperties, paymentOptions, callContext);
                 break;
             default:
                 return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " is not allowed for an account").build();
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index d7c5ab8..836d8c5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -83,6 +83,7 @@ public interface JaxrsResource {
     public static final String QUERY_PAYMENT_AMOUNT = "paymentAmount";
     public static final String QUERY_PAYMENT_WITH_REFUNDS_AND_CHARGEBACKS = "withRefundsAndChargebacks";
     public static final String QUERY_PAYMENT_PLUGIN_NAME = "pluginName";
+    public static final String QUERY_PAYMENT_CONTROL_PLUGIN_NAME = "controlPluginName";
 
     public static final String QUERY_TAGS = "tagList";
     public static final String QUERY_TAGS_INCLUDED_DELETED = "includedDeleted";
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 7d88bef..76ab053 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -372,6 +372,14 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
     }
 
     protected PaymentOptions createInvoicePaymentControlPluginApiPaymentOptions(final boolean isExternalPayment) {
+        return createControlPluginApiPaymentOptions(isExternalPayment, ImmutableList.<String>of("__INVOICE_PAYMENT_CONTROL_PLUGIN__"));
+    }
+
+    protected PaymentOptions createControlPluginApiPaymentOptions(@Nullable final List<String> paymentControlPluginNames) {
+        return createControlPluginApiPaymentOptions(false, paymentControlPluginNames);
+    }
+
+    protected PaymentOptions createControlPluginApiPaymentOptions(final boolean isExternalPayment, final List<String> paymentControlPluginNames) {
         return new PaymentOptions() {
             @Override
             public boolean isExternalPayment() {
@@ -380,8 +388,8 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
 
             @Override
             public List<String> getPaymentControlPluginNames() {
-                /* Will default to org.killbill.payment.invoice.plugin in payment sub-system */
-                return null;
+                // DefaultPaymentApi will add the default configured ones to this list
+                return paymentControlPluginNames;
             }
         };
     }
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 2bf74bb..2bb3816 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
@@ -32,6 +32,7 @@ import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.core.PaymentMethodProcessor;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.PluginRoutingPaymentProcessor;
+import org.killbill.billing.payment.invoice.InvoicePaymentRoutingPluginApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
@@ -40,6 +41,8 @@ import org.killbill.billing.util.entity.Pagination;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableList;
+
 public class DefaultPaymentApi implements PaymentApi {
 
     private static final boolean SHOULD_LOCK_ACCOUNT = true;
@@ -66,7 +69,6 @@ public class DefaultPaymentApi implements PaymentApi {
     @Override
     public Payment createAuthorization(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
                                        final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
-
         checkNotNullParameter(account, "account");
         checkNotNullParameter(paymentMethodId, "paymentMethodId");
         checkNotNullParameter(amount, "amount");
@@ -82,6 +84,29 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
+    public Payment createAuthorizationWithPaymentControl(final Account account, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                                         @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
+                                                         final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        if (paymentControlPluginNames.isEmpty()) {
+            return createAuthorization(account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
+        }
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentMethodId, "paymentMethodId");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
+
+        logAPICall(TransactionType.AUTHORIZE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return pluginRoutingPaymentProcessor.createAuthorization(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                 properties, paymentControlPluginNames, callContext, internalCallContext);
+    }
+
+    @Override
     public Payment createCapture(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentTransactionExternalKey,
                                  final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
 
@@ -101,7 +126,6 @@ public class DefaultPaymentApi implements PaymentApi {
     @Override
     public Payment createPurchase(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
                                   final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
-
         checkNotNullParameter(account, "account");
         checkNotNullParameter(paymentMethodId, "paymentMethodId");
         checkNotNullParameter(amount, "amount");
@@ -119,6 +143,10 @@ public class DefaultPaymentApi implements PaymentApi {
     @Override
     public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey, final String paymentTransactionExternalKey,
                                                     final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        if (paymentControlPluginNames.isEmpty()) {
+            return createPurchase(account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
+        }
 
         checkNotNullParameter(account, "account");
         checkNotNullParameter(amount, "amount");
@@ -140,7 +168,7 @@ public class DefaultPaymentApi implements PaymentApi {
                                            paymentMethodId :
                                            paymentMethodProcessor.createOrGetExternalPaymentMethod(UUID.randomUUID().toString(), account, properties, callContext, internalCallContext);
         return pluginRoutingPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                               properties, toPaymentControlPluginNames(paymentOptions), callContext, internalCallContext);
+                                                            properties, paymentControlPluginNames, callContext, internalCallContext);
 
     }
 
@@ -181,6 +209,10 @@ public class DefaultPaymentApi implements PaymentApi {
     @Override
     public Payment createRefundWithPaymentControl(final Account account, final UUID paymentId, @Nullable final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
                                                   final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        if (paymentControlPluginNames.isEmpty()) {
+            return createRefund(account, paymentId, amount, currency, paymentTransactionExternalKey, properties, callContext);
+        }
 
         checkNotNullParameter(account, "account");
         checkNotNullParameter(currency, "currency");
@@ -195,7 +227,7 @@ public class DefaultPaymentApi implements PaymentApi {
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return pluginRoutingPaymentProcessor.createRefund(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
-                                                             properties, toPaymentControlPluginNames(paymentOptions), callContext, internalCallContext);
+                                                          properties, paymentControlPluginNames, callContext, internalCallContext);
 
     }
 
@@ -203,7 +235,6 @@ public class DefaultPaymentApi implements PaymentApi {
     public Payment createCredit(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
                                 @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
                                 final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
-
         checkNotNullParameter(account, "account");
         checkNotNullParameter(paymentMethodId, "paymentMethodId");
         checkNotNullParameter(amount, "amount");
@@ -220,8 +251,30 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public Payment notifyPendingTransactionOfStateChanged(final Account account, final UUID paymentTransactionId, final boolean isSuccess, final CallContext callContext) throws PaymentApiException {
+    public Payment createCreditWithPaymentControl(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                                  @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
+                                                  final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        if (paymentControlPluginNames.isEmpty()) {
+            return createCredit(account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
+        }
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentMethodId, "paymentMethodId");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
 
+        logAPICall(TransactionType.CREDIT.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return pluginRoutingPaymentProcessor.createCredit(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                          properties, paymentControlPluginNames, callContext, internalCallContext);
+    }
+
+    @Override
+    public Payment notifyPendingTransactionOfStateChanged(final Account account, final UUID paymentTransactionId, final boolean isSuccess, final CallContext callContext) throws PaymentApiException {
         checkNotNullParameter(account, "account");
         checkNotNullParameter(paymentTransactionId, "paymentTransactionId");
 
@@ -254,6 +307,11 @@ public class DefaultPaymentApi implements PaymentApi {
 
     @Override
     public Payment createChargebackWithPaymentControl(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        if (paymentControlPluginNames.isEmpty()) {
+            return createChargeback(account, paymentId, amount, currency, paymentTransactionExternalKey, callContext);
+        }
+
         checkNotNullParameter(account, "account");
         checkNotNullParameter(amount, "amount");
         checkNotNullParameter(currency, "currency");
@@ -262,7 +320,7 @@ public class DefaultPaymentApi implements PaymentApi {
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return pluginRoutingPaymentProcessor.createChargeback(account, paymentId, paymentTransactionExternalKey, amount, currency,
-                                                                 toPaymentControlPluginNames(paymentOptions), callContext, internalCallContext);
+                                                              paymentControlPluginNames, callContext, internalCallContext);
     }
 
     @Override
@@ -427,7 +485,21 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     private List<String> toPaymentControlPluginNames(final PaymentOptions paymentOptions) {
-        return paymentOptions.getPaymentControlPluginNames() != null ? paymentOptions.getPaymentControlPluginNames() : paymentConfig.getPaymentControlPluginNames();
+        // Special path for JAX-RS InvoicePayment endpoints (see JaxRsResourceBase)
+        if (paymentConfig.getPaymentControlPluginNames() != null &&
+            paymentOptions.getPaymentControlPluginNames() != null &&
+            paymentOptions.getPaymentControlPluginNames().size() == 1 &&
+            InvoicePaymentRoutingPluginApi.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 {
diff --git a/payment/src/main/java/org/killbill/billing/payment/bus/InvoiceHandler.java b/payment/src/main/java/org/killbill/billing/payment/bus/InvoiceHandler.java
index a904698..80c1770 100644
--- a/payment/src/main/java/org/killbill/billing/payment/bus/InvoiceHandler.java
+++ b/payment/src/main/java/org/killbill/billing/payment/bus/InvoiceHandler.java
@@ -20,6 +20,7 @@ package org.killbill.billing.payment.bus;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
@@ -32,8 +33,8 @@ import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.events.InvoiceCreationInternalEvent;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
-import org.killbill.billing.payment.invoice.InvoicePaymentRoutingPluginApi;
 import org.killbill.billing.payment.core.PluginRoutingPaymentProcessor;
+import org.killbill.billing.payment.invoice.InvoicePaymentRoutingPluginApi;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.CallContext;
@@ -45,7 +46,6 @@ import org.killbill.billing.util.dao.NonEntityDao;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
 
@@ -93,9 +93,10 @@ public class InvoiceHandler {
             final CallContext callContext = internalContext.toCallContext(nonEntityDao.retrieveIdFromObject(internalContext.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID)));
 
             final BigDecimal amountToBePaid = null; // We let the plugin compute how much should be paid
-            final List<String> paymentControlPluginNames = paymentConfig.getPaymentControlPluginNames() != null ? paymentConfig.getPaymentControlPluginNames() : ImmutableList.of(InvoicePaymentRoutingPluginApi.PLUGIN_NAME);
+            final List<String> paymentControlPluginNames = paymentConfig.getPaymentControlPluginNames() != null ? new LinkedList<String>(paymentConfig.getPaymentControlPluginNames()) : new LinkedList<String>();
+            paymentControlPluginNames.add(InvoicePaymentRoutingPluginApi.PLUGIN_NAME);
             pluginRoutingPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amountToBePaid, account.getCurrency(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
-                                                            properties, paymentControlPluginNames, callContext, internalContext);
+                                                         properties, paymentControlPluginNames, callContext, internalContext);
         } catch (final AccountApiException e) {
             log.error("Failed to process invoice payment", e);
         } catch (final PaymentApiException e) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginRoutingPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginRoutingPaymentProcessor.java
index a1c4e75..79be2f0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PluginRoutingPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginRoutingPaymentProcessor.java
@@ -17,6 +17,7 @@
 package org.killbill.billing.payment.core;
 
 import java.math.BigDecimal;
+import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
@@ -88,124 +89,123 @@ public class PluginRoutingPaymentProcessor extends ProcessorBase {
     }
 
     public Payment createAuthorization(final boolean isApiPayment, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey, final String transactionExternalKey,
-                                             final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+                                       final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
-                                                                TransactionType.AUTHORIZE,
-                                                                account,
-                                                                paymentMethodId,
-                                                                paymentId,
-                                                                paymentExternalKey,
-                                                                transactionExternalKey,
-                                                                amount,
-                                                                currency,
-                                                                properties,
-                                                                paymentControlPluginNames,
-                                                                callContext, internalCallContext);
+                                                          TransactionType.AUTHORIZE,
+                                                          account,
+                                                          paymentMethodId,
+                                                          paymentId,
+                                                          paymentExternalKey,
+                                                          transactionExternalKey,
+                                                          amount,
+                                                          currency,
+                                                          properties,
+                                                          paymentControlPluginNames,
+                                                          callContext, internalCallContext);
     }
 
     public Payment createCapture(final boolean isApiPayment, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency,
-                                       final String transactionExternalKey,
-                                       final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames,
-                                       final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+                                 final String transactionExternalKey,
+                                 final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames,
+                                 final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
-                                                                TransactionType.CAPTURE,
-                                                                account,
-                                                                null,
-                                                                paymentId,
-                                                                null,
-                                                                transactionExternalKey,
-                                                                amount,
-                                                                currency,
-                                                                properties,
-                                                                paymentControlPluginNames,
-                                                                callContext, internalCallContext);
+                                                          TransactionType.CAPTURE,
+                                                          account,
+                                                          null,
+                                                          paymentId,
+                                                          null,
+                                                          transactionExternalKey,
+                                                          amount,
+                                                          currency,
+                                                          properties,
+                                                          paymentControlPluginNames,
+                                                          callContext, internalCallContext);
     }
 
     public Payment createPurchase(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency,
-                                        final String paymentExternalKey, final String transactionExternalKey, final Iterable<PluginProperty> properties,
-                                        final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+                                  final String paymentExternalKey, final String transactionExternalKey, final Iterable<PluginProperty> properties,
+                                  final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
-                                                                TransactionType.PURCHASE,
-                                                                account,
-                                                                paymentMethodId,
-                                                                paymentId,
-                                                                paymentExternalKey,
-                                                                transactionExternalKey,
-                                                                amount,
-                                                                currency,
-                                                                properties,
-                                                                paymentControlPluginNames,
-                                                                callContext, internalCallContext);
+                                                          TransactionType.PURCHASE,
+                                                          account,
+                                                          paymentMethodId,
+                                                          paymentId,
+                                                          paymentExternalKey,
+                                                          transactionExternalKey,
+                                                          amount,
+                                                          currency,
+                                                          properties,
+                                                          paymentControlPluginNames,
+                                                          callContext, internalCallContext);
     }
 
     public Payment createVoid(final boolean isApiPayment, final Account account, final UUID paymentId, final String transactionExternalKey,
-                                    final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+                              final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
-                                                                TransactionType.VOID,
-                                                                account,
-                                                                null,
-                                                                paymentId,
-                                                                null,
-                                                                transactionExternalKey,
-                                                                null,
-                                                                null,
-                                                                properties,
-                                                                null,
-                                                                callContext, internalCallContext);
+                                                          TransactionType.VOID,
+                                                          account,
+                                                          null,
+                                                          paymentId,
+                                                          null,
+                                                          transactionExternalKey,
+                                                          null,
+                                                          null,
+                                                          properties,
+                                                          null,
+                                                          callContext, internalCallContext);
     }
 
     public Payment createRefund(final boolean isApiPayment, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final String transactionExternalKey,
-                                      final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+                                final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
-                                                                TransactionType.REFUND,
-                                                                account,
-                                                                null,
-                                                                paymentId,
-                                                                null,
-                                                                transactionExternalKey,
-                                                                amount,
-                                                                currency,
-                                                                properties,
-                                                                paymentControlPluginNames,
-                                                                callContext, internalCallContext);
+                                                          TransactionType.REFUND,
+                                                          account,
+                                                          null,
+                                                          paymentId,
+                                                          null,
+                                                          transactionExternalKey,
+                                                          amount,
+                                                          currency,
+                                                          properties,
+                                                          paymentControlPluginNames,
+                                                          callContext, internalCallContext);
     }
 
     public Payment createCredit(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey,
-                                      final String transactionExternalKey, final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+                                final String transactionExternalKey, final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
 
         return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
-                                                                TransactionType.CREDIT,
-                                                                account,
-                                                                paymentMethodId,
-                                                                paymentId,
-                                                                paymentExternalKey,
-                                                                transactionExternalKey,
-                                                                amount,
-                                                                currency,
-                                                                properties,
-                                                                paymentControlPluginNames,
-                                                                callContext, internalCallContext);
+                                                          TransactionType.CREDIT,
+                                                          account,
+                                                          paymentMethodId,
+                                                          paymentId,
+                                                          paymentExternalKey,
+                                                          transactionExternalKey,
+                                                          amount,
+                                                          currency,
+                                                          properties,
+                                                          paymentControlPluginNames,
+                                                          callContext, internalCallContext);
     }
 
     public Payment createChargeback(final Account account, final UUID paymentId, final String transactionExternalKey, final BigDecimal amount, final Currency currency,
-                                          final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+                                    final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledPaymentAutomatonRunner.run(true,
-                                                                TransactionType.CHARGEBACK,
-                                                                account,
-                                                                null,
-                                                                paymentId,
-                                                                null,
-                                                                transactionExternalKey,
-                                                                amount,
-                                                                currency,
-                                                                ImmutableList.<PluginProperty>of(),
-                                                                paymentControlPluginNames,
-                                                                callContext, internalCallContext);
+                                                          TransactionType.CHARGEBACK,
+                                                          account,
+                                                          null,
+                                                          paymentId,
+                                                          null,
+                                                          transactionExternalKey,
+                                                          amount,
+                                                          currency,
+                                                          ImmutableList.<PluginProperty>of(),
+                                                          paymentControlPluginNames,
+                                                          callContext, internalCallContext);
     }
 
     public void retryPaymentTransaction(final UUID attemptId, final List<String> paymentControlPluginNames, final InternalCallContext internalCallContext) {
         try {
-
             final PaymentAttemptModelDao attempt = paymentDao.getPaymentAttempt(attemptId, internalCallContext);
             final PaymentModelDao payment = paymentDao.getPaymentByExternalKey(attempt.getPaymentExternalKey(), internalCallContext);
             final UUID paymentId = payment != null ? payment.getId() : null;
@@ -217,39 +217,35 @@ public class PluginRoutingPaymentProcessor extends ProcessorBase {
 
             final State state = retrySMHelper.getState(attempt.getStateName());
             pluginControlledPaymentAutomatonRunner.run(state,
-                                                             false,
-                                                             attempt.getTransactionType(),
-                                                             account,
-                                                             attempt.getPaymentMethodId(),
-                                                             paymentId,
-                                                             attempt.getPaymentExternalKey(),
-                                                             attempt.getTransactionExternalKey(),
-                                                             attempt.getAmount(),
-                                                             attempt.getCurrency(),
-                                                             pluginProperties,
-                                                             paymentControlPluginNames,
-                                                             callContext,
-                                                             internalCallContext);
-
-        } catch (AccountApiException e) {
+                                                       false,
+                                                       attempt.getTransactionType(),
+                                                       account,
+                                                       attempt.getPaymentMethodId(),
+                                                       paymentId,
+                                                       attempt.getPaymentExternalKey(),
+                                                       attempt.getTransactionExternalKey(),
+                                                       attempt.getAmount(),
+                                                       attempt.getCurrency(),
+                                                       pluginProperties,
+                                                       paymentControlPluginNames,
+                                                       callContext,
+                                                       internalCallContext);
+
+        } catch (final AccountApiException e) {
             log.warn("Failed to retry attempt " + attemptId + toPluginNamesOnError(" for plugins ", paymentControlPluginNames), e);
-        } catch (PaymentApiException e) {
+        } catch (final PaymentApiException e) {
             log.warn("Failed to retry attempt " + attemptId + toPluginNamesOnError(" for plugins ", paymentControlPluginNames), e);
-        } catch (PluginPropertySerializerException e) {
+        } catch (final PluginPropertySerializerException e) {
             log.warn("Failed to retry attempt " + attemptId + toPluginNamesOnError(" for plugins ", paymentControlPluginNames), e);
-        } catch (MissingEntryException e) {
+        } catch (final MissingEntryException e) {
             log.warn("Failed to retry attempt " + attemptId + toPluginNamesOnError(" for plugins ", paymentControlPluginNames), e);
         }
     }
 
-    private String toPluginNamesOnError(final String prefixMessage, final List<String> paymentControlPluginNames) {
-        if (paymentControlPluginNames == null || paymentControlPluginNames.size() == 0) {
+    private String toPluginNamesOnError(final String prefixMessage, final Collection<String> paymentControlPluginNames) {
+        if (paymentControlPluginNames == null || paymentControlPluginNames.isEmpty()) {
             return "";
         }
-        final StringBuilder tmp = new StringBuilder(prefixMessage)
-                .append("(")
-                .append(JOINER.join(paymentControlPluginNames))
-                .append(")");
-        return tmp.toString();
+        return prefixMessage + "(" + JOINER.join(paymentControlPluginNames) + ")";
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java
index 04afc77..d328dd3 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java
@@ -68,13 +68,13 @@ public class ChargebackOperation extends PaymentOperation {
             status = PaymentPluginStatus.PROCESSED;
         }
         return new DefaultNoOpPaymentInfoPlugin(paymentStateContext.getPaymentId(),
-                                                 paymentStateContext.getTransactionId(),
-                                                 TransactionType.CHARGEBACK,
-                                                 paymentStateContext.getAmount(),
-                                                 paymentStateContext.getCurrency(),
-                                                 null,
-                                                 null,
-                                                 status,
-                                                 null);
+                                                paymentStateContext.getTransactionId(),
+                                                TransactionType.CHARGEBACK,
+                                                paymentStateContext.getAmount(),
+                                                paymentStateContext.getCurrency(),
+                                                null,
+                                                null,
+                                                status,
+                                                null);
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
index 55fcb3f..4f72dae 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
@@ -25,17 +25,11 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
-import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.events.BusInternalEvent;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
-import org.killbill.billing.payment.api.DefaultPaymentErrorEvent;
-import org.killbill.billing.payment.api.DefaultPaymentInfoEvent;
-import org.killbill.billing.payment.api.DefaultPaymentPluginErrorEvent;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.TransactionStatus;
-import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentMethodModelDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
@@ -43,11 +37,9 @@ import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
 public class PaymentAutomatonDAOHelper {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
index 7d79cfb..aed47f5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -45,11 +45,11 @@ import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionType;
-import org.killbill.billing.payment.invoice.InvoicePaymentRoutingPluginApi;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.payment.invoice.InvoicePaymentRoutingPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.config.PaymentConfig;
@@ -102,7 +102,6 @@ public class PaymentAutomatonRunner {
                     @Nullable final BigDecimal amount, @Nullable final Currency currency,
                     final boolean shouldLockAccount, final OperationResult overridePluginOperationResult, final Iterable<PluginProperty> properties,
                     final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
-
         final DateTime utcNow = clock.getUTCNow();
 
         final PaymentStateContext paymentStateContext = new PaymentStateContext(isApiPayment, paymentId, transactionId, attemptId, paymentExternalKey, paymentTransactionExternalKey, transactionType,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java
index e31416e..f3a81f7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java
@@ -82,7 +82,7 @@ public abstract class PaymentEnteringStateCallback implements EnteringStateCallb
         // and decides to return null; in all cases this is seen as a PLUGIN_FAILURE
         //
         if (paymentInfoPlugin == null || paymentInfoPlugin.getStatus() == null) {
-                return TransactionStatus.PLUGIN_FAILURE;
+            return TransactionStatus.PLUGIN_FAILURE;
         }
 
         //
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentLeavingStateCallback.java
index 898669e..aa3a58b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentLeavingStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentLeavingStateCallback.java
@@ -54,7 +54,7 @@ public abstract class PaymentLeavingStateCallback implements LeavingStateCallbac
             if (paymentStateContext.getTransactionId() == null) {
                 daoHelper.createNewPaymentTransaction();
             } else {
-                final PaymentTransactionModelDao transactionModelDao =  daoHelper.paymentDao.getPaymentTransaction(paymentStateContext.getTransactionId(), paymentStateContext.getInternalCallContext());
+                final PaymentTransactionModelDao transactionModelDao = daoHelper.paymentDao.getPaymentTransaction(paymentStateContext.getTransactionId(), paymentStateContext.getInternalCallContext());
                 paymentStateContext.setPaymentTransactionModelDao(transactionModelDao);
             }
         } catch (PaymentApiException e) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java
index daf9c67..bc9bfdb 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java
@@ -205,7 +205,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
     }
 
     private OperationResult processPaymentInfoPlugin() {
-        if (paymentStateContext.getPaymentInfoPlugin() == null) {
+        if (paymentStateContext.getPaymentInfoPlugin() == null || paymentStateContext.getPaymentInfoPlugin().getStatus() == null) {
             return OperationResult.FAILURE;
         }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
index 3aafe01..28af33a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
@@ -47,12 +47,12 @@ public class PaymentStateContext {
     protected PaymentTransactionInfoPlugin paymentInfoPlugin;
     protected BigDecimal amount;
     protected String paymentExternalKey;
+    protected String paymentTransactionExternalKey;
     protected Currency currency;
 
     // Can be updated later via paymentTransactionModelDao (e.g. for auth or purchase)
     protected final UUID paymentId;
     protected final UUID transactionId;
-    protected final String paymentTransactionExternalKey;
     protected final Account account;
     protected final TransactionType transactionType;
     protected final boolean shouldLockAccountAndDispatch;
@@ -80,7 +80,7 @@ public class PaymentStateContext {
         this.isApiPayment = isApiPayment;
         this.paymentId = paymentId;
         this.transactionId = transactionId;
-        this.attemptId= attemptId;
+        this.attemptId = attemptId;
         this.paymentExternalKey = paymentExternalKey;
         this.paymentTransactionExternalKey = paymentTransactionExternalKey;
         this.transactionType = transactionType;
@@ -148,6 +148,10 @@ public class PaymentStateContext {
         return paymentTransactionExternalKey;
     }
 
+    public void setPaymentTransactionExternalKey(final String paymentTransactionExternalKey) {
+        this.paymentTransactionExternalKey = paymentTransactionExternalKey;
+    }
+
     public Account getAccount() {
         return account;
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
index bc58d65..dfea014 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
@@ -121,7 +121,6 @@ public class PaymentStateMachineHelper {
         }
     }
 
-
     public String getErroredStateForTransaction(final TransactionType transactionType) {
         switch (transactionType) {
             case AUTHORIZE:
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginRoutingPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginRoutingPaymentAutomatonRunner.java
index 00e4435..1731335 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginRoutingPaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginRoutingPaymentAutomatonRunner.java
@@ -71,7 +71,7 @@ public class PluginRoutingPaymentAutomatonRunner extends PaymentAutomatonRunner 
     @Inject
     public PluginRoutingPaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
                                                final OSGIServiceRegistration<PaymentRoutingPluginApi> retryPluginRegistry, final Clock clock, final PaymentProcessor paymentProcessor, @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler,
-                                               final PaymentConfig paymentConfig, @com.google.inject.name.Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, PaymentStateMachineHelper paymentSMHelper, RetryStateMachineHelper retrySMHelper, final PersistentBus eventBus) {
+                                               final PaymentConfig paymentConfig, @com.google.inject.name.Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentStateMachineHelper paymentSMHelper, final RetryStateMachineHelper retrySMHelper, final PersistentBus eventBus) {
         super(stateMachineConfig, paymentConfig, paymentDao, locker, pluginRegistry, clock, executor, eventBus, paymentSMHelper);
         this.paymentProcessor = paymentProcessor;
         this.paymentControlPluginRegistry = retryPluginRegistry;
@@ -93,23 +93,20 @@ public class PluginRoutingPaymentAutomatonRunner extends PaymentAutomatonRunner 
                        @Nullable final BigDecimal amount, @Nullable final Currency currency,
                        final Iterable<PluginProperty> properties, @Nullable final List<String> paymentControlPluginNames,
                        final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
-
         final RetryablePaymentStateContext paymentStateContext = createContext(isApiPayment, transactionType, account, paymentMethodId,
-                                                                                     paymentId, paymentExternalKey,
-                                                                                     paymentTransactionExternalKey,
-                                                                                     amount, currency,
-                                                                                     properties, paymentControlPluginNames, callContext, internalCallContext);
+                                                                               paymentId, paymentExternalKey,
+                                                                               paymentTransactionExternalKey,
+                                                                               amount, currency,
+                                                                               properties, paymentControlPluginNames, callContext, internalCallContext);
         try {
-
             final OperationCallback callback = createOperationCallback(transactionType, paymentStateContext);
             final LeavingStateCallback leavingStateCallback = new RetryLeavingStateCallback(this, paymentStateContext, paymentDao, retrySMHelper.getInitialState(), retrySMHelper.getRetriedState(), transactionType);
             final EnteringStateCallback enteringStateCallback = new RetryEnteringStateCallback(this, paymentStateContext, retryServiceScheduler);
 
             state.runOperation(retrySMHelper.getRetryOperation(), callback, enteringStateCallback, leavingStateCallback);
-
-        } catch (MissingEntryException e) {
+        } catch (final MissingEntryException e) {
             throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
-        } catch (OperationException e) {
+        } catch (final OperationException e) {
             if (e.getCause() == null) {
                 // Unclear if we should check whether there is a result that was set and return that result.
                 throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
@@ -123,18 +120,15 @@ public class PluginRoutingPaymentAutomatonRunner extends PaymentAutomatonRunner 
     }
 
     public Payment completeRun(final RetryablePaymentStateContext paymentStateContext) throws PaymentApiException {
-
         try {
-
             final OperationCallback callback = new RetryCompletionOperationCallback(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
             final LeavingStateCallback leavingStateCallback = new RetryNoopLeavingStateCallback();
             final EnteringStateCallback enteringStateCallback = new RetryEnteringStateCallback(this, paymentStateContext, retryServiceScheduler);
 
             retrySMHelper.getInitialState().runOperation(retrySMHelper.getRetryOperation(), callback, enteringStateCallback, leavingStateCallback);
-
-        } catch (MissingEntryException e) {
+        } catch (final MissingEntryException e) {
             throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
-        } catch (OperationException e) {
+        } catch (final OperationException e) {
             if (e.getCause() == null) {
                 throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
             } else if (e.getCause() instanceof PaymentApiException) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PurchaseOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PurchaseOperation.java
index 38757f5..446ca21 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PurchaseOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PurchaseOperation.java
@@ -40,12 +40,12 @@ public class PurchaseOperation extends PaymentOperation {
     protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
         logger.debug("Starting PURCHASE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
         return plugin.purchasePayment(paymentStateContext.getAccount().getId(),
-                                     paymentStateContext.getPaymentId(),
-                                     paymentStateContext.getTransactionId(),
-                                     paymentStateContext.getPaymentMethodId(),
-                                     paymentStateContext.getAmount(),
-                                     paymentStateContext.getCurrency(),
-                                     paymentStateContext.getProperties(),
-                                     paymentStateContext.getCallContext());
+                                      paymentStateContext.getPaymentId(),
+                                      paymentStateContext.getTransactionId(),
+                                      paymentStateContext.getPaymentMethodId(),
+                                      paymentStateContext.getAmount(),
+                                      paymentStateContext.getCurrency(),
+                                      paymentStateContext.getProperties(),
+                                      paymentStateContext.getCallContext());
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCaptureOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCaptureOperationCallback.java
index 1e5454e..e9d3a28 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCaptureOperationCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCaptureOperationCallback.java
@@ -34,15 +34,15 @@ public class RetryCaptureOperationCallback extends RetryOperationCallback {
     @Override
     protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
         return paymentProcessor.createCapture(retryablePaymentStateContext.isApiPayment(),
-                                                    retryablePaymentStateContext.getAttemptId(),
-                                                    retryablePaymentStateContext.getAccount(),
-                                                    retryablePaymentStateContext.getPaymentMethodId(),
-                                                    retryablePaymentStateContext.getAmount(),
-                                                    retryablePaymentStateContext.getCurrency(),
-                                                    retryablePaymentStateContext.getPaymentTransactionExternalKey(),
-                                                    false,
-                                                    retryablePaymentStateContext.getProperties(),
-                                                    retryablePaymentStateContext.getCallContext(),
-                                                    retryablePaymentStateContext.getInternalCallContext());
+                                              retryablePaymentStateContext.getAttemptId(),
+                                              retryablePaymentStateContext.getAccount(),
+                                              retryablePaymentStateContext.getPaymentMethodId(),
+                                              retryablePaymentStateContext.getAmount(),
+                                              retryablePaymentStateContext.getCurrency(),
+                                              retryablePaymentStateContext.getPaymentTransactionExternalKey(),
+                                              false,
+                                              retryablePaymentStateContext.getProperties(),
+                                              retryablePaymentStateContext.getCallContext(),
+                                              retryablePaymentStateContext.getInternalCallContext());
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java
index d0247b6..d2d0ce3 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java
@@ -42,7 +42,6 @@ public class RetryEnteringStateCallback implements EnteringStateCallback {
 
     @Override
     public void enteringState(final State state, final OperationCallback operationCallback, final OperationResult operationResult, final LeavingStateCallback leavingStateCallback) {
-
         final PaymentAttemptModelDao attempt = retryablePaymentAutomatonRunner.paymentDao.getPaymentAttempt(paymentStateContext.getAttemptId(), paymentStateContext.internalCallContext);
         final UUID transactionId = paymentStateContext.getCurrentTransaction() != null ?
                                    paymentStateContext.getCurrentTransaction().getId() :
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
index 8de364c..afe3140 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
@@ -16,14 +16,17 @@
 
 package org.killbill.billing.payment.core.sm;
 
+import java.util.UUID;
+
 import org.joda.time.DateTime;
 import org.killbill.automaton.OperationException;
 import org.killbill.automaton.State;
 import org.killbill.automaton.State.LeavingStateCallback;
 import org.killbill.billing.payment.api.TransactionType;
-import org.killbill.billing.payment.dao.PaymentModelDao;
 import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dao.PluginPropertySerializer;
 import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
 
@@ -31,7 +34,7 @@ import com.google.common.base.Preconditions;
 
 public class RetryLeavingStateCallback implements LeavingStateCallback {
 
-    private PluginRoutingPaymentAutomatonRunner retryablePaymentAutomatonRunner;
+    private final PluginRoutingPaymentAutomatonRunner retryablePaymentAutomatonRunner;
     private final RetryablePaymentStateContext stateContext;
     private final State initialState;
     private final State retriedState;
@@ -50,37 +53,39 @@ public class RetryLeavingStateCallback implements LeavingStateCallback {
 
     @Override
     public void leavingState(final State state) throws OperationException {
-
         final DateTime utcNow = retryablePaymentAutomatonRunner.clock.getUTCNow();
 
-        Preconditions.checkState(stateContext.getPaymentExternalKey() != null || /* CAPTURE, PURCHASE, CREDIT calls will provide the paymentId */
-                                 stateContext.getPaymentId() != null);
-        if (stateContext.getPaymentExternalKey() == null) {
+        if (stateContext.getPaymentId() != null && stateContext.getPaymentExternalKey() == null) {
             final PaymentModelDao payment = paymentDao.getPayment(stateContext.getPaymentId(), stateContext.internalCallContext);
-            Preconditions.checkState(payment != null);
+            Preconditions.checkNotNull(payment, "payment cannot be null for id " + stateContext.getPaymentId());
             stateContext.setPaymentExternalKey(payment.getExternalKey());
+        } else if (stateContext.getPaymentExternalKey() == null) {
+            stateContext.setPaymentExternalKey(UUID.randomUUID().toString());
+        }
+        if (stateContext.getTransactionId() != null && stateContext.getPaymentTransactionExternalKey() == null) {
+            final PaymentTransactionModelDao paymentTransactionModelDao = paymentDao.getPaymentTransaction(stateContext.getTransactionId(), stateContext.internalCallContext);
+            Preconditions.checkNotNull(paymentTransactionModelDao, "paymentTransaction cannot be null for id " + stateContext.getTransactionId());
+            stateContext.setPaymentTransactionExternalKey(paymentTransactionModelDao.getTransactionExternalKey());
+        } else if (stateContext.getPaymentTransactionExternalKey() == null) {
+            stateContext.setPaymentTransactionExternalKey(UUID.randomUUID().toString());
         }
 
-
-        if (state.getName().equals(initialState.getName()) ||
-            state.getName().equals(retriedState.getName())) {
-
+        if (state.getName().equals(initialState.getName()) || state.getName().equals(retriedState.getName())) {
             try {
-                final byte [] serializedProperties = PluginPropertySerializer.serialize(stateContext.getProperties());
-
+                final byte[] serializedProperties = PluginPropertySerializer.serialize(stateContext.getProperties());
 
                 final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(stateContext.getAccount().getId(), stateContext.getPaymentMethodId(),
-                                                                                  utcNow, utcNow, stateContext.getPaymentExternalKey(), null,
-                                                                                  stateContext.paymentTransactionExternalKey, transactionType, initialState.getName(),
+                                                                                  utcNow, utcNow, stateContext.getPaymentExternalKey(), stateContext.getTransactionId(),
+                                                                                  stateContext.getPaymentTransactionExternalKey(), transactionType, initialState.getName(),
                                                                                   stateContext.getAmount(), stateContext.getCurrency(),
                                                                                   stateContext.getPaymentControlPluginNames(), serializedProperties);
 
-                retryablePaymentAutomatonRunner.paymentDao.insertPaymentAttemptWithProperties(attempt, stateContext.internalCallContext);
+                retryablePaymentAutomatonRunner.paymentDao.insertPaymentAttemptWithProperties(attempt, stateContext.getInternalCallContext());
+
                 stateContext.setAttemptId(attempt.getId());
-            } catch (PluginPropertySerializerException e) {
+            } catch (final PluginPropertySerializerException e) {
                 throw new OperationException(e);
             }
-
         }
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
index c118d18..dcad8a0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
@@ -96,7 +96,7 @@ public abstract class RetryOperationCallback extends OperationCallbackBase<Payme
                 final PriorPaymentRoutingResult pluginResult;
                 try {
                     pluginResult = getPluginResult(retryablePaymentStateContext.getPaymentControlPluginNames(), paymentControlContext);
-                    if (pluginResult.isAborted()) {
+                    if (pluginResult != null && pluginResult.isAborted()) {
                         // Transition to ABORTED
                         return PluginDispatcher.createPluginDispatcherReturnType(OperationResult.EXCEPTION);
                     }
@@ -176,7 +176,7 @@ public abstract class RetryOperationCallback extends OperationCallbackBase<Payme
     }
 
     protected void onCompletion(final List<String> paymentControlPluginNames, final PaymentRoutingContext paymentControlContext) {
-        for (String pluginName : paymentControlPluginNames) {
+        for (final String pluginName : paymentControlPluginNames) {
             final PaymentRoutingPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
             if (plugin != null) {
                 try {
@@ -209,17 +209,16 @@ public abstract class RetryOperationCallback extends OperationCallbackBase<Payme
     }
 
     private PriorPaymentRoutingResult getPluginResult(final List<String> paymentControlPluginNames, final PaymentRoutingContext paymentControlContextArg) throws PaymentRoutingApiException {
-
         // Return as soon as the first plugin aborts, or the last result for the last plugin
         PriorPaymentRoutingResult prevResult = null;
 
         PaymentRoutingContext inputPaymentRoutingContext = paymentControlContextArg;
 
-        for (String pluginName : paymentControlPluginNames) {
+        for (final String pluginName : paymentControlPluginNames) {
             final PaymentRoutingPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
             if (plugin == null) {
                 // First call to plugin, we log warn, if plugin is not registered
-                logger.warn("Skipping payment plugin invoice {} when fetching results", pluginName);
+                logger.warn("Skipping unknown payment control plugin {} when fetching results", pluginName);
                 continue;
             }
             prevResult = plugin.priorCall(inputPaymentRoutingContext, paymentStateContext.getProperties());
@@ -255,7 +254,7 @@ public abstract class RetryOperationCallback extends OperationCallbackBase<Payme
 
     private DateTime getNextRetryDate(final List<String> paymentControlPluginNames, final PaymentRoutingContext paymentControlContext) {
         DateTime candidate = null;
-        for (String pluginName : paymentControlPluginNames) {
+        for (final String pluginName : paymentControlPluginNames) {
             final PaymentRoutingPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
             if (plugin != null) {
                 try {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java
index 0f261cc..30bb1ed 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java
@@ -37,7 +37,6 @@ public class RetryStateMachineHelper {
     private static final String INIT_STATE_NAME = "INIT";
     private static final String RETRIED_STATE_NAME = "RETRIED";
 
-
     private final StateMachineConfig retryStateMachineConfig;
     private final StateMachine retryStateMachine;
     private final Operation retryOperation;
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
index 85d218a..dfbb709 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
@@ -24,7 +24,6 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.util.entity.Entity;
@@ -48,13 +47,13 @@ public class PaymentAttemptModelDao extends EntityModelDaoBase implements Entity
     private BigDecimal amount;
     private Currency currency;
     private String pluginName;
-    private byte [] pluginProperties;
+    private byte[] pluginProperties;
 
     public PaymentAttemptModelDao() { /* For the DAO mapper */ }
 
     public PaymentAttemptModelDao(final UUID accountId, final UUID paymentMethodId, final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
                                   final String paymentExternalKey, final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType,
-                                  final String stateName, final BigDecimal amount, final Currency currency, final String pluginName, final byte [] pluginProperties) {
+                                  final String stateName, final BigDecimal amount, final Currency currency, final String pluginName, final byte[] pluginProperties) {
         super(id, createdDate, updatedDate);
         this.accountId = accountId;
         this.paymentMethodId = paymentMethodId;
@@ -71,7 +70,7 @@ public class PaymentAttemptModelDao extends EntityModelDaoBase implements Entity
 
     public PaymentAttemptModelDao(final UUID accountId, final UUID paymentMethodId, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
                                   final String paymentExternalKey, final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType, final String stateName,
-                                  final BigDecimal amount, final Currency currency, final List<String> paymentControlPluginNames,  final byte [] pluginProperties) {
+                                  final BigDecimal amount, final Currency currency, final List<String> paymentControlPluginNames, final byte[] pluginProperties) {
         this(accountId, paymentMethodId, UUID.randomUUID(), createdDate, updatedDate, paymentExternalKey, transactionId, transactionExternalKey, transactionType, stateName,
              amount, currency, toPluginNames(paymentControlPluginNames), pluginProperties);
     }
@@ -116,11 +115,11 @@ public class PaymentAttemptModelDao extends EntityModelDaoBase implements Entity
         this.pluginName = pluginName;
     }
 
-    public byte [] getPluginProperties() {
+    public byte[] getPluginProperties() {
         return pluginProperties;
     }
 
-    public void setPluginProperties(final byte [] pluginProperties) {
+    public void setPluginProperties(final byte[] pluginProperties) {
         this.pluginProperties = pluginProperties;
     }
 
@@ -168,7 +167,7 @@ public class PaymentAttemptModelDao extends EntityModelDaoBase implements Entity
         if (pluginName == null) {
             return ImmutableList.<String>of();
         }
-        final String [] parts = pluginName.split(",");
+        final String[] parts = pluginName.split(",");
         return ImmutableList.<String>copyOf(parts);
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java
index 1fffb07..a670f23 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java
@@ -21,7 +21,6 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
-import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
@@ -40,7 +39,6 @@ public class PaymentModelDao extends EntityModelDaoBase implements EntityModelDa
     private String stateName;
     private String lastSuccessStateName;
 
-
     public PaymentModelDao() { /* For the DAO mapper */ }
 
     public PaymentModelDao(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate, final UUID accountId,
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 72741f9..7bcf32e 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
@@ -60,6 +60,7 @@ import org.killbill.commons.concurrent.WithProfilingThreadPoolExecutor;
 import org.killbill.xmlloader.XMLLoader;
 import org.skife.config.ConfigurationObjectFactory;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.io.Resources;
 import com.google.inject.Key;
 import com.google.inject.TypeLiteral;
@@ -76,6 +77,11 @@ public class PaymentModule extends KillBillModule {
     public static final String STATE_MACHINE_RETRY = "RetryStateMachine";
     public static final String STATE_MACHINE_PAYMENT = "PaymentStateMachine";
 
+    @VisibleForTesting
+    static final String DEFAULT_STATE_MACHINE_RETRY_XML = "org/killbill/billing/payment/retry/RetryStates.xml";
+    @VisibleForTesting
+    static final String DEFAULT_STATE_MACHINE_PAYMENT_XML = "org/killbill/billing/payment/PaymentStates.xml";
+
     public PaymentModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
@@ -106,11 +112,11 @@ public class PaymentModule extends KillBillModule {
 
     protected void installStateMachines() {
 
-        bind(StateMachineProvider.class).annotatedWith(Names.named(STATE_MACHINE_RETRY)).toInstance(new StateMachineProvider("org/killbill/billing/payment/retry/RetryStates.xml"));
+        bind(StateMachineProvider.class).annotatedWith(Names.named(STATE_MACHINE_RETRY)).toInstance(new StateMachineProvider(DEFAULT_STATE_MACHINE_RETRY_XML));
         bind(StateMachineConfig.class).annotatedWith(Names.named(STATE_MACHINE_RETRY)).toProvider(Key.get(StateMachineProvider.class, Names.named(STATE_MACHINE_RETRY)));
         bind(RetryStateMachineHelper.class).asEagerSingleton();
 
-        bind(StateMachineProvider.class).annotatedWith(Names.named(STATE_MACHINE_PAYMENT)).toInstance(new StateMachineProvider("org/killbill/billing/payment/PaymentStates.xml"));
+        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();
     }
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index e0fc119..b795594 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -296,7 +296,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
             paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
                                                         createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
             Assert.fail("Unexpected success");
-        } catch (PaymentApiException e) {
+        } catch (final PaymentApiException e) {
             assertTrue(e.getCause() instanceof PaymentRoutingApiException);
         }
     }
@@ -377,7 +377,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         try {
             paymentApi.createRefundWithPaymentControl(account, payment.getId(), BigDecimal.TEN, Currency.USD, transactionExternalKey2,
                                                       refundProperties, INVOICE_PAYMENT, callContext);
-        } catch (PaymentApiException e) {
+        } catch (final PaymentApiException e) {
             assertTrue(e.getCause() instanceof PaymentRoutingApiException);
         }
     }
@@ -472,7 +472,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
             paymentApi.createPurchase(account, account.getPaymentMethodId(), payment.getId(), requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
                                       ImmutableList.<PluginProperty>of(), callContext);
             Assert.fail("Purchase not succeed after a chargeback");
-        } catch (PaymentApiException e) {
+        } catch (final PaymentApiException e) {
             Assert.assertTrue(true);
         }
     }
@@ -532,7 +532,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         try {
             paymentApi.createCapture(account, UUID.randomUUID(), requestedAmount, account.getCurrency(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
             Assert.fail("Expected capture to fail...");
-        } catch (PaymentApiException e) {
+        } catch (final PaymentApiException e) {
             Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_NO_SUCH_PAYMENT.getCode());
 
             final Payment latestPayment = paymentApi.getPayment(initialPayment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
@@ -552,7 +552,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         try {
             paymentApi.createCapture(account, initialPayment.getId(), requestedAmount, Currency.AMD, UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
             Assert.fail("Expected capture to fail...");
-        } catch (PaymentApiException e) {
+        } catch (final PaymentApiException e) {
             Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
 
             final Payment latestPayment = paymentApi.getPayment(initialPayment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
@@ -574,13 +574,13 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         // Hack the Database to make it look like it was a failure
         paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), payment.getId(), TransactionType.AUTHORIZE, "AUTH_ERRORED", null,
                                                            payment.getTransactions().get(0).getId(), TransactionStatus.PLUGIN_FAILURE, null, null, null, null, internalCallContext);
-        PaymentSqlDao paymentSqlDao = dbi.onDemand(PaymentSqlDao.class);
+        final PaymentSqlDao paymentSqlDao = dbi.onDemand(PaymentSqlDao.class);
         paymentSqlDao.updateLastSuccessPaymentStateName(payment.getId().toString(), "AUTH_ERRORED", null, internalCallContext);
 
         try {
             paymentApi.createCapture(account, payment.getId(), requestedAmount, Currency.EUR, "tetard", ImmutableList.<PluginProperty>of(), callContext);
             Assert.fail("Unexpected success");
-        } catch (PaymentApiException e){
+        } catch (final PaymentApiException e){
             Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
         }
     }
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/TestStateMachineProvider.java b/payment/src/test/java/org/killbill/billing/payment/glue/TestStateMachineProvider.java
new file mode 100644
index 0000000..7ab6922
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/TestStateMachineProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.glue;
+
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.killbill.billing.payment.glue.PaymentModule.StateMachineProvider;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestStateMachineProvider extends PaymentTestSuiteNoDB {
+
+    @Test(groups = "fast", description = "https://github.com/killbill/killbill/issues/226")
+    public void testStateMachineProvider() throws Exception {
+        final StateMachineProvider retryStateMachineProvider = new StateMachineProvider(PaymentModule.DEFAULT_STATE_MACHINE_RETRY_XML);
+        final StateMachineConfig retryStateMachineConfig = retryStateMachineProvider.get();
+        Assert.assertEquals(retryStateMachineConfig.getStateMachines().length, 1);
+
+        final StateMachineProvider paymentStateMachineProvider = new StateMachineProvider(PaymentModule.DEFAULT_STATE_MACHINE_PAYMENT_XML);
+        final StateMachineConfig paymentStateMachineConfig = paymentStateMachineProvider.get();
+        Assert.assertEquals(paymentStateMachineConfig.getStateMachines().length, 8);
+    }
+}

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index e3d87cf..2f8247c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.2</version>
+        <version>0.9.5-SNAPSHOT</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.12.2-SNAPSHOT</version>
diff --git a/profiles/killbill/src/main/webapp/api.html b/profiles/killbill/src/main/webapp/api.html
index 0ce3e81..8cd886f 100644
--- a/profiles/killbill/src/main/webapp/api.html
+++ b/profiles/killbill/src/main/webapp/api.html
@@ -43,14 +43,9 @@
   <script src='lib/swagger-oauth.js' type='text/javascript'></script>
   <script type="text/javascript">
     $(function () {
-      var url = window.location.search.match(/url=([^&]+)/);
-      if (url && url.length > 1) {
-        url = url[1];
-      } else {
-        url = "http://127.0.0.1:8080/api-docs";
-      }
+      var url = window.location.href.replace(/(.*)(\/[^\/]*?)$/, '$1/api-docs')
       window.swaggerUi = new SwaggerUi({
-        url: url,
+        url: (url || "http://127.0.0.1:8080/api-docs"),
         dom_id: "swagger-ui-container",
         supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
         onComplete: function(swaggerApi, swaggerUi){
diff --git a/profiles/killpay/src/main/webapp/api.html b/profiles/killpay/src/main/webapp/api.html
index 0ce3e81..8cd886f 100644
--- a/profiles/killpay/src/main/webapp/api.html
+++ b/profiles/killpay/src/main/webapp/api.html
@@ -43,14 +43,9 @@
   <script src='lib/swagger-oauth.js' type='text/javascript'></script>
   <script type="text/javascript">
     $(function () {
-      var url = window.location.search.match(/url=([^&]+)/);
-      if (url && url.length > 1) {
-        url = url[1];
-      } else {
-        url = "http://127.0.0.1:8080/api-docs";
-      }
+      var url = window.location.href.replace(/(.*)(\/[^\/]*?)$/, '$1/api-docs')
       window.swaggerUi = new SwaggerUi({
-        url: url,
+        url: (url || "http://127.0.0.1:8080/api-docs"),
         dom_id: "swagger-ui-container",
         supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
         onComplete: function(swaggerApi, swaggerUi){
diff --git a/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java b/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java
index 0b5826d..86a0014 100644
--- a/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java
@@ -81,8 +81,8 @@ public interface PaymentConfig extends KillbillConfig {
     public TimeSpan getJanitorRunningRate();
 
     @Config("org.killbill.payment.invoice.plugin")
-    @Default("__INVOICE_PAYMENT_CONTROL_PLUGIN__")
-    @Description("Whether the payment subsystem is off")
+    @Default("")
+    @Description("Default payment control plugin names")
     public List<String> getPaymentControlPluginNames();
 
     @Config("org.killbill.payment.off")
diff --git a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
index 55ca1d9..149d852 100644
--- a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
+++ b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
@@ -18,15 +18,15 @@ package org.killbill.billing.util.dao;
 
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.regex.Pattern;
 
 import org.antlr.stringtemplate.StringTemplateGroup;
+import org.killbill.billing.util.UtilTestSuiteNoDB;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import org.killbill.billing.util.UtilTestSuiteNoDB;
-
 import com.google.common.collect.ImmutableMap;
 
 public class TestStringTemplateInheritance extends UtilTestSuiteNoDB {
@@ -66,127 +66,131 @@ public class TestStringTemplateInheritance extends UtilTestSuiteNoDB {
         Assert.assertEquals(kombucha.getInstanceOf("isIsTimeForKombucha").toString(), "select hour(current_timestamp()) = 17 as is_time;");
 
         // Verify inherited templates
-        Assert.assertEquals(kombucha.getInstanceOf("getById").toString(), "select\n" +
-                                                                          "  t.record_id\n" +
-                                                                          ", t.id\n" +
-                                                                          ", t.tea\n" +
-                                                                          ", t.mushroom\n" +
-                                                                          ", t.sugar\n" +
-                                                                          ", t.account_record_id\n" +
-                                                                          ", t.tenant_record_id\n" +
-                                                                          "from kombucha t\n" +
-                                                                          "where t.id = :id\n" +
-                                                                          "and t.tenant_record_id = :tenantRecordId\n" +
+        assertPattern(kombucha.getInstanceOf("getById").toString(), "select\r?\n" +
+                                                                    "  t.record_id\r?\n" +
+                                                                    ", t.id\r?\n" +
+                                                                    ", t.tea\r?\n" +
+                                                                    ", t.mushroom\r?\n" +
+                                                                    ", t.sugar\r?\n" +
+                                                                    ", t.account_record_id\r?\n" +
+                                                                    ", t.tenant_record_id\r?\n" +
+                                                                    "from kombucha t\r?\n" +
+                                                                    "where t.id = :id\r?\n" +
+                                                                    "and t.tenant_record_id = :tenantRecordId\r?\n" +
+                                                                    ";");
+        assertPattern(kombucha.getInstanceOf("getByRecordId").toString(), "select\r?\n" +
+                                                                          "  t.record_id\r?\n" +
+                                                                          ", t.id\r?\n" +
+                                                                          ", t.tea\r?\n" +
+                                                                          ", t.mushroom\r?\n" +
+                                                                          ", t.sugar\r?\n" +
+                                                                          ", t.account_record_id\r?\n" +
+                                                                          ", t.tenant_record_id\r?\n" +
+                                                                          "from kombucha t\r?\n" +
+                                                                          "where t.record_id = :recordId\r?\n" +
+                                                                          "and t.tenant_record_id = :tenantRecordId\r?\n" +
                                                                           ";");
-        Assert.assertEquals(kombucha.getInstanceOf("getByRecordId").toString(), "select\n" +
-                                                                                "  t.record_id\n" +
-                                                                                ", t.id\n" +
-                                                                                ", t.tea\n" +
-                                                                                ", t.mushroom\n" +
-                                                                                ", t.sugar\n" +
-                                                                                ", t.account_record_id\n" +
-                                                                                ", t.tenant_record_id\n" +
-                                                                                "from kombucha t\n" +
-                                                                                "where t.record_id = :recordId\n" +
-                                                                                "and t.tenant_record_id = :tenantRecordId\n" +
-                                                                                ";");
-        Assert.assertEquals(kombucha.getInstanceOf("getRecordId").toString(), "select\n" +
-                                                                              "  t.record_id\n" +
-                                                                              "from kombucha t\n" +
-                                                                              "where t.id = :id\n" +
-                                                                              "and t.tenant_record_id = :tenantRecordId\n" +
-                                                                              ";");
-        Assert.assertEquals(kombucha.getInstanceOf("getHistoryRecordId").toString(), "select\n" +
-                                                                                     "  max(t.record_id)\n" +
-                                                                                     "from kombucha_history t\n" +
-                                                                                     "where t.target_record_id = :targetRecordId\n" +
-                                                                                     "and t.tenant_record_id = :tenantRecordId\n" +
-                                                                                     ";");
-        Assert.assertEquals(kombucha.getInstanceOf("getAll").toString(), "select\n" +
-                                                                         "  t.record_id\n" +
-                                                                         ", t.id\n" +
-                                                                         ", t.tea\n" +
-                                                                         ", t.mushroom\n" +
-                                                                         ", t.sugar\n" +
-                                                                         ", t.account_record_id\n" +
-                                                                         ", t.tenant_record_id\n" +
-                                                                         "from kombucha t\n" +
-                                                                         "where t.tenant_record_id = :tenantRecordId\n" +
-                                                                         "order by t.record_id ASC\n" +
-                                                                         ";");
-        Assert.assertEquals(kombucha.getInstanceOf("get", ImmutableMap.<String, String>of("orderBy", "record_id", "offset", "3", "rowCount", "12")).toString(), "select\n" +
-                                                                                                                                                               "  t.record_id\n" +
-                                                                                                                                                               ", t.id\n" +
-                                                                                                                                                               ", t.tea\n" +
-                                                                                                                                                               ", t.mushroom\n" +
-                                                                                                                                                               ", t.sugar\n" +
-                                                                                                                                                               ", t.account_record_id\n" +
-                                                                                                                                                               ", t.tenant_record_id\n" +
-                                                                                                                                                               "from kombucha t\n" +
-                                                                                                                                                               "where t.tenant_record_id = :tenantRecordId\n" +
-                                                                                                                                                               "order by t.record_id\n" +
-                                                                                                                                                               "limit :offset, :rowCount\n" +
-                                                                                                                                                               ";");
-        Assert.assertEquals(kombucha.getInstanceOf("test").toString(), "select\n" +
-                                                                       "  t.record_id\n" +
-                                                                       ", t.id\n" +
-                                                                       ", t.tea\n" +
-                                                                       ", t.mushroom\n" +
-                                                                       ", t.sugar\n" +
-                                                                       ", t.account_record_id\n" +
-                                                                       ", t.tenant_record_id\n" +
-                                                                       "from kombucha t\n" +
-                                                                       "where t.tenant_record_id = :tenantRecordId\n" +
-                                                                       "limit 1\n" +
-                                                                       ";");
-        Assert.assertEquals(kombucha.getInstanceOf("addHistoryFromTransaction").toString(), "insert into kombucha_history (\n" +
-                                                                                            "  id\n" +
-                                                                                            ", target_record_id\n" +
-                                                                                            ", change_type\n" +
-                                                                                            ", tea\n" +
-                                                                                            ", mushroom\n" +
-                                                                                            ", sugar\n" +
-                                                                                            ", account_record_id\n" +
-                                                                                            ", tenant_record_id\n" +
-                                                                                            ")\n" +
-                                                                                            "values (\n" +
-                                                                                            "  :id\n" +
-                                                                                            ", :targetRecordId\n" +
-                                                                                            ", :changeType\n" +
-                                                                                            ",   :tea\n" +
-                                                                                            ", :mushroom\n" +
-                                                                                            ", :sugar\n" +
-                                                                                            ", :accountRecordId\n" +
-                                                                                            ", :tenantRecordId\n" +
-                                                                                            ")\n" +
-                                                                                            ";");
+        assertPattern(kombucha.getInstanceOf("getRecordId").toString(), "select\r?\n" +
+                                                                        "  t.record_id\r?\n" +
+                                                                        "from kombucha t\r?\n" +
+                                                                        "where t.id = :id\r?\n" +
+                                                                        "and t.tenant_record_id = :tenantRecordId\r?\n" +
+                                                                        ";");
+        assertPattern(kombucha.getInstanceOf("getHistoryRecordId").toString(), "select\r?\n" +
+                                                                               "  max\\(t.record_id\\)\r?\n" +
+                                                                               "from kombucha_history t\r?\n" +
+                                                                               "where t.target_record_id = :targetRecordId\r?\n" +
+                                                                               "and t.tenant_record_id = :tenantRecordId\r?\n" +
+                                                                               ";");
+        assertPattern(kombucha.getInstanceOf("getAll").toString(), "select\r?\n" +
+                                                                   "  t.record_id\r?\n" +
+                                                                   ", t.id\r?\n" +
+                                                                   ", t.tea\r?\n" +
+                                                                   ", t.mushroom\r?\n" +
+                                                                   ", t.sugar\r?\n" +
+                                                                   ", t.account_record_id\r?\n" +
+                                                                   ", t.tenant_record_id\r?\n" +
+                                                                   "from kombucha t\r?\n" +
+                                                                   "where t.tenant_record_id = :tenantRecordId\r?\n" +
+                                                                   "order by t.record_id ASC\r?\n" +
+                                                                   ";");
+        assertPattern(kombucha.getInstanceOf("get", ImmutableMap.<String, String>of("orderBy", "record_id", "offset", "3", "rowCount", "12")).toString(), "select\r?\n" +
+                                                                                                                                                          "  t.record_id\r?\n" +
+                                                                                                                                                          ", t.id\r?\n" +
+                                                                                                                                                          ", t.tea\r?\n" +
+                                                                                                                                                          ", t.mushroom\r?\n" +
+                                                                                                                                                          ", t.sugar\r?\n" +
+                                                                                                                                                          ", t.account_record_id\r?\n" +
+                                                                                                                                                          ", t.tenant_record_id\r?\n" +
+                                                                                                                                                          "from kombucha t\r?\n" +
+                                                                                                                                                          "where t.tenant_record_id = :tenantRecordId\r?\n" +
+                                                                                                                                                          "order by t.record_id\r?\n" +
+                                                                                                                                                          "limit :offset, :rowCount\r?\n" +
+                                                                                                                                                          ";");
+        assertPattern(kombucha.getInstanceOf("test").toString(), "select\r?\n" +
+                                                                 "  t.record_id\r?\n" +
+                                                                 ", t.id\r?\n" +
+                                                                 ", t.tea\r?\n" +
+                                                                 ", t.mushroom\r?\n" +
+                                                                 ", t.sugar\r?\n" +
+                                                                 ", t.account_record_id\r?\n" +
+                                                                 ", t.tenant_record_id\r?\n" +
+                                                                 "from kombucha t\r?\n" +
+                                                                 "where t.tenant_record_id = :tenantRecordId\r?\n" +
+                                                                 "limit 1\r?\n" +
+                                                                 ";");
+        assertPattern(kombucha.getInstanceOf("addHistoryFromTransaction").toString(), "insert into kombucha_history \\(\r?\n" +
+                                                                                      "  id\r?\n" +
+                                                                                      ", target_record_id\r?\n" +
+                                                                                      ", change_type\r?\n" +
+                                                                                      ", tea\r?\n" +
+                                                                                      ", mushroom\r?\n" +
+                                                                                      ", sugar\r?\n" +
+                                                                                      ", account_record_id\r?\n" +
+                                                                                      ", tenant_record_id\r?\n" +
+                                                                                      "\\)\r?\n" +
+                                                                                      "values \\(\r?\n" +
+                                                                                      "  :id\r?\n" +
+                                                                                      ", :targetRecordId\r?\n" +
+                                                                                      ", :changeType\r?\n" +
+                                                                                      ",   :tea\r?\n" +
+                                                                                      ", :mushroom\r?\n" +
+                                                                                      ", :sugar\r?\n" +
+                                                                                      ", :accountRecordId\r?\n" +
+                                                                                      ", :tenantRecordId\r?\n" +
+                                                                                      "\\)\r?\n" +
+                                                                                      ";");
+
+        assertPattern(kombucha.getInstanceOf("insertAuditFromTransaction").toString(), "insert into audit_log \\(\r?\n" +
+                                                                                       "id\r?\n" +
+                                                                                       ", table_name\r?\n" +
+                                                                                       ", target_record_id\r?\n" +
+                                                                                       ", change_type\r?\n" +
+                                                                                       ", created_by\r?\n" +
+                                                                                       ", reason_code\r?\n" +
+                                                                                       ", comments\r?\n" +
+                                                                                       ", user_token\r?\n" +
+                                                                                       ", created_date\r?\n" +
+                                                                                       ", account_record_id\r?\n" +
+                                                                                       ", tenant_record_id\r?\n" +
+                                                                                       "\\)\r?\n" +
+                                                                                       "values \\(\r?\n" +
+                                                                                       "  :id\r?\n" +
+                                                                                       ", :tableName\r?\n" +
+                                                                                       ", :targetRecordId\r?\n" +
+                                                                                       ", :changeType\r?\n" +
+                                                                                       ", :createdBy\r?\n" +
+                                                                                       ", :reasonCode\r?\n" +
+                                                                                       ", :comments\r?\n" +
+                                                                                       ", :userToken\r?\n" +
+                                                                                       ", :createdDate\r?\n" +
+                                                                                       ", :accountRecordId\r?\n" +
+                                                                                       ", :tenantRecordId\r?\n" +
+                                                                                       "\\)\r?\n" +
+                                                                                       ";");
+    }
 
-        Assert.assertEquals(kombucha.getInstanceOf("insertAuditFromTransaction").toString(), "insert into audit_log (\n" +
-                                                                                             "id\n" +
-                                                                                             ", table_name\n" +
-                                                                                             ", target_record_id\n" +
-                                                                                             ", change_type\n" +
-                                                                                             ", created_by\n" +
-                                                                                             ", reason_code\n" +
-                                                                                             ", comments\n" +
-                                                                                             ", user_token\n" +
-                                                                                             ", created_date\n" +
-                                                                                             ", account_record_id\n" +
-                                                                                             ", tenant_record_id\n" +
-                                                                                             ")\n" +
-                                                                                             "values (\n" +
-                                                                                             "  :id\n" +
-                                                                                             ", :tableName\n" +
-                                                                                             ", :targetRecordId\n" +
-                                                                                             ", :changeType\n" +
-                                                                                             ", :createdBy\n" +
-                                                                                             ", :reasonCode\n" +
-                                                                                             ", :comments\n" +
-                                                                                             ", :userToken\n" +
-                                                                                             ", :createdDate\n" +
-                                                                                             ", :accountRecordId\n" +
-                                                                                             ", :tenantRecordId\n" +
-                                                                                             ")\n" +
-                                                                                             ";");
+    private void assertPattern(final String actual, final String expected) {
+        Assert.assertTrue(Pattern.compile(expected).matcher(actual).find(), String.format("Expected to see:\n%s\nin:\n%s", expected, actual));
     }
 }