killbill-aplcache

Changes

bin/db-helper 28(+26 -2)

NEWS 6(+6 -0)

overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java 151(+0 -151)

pom.xml 2(+1 -1)

util/src/main/java/org/killbill/billing/util/callcontext/MigrationCallContext.java 43(+0 -43)

Details

diff --git a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
index daf499e..f1186a2 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
@@ -94,7 +94,7 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
         try {
             eventBus.postFromTransaction(creationEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
         } catch (final EventBusException e) {
-            log.warn("Failed to post account creation event for account " + savedAccount.getId(), e);
+            log.warn("Failed to post account creation event for accountId='{}'", savedAccount.getId(), e);
         }
     }
 
@@ -166,7 +166,7 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
                 try {
                     eventBus.postFromTransaction(changeEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
                 } catch (final EventBusException e) {
-                    log.warn("Failed to post account change event for account " + accountId, e);
+                    log.warn("Failed to post account change event for accountId='{}'", accountId, e);
                 }
 
                 return null;
@@ -205,7 +205,7 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
                 try {
                     eventBus.postFromTransaction(changeEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
                 } catch (final EventBusException e) {
-                    log.warn("Failed to post account change event for account " + accountId, e);
+                    log.warn("Failed to post account change event for accountId='{}'", accountId, e);
                 }
                 return null;
             }
diff --git a/account/src/test/java/org/killbill/billing/account/glue/TestAccountModule.java b/account/src/test/java/org/killbill/billing/account/glue/TestAccountModule.java
index 044e4f9..0524129 100644
--- a/account/src/test/java/org/killbill/billing/account/glue/TestAccountModule.java
+++ b/account/src/test/java/org/killbill/billing/account/glue/TestAccountModule.java
@@ -24,6 +24,7 @@ import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.AuditModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
 import org.killbill.billing.util.glue.TagStoreModule;
 
@@ -39,6 +40,7 @@ public class TestAccountModule extends DefaultAccountModule {
 
         install(new AuditModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new CallContextModule(configSource));
         install(new CustomFieldModule(configSource));
         install(new MockTenantModule(configSource));
diff --git a/api/src/main/java/org/killbill/billing/glue/InvoiceModule.java b/api/src/main/java/org/killbill/billing/glue/InvoiceModule.java
index ca13341..ce939ec 100644
--- a/api/src/main/java/org/killbill/billing/glue/InvoiceModule.java
+++ b/api/src/main/java/org/killbill/billing/glue/InvoiceModule.java
@@ -18,6 +18,8 @@ package org.killbill.billing.glue;
 
 public interface InvoiceModule {
 
+    static final String STATIC_CONFIG = "StaticConfig";
+
     public void installInvoiceUserApi();
 
     public void installInvoicePaymentApi();
diff --git a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
index 4899368..3fba4da 100644
--- a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
@@ -37,9 +37,9 @@ public interface InvoiceInternalApi {
 
     public BigDecimal getAccountBalance(UUID accountId, InternalTenantContext context);
 
-    public void notifyOfPayment(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, Currency processedCurrency, UUID paymentId, DateTime paymentDate, boolean success, InternalCallContext context) throws InvoiceApiException;
+    public void recordPaymentAttemptInit(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, Currency processedCurrency, UUID paymentId, String transactionExternalKey, DateTime paymentDate, InternalCallContext context) throws InvoiceApiException;
 
-    public void notifyOfPayment(InvoicePayment invoicePayment, InternalCallContext context) throws InvoiceApiException;
+    public void recordPaymentAttemptCompletion(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, Currency processedCurrency, UUID paymentId, String transactionExternalKey, DateTime paymentDate, boolean success, InternalCallContext context) throws InvoiceApiException;
 
     public InvoicePayment getInvoicePaymentForAttempt(UUID paymentId, InternalTenantContext context) throws InvoiceApiException;
 
@@ -59,10 +59,10 @@ public interface InvoiceInternalApi {
      * @return the created invoice payment object associated with this refund
      * @throws InvoiceApiException
      */
-    public InvoicePayment createRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts,
+    public InvoicePayment recordRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts,
                                        String transactionExternalKey, InternalCallContext context) throws InvoiceApiException;
 
-    public InvoicePayment createChargeback(UUID paymentId, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
+    public InvoicePayment recordChargeback(UUID paymentId, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
 
     /**
      * Rebalance CBA for account which have credit and unpaid invoices
diff --git a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java
index a08e8c3..4e32345 100644
--- a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java
+++ b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java
@@ -18,13 +18,16 @@ package org.killbill.billing.junction;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Set;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
+import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
@@ -80,7 +83,7 @@ public interface BillingEvent extends Comparable<BillingEvent> {
     /**
      * @return the recurring price for the phase
      */
-    BigDecimal getRecurringPrice();
+    BigDecimal getRecurringPrice(DateTime effectiveDate) throws CatalogApiException;
 
     /**
      * @return the currency for the account being invoiced
@@ -107,4 +110,5 @@ public interface BillingEvent extends Comparable<BillingEvent> {
      * @return the list of {@code Usage} section
      */
     List<Usage> getUsages();
+
 }
diff --git a/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java b/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java
index b44b81d..2567f94 100644
--- a/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java
+++ b/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java
@@ -25,7 +25,6 @@ import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.UUIDs;
-import org.killbill.clock.Clock;
 
 public class DefaultBlockingState extends EntityBase implements BlockingState {
 
@@ -87,6 +86,18 @@ public class DefaultBlockingState extends EntityBase implements BlockingState {
              0L);
     }
 
+    public DefaultBlockingState(final BlockingState input,
+                                final DateTime effectiveDate) {
+        this(input.getBlockedId(),
+             input.getType(),
+             input.getStateName(),
+             input.getService(),
+             input.isBlockChange(),
+             input.isBlockEntitlement(),
+             input.isBlockBilling(),
+             effectiveDate);
+    }
+
     @Override
     public UUID getBlockedId() {
         return blockedId;
diff --git a/api/src/main/java/org/killbill/billing/tenant/api/TenantInternalApi.java b/api/src/main/java/org/killbill/billing/tenant/api/TenantInternalApi.java
index 3a39d4f..89c7cb7 100644
--- a/api/src/main/java/org/killbill/billing/tenant/api/TenantInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/tenant/api/TenantInternalApi.java
@@ -41,6 +41,8 @@ public interface TenantInternalApi {
 
     public String getTenantOverdueConfig(InternalTenantContext tenantContext);
 
+    public String getTenantConfig(InternalTenantContext tenantContext);
+
     public String getInvoiceTemplate(Locale locale, InternalTenantContext tenantContext);
 
     public String getManualPayInvoiceTemplate(Locale locale, InternalTenantContext tenantContext);
diff --git a/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java b/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java
index 3b88c15..1d2564f 100644
--- a/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java
+++ b/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java
@@ -109,10 +109,10 @@ public class BeatrixListener {
             // However when using InMemoryBus, this can happen as there is no retry logic (at the 'ext' bus level) and so we should re-throw at this level to kick-in the retry logic from the 'main' bus
             // (The use of RuntimeException is somewhat arbitrary)
             //
-            log.warn("Failed to dispatch external bus events", e);
+            log.warn("Failed to post event {}", event, e);
             throw new RuntimeException(e);
         } catch (JsonProcessingException e) {
-            log.warn("Failed to dispatch external bus events", e);
+            log.warn("Failed to post event {}", event, e);
         }
     }
 
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
index db9efa1..6c9b9b0 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
@@ -42,13 +42,14 @@ import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.glue.DefaultSubscriptionModule;
 import org.killbill.billing.tenant.glue.DefaultTenantModule;
 import org.killbill.billing.usage.glue.UsageModule;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.email.EmailModule;
 import org.killbill.billing.util.email.templates.TemplateModule;
 import org.killbill.billing.util.glue.AuditModule;
 import org.killbill.billing.util.glue.BroadcastModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
 import org.killbill.billing.util.glue.ExportModule;
 import org.killbill.billing.util.glue.GlobalLockerModule;
@@ -59,8 +60,6 @@ import org.killbill.billing.util.glue.NonEntityDaoModule;
 import org.killbill.billing.util.glue.RecordIdModule;
 import org.killbill.billing.util.glue.SecurityModule;
 import org.killbill.billing.util.glue.TagStoreModule;
-import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
-import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
 
 public class BeatrixIntegrationModule extends KillBillModule {
 
@@ -78,6 +77,7 @@ public class BeatrixIntegrationModule extends KillBillModule {
         install(new GuicyKillbillTestWithEmbeddedDBModule(true, configSource));
         install(new GlobalLockerModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new EmailModule(configSource));
         install(new CallContextModule(configSource));
         install(new TagStoreModule(configSource));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index dbdd9c0..c0a516d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -281,6 +281,10 @@ public class TestOverdueIntegration extends TestOverdueBase {
         addDaysAndCheckForCompletion(5);
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
+        // Make sure the 'invoice-service:next-billing-date-queue' gets processed before we continue and since we are in AUTO_INVOICING_OFF
+        // no event (NULL_INVOICE) will be generated and so we can't synchronize on any event, and we need to add a small amount of sleep
+        Thread.sleep(1000);
+
         allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(true);
 
         invoiceChecker.checkInvoice(account.getId(), 3, callContext,
@@ -942,6 +946,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
     }
 
     private void allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(final boolean extraPayment) {
+
         // Reset plugin so payments should now succeed
         paymentPlugin.makeAllInvoicesFailWithError(false);
 
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
index e312a33..6d98f81 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
@@ -45,7 +45,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
 
     @Override
     protected KillbillConfigSource getConfigSource() {
-        return super.getConfigSource("/beatrixVersionedCatalog.properties");
+        return super.getConfigSource("/beatrixCatalogRetireElements.properties");
     }
 
     @Test(groups = "slow")
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index acee264..fd03318 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -732,4 +732,117 @@ public class TestIntegration extends TestIntegrationBase {
         invoiceChecker.checkInvoice(account.getId(), 14, callContext, expectedInvoices);
         expectedInvoices.clear();
     }
+
+    @Test(groups = "slow")
+    public void testThirtyDaysPlanWithFixedTermMonthlyAddOn() throws Exception {
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2015, 4, 1));
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+
+        // First invoice
+        final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.THIRTY_DAYS, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // Second invoice -> first recurring for Refurbish-Maintenance
+        addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Refurbish-Maintenance", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("599.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 4, 1), new LocalDate(2015, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("199.95")));
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2015-5-1
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 5, 1), new LocalDate(2015, 5, 31), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 5, 1), new LocalDate(2015, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("199.95")));
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30); // Also = 1 month because or initial date 2015, 4, 1
+        assertListenerStatus();
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // Next 20 invoices, including last recurring for Refurbish-Maintenance
+        LocalDate startDateBase = new LocalDate(2015, 5, 31);
+        LocalDate startDateAddOn = new LocalDate(2015, 6, 1);
+        for (int i = 0; i < 10; i++) {
+            final LocalDate endDateBase = startDateBase.plusDays(30);
+            expectedInvoices.add(new ExpectedInvoiceItemCheck(startDateBase, endDateBase, InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+            busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+            clock.setDay(startDateBase);
+            assertListenerStatus();
+            invoiceChecker.checkInvoice(account.getId(), 4 + 2 * i, callContext, expectedInvoices);
+            expectedInvoices.clear();
+
+            final LocalDate endDateAddOn = startDateAddOn.plusMonths(1);
+            expectedInvoices.add(new ExpectedInvoiceItemCheck(startDateAddOn, endDateAddOn, InvoiceItemType.RECURRING, new BigDecimal("199.95")));
+            busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+            clock.setDay(startDateAddOn);
+            assertListenerStatus();
+            invoiceChecker.checkInvoice(account.getId(), 5 + 2 * i, callContext, expectedInvoices);
+            expectedInvoices.clear();
+
+            startDateBase = endDateBase;
+            startDateAddOn = endDateAddOn;
+        }
+        // clock at 2016-03-01
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 3, 26), new LocalDate(2016, 4, 25), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.setDay(new LocalDate(2016, 3, 26));
+        assertListenerStatus();
+        invoiceChecker.checkInvoice(account.getId(), 24, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // We check there is no more recurring for Refurbish-Maintenance
+        busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
+        clock.setDay(new LocalDate(2016, 4, 1));
+        assertListenerStatus();
+    }
+
+    @Test(groups = "slow")
+    public void testWeeklyPlan() throws Exception {
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2015, 4, 1));
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+
+        // First invoice
+        createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.WEEKLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2015-5-1
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 5, 1), new LocalDate(2015, 5, 8), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30); // Also = 1 month because or initial date 2015, 4, 1
+        assertListenerStatus();
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        LocalDate startDateBase = new LocalDate(2015, 5, 8);
+        for (int i = 0; i < 10; i++) {
+            final LocalDate endDateBase = startDateBase.plusDays(7);
+            expectedInvoices.add(new ExpectedInvoiceItemCheck(startDateBase, endDateBase, InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+            busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+            clock.setDay(startDateBase);
+            assertListenerStatus();
+            invoiceChecker.checkInvoice(account.getId(), 3 + i, callContext, expectedInvoices);
+            expectedInvoices.clear();
+
+            startDateBase = endDateBase;
+        }
+    }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index d2e8b3b..690c36e 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -75,7 +75,7 @@ import org.killbill.billing.lifecycle.api.Lifecycle;
 import org.killbill.billing.lifecycle.glue.BusModule;
 import org.killbill.billing.mock.MockAccountBuilder;
 import org.killbill.billing.osgi.config.OSGIConfig;
-import org.killbill.billing.overdue.OverdueInternalApi;
+import org.killbill.billing.overdue.api.OverdueApi;
 import org.killbill.billing.overdue.api.OverdueConfig;
 import org.killbill.billing.overdue.caching.OverdueConfigCache;
 import org.killbill.billing.overdue.listener.OverdueListener;
@@ -179,7 +179,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     protected SubscriptionBaseTimelineApi repairApi;
 
     @Inject
-    protected OverdueInternalApi overdueUserApi;
+    protected OverdueApi overdueUserApi;
 
     @Inject
     protected InvoiceUserApi invoiceUserApi;
@@ -247,9 +247,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     protected RecordIdApi recordIdApi;
 
     @Inject
-    protected IDBI idbi;
-
-    @Inject
     protected NonEntityDao nonEntityDao;
 
     @Inject
@@ -517,24 +514,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         }, events);
     }
 
-    protected Payment refundPaymentWithAdjustmentAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
-        return doCallAndCheckForCompletion(new Function<Void, Payment>() {
-            @Override
-            public Payment apply(@Nullable final Void input) {
-
-                final List<PluginProperty> properties = new ArrayList<PluginProperty>();
-                final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_WITH_ADJUSTMENTS, "true", false);
-                properties.add(prop1);
-                try {
-                    return paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
-                                                                     properties, PAYMENT_OPTIONS, callContext);
-                } catch (final PaymentApiException e) {
-                    fail(e.toString());
-                    return null;
-                }
-            }
-        }, events);
-    }
 
     protected Payment refundPaymentWithInvoiceItemAdjAndCheckForCompletion(final Account account, final Payment payment, final Map<UUID, BigDecimal> iias, final NextEvent... events) {
         return refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment, payment.getPurchasedAmount(), payment.getCurrency(), iias, events);
@@ -600,7 +579,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                     assertNotNull(entitlement);
                     return entitlement;
                 } catch (final EntitlementApiException e) {
-                    fail(e.toString());
+                    fail("Unable to create entitlement", e);
                     return null;
                 }
             }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
index 73ee3cd..4d911f6 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
@@ -34,7 +34,7 @@ import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 
 import com.google.inject.Inject;
 
@@ -145,7 +145,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
         }
         assertListenerStatus();
 
-        int nbDaysBeforeRetry = paymentConfig.getPaymentFailureRetryDays().get(0);
+        int nbDaysBeforeRetry = paymentConfig.getPaymentFailureRetryDays(internalCallContext).get(0);
 
         // MOVE TIME FOR RETRY TO HAPPEN
         busHandler.pushExpectedEvents(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -206,7 +206,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
         assertListenerStatus();
 
         // RE-ADD AUTO_PAY_OFF to ON
-        int nbDaysBeforeRetry = paymentConfig.getPaymentFailureRetryDays().get(0);
+        int nbDaysBeforeRetry = paymentConfig.getPaymentFailureRetryDays(internalCallContext).get(0);
         add_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
 
         // MOVE TIME FOR RETRY TO HAPPEN -> WILL BE DISCARDED SINCE AUTO_PAY_OFF IS SET
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
index 6d84c20..77d24bb 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
@@ -18,6 +18,7 @@
 package org.killbill.billing.beatrix.integration;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -25,6 +26,7 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountData;
@@ -41,8 +43,14 @@ import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
 import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.tweak.HandleCallback;
+import org.skife.jdbi.v2.tweak.VoidHandleCallback;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -388,7 +396,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
         // Verify links for payment 1
         Assert.assertEquals(invoice1.getPayments().get(0).getAmount().compareTo(new BigDecimal("4.00")), 0);
         Assert.assertNull(invoice1.getPayments().get(0).getLinkedInvoicePaymentId());
-        Assert.assertNull(invoice1.getPayments().get(0).getPaymentCookieId());
+        Assert.assertEquals(invoice1.getPayments().get(0).getPaymentCookieId(), payment1.getTransactions().get(0).getExternalKey());
         Assert.assertEquals(invoice1.getPayments().get(0).getPaymentId(), payment1.getId());
         Assert.assertEquals(invoice1.getPayments().get(0).getType(), InvoicePaymentType.ATTEMPT);
         Assert.assertTrue(invoice1.getPayments().get(0).isSuccess());
@@ -396,7 +404,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
         // Verify links for payment 2
         Assert.assertEquals(invoice1.getPayments().get(1).getAmount().compareTo(new BigDecimal("6.00")), 0);
         Assert.assertNull(invoice1.getPayments().get(1).getLinkedInvoicePaymentId());
-        Assert.assertNull(invoice1.getPayments().get(1).getPaymentCookieId());
+        Assert.assertEquals(invoice1.getPayments().get(1).getPaymentCookieId(), payment2.getTransactions().get(0).getExternalKey());
         Assert.assertEquals(invoice1.getPayments().get(1).getPaymentId(), payment2.getId());
         Assert.assertEquals(invoice1.getPayments().get(1).getType(), InvoicePaymentType.ATTEMPT);
         Assert.assertTrue(invoice1.getPayments().get(1).isSuccess());
@@ -482,4 +490,79 @@ public class TestInvoicePayment extends TestIntegrationBase {
         assertEquals(payments2.get(0).getTransactions().get(1).getProcessedAmount().compareTo(new BigDecimal("249.95")), 0);
         assertEquals(payments2.get(0).getTransactions().get(1).getProcessedCurrency(), Currency.USD);
     }
+
+    @Test(groups = "slow")
+    public void testWithIncompletePaymentAttempt() throws Exception {
+        // 2012-05-01T00:03:42.000Z
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        final AccountData accountData = getAccountData(0);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        Invoice invoice2 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // Invoice is fully paid
+        final Payment originalPayment = paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 31), new BigDecimal("249.95"), TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
+        Assert.assertEquals(originalPayment.getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(originalPayment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(originalPayment.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+
+        final PaymentTransaction originalTransaction = originalPayment.getTransactions().get(0);
+
+        // Let 's hack invoice_payment table by hand to simulate a non completion of the payment (onSuccessCall was never called)
+        dbi.withHandle(new HandleCallback<Void>() {
+            @Override
+            public Void withHandle(final Handle handle) throws Exception {
+                handle.execute("update invoice_payments set success = false where payment_cookie_id = ?", originalTransaction.getExternalKey());
+                return null;
+            }
+        });
+
+        final Invoice updateInvoice2 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        // Invoice now shows as unpaid
+        Assert.assertEquals(updateInvoice2.getBalance().compareTo(originalPayment.getPurchasedAmount()), 0);
+        Assert.assertEquals(updateInvoice2.getPayments().size(), 1);
+        Assert.assertEquals(updateInvoice2.getPayments().get(0).getPaymentCookieId(), originalTransaction.getExternalKey());
+
+        //
+        // Now trigger invoice payment again (no new payment should be made as code should detect broken state and fix it by itself)
+        // We expect an INVOICE_PAYMENT that indicates the invoice was repaired, and also an exception because plugin aborts payment call since there is nothing to do.
+        //
+        busHandler.pushExpectedEvents(NextEvent.INVOICE_PAYMENT);
+        final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+        final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, updateInvoice2.getId().toString(), false);
+        properties.add(prop1);
+        try {
+            paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, updateInvoice2.getBalance(), updateInvoice2.getCurrency(), UUID.randomUUID().toString(),
+                                                        UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
+            Assert.fail("The payment should not succeed (and yet it will repair the broken state....)");
+        } catch (final PaymentApiException expected) {
+            Assert.assertEquals(expected.getCode(), ErrorCode.PAYMENT_PLUGIN_EXCEPTION.getCode());
+        }
+        assertListenerStatus();
+
+        final Invoice updateInvoice3 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        Assert.assertEquals(updateInvoice3.getBalance().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(updateInvoice3.getPayments().size(), 1);
+        Assert.assertEquals(updateInvoice3.getPayments().get(0).getPaymentCookieId(), originalTransaction.getExternalKey());
+        Assert.assertTrue(updateInvoice3.getPayments().get(0).isSuccess());
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+        Assert.assertEquals(payments.size(), 1);
+        Assert.assertEquals(payments.get(0).getTransactions().size(), 1);
+
+    }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
index cd0ef09..81d7a94 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
@@ -19,8 +19,10 @@
 package org.killbill.billing.beatrix.integration;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
@@ -29,6 +31,7 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
@@ -42,7 +45,11 @@ import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
+import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -51,6 +58,7 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
 
 public class TestPaymentRefund extends TestIntegrationBase {
 
@@ -92,14 +100,20 @@ public class TestPaymentRefund extends TestIntegrationBase {
     }
 
     @Test(groups = "slow")
-    public void testRefundWithInvoiceAdjustment() throws Exception {
-        refundPaymentWithAdjustmentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
-        refundChecker.checkRefund(payment.getId(), callContext, new ExpectedRefundCheck(payment.getId(), true, new BigDecimal("233.82"), Currency.USD, initialCreationDate.toLocalDate()));
-        invoice = invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext,
-                                              new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
-                                                                           new LocalDate(2012, 3, 31), InvoiceItemType.RECURRING, new BigDecimal("233.82")),
-                                              new ExpectedInvoiceItemCheck(InvoiceItemType.REFUND_ADJ, new BigDecimal("-233.82"))
-                                             );
+    public void testFailedRefundWithInvoiceAdjustment() throws Exception {
+
+        final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+        final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_WITH_ADJUSTMENTS, "true", false);
+        properties.add(prop1);
+        try {
+            paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
+                                                             properties, PAYMENT_OPTIONS, callContext);
+            fail("Refund with invoice adjustment should now throw an Exception");
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCause(), null);
+            // Unfortunately we lose the original error code : INVOICE_ITEMS_ADJUSTMENT_MISSING
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_PLUGIN_EXCEPTION.getCode());
+        }
     }
 
     private void setupRefundTest() throws Exception {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
index 4a351ac..4113d01 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
@@ -37,6 +37,7 @@ import org.killbill.billing.notification.plugin.api.ExtBusEvent;
 import org.killbill.billing.notification.plugin.api.ExtBusEventType;
 import org.killbill.billing.osgi.BundleRegistry;
 import org.killbill.billing.osgi.BundleWithConfig;
+import org.killbill.billing.osgi.FileInstall;
 import org.killbill.billing.osgi.PureOSGIBundleFinder;
 import org.killbill.billing.osgi.api.PluginInfo;
 import org.killbill.billing.osgi.api.PluginStateChange;
@@ -50,6 +51,7 @@ import org.killbill.billing.osgi.pluginconf.PluginConfigException;
 import org.killbill.billing.osgi.pluginconf.PluginFinder;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.jackson.ObjectMapper;
+import org.killbill.billing.util.nodes.KillbillNodesApi;
 import org.killbill.billing.util.nodes.NodeCommand;
 import org.killbill.billing.util.nodes.NodeCommandMetadata;
 import org.killbill.billing.util.nodes.NodeCommandProperty;
@@ -223,8 +225,8 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
         private final List<BundleWithMetadata> bundles;
 
         @Inject
-        public FakeBundleRegistry(final PureOSGIBundleFinder osgiBundleFinder, final PluginFinder pluginFinder, final PluginConfigServiceApi pluginConfigServiceApi) {
-            super(osgiBundleFinder, pluginFinder, pluginConfigServiceApi);
+        public FakeBundleRegistry() {
+            super(null);
             bundles = new ArrayList<BundleWithMetadata>();
         }
 
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
index 092d2b5..9aa8eb1 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
@@ -41,6 +41,7 @@ import org.killbill.billing.mock.MockAccountBuilder;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.usage.api.SubscriptionUsageRecord;
 import org.killbill.billing.usage.api.UnitUsageRecord;
+import org.killbill.billing.usage.api.UsageApiException;
 import org.killbill.billing.usage.api.UsageRecord;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.skife.jdbi.v2.Handle;
@@ -245,12 +246,12 @@ public class TestConsumableInArrear extends TestIntegrationBase {
         Assert.assertEquals(countNotifications.intValue(), 4);
     }
 
-    private void setUsage(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) {
+    private void setUsage(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) throws UsageApiException {
         final List<UsageRecord> usageRecords = new ArrayList<UsageRecord>();
         usageRecords.add(new UsageRecord(startDate, amount));
         final List<UnitUsageRecord> unitUsageRecords = new ArrayList<UnitUsageRecord>();
         unitUsageRecords.add(new UnitUsageRecord(unitType, usageRecords));
-        final SubscriptionUsageRecord record = new SubscriptionUsageRecord(subscriptionId, unitUsageRecords);
+        final SubscriptionUsageRecord record = new SubscriptionUsageRecord(subscriptionId, UUID.randomUUID().toString(), unitUsageRecords);
         usageUserApi.recordRolledUpUsage(record, context);
     }
 }
diff --git a/beatrix/src/test/resources/retiredCatalogs/WeaponsHireSmall-v1.xml b/beatrix/src/test/resources/retiredCatalogs/WeaponsHireSmall-v1.xml
index a8456f3..4701c94 100644
--- a/beatrix/src/test/resources/retiredCatalogs/WeaponsHireSmall-v1.xml
+++ b/beatrix/src/test/resources/retiredCatalogs/WeaponsHireSmall-v1.xml
@@ -96,7 +96,7 @@
 
     <plans>
         <plan name="pistol-monthly">
-            <effectiveDateForExistingSubscriptons>2011-03-14T00:00:00+00:00</effectiveDateForExistingSubscriptons>
+            <effectiveDateForExistingSubscriptions>2011-03-14T00:00:00+00:00</effectiveDateForExistingSubscriptions>
             <product>Pistol</product>
             <initialPhases>
                 <phase type="TRIAL">

bin/db-helper 28(+26 -2)

diff --git a/bin/db-helper b/bin/db-helper
index c642b1c..7b9c051 100755
--- a/bin/db-helper
+++ b/bin/db-helper
@@ -60,7 +60,7 @@ eval set -- "${ARGS}"
 
 function usage() {
     echo -n "./db_helper"
-    echo -n " -a|--action <create|clean|dump>"
+    echo -n " -a|--action <create|clean|dump|migrate|dryRunMigrate|repair|info>"
     echo -n " --driver <mysql|postgres> (default = mysql)"
     echo -n " -h|--host host (default = localhost)"
     echo -n " --port port"
@@ -159,6 +159,20 @@ function cleanup() {
     rm -f "/tmp/*.$$"
 }
 
+function flyway() {
+    flyway_bin=util/target/killbill-flyway.jar
+    if [ ! -f "$flyway_bin" ]; then
+        echo "File $flyway_bin does not exists - build util first"
+        usage
+    fi
+
+    locations=
+    for migration_dir in `find */src/main/resources -type d -name migration`; do
+        locations="${locations}filesystem:$migration_dir,"
+    done
+
+    java -jar $flyway_bin -locations=$locations -user=$USER -password=$PWD -url=$URL ${@}
+}
 
 while true; do
   case "$1" in
@@ -178,7 +192,7 @@ done
 
 
 if [ -z $ACTION ]; then
-    echo "Need to specify an action <CREATE|CLEAN|DUMP>"
+    echo "Need to specify an action"
     usage
 fi
 
@@ -195,6 +209,12 @@ fi
 if [ $DRIVER == "postgres" ] && [ -z $PORT ]; then
     PORT=$PORT_POSTGRES
 fi
+if [ $DRIVER == "mysql" ] && [ -z $URL ]; then
+    URL=jdbc:mysql://$HOST:$PORT/$DATABASE
+fi
+if [ $DRIVER == "postgres" ] && [ -z $URL ]; then
+    URL=jdbc:postgresql://$HOST:$PORT/$DATABASE
+fi
 
 
 if [ $ACTION == "dump" ]; then
@@ -233,4 +253,8 @@ if [ $ACTION == "clean" ]; then
     fi
 fi
 
+if [ $ACTION == "migrate" ] || [ $ACTION == "dryRunMigrate" ] || [ $ACTION == "repair" ] || [ $ACTION == "info" ]; then
+    flyway $ACTION
+fi
+
 cleanup
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/CatalogCacheInvalidationCallback.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/CatalogCacheInvalidationCallback.java
index 63548dc..852cd06 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/CatalogCacheInvalidationCallback.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/CatalogCacheInvalidationCallback.java
@@ -38,7 +38,7 @@ public class CatalogCacheInvalidationCallback implements CacheInvalidationCallba
 
     @Override
     public void invalidateCache(TenantKey key, final Object cookie, final InternalTenantContext tenantContext) {
-        log.info("Invalidate catalog cache for tenant {} ", tenantContext.getTenantRecordId());
+        log.info("Invalidate catalog cache for tenantRecordId='{}'", tenantContext.getTenantRecordId());
         catalogCache.clearCatalog(tenantContext);
     }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
index 44e704d..5713049 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
@@ -155,7 +155,7 @@ public class EhCacheCatalogCache implements CatalogCache {
             this.defaultCatalog = loader.loadDefaultCatalog("EmptyCatalog.xml");
         } catch (final CatalogApiException e) {
             this.defaultCatalog = new VersionedCatalog();
-            logger.warn("Exception loading EmptyCatalog - should never happen!", e);
+            logger.error("Exception loading EmptyCatalog - should never happen!", e);
         }
     }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultCatalogService.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultCatalogService.java
index 053122c..cc12fa6 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultCatalogService.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultCatalogService.java
@@ -26,7 +26,6 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.billing.catalog.caching.CatalogCache;
-import org.killbill.billing.catalog.caching.CatalogCacheInvalidationCallback;
 import org.killbill.billing.catalog.glue.CatalogModule;
 import org.killbill.billing.platform.api.KillbillService;
 import org.killbill.billing.platform.api.LifecycleHandlerType;
@@ -34,7 +33,7 @@ import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
 import org.killbill.billing.tenant.api.TenantInternalApi;
 import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
 import org.killbill.billing.tenant.api.TenantKV.TenantKey;
-import org.killbill.billing.util.config.CatalogConfig;
+import org.killbill.billing.util.config.definition.CatalogConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -72,7 +71,7 @@ public class DefaultCatalogService implements KillbillService, CatalogService {
                 // In multi-tenant mode, the property is not required
                 if (config.getCatalogURI() != null && !config.getCatalogURI().isEmpty()) {
                     catalogCache.loadDefaultCatalog(config.getCatalogURI());
-                    log.info("Successfully loaded the default catalog " + config.getCatalogURI());
+                    log.info("Successfully loaded the default catalog {}", config.getCatalogURI());
                 }
                 isInitialized = true;
             } catch (Exception e) {
@@ -86,7 +85,7 @@ public class DefaultCatalogService implements KillbillService, CatalogService {
         tenantInternalApi.initializeCacheInvalidationCallback(TenantKey.CATALOG, cacheInvalidationCallback);
     }
 
-        @Override
+    @Override
     public String getName() {
         return CATALOG_SERVICE_NAME;
     }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
index 6fc145a..cc1e343 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
@@ -24,6 +24,8 @@ import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.joda.time.Period;
 
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Duration;
 import org.killbill.billing.catalog.api.TimeUnit;
 import org.killbill.xmlloader.ValidatingConfig;
@@ -61,7 +63,7 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
     }
 
     @Override
-    public DateTime addToDateTime(final DateTime dateTime) {
+    public DateTime addToDateTime(final DateTime dateTime) throws CatalogApiException {
         if ((number == null) && (unit != TimeUnit.UNLIMITED)) {
             return dateTime;
         }
@@ -75,12 +77,12 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
                 return dateTime.plusYears(number);
             case UNLIMITED:
             default:
-                throw new  IllegalStateException("Unexpected duration unit " + unit);
+                throw new CatalogApiException(ErrorCode.CAT_UNDEFINED_DURATION, unit);
         }
     }
 
     @Override
-    public LocalDate addToLocalDate(final LocalDate localDate) {
+    public LocalDate addToLocalDate(final LocalDate localDate) throws CatalogApiException {
         if ((number == null) && (unit != TimeUnit.UNLIMITED)) {
             return localDate;
         }
@@ -94,7 +96,7 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
                 return localDate.plusYears(number);
             case UNLIMITED:
             default:
-                throw new  IllegalStateException("Unexpected duration unit " + unit);
+                throw new CatalogApiException(ErrorCode.CAT_UNDEFINED_DURATION, unit);
         }
     }
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index 1955c5e..29cbeb2 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -56,9 +56,9 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
     @XmlID
     private String name;
 
-    //TODO MDW Validation - effectiveDateForExistingSubscriptons > catalog effectiveDate
+    //TODO MDW Validation - effectiveDateForExistingSubscriptions > catalog effectiveDate
     @XmlElement(required = false)
-    private Date effectiveDateForExistingSubscriptons;
+    private Date effectiveDateForExistingSubscriptions;
 
     @XmlElement(required = true)
     @XmlIDREF
@@ -86,7 +86,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
 
     public DefaultPlan(final String planName, final DefaultPlan in, final PlanPhasePriceOverride[] overrides) {
         this.name = planName;
-        this.effectiveDateForExistingSubscriptons = in.getEffectiveDateForExistingSubscriptons();
+        this.effectiveDateForExistingSubscriptions = in.getEffectiveDateForExistingSubscriptions();
         this.product = (DefaultProduct) in.getProduct();
         this.initialPhases = new DefaultPlanPhase[in.getInitialPhases().length];
         for (int i = 0; i < overrides.length - 1; i++) {
@@ -98,8 +98,8 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
     }
 
     @Override
-    public Date getEffectiveDateForExistingSubscriptons() {
-        return effectiveDateForExistingSubscriptons;
+    public Date getEffectiveDateForExistingSubscriptions() {
+        return effectiveDateForExistingSubscriptions;
     }
 
     @Override
@@ -190,10 +190,10 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
 
     @Override
     public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
-        if (effectiveDateForExistingSubscriptons != null &&
-            catalog.getEffectiveDate().getTime() > effectiveDateForExistingSubscriptons.getTime()) {
+        if (effectiveDateForExistingSubscriptions != null &&
+            catalog.getEffectiveDate().getTime() > effectiveDateForExistingSubscriptions.getTime()) {
             errors.add(new ValidationError(String.format("Price effective date %s is before catalog effective date '%s'",
-                                                         effectiveDateForExistingSubscriptons,
+                                                         effectiveDateForExistingSubscriptions,
                                                          catalog.getEffectiveDate().getTime()),
                                            catalog.getCatalogURI(), DefaultInternationalPrice.class, ""));
         }
@@ -203,9 +203,9 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
         return errors;
     }
 
-    public void setEffectiveDateForExistingSubscriptons(
-            final Date effectiveDateForExistingSubscriptons) {
-        this.effectiveDateForExistingSubscriptons = effectiveDateForExistingSubscriptons;
+    public void setEffectiveDateForExistingSubscriptions(
+            final Date effectiveDateForExistingSubscriptions) {
+        this.effectiveDateForExistingSubscriptions = effectiveDateForExistingSubscriptions;
     }
 
     public DefaultPlan setName(final String name) {
@@ -252,7 +252,10 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
             }
             final Recurring recurring = phase.getRecurring();
             if (recurring == null || recurring.getRecurringPrice() == null || recurring.getRecurringPrice().isZero()) {
-                result = phase.getDuration().addToDateTime(result);
+                try {
+                    result = phase.getDuration().addToDateTime(result);
+                } catch (final CatalogApiException ignored) {
+                }
             } else {
                 break;
             }
@@ -271,7 +274,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
 
         final DefaultPlan that = (DefaultPlan) o;
 
-        if (effectiveDateForExistingSubscriptons != null ? !effectiveDateForExistingSubscriptons.equals(that.effectiveDateForExistingSubscriptons) : that.effectiveDateForExistingSubscriptons != null) {
+        if (effectiveDateForExistingSubscriptions != null ? !effectiveDateForExistingSubscriptions.equals(that.effectiveDateForExistingSubscriptions) : that.effectiveDateForExistingSubscriptions != null) {
             return false;
         }
         if (finalPhase != null ? !finalPhase.equals(that.finalPhase) : that.finalPhase != null) {
@@ -295,7 +298,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
     @Override
     public int hashCode() {
         int result = name != null ? name.hashCode() : 0;
-        result = 31 * result + (effectiveDateForExistingSubscriptons != null ? effectiveDateForExistingSubscriptons.hashCode() : 0);
+        result = 31 * result + (effectiveDateForExistingSubscriptions != null ? effectiveDateForExistingSubscriptions.hashCode() : 0);
         result = 31 * result + (initialPhases != null ? Arrays.hashCode(initialPhases) : 0);
         result = 31 * result + (finalPhase != null ? finalPhase.hashCode() : 0);
         result = 31 * result + (plansAllowedInBundle != null ? plansAllowedInBundle.hashCode() : 0);
@@ -304,8 +307,8 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
 
     @Override
     public String toString() {
-        return "DefaultPlan [name=" + name + ", effectiveDateForExistingSubscriptons="
-               + effectiveDateForExistingSubscriptons + ", product=" + product + ", initialPhases="
+        return "DefaultPlan [name=" + name + ", effectiveDateForExistingSubscriptions="
+               + effectiveDateForExistingSubscriptions + ", product=" + product + ", initialPhases="
                + Arrays.toString(initialPhases) + ", finalPhase=" + finalPhase + ", plansAllowedInBundle="
                + plansAllowedInBundle + "]";
     }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
index 7651aad..794abd5 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
@@ -38,7 +38,7 @@ import org.killbill.billing.catalog.plugin.api.CatalogPluginApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
-import org.killbill.billing.util.config.CatalogConfig;
+import org.killbill.billing.util.config.definition.CatalogConfig;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.skife.config.ConfigurationObjectFactory;
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
index 7bf1326..a665d7d 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
@@ -20,6 +20,7 @@ package org.killbill.billing.catalog.io;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
+import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
@@ -37,6 +38,7 @@ import org.killbill.clock.Clock;
 import org.killbill.xmlloader.UriAccessor;
 import org.killbill.xmlloader.XMLLoader;
 
+import com.google.common.io.Resources;
 import com.google.inject.Inject;
 
 public class VersionedCatalogLoader implements CatalogLoader {
@@ -63,8 +65,9 @@ public class VersionedCatalogLoader implements CatalogLoader {
                 xmlURIs = new ArrayList<URI>();
                 xmlURIs.add(new URI(uriString));
             } else { // Assume its a directory
+                final URL url = getURLFromString(uriString);
                 final String directoryContents = UriAccessor.accessUriAsString(uriString);
-                xmlURIs = findXmlReferences(directoryContents, new URL(uriString));
+                xmlURIs = findXmlReferences(directoryContents, url);
             }
 
             final VersionedCatalog result = new VersionedCatalog(clock);
@@ -78,6 +81,16 @@ public class VersionedCatalogLoader implements CatalogLoader {
         }
     }
 
+    private URL getURLFromString(final String urlString) {
+        try {
+            // If the string provided is already a URL (with correct scheme, ...) return the URL object
+            return new URL(urlString);
+        } catch (final MalformedURLException ignore) {
+        }
+        // If not, this must be something on the classpath
+        return Resources.getResource(urlString);
+    }
+
     public VersionedCatalog load(final Iterable<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException {
         final VersionedCatalog result = new VersionedCatalog(clock);
         final URI uri;
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java b/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
index b8335b0..1b42d71 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
@@ -427,7 +427,7 @@ public class StandaloneCatalogMapper {
         }
         final DefaultPlan result = new DefaultPlan();
         result.setName(input.getName());
-        result.setEffectiveDateForExistingSubscriptons(input.getEffectiveDateForExistingSubscriptons());
+        result.setEffectiveDateForExistingSubscriptions(input.getEffectiveDateForExistingSubscriptions());
         result.setFinalPhase(toDefaultPlanPhase(input.getFinalPhase()));
         result.setInitialPhases(toDefaultPlanPhases(ImmutableList.copyOf(input.getInitialPhases())));
         result.setPlansAllowedInBundle(input.getPlansAllowedInBundle());
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/provider/DefaultCatalogProviderPluginRegistry.java b/catalog/src/main/java/org/killbill/billing/catalog/provider/DefaultCatalogProviderPluginRegistry.java
index e7e4005..d29ce36 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/provider/DefaultCatalogProviderPluginRegistry.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/provider/DefaultCatalogProviderPluginRegistry.java
@@ -41,13 +41,13 @@ public class DefaultCatalogProviderPluginRegistry implements OSGIServiceRegistra
 
     @Override
     public void registerService(final OSGIServiceDescriptor desc, final CatalogPluginApi service) {
-        log.info("DefaultInvoiceProviderPluginRegistry registering service " + desc.getRegistrationName());
+        log.info("Registering service='{}'", desc.getRegistrationName());
         pluginsByName.put(desc.getRegistrationName(), service);
     }
 
     @Override
     public void unregisterService(final String serviceName) {
-        log.info("DefaultInvoiceProviderPluginRegistry unregistering service " + serviceName);
+        log.info("Unregistering service='{}'", serviceName);
         pluginsByName.remove(serviceName);
     }
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index b4a222a..de4e38a 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
@@ -23,8 +23,10 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import javax.annotation.Nullable;
 import javax.xml.bind.annotation.XmlAccessType;
@@ -174,8 +176,8 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalogWithPric
             if (!subscriptionStartDate.isBefore(catalogEffectiveDate)) { // Its a new subscription this plan always applies
                 return plan;
             } else { //Its an existing subscription
-                if (plan.getEffectiveDateForExistingSubscriptons() != null) { //if it is null any change to this does not apply to existing subscriptions
-                    final DateTime existingSubscriptionDate = CatalogDateHelper.toUTCDateTime(plan.getEffectiveDateForExistingSubscriptons());
+                if (plan.getEffectiveDateForExistingSubscriptions() != null) { //if it is null any change to this does not apply to existing subscriptions
+                    final DateTime existingSubscriptionDate = CatalogDateHelper.toUTCDateTime(plan.getEffectiveDateForExistingSubscriptions());
                     if (requestedDate.isAfter(existingSubscriptionDate)) { // this plan is now applicable to existing subs
                         return plan;
                     }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java
index e3bef69..182bd7f 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java
@@ -24,6 +24,7 @@ import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
 import org.killbill.billing.mock.glue.MockTenantModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.CacheModule;
+import org.killbill.billing.util.glue.ConfigModule;
 
 public class TestCatalogModule extends CatalogModule {
 
@@ -36,6 +37,7 @@ public class TestCatalogModule extends CatalogModule {
         super.configure();
         install(new MockNonEntityDaoModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new MockTenantModule(configSource));
         install(new MockAccountModule(configSource));
     }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
index f4a20e3..2bea796 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
@@ -17,6 +17,7 @@
 package org.killbill.billing.catalog;
 
 import java.util.Date;
+import java.util.Set;
 
 import org.joda.time.DateTime;
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogService.java b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogService.java
index 08a434b..2557916 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogService.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogService.java
@@ -19,10 +19,8 @@
 package org.killbill.billing.catalog;
 
 import org.killbill.billing.catalog.api.CatalogApiException;
-import org.killbill.billing.catalog.io.VersionedCatalogLoader;
 import org.killbill.billing.platform.api.KillbillService.ServiceException;
-import org.killbill.billing.util.config.CatalogConfig;
-import org.killbill.clock.DefaultClock;
+import org.killbill.billing.util.config.definition.CatalogConfig;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -33,7 +31,7 @@ public class TestCatalogService extends CatalogTestSuiteNoDB {
         final DefaultCatalogService service = new DefaultCatalogService(new CatalogConfig() {
             @Override
             public String getCatalogURI() {
-                return "file:src/test/resources/versionedCatalog";
+                return "versionedCatalog";
             }
 
         }, tenantInternalApi, catalogCache, cacheInvalidationCallback);
@@ -47,7 +45,7 @@ public class TestCatalogService extends CatalogTestSuiteNoDB {
         final DefaultCatalogService service = new DefaultCatalogService(new CatalogConfig() {
             @Override
             public String getCatalogURI() {
-                return "file:src/test/resources/WeaponsHire.xml";
+                return "WeaponsHire.xml";
             }
 
         },  tenantInternalApi, catalogCache, cacheInvalidationCallback);
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java b/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
index 0af9c4a..858dde8 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
@@ -64,8 +64,8 @@ public class TestDefaultPriceOverride extends CatalogTestSuiteWithEmbeddedDB {
 
         assertEquals(overriddenPlan.getProduct().getName(), plan.getProduct().getName());
         assertEquals(overriddenPlan.getRecurringBillingPeriod(), plan.getRecurringBillingPeriod());
-        if (plan.getEffectiveDateForExistingSubscriptons() != null) {
-            assertEquals(overriddenPlan.getEffectiveDateForExistingSubscriptons().compareTo(plan.getEffectiveDateForExistingSubscriptons()), 0);
+        if (plan.getEffectiveDateForExistingSubscriptions() != null) {
+            assertEquals(overriddenPlan.getEffectiveDateForExistingSubscriptions().compareTo(plan.getEffectiveDateForExistingSubscriptions()), 0);
         }
         assertNotEquals(overriddenPlan.getFinalPhase().getName(), plan.getFinalPhase().getName());
         assertEquals(overriddenPlan.getPlansAllowedInBundle(), plan.getPlansAllowedInBundle());
@@ -131,8 +131,8 @@ public class TestDefaultPriceOverride extends CatalogTestSuiteWithEmbeddedDB {
 
         assertEquals(overriddenPlan.getProduct().getName(), plan.getProduct().getName());
         assertEquals(overriddenPlan.getRecurringBillingPeriod(), plan.getRecurringBillingPeriod());
-        if (plan.getEffectiveDateForExistingSubscriptons() != null) {
-            assertEquals(overriddenPlan.getEffectiveDateForExistingSubscriptons().compareTo(plan.getEffectiveDateForExistingSubscriptons()), 0);
+        if (plan.getEffectiveDateForExistingSubscriptions() != null) {
+            assertEquals(overriddenPlan.getEffectiveDateForExistingSubscriptions().compareTo(plan.getEffectiveDateForExistingSubscriptions()), 0);
         }
         assertNotEquals(overriddenPlan.getFinalPhase().getName(), plan.getFinalPhase().getName());
         assertEquals(overriddenPlan.getPlansAllowedInBundle(), plan.getPlansAllowedInBundle());
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestPlan.java b/catalog/src/test/java/org/killbill/billing/catalog/TestPlan.java
index 466b1c4..86f5033 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestPlan.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestPlan.java
@@ -32,7 +32,7 @@ public class TestPlan extends CatalogTestSuiteNoDB {
         final StandaloneCatalog c = new MockCatalog();
         c.setSupportedCurrencies(new Currency[]{Currency.GBP, Currency.EUR, Currency.USD, Currency.BRL, Currency.MXN});
         final DefaultPlan p1 = MockPlan.createBicycleTrialEvergreen1USD();
-        p1.setEffectiveDateForExistingSubscriptons(new Date((new Date().getTime()) - (1000 * 60 * 60 * 24)));
+        p1.setEffectiveDateForExistingSubscriptions(new Date((new Date().getTime()) - (1000 * 60 * 60 * 24)));
         final ValidationErrors errors = p1.validate(c, new ValidationErrors());
         Assert.assertEquals(errors.size(), 1);
         errors.log(log);
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
index 12d60f5..5aa65bd 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
@@ -47,7 +47,7 @@ public class TestVersionedCatalog extends CatalogTestSuiteNoDB {
     @BeforeClass(groups = "fast")
     public void beforeClass() throws Exception {
         super.beforeClass();
-        vc = loader.loadDefaultCatalog(Resources.getResource("versionedCatalog").toString());
+        vc = loader.loadDefaultCatalog("versionedCatalog");
     }
 
     @Test(groups = "fast")
diff --git a/catalog/src/test/resources/catalogTest.xml b/catalog/src/test/resources/catalogTest.xml
index 4b1ee25..a1cdec3 100644
--- a/catalog/src/test/resources/catalogTest.xml
+++ b/catalog/src/test/resources/catalogTest.xml
@@ -275,7 +275,80 @@
                 </recurring>
             </finalPhase>
         </plan>
-
+        <plan name="pistol-weekly">
+            <product>Pistol</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>WEEKLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>USD</currency>
+                            <value>29.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="pistol-thirty-days">
+            <product>Pistol</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>THIRTY_DAYS</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>USD</currency>
+                            <value>29.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
         <plan name="pistol-monthly">
             <product>Pistol</product>
             <initialPhases>
@@ -1101,6 +1174,8 @@
     <priceLists>
         <defaultPriceList name="DEFAULT">
             <plans>
+                <plan>pistol-weekly</plan>
+                <plan>pistol-thirty-days</plan>
                 <plan>blowdart-monthly</plan>
                 <plan>pistol-monthly</plan>
                 <plan>shotgun-monthly</plan>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
index 251678b..f04ff0e 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
@@ -87,7 +87,7 @@
 
     <plans>
         <plan name="pistol-monthly">
-            <effectiveDateForExistingSubscriptons>2011-02-14T00:00:00+00:00</effectiveDateForExistingSubscriptons>
+            <effectiveDateForExistingSubscriptions>2011-02-14T00:00:00+00:00</effectiveDateForExistingSubscriptions>
 
             <product>Pistol</product>
             <initialPhases>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
index bdeca61..dc9c0b1 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
@@ -87,7 +87,7 @@
 
     <plans>
         <plan name="pistol-monthly">
-            <effectiveDateForExistingSubscriptons>2011-03-14T00:00:00+00:00</effectiveDateForExistingSubscriptons>
+            <effectiveDateForExistingSubscriptions>2011-03-14T00:00:00+00:00</effectiveDateForExistingSubscriptions>
             <product>Pistol</product>
             <initialPhases>
                 <phase type="TRIAL">
diff --git a/currency/src/main/java/org/killbill/billing/currency/api/DefaultCurrencyConversionApi.java b/currency/src/main/java/org/killbill/billing/currency/api/DefaultCurrencyConversionApi.java
index e9e4559..4d8ecff 100644
--- a/currency/src/main/java/org/killbill/billing/currency/api/DefaultCurrencyConversionApi.java
+++ b/currency/src/main/java/org/killbill/billing/currency/api/DefaultCurrencyConversionApi.java
@@ -26,7 +26,7 @@ import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.currency.plugin.api.CurrencyPluginApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
-import org.killbill.billing.util.config.CurrencyConfig;
+import org.killbill.billing.util.config.definition.CurrencyConfig;
 
 public class DefaultCurrencyConversionApi implements CurrencyConversionApi {
 
diff --git a/currency/src/main/java/org/killbill/billing/currency/DefaultCurrencyProviderPluginRegistry.java b/currency/src/main/java/org/killbill/billing/currency/DefaultCurrencyProviderPluginRegistry.java
index abb6abd..2962722 100644
--- a/currency/src/main/java/org/killbill/billing/currency/DefaultCurrencyProviderPluginRegistry.java
+++ b/currency/src/main/java/org/killbill/billing/currency/DefaultCurrencyProviderPluginRegistry.java
@@ -26,7 +26,6 @@ import org.slf4j.LoggerFactory;
 import org.killbill.billing.currency.plugin.api.CurrencyPluginApi;
 import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
-import org.killbill.billing.util.config.CurrencyConfig;
 
 import com.google.inject.Inject;
 
@@ -42,13 +41,13 @@ public class DefaultCurrencyProviderPluginRegistry implements OSGIServiceRegistr
 
     @Override
     public void registerService(final OSGIServiceDescriptor desc, final CurrencyPluginApi service) {
-        log.info("DefaultCurrencyProviderPluginRegistry registering service " + desc.getRegistrationName());
+        log.info("Registering service='{}'", desc.getRegistrationName());
         pluginsByName.put(desc.getRegistrationName(), service);
     }
 
     @Override
     public void unregisterService(final String serviceName) {
-        log.info("DefaultCurrencyProviderPluginRegistry unregistering service " + serviceName);
+        log.info("Unregistering service='{}'", serviceName);
         pluginsByName.remove(serviceName);
     }
 
diff --git a/currency/src/main/java/org/killbill/billing/currency/glue/CurrencyModule.java b/currency/src/main/java/org/killbill/billing/currency/glue/CurrencyModule.java
index 3865eef..4252f6e 100644
--- a/currency/src/main/java/org/killbill/billing/currency/glue/CurrencyModule.java
+++ b/currency/src/main/java/org/killbill/billing/currency/glue/CurrencyModule.java
@@ -25,7 +25,7 @@ import org.killbill.billing.currency.api.DefaultCurrencyConversionApi;
 import org.killbill.billing.currency.plugin.api.CurrencyPluginApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.platform.api.KillbillConfigSource;
-import org.killbill.billing.util.config.CurrencyConfig;
+import org.killbill.billing.util.config.definition.CurrencyConfig;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.skife.config.ConfigurationObjectFactory;
 
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
index c004046..4cab8ea 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
@@ -385,19 +385,6 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
     }
 
     @Override
-    public void setBlockingState(final UUID bundleId, final String stateName, final String serviceName, final LocalDate effectiveDate, final boolean blockBilling, final boolean blockEntitlement, final boolean blockChange, final Iterable<PluginProperty> properties, final CallContext context)
-            throws EntitlementApiException {
-        final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context);
-        super.setBlockingState(bundleId, stateName, serviceName, effectiveDate, blockBilling, blockEntitlement, blockChange, properties, contextWithValidAccountRecordId);
-    }
-
-    @Override
-    public Iterable<BlockingState> getBlockingStatesForServiceAndType(final UUID blockableId, final BlockingStateType blockingStateType, final String serviceName, final TenantContext tenantContext) {
-        // Not implemented see #431
-        return null;
-    }
-
-    @Override
     public UUID transferEntitlements(final UUID sourceAccountId, final UUID destAccountId, final String externalKey, final LocalDate effectiveDate, final Iterable<PluginProperty> properties, final CallContext context) throws EntitlementApiException {
         return transferEntitlementsOverrideBillingPolicy(sourceAccountId, destAccountId, externalKey, effectiveDate, BillingActionPolicy.IMMEDIATE, properties, context);
     }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
index fc63c7b..3c3473a 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
@@ -29,6 +29,7 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
+import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
@@ -46,6 +47,7 @@ import org.killbill.billing.entitlement.dao.BlockingStateDao;
 import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
 import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
 import org.killbill.billing.entitlement.plugin.api.OperationType;
+import org.killbill.billing.junction.DefaultBlockingState;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
@@ -226,7 +228,7 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
                                                       try {
                                                           return getSubscriptionBundle(subscriptionBaseBundle.getId(), context);
                                                       } catch (final SubscriptionApiException e) {
-                                                          log.warn("Error retrieving subscription", e);
+                                                          log.warn("Error retrieving bundleId='{}'", subscriptionBaseBundle.getId(), e);
                                                           return null;
                                                       }
                                                   }
@@ -250,7 +252,7 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
                                                       try {
                                                           return getSubscriptionBundle(subscriptionBaseBundle.getId(), context);
                                                       } catch (final SubscriptionApiException e) {
-                                                          log.warn("Error retrieving subscription", e);
+                                                          log.warn("Error retrieving bundleId='{}'", subscriptionBaseBundle.getId(), e);
                                                           return null;
                                                       }
                                                   }
@@ -300,17 +302,17 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
     }
 
     @Override
-    public void addBlockingState(final BlockingState blockingState, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+    public void addBlockingState(final BlockingState inputBlockingState, @Nullable final LocalDate inputEffectiveDate, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
 
         // This is in no way an exhaustive arg validation, but to to ensure plugin would not hijack private entitlement state or service name
-        if (blockingState.getService() == null || blockingState.getService().equals(EntitlementService.ENTITLEMENT_SERVICE_NAME)) {
+        if (inputBlockingState.getService() == null || inputBlockingState.getService().equals(EntitlementService.ENTITLEMENT_SERVICE_NAME)) {
             throw new EntitlementApiException(ErrorCode.SUB_BLOCKING_STATE_INVALID_ARG, "Need to specify a valid serviceName");
         }
 
-        if (blockingState.getStateName() == null ||
-            blockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED) ||
-            blockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_BLOCKED) ||
-            blockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CLEAR)) {
+        if (inputBlockingState.getStateName() == null ||
+            inputBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED) ||
+            inputBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_BLOCKED) ||
+            inputBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CLEAR)) {
             throw new EntitlementApiException(ErrorCode.SUB_BLOCKING_STATE_INVALID_ARG, "Need to specify a valid stateName");
         }
 
@@ -320,35 +322,33 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
         final UUID bundleId;
         final String externalKey;
         try {
-            switch (blockingState.getType()) {
+            switch (inputBlockingState.getType()) {
                 case ACCOUNT:
-                    internalCallContextWithValidAccountId = internalCallContextFactory.createInternalCallContext(blockingState.getBlockedId(), ObjectType.ACCOUNT, callContext);
-                    account = accountApi.getImmutableAccountDataById(blockingState.getBlockedId(), internalCallContextWithValidAccountId);
+                    internalCallContextWithValidAccountId = internalCallContextFactory.createInternalCallContext(inputBlockingState.getBlockedId(), ObjectType.ACCOUNT, callContext);
+                    account = accountApi.getImmutableAccountDataById(inputBlockingState.getBlockedId(), internalCallContextWithValidAccountId);
                     externalKey = account.getExternalKey();
                     accountId = account.getId();
                     bundleId = null;
                     break;
 
                 case SUBSCRIPTION_BUNDLE:
-                    internalCallContextWithValidAccountId = internalCallContextFactory.createInternalCallContext(blockingState.getBlockedId(), ObjectType.BUNDLE, callContext);
-                    final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getBundleFromId(blockingState.getBlockedId(), internalCallContextWithValidAccountId);
+                    internalCallContextWithValidAccountId = internalCallContextFactory.createInternalCallContext(inputBlockingState.getBlockedId(), ObjectType.BUNDLE, callContext);
+                    final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getBundleFromId(inputBlockingState.getBlockedId(), internalCallContextWithValidAccountId);
                     externalKey = bundle.getExternalKey();
                     bundleId = bundle.getId();
                     accountId = bundle.getAccountId();
-                    account = accountApi.getImmutableAccountDataById(accountId, internalCallContextWithValidAccountId);
                     break;
 
                 case SUBSCRIPTION:
-                    internalCallContextWithValidAccountId = internalCallContextFactory.createInternalCallContext(blockingState.getBlockedId(), ObjectType.SUBSCRIPTION, callContext);
-                    final Entitlement entitlement = entitlementInternalApi.getEntitlementForId(blockingState.getBlockedId(), internalCallContextWithValidAccountId);
+                    internalCallContextWithValidAccountId = internalCallContextFactory.createInternalCallContext(inputBlockingState.getBlockedId(), ObjectType.SUBSCRIPTION, callContext);
+                    final Entitlement entitlement = entitlementInternalApi.getEntitlementForId(inputBlockingState.getBlockedId(), internalCallContextWithValidAccountId);
                     bundleId = entitlement.getBundleId();
                     accountId = entitlement.getAccountId();
-                    account = accountApi.getImmutableAccountDataById(accountId, internalCallContextWithValidAccountId);
                     externalKey = null;
                     break;
 
                 default:
-                    throw new IllegalStateException("Invalid blockingStateType " + blockingState.getType());
+                    throw new IllegalStateException("Invalid blockingStateType " + inputBlockingState.getType());
             }
         } catch (final AccountApiException e) {
             throw new EntitlementApiException(e);
@@ -356,7 +356,8 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
             throw new EntitlementApiException(e);
         }
 
-
+        final DateTime effectiveDate = inputEffectiveDate == null ? clock.getUTCNow() : internalCallContextWithValidAccountId.toUTCDateTime(inputEffectiveDate);
+        final DefaultBlockingState blockingState = new DefaultBlockingState(inputBlockingState, effectiveDate);
 
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.ADD_BLOCKING_STATE,
                                                                                accountId,
@@ -364,13 +365,12 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
                                                                                bundleId,
                                                                                externalKey,
                                                                                new ArrayList<EntitlementSpecifier>(),
-                                                                               new LocalDate(blockingState.getEffectiveDate(), account.getTimeZone()),
+                                                                               internalCallContextWithValidAccountId.toLocalDate(effectiveDate),
                                                                                null,
                                                                                null,
                                                                                properties,
                                                                                callContext);
 
-
         final WithEntitlementPlugin<Void> addBlockingStateWithPlugin = new WithEntitlementPlugin<Void>() {
 
             @Override
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionEvent.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionEvent.java
index ea983ad..8e6b127 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionEvent.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionEvent.java
@@ -29,6 +29,7 @@ import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.Product;
 
+
 public class DefaultSubscriptionEvent implements SubscriptionEvent {
 
     private final UUID id;
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
index b1a720c..5899f53 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
@@ -177,7 +177,6 @@ public class DefaultEntitlementApiBase {
                     }
 
                     blockUnblockBundle(bundleId, DefaultEntitlementApi.ENT_STATE_BLOCKED, EntitlementService.ENTITLEMENT_SERVICE_NAME, localEffectiveDate, true, true, true, baseSubscription, internalCallContext);
-
                 } catch (SubscriptionBaseApiException e) {
                     throw new EntitlementApiException(e);
                 } catch (AccountApiException e) {
@@ -218,7 +217,6 @@ public class DefaultEntitlementApiBase {
                     }
 
                     blockUnblockBundle(bundleId, DefaultEntitlementApi.ENT_STATE_CLEAR, EntitlementService.ENTITLEMENT_SERVICE_NAME, localEffectiveDate, false, false, false, baseSubscription, internalCallContext);
-
                 } catch (SubscriptionBaseApiException e) {
                     throw new EntitlementApiException(e);
                 } catch (AccountApiException e) {
@@ -230,11 +228,6 @@ public class DefaultEntitlementApiBase {
         pluginExecution.executeWithPlugin(resumeWithPlugin, pluginContext);
     }
 
-    public void setBlockingState(final UUID bundleId, final String stateName, final String serviceName, final LocalDate localEffectiveDate, boolean blockBilling, boolean blockEntitlement, boolean blockChange, final Iterable<PluginProperty> properties, final InternalCallContext internalCallContext)
-            throws EntitlementApiException {
-        blockUnblockBundle(bundleId, stateName, serviceName, localEffectiveDate, blockBilling, blockEntitlement, blockChange, null, internalCallContext);
-    }
-
     private UUID blockUnblockBundle(final UUID bundleId, final String stateName, final String serviceName, @Nullable final LocalDate localEffectiveDate, boolean blockBilling, boolean blockEntitlement, boolean blockChange, @Nullable final SubscriptionBase inputBaseSubscription, final InternalCallContext internalCallContext)
             throws EntitlementApiException {
         final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(localEffectiveDate, internalCallContext);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
index 056bd3a..db2577e 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
@@ -353,7 +353,7 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
         try {
             eventBus.postFromTransaction(event, entitySqlDaoWrapperFactory.getHandle().getConnection());
         } catch (final EventBusException e) {
-            log.warn("Failed to post event {}", e);
+            log.warn("Failed to post event {}", event, e);
         }
     }
 
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java b/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
index 68465ae..0dcf6f6 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
@@ -107,7 +107,7 @@ public class DefaultEntitlementService implements EntitlementService {
                     } else if (inputKey instanceof BlockingTransitionNotificationKey) {
                         processBlockingNotification((BlockingTransitionNotificationKey) inputKey, internalCallContext);
                     } else if (inputKey != null) {
-                        log.error("Entitlement service received an unexpected event type {}" + inputKey.getClass());
+                        log.error("Entitlement service received an unexpected event className='{}", inputKey.getClass());
                     } else {
                         log.error("Entitlement service received an unexpected null event");
                     }
@@ -127,12 +127,12 @@ public class DefaultEntitlementService implements EntitlementService {
         try {
             entitlement = entitlementInternalApi.getEntitlementForId(key.getEntitlementId(), internalCallContext);
         } catch (final EntitlementApiException e) {
-            log.error("Error retrieving entitlement for id " + key.getEntitlementId(), e);
+            log.error("Error retrieving entitlementId='{}'", key.getEntitlementId(), e);
             return;
         }
 
         if (!(entitlement instanceof DefaultEntitlement)) {
-            log.error("Entitlement service received an unexpected entitlement class type {}" + entitlement.getClass().getName());
+            log.error("Error retrieving entitlementId='{}', unexpected entitlement className='{}'", key.getEntitlementId(), entitlement.getClass().getName());
             return;
         }
 
@@ -147,7 +147,7 @@ public class DefaultEntitlementService implements EntitlementService {
                 entitlementInternalApi.resume(key.getBundleId(), internalCallContext.toLocalDate(key.getEffectiveDate()), ImmutableList.<PluginProperty>of(), internalCallContext);
             }
         } catch (final EntitlementApiException e) {
-            log.error("Error processing event for entitlement {}" + entitlement.getId(), e);
+            log.error("Error processing event for entitlementId='{}'", entitlement.getId(), e);
         }
     }
 
@@ -198,7 +198,7 @@ public class DefaultEntitlementService implements EntitlementService {
         try {
             eventBus.post(event);
         } catch (final EventBusException e) {
-            log.warn("Failed to post event {}", e);
+            log.warn("Failed to post event {}", event, e);
         }
     }
 
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/provider/DefaultEntitlementProviderPluginRegistry.java b/entitlement/src/main/java/org/killbill/billing/entitlement/provider/DefaultEntitlementProviderPluginRegistry.java
index 54b1f19..9f0b11a 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/provider/DefaultEntitlementProviderPluginRegistry.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/provider/DefaultEntitlementProviderPluginRegistry.java
@@ -41,13 +41,13 @@ public class DefaultEntitlementProviderPluginRegistry implements OSGIServiceRegi
 
     @Override
     public void registerService(final OSGIServiceDescriptor desc, final EntitlementPluginApi service) {
-        log.info("DefaultEntitlementProviderPluginRegistry registering service " + desc.getRegistrationName());
+        log.info("Registering service='{}'", desc.getRegistrationName());
         pluginsByName.put(desc.getRegistrationName(), service);
     }
 
     @Override
     public void unregisterService(final String serviceName) {
-        log.info("DefaultEntitlementProviderPluginRegistry unregistering service " + serviceName);
+        log.info("Unregistering service='{}'", serviceName);
         pluginsByName.remove(serviceName);
     }
 
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
index 7052c35..327046c 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
@@ -280,55 +280,6 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
         assertEquals(subscription.getBillingEndDate(), new LocalDate(2013, 8, 7));
     }
 
-    @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/452")
-    public void testBlockedEntitlementChange() throws AccountApiException, EntitlementApiException {
-        final LocalDate initialDate = new LocalDate(2013, 8, 7);
-        clock.setDay(initialDate);
-
-        final Account account = createAccount(getAccountData(7));
-
-        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-
-        // Create entitlement and check each field
-        testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
-        assertListenerStatus();
-
-        clock.addDays(1);
-        assertListenerStatus();
-
-        testListener.pushExpectedEvent(NextEvent.BLOCK);
-        entitlementApi.setBlockingState(entitlement.getBundleId(), "MY_BLOCK", "test", null, false, false, true, ImmutableList.<PluginProperty>of(), callContext);
-        assertListenerStatus();
-
-        try {
-            entitlement.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, ImmutableList.<PluginProperty>of(), callContext);
-            fail();
-        } catch (final EntitlementApiException e) {
-            assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
-            final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
-            assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
-        }
-
-        try {
-            entitlement.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
-            fail();
-        } catch (final EntitlementApiException e) {
-            assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
-            final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
-            assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
-        }
-
-        try {
-            entitlement.changePlanOverrideBillingPolicy("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
-            fail();
-        } catch (final EntitlementApiException e) {
-            assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
-            final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
-            assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
-        }
-    }
-
     @Test(groups = "slow")
     public void testEntitlementStartedInFuture() throws AccountApiException, EntitlementApiException {
         final LocalDate initialDate = new LocalDate(2013, 8, 7);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
index 256616d..62824a9 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -444,73 +444,6 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
 
     }
 
-    @Test(groups = "slow")
-    public void testBlockBundle() throws AccountApiException, EntitlementApiException {
-        final LocalDate initialDate = new LocalDate(2013, 8, 7);
-        clock.setDay(initialDate);
-
-        final Account account = createAccount(getAccountData(7));
-
-        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-
-        // Create entitlement and check each field
-        testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
-        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
-        assertListenerStatus();
-
-        clock.addDays(5);
-
-        testListener.pushExpectedEvents(NextEvent.BLOCK);
-        entitlementApi.setBlockingState(baseEntitlement.getBundleId(), "BLOCK", "foo", null, true, true, true, ImmutableList.<PluginProperty>of(), callContext);
-        assertListenerStatus();
-
-        List<Entitlement> bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
-        assertEquals(bundleEntitlements.size(), 1);
-        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.BLOCKED);
-
-        final BlockingState blockingState = blockingInternalApi.getBlockingStateForService(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "foo", internalCallContext);
-        assertTrue(blockingState.isBlockBilling());
-        assertTrue(blockingState.isBlockChange());
-        assertTrue(blockingState.isBlockEntitlement());
-
-        // Check unblocking on another service will not bring the state back to ACTIVE
-        clock.addDays(1);
-        testListener.pushExpectedEvents(NextEvent.BLOCK);
-        entitlementApi.setBlockingState(baseEntitlement.getBundleId(), "UNBLOCK", "bar", null, false, false, false, ImmutableList.<PluginProperty>of(), callContext);
-        assertListenerStatus();
-
-        bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
-        assertEquals(bundleEntitlements.size(), 1);
-        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.BLOCKED);
-
-        testListener.pushExpectedEvents(NextEvent.BLOCK);
-        entitlementApi.setBlockingState(baseEntitlement.getBundleId(), "UNBLOCK", "foo", null, false, false, false, ImmutableList.<PluginProperty>of(), callContext);
-        assertListenerStatus();
-
-        bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
-        assertEquals(bundleEntitlements.size(), 1);
-        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.ACTIVE);
-
-        blockingInternalApi.getBlockingStateForService(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "foo", internalCallContext);
-        clock.addDays(1);
-
-        testListener.pushExpectedEvents(NextEvent.BLOCK);
-        entitlementApi.setBlockingState(baseEntitlement.getBundleId(), "BLOCK", "foo", null, true, true, true, ImmutableList.<PluginProperty>of(), callContext);
-        assertListenerStatus();
-
-        bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
-        assertEquals(bundleEntitlements.size(), 1);
-        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.BLOCKED);
-
-        // Same day but happened after so should take precedence
-        testListener.pushExpectedEvents(NextEvent.BLOCK);
-        entitlementApi.setBlockingState(baseEntitlement.getBundleId(), "UNBLOCK", "foo", null, false, false, false, ImmutableList.<PluginProperty>of(), callContext);
-        assertListenerStatus();
-
-        bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
-        assertEquals(bundleEntitlements.size(), 1);
-        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.ACTIVE);
-    }
 
     @Test(groups = "slow")
     public void testCreateEntitlementInThePast() throws AccountApiException, EntitlementApiException, SubscriptionBaseApiException {
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
index 171be11..4d6c5f2 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
@@ -49,6 +49,7 @@ import com.google.common.collect.ImmutableList;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbeddedDB {
 
@@ -346,7 +347,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
 
         testListener.pushExpectedEvent(NextEvent.BLOCK);
         final BlockingState state1 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "accountBlock", "svc1", false, true, false, clock.getUTCNow());
-        subscriptionApi.addBlockingState(state1, ImmutableList.<PluginProperty>of(), callContext);
+        subscriptionApi.addBlockingState(state1, null, ImmutableList.<PluginProperty>of(), callContext);
         assertListenerStatus();
 
         Entitlement updateEntitlement = entitlementApi.getEntitlementForId(createdEntitlement.getId(), callContext);
@@ -356,7 +357,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
 
         testListener.pushExpectedEvent(NextEvent.BLOCK);
         final BlockingState state2 = new DefaultBlockingState(createdEntitlement.getId(), BlockingStateType.SUBSCRIPTION, "subscriptionBlock", "svc2", false, false, false, clock.getUTCNow());
-        subscriptionApi.addBlockingState(state2, ImmutableList.<PluginProperty>of(), callContext);
+        subscriptionApi.addBlockingState(state2, null, ImmutableList.<PluginProperty>of(), callContext);
         assertListenerStatus();
 
         // Still blocked because this is a different service
@@ -366,7 +367,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
         // Now we remove the blocking state for the same service but at the SUBSCRIPTION level
         testListener.pushExpectedEvent(NextEvent.BLOCK);
         final BlockingState state3 = new DefaultBlockingState(createdEntitlement.getId(), BlockingStateType.SUBSCRIPTION, "subscriptionUnBlock", "svc1", false, false, false, clock.getUTCNow());
-        subscriptionApi.addBlockingState(state3, ImmutableList.<PluginProperty>of(), callContext);
+        subscriptionApi.addBlockingState(state3, null, ImmutableList.<PluginProperty>of(), callContext);
         assertListenerStatus();
 
         updateEntitlement = entitlementApi.getEntitlementForId(createdEntitlement.getId(), callContext);
@@ -374,7 +375,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
 
         final DateTime futureEffectiveDate = clock.getUTCNow().plusDays(1);
         final BlockingState state4 = new DefaultBlockingState(createdEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "blockBilling", "svc1", true, false, false, futureEffectiveDate);
-        subscriptionApi.addBlockingState(state4, ImmutableList.<PluginProperty>of(), callContext);
+        subscriptionApi.addBlockingState(state4, internalCallContext.toLocalDate(futureEffectiveDate), ImmutableList.<PluginProperty>of(), callContext);
 
         final Iterable<BlockingState> blockingStates1 = subscriptionApi.getBlockingStates(account.getId(), ImmutableList.of(BlockingStateType.ACCOUNT, BlockingStateType.SUBSCRIPTION), ImmutableList.of("svc1", "svc2"), OrderingType.ASCENDING, SubscriptionApi.PAST_OR_PRESENT_EVENTS, callContext);
         verifyBlockingStates(blockingStates1, ImmutableList.<BlockingState>of(state1, state2, state3));
@@ -400,6 +401,131 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
 
     }
 
+    @Test(groups = "slow")
+    public void testBlockBundle() throws AccountApiException, EntitlementApiException {
+        final LocalDate initialDate = new LocalDate(2013, 8, 7);
+        clock.setDay(initialDate);
+
+        final Account account = createAccount(getAccountData(7));
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+        // Create entitlement and check each field
+        testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        clock.addDays(5);
+
+        testListener.pushExpectedEvents(NextEvent.BLOCK);
+        final BlockingState state1 = new DefaultBlockingState(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "BLOCK", "foo", true, true, true, null);
+        subscriptionApi.addBlockingState(state1, null, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        List<Entitlement> bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
+        assertEquals(bundleEntitlements.size(), 1);
+        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.BLOCKED);
+
+        final BlockingState blockingState = blockingInternalApi.getBlockingStateForService(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "foo", internalCallContext);
+        assertTrue(blockingState.isBlockBilling());
+        assertTrue(blockingState.isBlockChange());
+        assertTrue(blockingState.isBlockEntitlement());
+
+        // Check unblocking on another service will not bring the state back to ACTIVE
+        clock.addDays(1);
+        testListener.pushExpectedEvents(NextEvent.BLOCK);
+        final BlockingState state2 = new DefaultBlockingState(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "UNBLOCK", "bar", false, false, false, null);
+        subscriptionApi.addBlockingState(state2, null, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
+        assertEquals(bundleEntitlements.size(), 1);
+        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.BLOCKED);
+
+        testListener.pushExpectedEvents(NextEvent.BLOCK);
+        final BlockingState state3 = new DefaultBlockingState(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "UNBLOCK", "foo", false, false, false, null);
+        subscriptionApi.addBlockingState(state3, null, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
+        assertEquals(bundleEntitlements.size(), 1);
+        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.ACTIVE);
+
+        blockingInternalApi.getBlockingStateForService(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "foo", internalCallContext);
+        clock.addDays(1);
+
+        testListener.pushExpectedEvents(NextEvent.BLOCK);
+        final BlockingState state4 = new DefaultBlockingState(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "BLOCK", "foo", true, true, true, null);
+        subscriptionApi.addBlockingState(state4, null, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
+        assertEquals(bundleEntitlements.size(), 1);
+        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.BLOCKED);
+
+        // Same day but happened after so should take precedence
+        testListener.pushExpectedEvents(NextEvent.BLOCK);
+        final BlockingState state5 = new DefaultBlockingState(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "UNBLOCK", "foo", false, false, false, null);
+        subscriptionApi.addBlockingState(state5, null, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        bundleEntitlements = entitlementApi.getAllEntitlementsForBundle(baseEntitlement.getBundleId(), callContext);
+        assertEquals(bundleEntitlements.size(), 1);
+        assertEquals(bundleEntitlements.get(0).getState(), EntitlementState.ACTIVE);
+    }
+
+
+    @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/452")
+    public void testBlockedEntitlementChange() throws AccountApiException, EntitlementApiException {
+        final LocalDate initialDate = new LocalDate(2013, 8, 7);
+        clock.setDay(initialDate);
+
+        final Account account = createAccount(getAccountData(7));
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+        // Create entitlement and check each field
+        testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        clock.addDays(1);
+        assertListenerStatus();
+
+        testListener.pushExpectedEvent(NextEvent.BLOCK);
+        final BlockingState state = new DefaultBlockingState(entitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "MY_BLOCK", "test", true, false, false, null);
+        subscriptionApi.addBlockingState(state, null, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        try {
+            entitlement.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, ImmutableList.<PluginProperty>of(), callContext);
+            fail();
+        } catch (final EntitlementApiException e) {
+            assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+            final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+            assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
+        }
+
+        try {
+            entitlement.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
+            fail();
+        } catch (final EntitlementApiException e) {
+            assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+            final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+            assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
+        }
+
+        try {
+            entitlement.changePlanOverrideBillingPolicy("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+            fail();
+        } catch (final EntitlementApiException e) {
+            assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+            final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+            assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
+        }
+    }
+
+
     private void verifyBlockingStates(final Iterable<BlockingState> result, final List<BlockingState> expected) {
         int i = 0;
         final Iterator<BlockingState> iterator = result.iterator();
@@ -412,7 +538,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
             assertEquals(cur.getService(), expectedItem.getService());
             assertEquals(cur.getStateName(), expectedItem.getStateName());
             assertEquals(cur.getBlockedId(), expectedItem.getBlockedId());
-            assertEquals(cur.getEffectiveDate().compareTo(expectedItem.getEffectiveDate()), 0);
+            assertEquals(internalCallContext.toLocalDate(cur.getEffectiveDate()).compareTo(internalCallContext.toLocalDate(expectedItem.getEffectiveDate())), 0);
             i++;
         }
         assertEquals(i, expected.size());
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java
index 3867c9f..e5f00d3 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java
@@ -132,7 +132,7 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
 
         testListener.pushExpectedEvent(NextEvent.BLOCK);
         final BlockingState state1 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, stateNameBlock, service, blockChange, blockEntitlement, blockBilling, clock.getUTCNow());
-        blockingInternalApi.setBlockingState(state1, internalCallContext);
+        subscriptionApi.addBlockingState(state1, null, ImmutableList.<PluginProperty>of(), callContext);
         assertListenerStatus();
 
         final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
@@ -146,7 +146,8 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
         // Add blocking at bundle level.
         clock.addDays(1);
         testListener.pushExpectedEvent(NextEvent.BLOCK);
-        entitlementApi.setBlockingState(baseEntitlement.getBundleId(), stateNameBlock, service, null, blockBilling, blockEntitlement, blockChange, ImmutableList.<PluginProperty>of(), callContext);
+        final BlockingState state2 = new DefaultBlockingState(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, stateNameBlock, service, blockChange, blockEntitlement, blockBilling, null);
+        subscriptionApi.addBlockingState(state2, null, ImmutableList.<PluginProperty>of(), callContext);
         assertListenerStatus();
 
 
@@ -156,8 +157,8 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
         // Remove blocking at account level
         clock.addDays(1);
         testListener.pushExpectedEvent(NextEvent.BLOCK);
-        final BlockingState state2 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, stateNameUnBlock, service, false, false, false, clock.getUTCNow());
-        blockingInternalApi.setBlockingState(state2, internalCallContext);
+        final BlockingState state3 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, stateNameUnBlock, service, false, false, false, clock.getUTCNow());
+        subscriptionApi.addBlockingState(state3, null, ImmutableList.<PluginProperty>of(), callContext);
         assertListenerStatus();
 
         baseEntitlement = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
@@ -167,7 +168,8 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
         // Remove blocking at bundle level.
         clock.addDays(1);
         testListener.pushExpectedEvent(NextEvent.BLOCK);
-        entitlementApi.setBlockingState(baseEntitlement.getBundleId(), stateNameUnBlock, service, null, false, false, false, ImmutableList.<PluginProperty>of(), callContext);
+        final BlockingState state4 = new DefaultBlockingState(baseEntitlement.getBundleId(), BlockingStateType.SUBSCRIPTION_BUNDLE, stateNameUnBlock, service, false, false, false, null);
+        subscriptionApi.addBlockingState(state4, null, ImmutableList.<PluginProperty>of(), callContext);
         assertListenerStatus();
 
         baseEntitlement = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModule.java b/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModule.java
index 5b7aab2..0694c41 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModule.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModule.java
@@ -22,6 +22,7 @@ import org.killbill.billing.mock.glue.MockTenantModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.KillBillShiroAopModule;
 import org.killbill.billing.util.glue.KillBillShiroModule;
 import org.killbill.billing.util.glue.SecurityModule;
@@ -40,6 +41,7 @@ public class TestEntitlementModule extends DefaultEntitlementModule {
     protected void configure() {
         super.configure();
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new CallContextModule(configSource));
         install(new MockTenantModule(configSource));
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java
index 82dab56..a081e7b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java
@@ -60,7 +60,7 @@ public class DefaultInvoiceService implements InvoiceService {
             eventBus.register(invoiceListener);
             eventBus.register(tagHandler);
         } catch (PersistentBus.EventBusException e) {
-            throw new RuntimeException("Unable to register to the EventBus!", e);
+            throw new RuntimeException("Failed to register bus handlers", e);
         }
         dateNotifier.initialize();
         parentInvoiceNotifier.initialize();
@@ -78,7 +78,7 @@ public class DefaultInvoiceService implements InvoiceService {
             eventBus.unregister(invoiceListener);
             eventBus.unregister(tagHandler);
         } catch (PersistentBus.EventBusException e) {
-            throw new RuntimeException("Unable to unregister to the EventBus!", e);
+            throw new RuntimeException("Failed to unregister bus handlers", e);
         }
         dateNotifier.stop();
         parentInvoiceNotifier.stop();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index fc0cbb8..811c11c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -46,7 +46,7 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
 
     @Override
     public List<InvoicePayment> getInvoicePayments(final UUID paymentId, final TenantContext context) {
-        return ImmutableList.<InvoicePayment>copyOf(Collections2.transform(dao.getInvoicePayments(paymentId, internalCallContextFactory.createInternalTenantContext(paymentId, ObjectType.PAYMENT, context)),
+        return ImmutableList.<InvoicePayment>copyOf(Collections2.transform(dao.getInvoicePaymentsByPaymentId(paymentId, internalCallContextFactory.createInternalTenantContext(paymentId, ObjectType.PAYMENT, context)),
                                                                            new Function<InvoicePaymentModelDao, InvoicePayment>() {
                                                                                @Override
                                                                                public InvoicePayment apply(final InvoicePaymentModelDao input) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
index dd29cd1..7e8c68c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
@@ -41,7 +41,7 @@ import org.killbill.billing.invoice.model.ItemAdjInvoiceItem;
 import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.commons.locker.GlobalLock;
 import org.killbill.commons.locker.GlobalLocker;
@@ -54,7 +54,6 @@ import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 
 public class InvoiceApiHelper {
@@ -103,7 +102,7 @@ public class InvoiceApiHelper {
             final List<InvoiceItemModelDao> createdInvoiceItems = dao.createInvoices(invoiceModelDaos, internalCallContext);
             return fromInvoiceItemModelDao(createdInvoiceItems);
         } catch (final LockFailedException e) {
-            log.error(String.format("Failed to process invoice items for account %s", accountId.toString()), e);
+            log.warn("Failed to process invoice items for accountId='{}'", accountId.toString(), e);
             return ImmutableList.<InvoiceItem>of();
         } finally {
             if (lock != null) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
index 7c69cd5..acbd103 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
@@ -95,14 +95,16 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
     }
 
     @Override
-    public void notifyOfPayment(final UUID invoiceId, final BigDecimal amount, final Currency currency, final Currency processedCurrency, final UUID paymentId, final DateTime paymentDate, final boolean success, final InternalCallContext context) throws InvoiceApiException {
-        final InvoicePayment invoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, success);
-        notifyOfPayment(invoicePayment, context);
+    public void recordPaymentAttemptInit(final UUID invoiceId, final BigDecimal amount, final Currency currency, final Currency processedCurrency, final UUID paymentId, final String transactionExternalKey, final DateTime paymentDate, final InternalCallContext context) throws InvoiceApiException {
+        final InvoicePayment invoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, transactionExternalKey, false);
+        dao.notifyOfPaymentInit(new InvoicePaymentModelDao(invoicePayment), context);
     }
 
+
     @Override
-    public void notifyOfPayment(final InvoicePayment invoicePayment, final InternalCallContext context) throws InvoiceApiException {
-        dao.notifyOfPayment(new InvoicePaymentModelDao(invoicePayment), context);
+    public void recordPaymentAttemptCompletion(final UUID invoiceId, final BigDecimal amount, final Currency currency, final Currency processedCurrency, final UUID paymentId, final String transactionExternalKey, final DateTime paymentDate, final boolean success, final InternalCallContext context) throws InvoiceApiException {
+        final InvoicePayment invoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, transactionExternalKey, success);
+        dao.notifyOfPaymentCompletion(new InvoicePaymentModelDao(invoicePayment), context);
     }
 
     @Override
@@ -122,7 +124,7 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
     }
 
     @Override
-    public InvoicePayment createRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final String transactionExternalKey, final InternalCallContext context) throws InvoiceApiException {
+    public InvoicePayment recordRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final String transactionExternalKey, final InternalCallContext context) throws InvoiceApiException {
         if (amount.compareTo(BigDecimal.ZERO) <= 0) {
             throw new InvoiceApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL, paymentId, amount);
         }
@@ -145,7 +147,7 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
     }
 
     @Override
-    public InvoicePayment createChargeback(final UUID paymentId, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
+    public InvoicePayment recordChargeback(final UUID paymentId, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
         return new DefaultInvoicePayment(dao.postChargeback(paymentId, amount, currency, context));
     }
 
@@ -156,26 +158,25 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
 
     @Override
     public Map<UUID, BigDecimal> validateInvoiceItemAdjustments(final UUID paymentId, final Map<UUID, BigDecimal> idWithAmount, final InternalTenantContext context) throws InvoiceApiException {
+        // We want to validate that only refund with invoice *item* adjustments are allowed (as opposed to refund with invoice adjustment)
+        if (idWithAmount.size() == 0) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_ITEMS_ADJUSTMENT_MISSING);
+        }
         final InvoicePayment invoicePayment = getInvoicePayment(paymentId, InvoicePaymentType.ATTEMPT, context);
         return dao.computeItemAdjustments(invoicePayment.getInvoiceId().toString(), idWithAmount, context);
     }
 
     private InvoicePayment getInvoicePayment(final UUID paymentId, final InvoicePaymentType type, final InternalTenantContext context) throws InvoiceApiException {
-        final Collection<InvoicePayment> invoicePayments = Collections2.transform(dao.getInvoicePayments(paymentId, context), new Function<InvoicePaymentModelDao, InvoicePayment>() {
-            @Override
-            public InvoicePayment apply(final InvoicePaymentModelDao input) {
-                return new DefaultInvoicePayment(input);
-            }
-        });
-        if (invoicePayments.isEmpty()) {
-            return null;
-        }
-        return Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
+
+        final List<InvoicePaymentModelDao> invoicePayments = dao.getInvoicePaymentsByPaymentId(paymentId, context);
+        final InvoicePaymentModelDao resultOrNull = Iterables.tryFind(invoicePayments, new Predicate<InvoicePaymentModelDao>() {
             @Override
-            public boolean apply(final InvoicePayment input) {
-                return input.getType() == type;
+            public boolean apply(final InvoicePaymentModelDao input) {
+                return input.getType() == type &&
+                        input.getSuccess();
             }
         }).orNull();
+        return resultOrNull != null ? new DefaultInvoicePayment(resultOrNull) : null;
     }
 
     @Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 9499079..51949f1 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -500,10 +500,11 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     private void notifyBusOfInvoiceAdjustment(final UUID invoiceId, final UUID accountId, final InternalCallContext context) {
+        final DefaultInvoiceAdjustmentEvent event = new DefaultInvoiceAdjustmentEvent(invoiceId, accountId, context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
         try {
-            eventBus.post(new DefaultInvoiceAdjustmentEvent(invoiceId, accountId, context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()));
+            eventBus.post(event);
         } catch (final EventBusException e) {
-            log.warn("Failed to post adjustment event for invoice " + invoiceId, e);
+            log.warn("Failed to post event {}", event, e);
         }
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java b/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
new file mode 100644
index 0000000..9ab5ba3
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.invoice.config;
+
+import java.lang.reflect.Method;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.glue.InvoiceModule;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
+import org.killbill.billing.util.config.tenant.CacheConfig;
+import org.killbill.billing.util.config.tenant.MultiTenantConfigBase;
+import org.skife.config.TimeSpan;
+
+public class MultiTenantInvoiceConfig extends MultiTenantConfigBase implements InvoiceConfig {
+
+    private final InvoiceConfig staticConfig;
+
+    @Inject
+    public MultiTenantInvoiceConfig(@Named(InvoiceModule.STATIC_CONFIG) final InvoiceConfig staticConfig, final CacheConfig cacheConfig) {
+        super(cacheConfig);
+        this.staticConfig = staticConfig;
+    }
+
+    @Override
+    public int getNumberOfMonthsInFuture(final InternalTenantContext tenantContext) {
+
+        final Method method = new Object(){}.getClass().getEnclosingMethod();
+        final String result = getStringTenantConfig(method.getName(), tenantContext);
+        if (result != null) {
+            return Integer.parseInt(result);
+        }
+        return staticConfig.getNumberOfMonthsInFuture(tenantContext);
+    }
+
+    @Override
+    public TimeSpan getDryRunNotificationSchedule(final InternalTenantContext tenantContext) {
+        final Method method = new Object(){}.getClass().getEnclosingMethod();
+        final String result = getStringTenantConfig(method.getName(), tenantContext);
+        if (result != null) {
+            return new TimeSpan(result);
+        }
+        return staticConfig.getDryRunNotificationSchedule(tenantContext);
+    }
+
+    @Override
+    public int getMaxRawUsagePreviousPeriod(final InternalTenantContext tenantContext) {
+        final Method method = new Object(){}.getClass().getEnclosingMethod();
+        final String result = getStringTenantConfig(method.getName(), tenantContext);
+        if (result != null) {
+            return Integer.parseInt(result);
+        }
+        return staticConfig.getMaxRawUsagePreviousPeriod(tenantContext);
+    }
+
+    @Override
+    public boolean isEmailNotificationsEnabled() {
+        return staticConfig.isEmailNotificationsEnabled();
+    }
+
+    @Override
+    public int getMaxGlobalLockRetries() {
+        return staticConfig.getMaxGlobalLockRetries();
+    }
+
+    @Override
+    protected Method getConfigStaticMethod(final String methodName) {
+        try {
+            return InvoiceConfig.class.getMethod(methodName, InternalTenantContext.class);
+        } catch (final NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index a322b95..8e7ee15 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -55,7 +55,7 @@ import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
@@ -432,7 +432,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     }
 
     @Override
-    public List<InvoicePaymentModelDao> getInvoicePayments(final UUID paymentId, final InternalTenantContext context) {
+    public List<InvoicePaymentModelDao> getInvoicePaymentsByPaymentId(final UUID paymentId, final InternalTenantContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoicePaymentModelDao>>() {
             @Override
             public List<InvoicePaymentModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -455,7 +455,11 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     public InvoicePaymentModelDao createRefund(final UUID paymentId, final BigDecimal requestedRefundAmount, final boolean isInvoiceAdjusted,
                                                final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts, final String transactionExternalKey,
                                                final InternalCallContext context) throws InvoiceApiException {
-        final boolean isInvoiceItemAdjusted = isInvoiceAdjusted && invoiceItemIdsWithNullAmounts.size() > 0;
+
+
+        if (isInvoiceAdjusted && invoiceItemIdsWithNullAmounts.size() == 0) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_ITEMS_ADJUSTMENT_MISSING);
+        }
 
         return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<InvoicePaymentModelDao>() {
             @Override
@@ -468,7 +472,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 final InvoicePaymentModelDao payment = Iterables.tryFind(paymentsForId, new Predicate<InvoicePaymentModelDao>() {
                     @Override
                     public boolean apply(final InvoicePaymentModelDao input) {
-                        return input.getType() == InvoicePaymentType.ATTEMPT;
+                        return input.getType() == InvoicePaymentType.ATTEMPT && input.getSuccess();
                     }
                 }).orNull();
                 if (payment == null) {
@@ -508,18 +512,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 // At this point, we created the refund which made the invoice balance positive and applied any existing
                 // available CBA to that invoice.
                 // We now need to adjust the invoice and/or invoice items if needed and specified.
-                if (isInvoiceAdjusted && !isInvoiceItemAdjusted) {
-                    // Invoice adjustment
-                    final BigDecimal maxBalanceToAdjust = (invoiceBalanceAfterRefund.compareTo(BigDecimal.ZERO) <= 0) ? BigDecimal.ZERO : invoiceBalanceAfterRefund;
-                    final BigDecimal requestedPositiveAmountToAdjust = requestedPositiveAmount.compareTo(maxBalanceToAdjust) > 0 ? maxBalanceToAdjust : requestedPositiveAmount;
-                    if (requestedPositiveAmountToAdjust.compareTo(BigDecimal.ZERO) > 0) {
-                        final InvoiceItemModelDao adjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.REFUND_ADJ, invoice.getId(), invoice.getAccountId(),
-                                                                                    null, null, null, null, null, null, context.getCreatedDate().toLocalDate(), null,
-                                                                                    requestedPositiveAmountToAdjust.negate(), null, invoice.getCurrency(), null);
-                        createInvoiceItemFromTransaction(transInvoiceItemDao, adjItem, context);
-                        invoice.addInvoiceItem(adjItem);
-                    }
-                } else if (isInvoiceAdjusted) {
+                if (isInvoiceAdjusted) {
                     // Invoice item adjustment
                     for (final UUID invoiceItemId : invoiceItemIdsWithAmounts.keySet()) {
                         final BigDecimal adjAmount = invoiceItemIdsWithAmounts.get(invoiceItemId);
@@ -693,32 +686,61 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         });
     }
 
+
     @Override
-    public void notifyOfPayment(final InvoicePaymentModelDao invoicePayment, final InternalCallContext context) {
+    public void notifyOfPaymentInit(final InvoicePaymentModelDao invoicePayment, final InternalCallContext context) {
+        notifyOfPaymentCompletionInternal(invoicePayment, false, context);
+    }
+
+
+    @Override
+    public void notifyOfPaymentCompletion(final InvoicePaymentModelDao invoicePayment, final InternalCallContext context) {
+        notifyOfPaymentCompletionInternal(invoicePayment, true, context);
+    }
+
+    public void notifyOfPaymentCompletionInternal(final InvoicePaymentModelDao invoicePayment, final boolean completion, final InternalCallContext context) {
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
                 final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
-
-                // If the payment id is null, the payment wasn't attempted (e.g. no payment method). We don't record an attempt but send an event nonetheless (e.g. for Overdue)
-                if (invoicePayment.getPaymentId() != null) {
-                    final List<InvoicePaymentModelDao> invoicePayments = transactional.getInvoicePayments(invoicePayment.getPaymentId().toString(), context);
+                //
+                // In case of notifyOfPaymentInit we always want to record the row with success = false
+                // Otherwise, if the payment id is null, the payment wasn't attempted (e.g. no payment method so we don't record an attempt but send
+                // an event nonetheless (e.g. for Overdue)
+                //
+                if (!completion || invoicePayment.getPaymentId() != null) {
+                    //
+                    // extract entries by invoiceId (which is always set, as opposed to paymentId) and then filter based on type and
+                    // paymentCookieId = transactionExternalKey
+                    //
+                    final List<InvoicePaymentModelDao> invoicePayments = transactional.getPaymentsForInvoice(invoicePayment.getInvoiceId().toString(), context);
                     final InvoicePaymentModelDao existingAttempt = Iterables.tryFind(invoicePayments, new Predicate<InvoicePaymentModelDao>() {
                         @Override
                         public boolean apply(final InvoicePaymentModelDao input) {
-                            return input.getType() == InvoicePaymentType.ATTEMPT;
+                            return input.getType() == InvoicePaymentType.ATTEMPT &&
+                                   input.getPaymentCookieId().equals(invoicePayment.getPaymentCookieId());
                         }
                     }).orNull();
+
                     if (existingAttempt == null) {
                         transactional.create(invoicePayment, context);
                     } else if (!existingAttempt.getSuccess() && invoicePayment.getSuccess()) {
-                        transactional.updateAttempt(existingAttempt.getRecordId(), invoicePayment.getPaymentDate().toDate(), invoicePayment.getAmount(), invoicePayment.getCurrency(), invoicePayment.getProcessedCurrency(), context);
+                        transactional.updateAttempt(existingAttempt.getRecordId(),
+                                                    invoicePayment.getPaymentId().toString(),
+                                                    invoicePayment.getPaymentDate().toDate(),
+                                                    invoicePayment.getAmount(),
+                                                    invoicePayment.getCurrency(),
+                                                    invoicePayment.getProcessedCurrency(),
+                                                    invoicePayment.getPaymentCookieId(),
+                                                    null,
+                                                    context);
                     }
                 }
 
-                final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), entitySqlDaoWrapperFactory.getHandle());
-                notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, invoicePayment, accountId, context.getUserToken(), context);
-
+                if (completion) {
+                    final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), entitySqlDaoWrapperFactory.getHandle());
+                    notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, invoicePayment, accountId, context.getUserToken(), context);
+                }
                 return null;
             }
         });
@@ -846,7 +868,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     private void notifyOfFutureBillingEvents(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID accountId,
                                              final FutureAccountNotifications callbackDateTimePerSubscriptions, final InternalCallContext internalCallContext) {
 
-        final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule().getMillis();
+        final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(internalCallContext).getMillis();
         final boolean isInvoiceNotificationEnabled = dryRunNotificationTime > 0;
         for (final UUID subscriptionId : callbackDateTimePerSubscriptions.getNotifications().keySet()) {
             final List<SubscriptionNotification> callbackDateTimeUTC = callbackDateTimePerSubscriptions.getNotifications().get(subscriptionId);
@@ -869,7 +891,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
             eventBus.postFromTransaction(new DefaultInvoiceAdjustmentEvent(invoiceId, accountId, context.getAccountRecordId(), context.getTenantRecordId(), userToken),
                                          entitySqlDaoWrapperFactory.getHandle().getConnection());
         } catch (final EventBusException e) {
-            log.warn("Failed to post adjustment event for invoice " + invoiceId, e);
+            log.warn("Failed to post adjustment event for invoiceId='{}'", invoiceId, e);
         }
     }
 
@@ -908,7 +930,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         try {
             eventBus.postFromTransaction(busEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
         } catch (final EventBusException e) {
-            log.warn("Failed to post invoice payment event for invoice " + invoicePaymentModelDao.getInvoiceId(), e);
+            log.warn("Failed to post invoice payment event for invoiceId='{}'", invoicePaymentModelDao.getInvoiceId(), e);
         }
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
index a8562f8..515f05e 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
@@ -59,7 +59,7 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
 
     UUID getInvoiceIdByPaymentId(UUID paymentId, InternalTenantContext context);
 
-    List<InvoicePaymentModelDao> getInvoicePayments(UUID paymentId, InternalTenantContext context);
+    List<InvoicePaymentModelDao> getInvoicePaymentsByPaymentId(UUID paymentId, InternalTenantContext context);
 
     List<InvoicePaymentModelDao> getInvoicePaymentsByAccount(InternalTenantContext context);
 
@@ -132,7 +132,9 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
      */
     void deleteCBA(UUID accountId, UUID invoiceId, UUID invoiceItemId, InternalCallContext context) throws InvoiceApiException;
 
-    void notifyOfPayment(InvoicePaymentModelDao invoicePayment, InternalCallContext context);
+    void notifyOfPaymentInit(InvoicePaymentModelDao invoicePayment, InternalCallContext context);
+
+    void notifyOfPaymentCompletion(InvoicePaymentModelDao invoicePayment, InternalCallContext context);
 
     /**
      * @param accountId the account for which we need to rebalance the CBA
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
index 0352a9b..d4b939a 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
@@ -85,10 +85,6 @@ public class InvoiceDaoHelper {
                                                         final InternalTenantContext context) throws InvoiceApiException {
         // Populate the missing amounts for individual items, if needed
         final Map<UUID, BigDecimal> outputItemIdsWithAmounts = new HashMap<UUID, BigDecimal>();
-        if (invoiceItemIdsWithNullAmounts.size() == 0) {
-            return outputItemIdsWithAmounts;
-        }
-
         // Retrieve invoice before the Refund
         final InvoiceModelDao invoice = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class).getById(invoiceId, context);
         if (invoice != null) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
index 279aea3..7b8c5ad 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -69,12 +69,14 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDa
                                                            @BindBean final InternalTenantContext context);
 
 
-
     @SqlUpdate
     void updateAttempt(@Bind("recordId") Long recordId,
+                       @Bind("paymentId") final String paymentId,
                        @Bind("paymentDate") final Date paymentDate,
                        @Bind("amount") final BigDecimal amount,
                        @Bind("currency") final Currency currency,
                        @Bind("processedCurrency") final Currency processedCurrency,
+                       @Bind("paymentCookieId") final String paymentCookieId,
+                       @Bind("linkedInvoicePaymentId") final String linkedInvoicePaymentId,
                        @BindBean final InternalTenantContext context);
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
index 76843fd..846b057 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -45,7 +47,11 @@ public class BillingIntervalDetail {
         this.startDate = startDate;
         this.endDate = endDate;
         this.targetDate = targetDate;
-        this.billingCycleDay = billingCycleDay;
+        if (billingPeriod.getPeriod().getMonths() != 0 || billingPeriod.getPeriod().getYears() != 0) {
+            this.billingCycleDay = billingCycleDay;
+        } else {
+            this.billingCycleDay = startDate.getDayOfMonth();
+        }
         this.billingPeriod = billingPeriod;
         this.billingMode = billingMode;
         computeAll();
@@ -59,9 +65,8 @@ public class BillingIntervalDetail {
         return effectiveEndDate;
     }
 
-    public LocalDate getFutureBillingDateFor(int nbPeriod) {
-        final int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
-        LocalDate proposedDate = firstBillingCycleDate.plusMonths((nbPeriod) * numberOfMonthsPerBillingPeriod);
+    public LocalDate getFutureBillingDateFor(final int nbPeriod) {
+        final LocalDate proposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, nbPeriod);
         return alignProposedBillCycleDate(proposedDate, billingCycleDay);
     }
 
@@ -70,8 +75,7 @@ public class BillingIntervalDetail {
     }
 
     public LocalDate getNextBillingCycleDate() {
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-        final LocalDate proposedDate = lastBillingCycleDate != null ? lastBillingCycleDate.plusMonths(numberOfMonthsInPeriod) : firstBillingCycleDate;
+        final LocalDate proposedDate = lastBillingCycleDate != null ? lastBillingCycleDate.plus(billingPeriod.getPeriod()) : firstBillingCycleDate;
         final LocalDate nextBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
         return nextBillingCycleDate;
     }
@@ -88,8 +92,7 @@ public class BillingIntervalDetail {
         calculateLastBillingCycleDate();
     }
 
-    @VisibleForTesting
-    void calculateFirstBillingCycleDate() {
+    private void calculateFirstBillingCycleDate() {
 
         final int lastDayOfMonth = startDate.dayOfMonth().getMaximumValue();
         final LocalDate billingCycleDate;
@@ -99,10 +102,9 @@ public class BillingIntervalDetail {
             billingCycleDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), billingCycleDay, startDate.getChronology());
         }
 
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
         LocalDate proposedDate = billingCycleDate;
         while (proposedDate.isBefore(startDate)) {
-            proposedDate = proposedDate.plusMonths(numberOfMonthsInPeriod);
+            proposedDate = proposedDate.plus(billingPeriod.getPeriod());
         }
         firstBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
     }
@@ -127,15 +129,13 @@ public class BillingIntervalDetail {
             return;
         }
 
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
         int numberOfPeriods = 0;
         LocalDate proposedDate = firstBillingCycleDate;
-        LocalDate nextProposedDate = proposedDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
-
+        LocalDate nextProposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
 
         while (!nextProposedDate.isAfter(targetDate)) {
             proposedDate = nextProposedDate;
-            nextProposedDate = firstBillingCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+            nextProposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
             numberOfPeriods += 1;
         }
         proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
@@ -161,12 +161,11 @@ public class BillingIntervalDetail {
             return;
         }
 
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
         int numberOfPeriods = 0;
         LocalDate proposedDate = firstBillingCycleDate;
 
         while (!proposedDate.isAfter(targetDate)) {
-            proposedDate = firstBillingCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+            proposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
             numberOfPeriods += 1;
         }
         proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
@@ -191,12 +190,12 @@ public class BillingIntervalDetail {
         LocalDate proposedDate = firstBillingCycleDate;
         int numberOfPeriods = 0;
         while (!proposedDate.isAfter(effectiveEndDate)) {
-            proposedDate = firstBillingCycleDate.plusMonths(numberOfPeriods * billingPeriod.getNumberOfMonths());
+            proposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
             numberOfPeriods += 1;
         }
 
         // Our proposed date is billingCycleDate prior to the effectiveEndDate
-        proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+        proposedDate = proposedDate.minus(billingPeriod.getPeriod());
         proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
 
         if (proposedDate.isBefore(firstBillingCycleDate)) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
index 638c5d8..f35087b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -30,6 +30,7 @@ import org.joda.time.Months;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
@@ -37,7 +38,7 @@ import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates;
 import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.junction.BillingEventSet;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.clock.Clock;
 
 import com.google.common.base.Objects;
@@ -72,7 +73,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             return new InvoiceWithMetadata(null, ImmutableMap.<UUID, SubscriptionFutureNotificationDates>of());
         }
 
-        validateTargetDate(targetDate);
+        validateTargetDate(targetDate, context);
         final LocalDate adjustedTargetDate = adjustTargetDate(existingInvoices, targetDate);
 
         final LocalDate invoiceDate = context.toLocalDate(clock.getUTCNow());
@@ -90,8 +91,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         return new InvoiceWithMetadata(invoice.getInvoiceItems().isEmpty() ? null : invoice, perSubscriptionFutureNotificationDates);
     }
 
-    private void validateTargetDate(final LocalDate targetDate) throws InvoiceApiException {
-        final int maximumNumberOfMonths = config.getNumberOfMonthsInFuture();
+    private void validateTargetDate(final LocalDate targetDate, final InternalTenantContext context) throws InvoiceApiException {
+        final int maximumNumberOfMonths = config.getNumberOfMonthsInFuture(context);
 
         if (Months.monthsBetween(clock.getUTCToday(), targetDate).getMonths() > maximumNumberOfMonths) {
             throw new InvoiceApiException(ErrorCode.INVOICE_TARGET_DATE_TOO_FAR_IN_THE_FUTURE, targetDate.toString());
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
index e77696e..1370254 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
@@ -32,6 +32,7 @@ import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.invoice.api.Invoice;
@@ -47,6 +48,7 @@ import org.killbill.billing.invoice.tree.AccountItemTree;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.util.currency.KillBillMoney;
+import org.killbill.clock.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -61,8 +63,11 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator 
 
     private static final Logger log = LoggerFactory.getLogger(FixedAndRecurringInvoiceItemGenerator.class);
 
+    private final Clock clock;
+
     @Inject
-    public FixedAndRecurringInvoiceItemGenerator() {
+    public FixedAndRecurringInvoiceItemGenerator(final Clock clock) {
+        this.clock = clock;
     }
 
     public List<InvoiceItem> generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet,
@@ -100,10 +105,11 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator 
         }
 
         // Pretty-print the generated invoice items from the junction events
-        final StringBuilder logStringBuilder = new StringBuilder("Proposed Invoice items for invoiceId ")
+        final StringBuilder logStringBuilder = new StringBuilder("Proposed Invoice items for invoiceId='")
                 .append(invoiceId)
-                .append(" and accountId ")
-                .append(accountId);
+                .append("', accountId='")
+                .append(accountId)
+                .append("'");
 
         final Iterator<BillingEvent> eventIt = events.iterator();
         BillingEvent nextEvent = eventIt.next();
@@ -128,7 +134,7 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator 
 
     @VisibleForTesting
     void processFixedBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate,
-                                   final Currency currency, final List<InvoiceItem> proposedItems, final InternalCallContext internalCallContext) {
+                                   final Currency currency, final List<InvoiceItem> proposedItems, final InternalCallContext internalCallContext) throws InvoiceApiException {
         InvoiceItem prevItem = null;
 
         final Iterator<BillingEvent> eventIt = events.iterator();
@@ -165,66 +171,74 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator 
                                                     final StringBuilder logStringBuilder, final BillingMode billingMode,
                                                     final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate,
                                                     final InternalCallContext internalCallContext) throws InvoiceApiException {
-        final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
 
-        // For FIXEDTERM phases we need to stop when the specified duration has been reached
-        final LocalDate maxEndDate = thisEvent.getPlanPhase().getPhaseType() == PhaseType.FIXEDTERM ?
-                                     thisEvent.getPlanPhase().getDuration().addToLocalDate(internalCallContext.toLocalDate(thisEvent.getEffectiveDate())) :
-                                     null;
+        try {
 
-        // Handle recurring items
-        final BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
-        if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
-            final LocalDate startDate = internalCallContext.toLocalDate(thisEvent.getEffectiveDate());
+            final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
 
-            if (!startDate.isAfter(targetDate)) {
-                final LocalDate endDate = (nextEvent == null) ? null : internalCallContext.toLocalDate(nextEvent.getEffectiveDate());
+            // For FIXEDTERM phases we need to stop when the specified duration has been reached
+            final LocalDate maxEndDate = thisEvent.getPlanPhase().getPhaseType() == PhaseType.FIXEDTERM ?
+                                         thisEvent.getPlanPhase().getDuration().addToLocalDate(internalCallContext.toLocalDate(thisEvent.getEffectiveDate())) :
+                                         null;
 
-                final int billCycleDayLocal = thisEvent.getBillCycleDayLocal();
+            // Handle recurring items
+            final BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
+            if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+                final LocalDate startDate = internalCallContext.toLocalDate(thisEvent.getEffectiveDate());
 
-                final RecurringInvoiceItemDataWithNextBillingCycleDate itemDataWithNextBillingCycleDate;
-                try {
-                    itemDataWithNextBillingCycleDate = generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode);
-                } catch (InvalidDateSequenceException e) {
-                    throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
-                }
+                if (!startDate.isAfter(targetDate)) {
+                    final LocalDate endDate = (nextEvent == null) ? null : internalCallContext.toLocalDate(nextEvent.getEffectiveDate());
 
-                for (final RecurringInvoiceItemData itemDatum : itemDataWithNextBillingCycleDate.getItemData()) {
+                    final int billCycleDayLocal = thisEvent.getBillCycleDayLocal();
 
-                    // Stop if there a maxEndDate and we have reached it
-                    if (maxEndDate != null && maxEndDate.compareTo(itemDatum.getEndDate()) < 0) {
-                        break;
+                    final RecurringInvoiceItemDataWithNextBillingCycleDate itemDataWithNextBillingCycleDate;
+                    try {
+                        itemDataWithNextBillingCycleDate = generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode);
+                    } catch (InvalidDateSequenceException e) {
+                        throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
                     }
-                    final BigDecimal rate = thisEvent.getRecurringPrice();
-                    if (rate != null) {
-                        final BigDecimal amount = KillBillMoney.of(itemDatum.getNumberOfCycles().multiply(rate), currency);
-                        final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId,
-                                                                                            accountId,
-                                                                                            thisEvent.getSubscription().getBundleId(),
-                                                                                            thisEvent.getSubscription().getId(),
-                                                                                            thisEvent.getPlan().getName(),
-                                                                                            thisEvent.getPlanPhase().getName(),
-                                                                                            itemDatum.getStartDate(),
-                                                                                            itemDatum.getEndDate(),
-                                                                                            amount, rate, currency);
-                        items.add(recurringItem);
+                    for (final RecurringInvoiceItemData itemDatum : itemDataWithNextBillingCycleDate.getItemData()) {
+
+                        // Stop if there a maxEndDate and we have reached it
+                        if (maxEndDate != null && maxEndDate.compareTo(itemDatum.getEndDate()) < 0) {
+                            break;
+                        }
+                        final BigDecimal rate = thisEvent.getRecurringPrice(internalCallContext.toUTCDateTime(itemDatum.getStartDate()));
+                        if (rate != null) {
+                            final BigDecimal amount = KillBillMoney.of(itemDatum.getNumberOfCycles().multiply(rate), currency);
+                            final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId,
+                                                                                                accountId,
+                                                                                                thisEvent.getSubscription().getBundleId(),
+                                                                                                thisEvent.getSubscription().getId(),
+                                                                                                thisEvent.getPlan().getName(),
+                                                                                                thisEvent.getPlanPhase().getName(),
+                                                                                                itemDatum.getStartDate(),
+                                                                                                itemDatum.getEndDate(),
+                                                                                                amount, rate, currency);
+                            items.add(recurringItem);
+                        }
                     }
+                    updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode,
+                                                              perSubscriptionFutureNotificationDate);
                 }
-                updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate);
             }
-        }
 
-        // For debugging purposes
-        logStringBuilder.append("\n")
-                        .append(thisEvent);
-        for (final InvoiceItem item : items) {
-            logStringBuilder.append("\n\t")
-                            .append(item);
+            // For debugging purposes
+            logStringBuilder.append("\n")
+                            .append(thisEvent);
+            for (final InvoiceItem item : items) {
+                logStringBuilder.append("\n\t")
+                                .append(item);
+            }
+            return items;
+
+        } catch (final CatalogApiException e) {
+            throw new InvoiceApiException(e);
         }
-        return items;
     }
 
-    private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List<InvoiceItem> newProposedItems, final BillingMode billingMode, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
+    private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List<InvoiceItem> newProposedItems, final BillingMode billingMode,
+                                                           final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
 
         LocalDate nextNotificationDate = null;
         switch (billingMode) {
@@ -247,6 +261,7 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator 
             default:
                 throw new IllegalStateException("Unrecognized billing mode " + billingMode);
         }
+
         if (nextNotificationDate != null) {
             SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId);
             if (subscriptionFutureNotificationDates == null) {
@@ -350,7 +365,7 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator 
     }
 
     private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent,
-                                               final LocalDate targetDate, final Currency currency, final InternalCallContext internalCallContext) {
+                                               final LocalDate targetDate, final Currency currency, final InternalCallContext internalCallContext) throws InvoiceApiException {
         final LocalDate roundedStartDate = internalCallContext.toLocalDate(thisEvent.getEffectiveDate());
         if (roundedStartDate.isAfter(targetDate)) {
             return null;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceDateUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceDateUtils.java
index 879ef43..fabac45 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceDateUtils.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceDateUtils.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -21,12 +23,45 @@ import java.math.BigDecimal;
 import org.joda.time.Days;
 import org.joda.time.LocalDate;
 import org.joda.time.Months;
-
+import org.joda.time.Weeks;
+import org.joda.time.Years;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.util.currency.KillBillMoney;
 
 public class InvoiceDateUtils {
 
+    public static int calculateNumberOfWholeBillingPeriods(final LocalDate startDate, final LocalDate endDate, final BillingPeriod billingPeriod) {
+        final int numberBetween;
+        final int numberInPeriod;
+        if (billingPeriod.getPeriod().getDays() != 0) {
+            numberBetween = Days.daysBetween(startDate, endDate).getDays();
+            numberInPeriod = billingPeriod.getPeriod().getDays();
+        } else if (billingPeriod.getPeriod().getWeeks() != 0) {
+            numberBetween = Weeks.weeksBetween(startDate, endDate).getWeeks();
+            numberInPeriod = billingPeriod.getPeriod().getWeeks();
+        } else if (billingPeriod.getPeriod().getMonths() != 0) {
+            numberBetween = Months.monthsBetween(startDate, endDate).getMonths();
+            numberInPeriod = billingPeriod.getPeriod().getMonths();
+        } else {
+            numberBetween = Years.yearsBetween(startDate, endDate).getYears();
+            numberInPeriod = billingPeriod.getPeriod().getYears();
+        }
+        return numberBetween / numberInPeriod;
+    }
+
+    public static BigDecimal calculateProRationBeforeFirstBillingPeriod(final LocalDate startDate, final LocalDate nextBillingCycleDate,
+                                                                        final BillingPeriod billingPeriod) {
+        final LocalDate previousBillingCycleDate = nextBillingCycleDate.minus(billingPeriod.getPeriod());
+        return calculateProrationBetweenDates(startDate, nextBillingCycleDate, previousBillingCycleDate, nextBillingCycleDate);
+    }
+
+    public static BigDecimal calculateProRationAfterLastBillingCycleDate(final LocalDate endDate, final LocalDate previousBillThroughDate,
+                                                                         final BillingPeriod billingPeriod) {
+        // Note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
+        final LocalDate nextBillThroughDate = previousBillThroughDate.plus(billingPeriod.getPeriod());
+        return calculateProrationBetweenDates(previousBillThroughDate, endDate, previousBillThroughDate, nextBillThroughDate);
+    }
+
     /**
      * Called internally to calculate proration or when we recalculate approximate repair amount
      *
@@ -34,9 +69,8 @@ public class InvoiceDateUtils {
      * @param endDate                  end date of the prorated interval
      * @param previousBillingCycleDate start date of the period
      * @param nextBillingCycleDate     end date of the period
-     * @return
      */
-    public static BigDecimal calculateProrationBetweenDates(final LocalDate startDate, final LocalDate endDate, final LocalDate previousBillingCycleDate, final LocalDate nextBillingCycleDate) {
+    private static BigDecimal calculateProrationBetweenDates(final LocalDate startDate, final LocalDate endDate, final LocalDate previousBillingCycleDate, final LocalDate nextBillingCycleDate) {
         final int daysBetween = Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays();
         return calculateProrationBetweenDates(startDate, endDate, daysBetween);
     }
@@ -52,122 +86,18 @@ public class InvoiceDateUtils {
         return days.divide(daysInPeriod, KillBillMoney.MAX_SCALE, KillBillMoney.ROUNDING_METHOD);
     }
 
-    public static BigDecimal calculateProRationBeforeFirstBillingPeriod(final LocalDate startDate, final LocalDate nextBillingCycleDate,
-                                                                        final BillingPeriod billingPeriod) {
-        final LocalDate previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
-
-        return calculateProrationBetweenDates(startDate, nextBillingCycleDate, previousBillingCycleDate, nextBillingCycleDate);
-    }
-
-    public static int calculateNumberOfWholeBillingPeriods(final LocalDate startDate, final LocalDate endDate, final BillingPeriod billingPeriod) {
-        final int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-        return numberOfMonths / numberOfMonthsInPeriod;
-    }
-
-    public static LocalDate calculateLastBillingCycleDateBefore(final LocalDate date, final LocalDate previousBillCycleDate,
-                                                                final int billingCycleDay, final BillingPeriod billingPeriod) {
-        LocalDate proposedDate = previousBillCycleDate;
-
-        int numberOfPeriods = 0;
-        while (!proposedDate.isAfter(date)) {
-            proposedDate = previousBillCycleDate.plusMonths(numberOfPeriods * billingPeriod.getNumberOfMonths());
-            numberOfPeriods += 1;
-        }
-
-        proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
-
-        if (proposedDate.dayOfMonth().get() < billingCycleDay) {
-            final int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
-            if (lastDayOfTheMonth < billingCycleDay) {
-                proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
-            } else {
-                proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
-            }
-        }
-
-        if (proposedDate.isBefore(previousBillCycleDate)) {
-            // Make sure not to go too far in the past
-            return previousBillCycleDate;
-        } else {
-            return proposedDate;
-        }
-    }
-
-    public static LocalDate calculateEffectiveEndDate(final LocalDate billCycleDate, final LocalDate targetDate,
-                                                      final BillingPeriod billingPeriod) {
-        if (targetDate.isBefore(billCycleDate)) {
-            return billCycleDate;
-        }
-
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-        int numberOfPeriods = 0;
-        LocalDate proposedDate = billCycleDate;
-
-        while (!proposedDate.isAfter(targetDate)) {
-            proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
-            numberOfPeriods += 1;
-        }
-
-        return proposedDate;
-    }
-
-    public static LocalDate calculateEffectiveEndDate(final LocalDate billCycleDate, final LocalDate targetDate,
-                                                      final LocalDate endDate, final BillingPeriod billingPeriod) {
-        if (targetDate.isBefore(endDate)) {
-            if (targetDate.isBefore(billCycleDate)) {
-                return billCycleDate;
-            }
-
-            final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-            int numberOfPeriods = 0;
-            LocalDate proposedDate = billCycleDate;
-
-            while (!proposedDate.isAfter(targetDate)) {
-                proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
-                numberOfPeriods += 1;
-            }
-
-            // the current period includes the target date
-            // check to see whether the end date truncates the period
-            if (endDate.isBefore(proposedDate)) {
-                return endDate;
-            } else {
-                return proposedDate;
-            }
-        } else {
-            return endDate;
-        }
-    }
-
-    public static BigDecimal calculateProRationAfterLastBillingCycleDate(final LocalDate endDate, final LocalDate previousBillThroughDate,
-                                                                         final BillingPeriod billingPeriod) {
-        // Note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
-        final LocalDate nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
-        return calculateProrationBetweenDates(previousBillThroughDate, endDate, previousBillThroughDate, nextBillThroughDate);
-    }
-
-    public static LocalDate calculateBillingCycleDateOnOrAfter(final LocalDate date, final int billingCycleDayLocal) {
-        final int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
-
-        final LocalDate fixedDate;
-        if (billingCycleDayLocal > lastDayOfMonth) {
-            fixedDate = new LocalDate(date.getYear(), date.getMonthOfYear(), lastDayOfMonth, date.getChronology());
-        } else {
-            fixedDate = new LocalDate(date.getYear(), date.getMonthOfYear(), billingCycleDayLocal, date.getChronology());
-        }
-
-        LocalDate proposedDate = fixedDate;
-        while (proposedDate.isBefore(date)) {
-            proposedDate = proposedDate.plusMonths(1);
+    public static LocalDate advanceByNPeriods(final LocalDate initialDate, final BillingPeriod billingPeriod, final int nbPeriods) {
+        LocalDate proposedDate = initialDate;
+        for (int i = 0; i < nbPeriods; i++) {
+            proposedDate = proposedDate.plus(billingPeriod.getPeriod());
         }
         return proposedDate;
     }
 
-    public static LocalDate calculateBillingCycleDateAfter(final LocalDate date, final int billingCycleDayLocal) {
-        LocalDate proposedDate = calculateBillingCycleDateOnOrAfter(date, billingCycleDayLocal);
-        if (date.compareTo(proposedDate) == 0) {
-            proposedDate = proposedDate.plusMonths(1);
+    public static LocalDate recedeByNPeriods(final LocalDate initialDate, final BillingPeriod billingPeriod, final int nbPeriods) {
+        LocalDate proposedDate = initialDate;
+        for (int i = 0; i < nbPeriods; i++) {
+            proposedDate = proposedDate.minus(billingPeriod.getPeriod());
         }
         return proposedDate;
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
index b59dd34..a9aa2dc 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
@@ -34,6 +34,7 @@ import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory;
 import org.killbill.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
 import org.killbill.billing.invoice.api.svcs.DefaultInvoiceInternalApi;
 import org.killbill.billing.invoice.api.user.DefaultInvoiceUserApi;
+import org.killbill.billing.invoice.config.MultiTenantInvoiceConfig;
 import org.killbill.billing.invoice.dao.CBADao;
 import org.killbill.billing.invoice.dao.DefaultInvoiceDao;
 import org.killbill.billing.invoice.dao.InvoiceDao;
@@ -53,16 +54,18 @@ import org.killbill.billing.invoice.template.bundles.DefaultResourceBundleFactor
 import org.killbill.billing.invoice.usage.RawUsageOptimizer;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.platform.api.KillbillConfigSource;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.billing.util.template.translation.TranslatorConfig;
 import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
 
 public class DefaultInvoiceModule extends KillBillModule implements InvoiceModule {
 
-    InvoiceConfig config;
+
+    InvoiceConfig staticInvoiceConfig;
 
     public DefaultInvoiceModule(final KillbillConfigSource configSource) {
         super(configSource);
@@ -90,8 +93,9 @@ public class DefaultInvoiceModule extends KillBillModule implements InvoiceModul
     }
 
     protected void installConfig() {
-        config = new ConfigurationObjectFactory(skifeConfigSource).build(InvoiceConfig.class);
-        bind(InvoiceConfig.class).toInstance(config);
+        staticInvoiceConfig = new ConfigurationObjectFactory(skifeConfigSource).build(InvoiceConfig.class);
+        bind(InvoiceConfig.class).annotatedWith(Names.named(STATIC_CONFIG)).toInstance(staticInvoiceConfig);
+        bind(InvoiceConfig.class).to(MultiTenantInvoiceConfig.class).asEagerSingleton();
     }
 
     protected void installInvoiceService() {
@@ -112,7 +116,7 @@ public class DefaultInvoiceModule extends KillBillModule implements InvoiceModul
     }
 
     protected void installInvoiceNotifier() {
-        if (config.isEmailNotificationsEnabled()) {
+        if (staticInvoiceConfig.isEmailNotificationsEnabled()) {
             bind(InvoiceNotifier.class).to(EmailInvoiceNotifier.class).asEagerSingleton();
         } else {
             bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index 29e410c..5ff7171 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -90,7 +90,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.bus.api.PersistentBus.EventBusException;
@@ -186,8 +186,8 @@ public class InvoiceDispatcher {
                                                                                                        context.toUTCDateTime(targetDate), context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
             try {
                 eventBus.post(event);
-            } catch (final EventBusException e) {
-                log.error("Failed to post event " + event, e);
+            } catch (EventBusException e) {
+                log.warn("Failed to post event {}", event, e);
             }
         }
     }
@@ -195,7 +195,7 @@ public class InvoiceDispatcher {
     private Invoice processSubscriptionInternal(final UUID subscriptionId, final LocalDate targetDate, final boolean dryRunForNotification, final InternalCallContext context) throws InvoiceApiException {
         try {
             if (subscriptionId == null) {
-                log.error("Failed handling SubscriptionBase change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
+                log.warn("Failed handling SubscriptionBase change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
                 return null;
             }
             final UUID accountId = subscriptionApi.getAccountIdFromSubscriptionId(subscriptionId, context);
@@ -203,7 +203,7 @@ public class InvoiceDispatcher {
 
             return processAccount(accountId, targetDate, dryRunArguments, context);
         } catch (final SubscriptionBaseApiException e) {
-            log.error("Failed handling SubscriptionBase change.",
+            log.warn("Failed handling SubscriptionBase change.",
                       new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
             return null;
         }
@@ -217,9 +217,7 @@ public class InvoiceDispatcher {
 
             return processAccountWithLock(accountId, targetDate, dryRunArguments, context);
         } catch (final LockFailedException e) {
-            // Not good!
-            log.error(String.format("Failed to process invoice for account %s, targetDate %s",
-                                    accountId.toString(), targetDate), e);
+            log.warn("Failed to process invoice for accountId='{}', targetDate='{}'", accountId.toString(), targetDate, e);
         } finally {
             if (lock != null) {
                 lock.release();
@@ -333,15 +331,15 @@ public class InvoiceDispatcher {
             //
             if (invoice == null) {
                 if (isDryRun) {
-                    log.info("Generated null dryRun invoice for accountId {} and targetDate {}", accountId, targetDate);
+                    log.info("Generated null dryRun invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
                 } else {
-                    log.info("Generated null invoice for accountId {} and targetDate {}", accountId, targetDate);
+                    log.info("Generated null invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
 
                     final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(),
                                                                                context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
 
                     commitInvoiceAndSetFutureNotifications(account, null, ImmutableList.<InvoiceItemModelDao>of(), futureAccountNotifications, false, context);
-                    postEvent(event, accountId, context);
+                    postEvent(event);
                 }
                 return null;
             }
@@ -420,7 +418,7 @@ public class InvoiceDispatcher {
         }
 
         // If dryRunNotification is enabled we also need to fetch the upcoming PHASE dates (we add SubscriptionNotification with isForInvoiceNotificationTrigger = false)
-        final boolean isInvoiceNotificationEnabled = invoiceConfig.getDryRunNotificationSchedule().getMillis() > 0;
+        final boolean isInvoiceNotificationEnabled = invoiceConfig.getDryRunNotificationSchedule(context).getMillis() > 0;
         if (isInvoiceNotificationEnabled) {
             final Map<UUID, DateTime> upcomingPhasesForSubscriptions = subscriptionApi.getNextFutureEventForSubscriptions(SubscriptionBaseTransitionType.PHASE, context);
             for (final UUID cur : upcomingPhasesForSubscriptions.keySet()) {
@@ -461,10 +459,10 @@ public class InvoiceDispatcher {
     private void logInvoiceWithItems(final ImmutableAccountData account, final Invoice invoice, final LocalDate targetDate, final Set<UUID> adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithItems) {
         final StringBuilder tmp = new StringBuilder();
         if (isRealInvoiceWithItems) {
-            tmp.append(String.format("Generated invoice %s with %d items for accountId %s and targetDate %s:\n", invoice.getId(), invoice.getNumberOfItems(), account.getId(), targetDate));
+            tmp.append(String.format("Generated invoiceId='%s', numberOfItems='%d', accountId='%s', targetDate='%s':\n", invoice.getId(), invoice.getNumberOfItems(), account.getId(), targetDate));
         } else {
             final String adjustedInvoices = JOINER_COMMA.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()]));
-            tmp.append(String.format("Adjusting existing invoices %s with %d items for accountId %s and targetDate %s:\n",
+            tmp.append(String.format("Adjusting existing invoiceId='%s', numberOfItems='%d', accountId='%s', targetDate='%s':\n",
                                      adjustedInvoices, invoice.getNumberOfItems(), account.getId(), targetDate));
         }
         for (final InvoiceItem item : invoice.getInvoiceItems()) {
@@ -509,7 +507,7 @@ public class InvoiceDispatcher {
             events.add(event);
         }
         for (final InvoiceInternalEvent event : events) {
-            postEvent(event, account.getId(), context);
+            postEvent(event);
         }
     }
 
@@ -563,11 +561,11 @@ public class InvoiceDispatcher {
         }
     }
 
-    private void postEvent(final BusInternalEvent event, final UUID accountId, final InternalCallContext context) {
+    private void postEvent(final BusInternalEvent event) {
         try {
             eventBus.post(event);
         } catch (final EventBusException e) {
-            log.error(String.format("Failed to post event %s for account %s", event.getBusEventType(), accountId), e);
+            log.warn("Failed to post event {}", event, e);
         }
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
index ff895d1..a0776ce 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
@@ -28,14 +28,13 @@ import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.events.BlockingTransitionInternalEvent;
 import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
 import org.killbill.billing.events.InvoiceCreationInternalEvent;
-import org.killbill.billing.events.RepairSubscriptionInternalEvent;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
 import org.killbill.billing.util.callcontext.CallOrigin;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.UserType;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.clock.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -68,17 +67,6 @@ public class InvoiceListener {
 
     @AllowConcurrentEvents
     @Subscribe
-    public void handleRepairSubscriptionEvent(final RepairSubscriptionInternalEvent event) {
-        try {
-            final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "RepairBundle", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
-            dispatcher.processAccount(event.getAccountId(), context.toLocalDate(event.getEffectiveDate()), null, context);
-        } catch (final InvoiceApiException e) {
-            log.error(e.getMessage());
-        }
-    }
-
-    @AllowConcurrentEvents
-    @Subscribe
     public void handleSubscriptionTransition(final EffectiveSubscriptionInternalEvent event) {
         try {
             //  Skip future uncancel event
@@ -89,11 +77,12 @@ public class InvoiceListener {
             }
             final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
             dispatcher.processSubscriptionForInvoiceGeneration(event, context);
-        } catch (final InvoiceApiException e) {
-            log.error(e.getMessage());
+        } catch (InvoiceApiException e) {
+            log.warn("Unable to process event {}", event, e);
         }
     }
 
+
     @AllowConcurrentEvents
     @Subscribe
     public void handleBlockingStateTransition(final BlockingTransitionInternalEvent event) {
@@ -106,10 +95,10 @@ public class InvoiceListener {
             final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
             final UUID accountId = accountApi.getByRecordId(event.getSearchKey1(), context);
             dispatcher.processAccount(accountId, null, null, context);
-        } catch (final InvoiceApiException e) {
-            log.error(e.getMessage());
-        } catch (final AccountApiException e) {
-            log.error(e.getMessage());
+        } catch (InvoiceApiException e) {
+            log.warn("Unable to process event {}", event, e);
+        } catch (AccountApiException e) {
+            log.warn("Unable to process event {}", event, e);
         }
     }
 
@@ -117,8 +106,8 @@ public class InvoiceListener {
         try {
             final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
             dispatcher.processSubscriptionForInvoiceGeneration(subscriptionId, context.toLocalDate(eventDateTime), context);
-        } catch (final InvoiceApiException e) {
-            log.error(e.getMessage());
+        } catch (InvoiceApiException e) {
+            log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e);
         }
     }
 
@@ -126,8 +115,8 @@ public class InvoiceListener {
         try {
             final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
             dispatcher.processSubscriptionForInvoiceNotification(subscriptionId, context.toLocalDate(eventDateTime), context);
-        } catch (final InvoiceApiException e) {
-            log.error(e.getMessage());
+        } catch (InvoiceApiException e) {
+            log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e);
         }
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
index d05b13a..82c62f5 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
@@ -45,6 +45,7 @@ public class InvoicePluginDispatcher {
 
     private static final Collection<InvoiceItemType> ALLOWED_INVOICE_ITEM_TYPES = ImmutableList.<InvoiceItemType>of(InvoiceItemType.EXTERNAL_CHARGE,
                                                                                                                     InvoiceItemType.ITEM_ADJ,
+                                                                                                                    InvoiceItemType.CREDIT_ADJ,
                                                                                                                     InvoiceItemType.TAX);
 
     private final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java
index c8bc558..3d20a84 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java
@@ -63,7 +63,7 @@ public class InvoiceTagHandler {
         try {
             dispatcher.processAccount(accountId, null, null, context);
         } catch (final InvoiceApiException e) {
-            log.warn(String.format("Failed to process process removal AUTO_INVOICING_OFF for account %s", accountId), e);
+            log.warn("Failed to process tag removal AUTO_INVOICING_OFF for accountId='{}'", accountId, e);
         }
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java
index 54363ab..8402c5b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java
@@ -44,8 +44,8 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment 
     private final Boolean isSuccess;
 
     public DefaultInvoicePayment(final InvoicePaymentType type, final UUID paymentId, final UUID invoiceId, final DateTime paymentDate,
-                                 final BigDecimal amount, final Currency currency, final Currency processedCurrency, final Boolean isSuccess) {
-        this(UUIDs.randomUUID(), null, type, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, null, null, isSuccess);
+                                 final BigDecimal amount, final Currency currency, final Currency processedCurrency, final String paymentCookieId, final Boolean isSuccess) {
+        this(UUIDs.randomUUID(), null, type, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, paymentCookieId, null, isSuccess);
     }
 
     public DefaultInvoicePayment(final UUID id, final InvoicePaymentType type, final UUID paymentId, final UUID invoiceId, final DateTime paymentDate,
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
index 5b9b907..3f9b13b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -27,6 +29,7 @@ import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.util.currency.KillBillMoney;
 
 public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem {
 
@@ -117,7 +120,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
         this.usageName = usageName;
         this.startDate = startDate;
         this.endDate = endDate;
-        this.amount = amount;
+        this.amount = amount == null || currency == null ? amount : KillBillMoney.of(amount, currency);
         this.currency = currency;
         this.rate = rate;
         this.linkedItemId = reversedItemId;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/RefundAdjInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/RefundAdjInvoiceItem.java
index a0e4dd4..3f2ff23 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/RefundAdjInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/RefundAdjInvoiceItem.java
@@ -31,6 +31,9 @@ import org.killbill.billing.util.UUIDs;
 
 import com.google.common.base.Objects;
 
+// See killbill/killbill#30
+// At some point we want to remove all code related to invoice adjustment
+@Deprecated()
 public class RefundAdjInvoiceItem extends AdjInvoiceItem {
 
     public RefundAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date,
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index e5ec2ae..3131548 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -25,7 +25,7 @@ import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.notificationq.api.NotificationEvent;
 import org.killbill.notificationq.api.NotificationQueue;
 import org.killbill.notificationq.api.NotificationQueueService;
@@ -70,33 +70,29 @@ public class DefaultNextBillingDateNotifier implements NextBillingDateNotifier {
         final NotificationQueueHandler notificationQueueHandler = new NotificationQueueHandler() {
             @Override
             public void handleReadyNotification(final NotificationEvent notificationKey, final DateTime eventDate, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+                if (!(notificationKey instanceof NextBillingDateNotificationKey)) {
+                    log.error("Invoice service received an unexpected event className='{}", notificationKey.getClass());
+                    return;
+                }
+
+                final NextBillingDateNotificationKey key = (NextBillingDateNotificationKey) notificationKey;
+
+                // Just to ensure compatibility with json that might not have that targetDate field (old versions < 0.13.6)
+                final DateTime targetDate = key.getTargetDate() != null ? key.getTargetDate() : eventDate;
                 try {
-                    if (!(notificationKey instanceof NextBillingDateNotificationKey)) {
-                        log.error("Invoice service received an unexpected event type {}", notificationKey.getClass().getName());
+                    final SubscriptionBase subscription = subscriptionApi.getSubscriptionFromId(key.getUuidKey(), callContextFactory.createInternalTenantContext(tenantRecordId, accountRecordId));
+                    if (subscription == null) {
+                        log.warn("Unable to retrieve subscriptionId='{}' for event {}", key.getUuidKey(), key);
                         return;
                     }
-
-                    final NextBillingDateNotificationKey key = (NextBillingDateNotificationKey) notificationKey;
-
-                    // Just to ensure compatibility with json that might not have that targetDate field (old versions < 0.13.6)
-                    final DateTime targetDate = key.getTargetDate() != null ? key.getTargetDate() : eventDate;
-                    try {
-                        final SubscriptionBase subscription = subscriptionApi.getSubscriptionFromId(key.getUuidKey(), callContextFactory.createInternalTenantContext(tenantRecordId, accountRecordId));
-                        if (subscription == null) {
-                            log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")");
-                            return;
-                        }
-                        if (key.isDryRunForInvoiceNotification() != null && // Just to ensure compatibility with json that might not have that field (old versions < 0.13.6)
-                            key.isDryRunForInvoiceNotification()) {
-                            processEventForInvoiceNotification(key.getUuidKey(), targetDate, userToken, accountRecordId, tenantRecordId);
-                        } else {
-                            processEventForInvoiceGeneration(key.getUuidKey(), targetDate, userToken, accountRecordId, tenantRecordId);
-                        }
-                    } catch (SubscriptionBaseApiException e) {
-                        log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")", e);
+                    if (key.isDryRunForInvoiceNotification() != null && // Just to ensure compatibility with json that might not have that field (old versions < 0.13.6)
+                        key.isDryRunForInvoiceNotification()) {
+                        processEventForInvoiceNotification(key.getUuidKey(), targetDate, userToken, accountRecordId, tenantRecordId);
+                    } else {
+                        processEventForInvoiceGeneration(key.getUuidKey(), targetDate, userToken, accountRecordId, tenantRecordId);
                     }
-                } catch (IllegalArgumentException e) {
-                    log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
+                } catch (SubscriptionBaseApiException e) {
+                    log.warn("Error retrieving subscriptionId='{}'", key.getUuidKey(), e);
                 }
             }
         };
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultInvoiceProviderPluginRegistry.java b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultInvoiceProviderPluginRegistry.java
index 404b9fc..1860257 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultInvoiceProviderPluginRegistry.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultInvoiceProviderPluginRegistry.java
@@ -41,13 +41,13 @@ public class DefaultInvoiceProviderPluginRegistry implements OSGIServiceRegistra
 
     @Override
     public void registerService(final OSGIServiceDescriptor desc, final InvoicePluginApi service) {
-        log.info("DefaultInvoiceProviderPluginRegistry registering service " + desc.getRegistrationName());
+        log.info("Registering service='{}'", desc.getRegistrationName());
         pluginsByName.put(desc.getRegistrationName(), service);
     }
 
     @Override
     public void unregisterService(final String serviceName) {
-        log.info("DefaultInvoiceProviderPluginRegistry unregistering service " + serviceName);
+        log.info("Unregistering service='{}'", serviceName);
         pluginsByName.remove(serviceName);
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
index 0cf08e9..058f139 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -285,10 +285,10 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
                 }
             }
         } catch (final CurrencyConversionException e) {
-            logger.warn("Failed to retrieve currency conversion rates for currency = " + currency + " and date = " + latestPaymentDate, e);
+            logger.warn("Failed to retrieve currency conversion rates for currency='{}', dateConversion='{}'", currency, latestPaymentDate, e);
             return null;
         }
-        logger.warn("Failed to retrieve currency conversion rates for currency = " + currency + " and date = " + latestPaymentDate);
+        logger.warn("Failed to retrieve currency conversion rates for currency='{}', dateConversion='{}'", currency, latestPaymentDate);
         return null;
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/Item.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/Item.java
index f164b76..c5dd0d3 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/Item.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/Item.java
@@ -128,13 +128,13 @@ public class Item {
                                                                      .multiply(amount) : amount;
 
         if (action == ItemAction.ADD) {
-            return new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, newStartDate, newEndDate, KillBillMoney.of(positiveAmount, currency), rate, currency);
+            return new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, newStartDate, newEndDate, positiveAmount, rate, currency);
         } else {
             // We first compute the maximum amount after adjustment and that sets the amount limit of how much can be repaired.
             final BigDecimal maxAvailableAmountAfterAdj = amount.subtract(adjustedAmount);
             final BigDecimal maxAvailableAmountForRepair = maxAvailableAmountAfterAdj.subtract(currentRepairedAmount);
             final BigDecimal positiveAmountForRepair = positiveAmount.compareTo(maxAvailableAmountForRepair) <= 0 ? positiveAmount : maxAvailableAmountForRepair;
-            return positiveAmountForRepair.compareTo(BigDecimal.ZERO) > 0 ? new RepairAdjInvoiceItem(targetInvoiceId, accountId, newStartDate, newEndDate, KillBillMoney.of(positiveAmountForRepair.negate(), currency), currency, linkedId) : null;
+            return positiveAmountForRepair.compareTo(BigDecimal.ZERO) > 0 ? new RepairAdjInvoiceItem(targetInvoiceId, accountId, newStartDate, newEndDate, positiveAmountForRepair.negate(), currency, linkedId) : null;
         }
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
index afdc98d..854a91f 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -30,10 +30,11 @@ import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.generator.InvoiceDateUtils;
 import org.killbill.billing.invoice.model.UsageInvoiceItem;
 import org.killbill.billing.usage.InternalUserApi;
 import org.killbill.billing.usage.RawUsage;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -66,16 +67,15 @@ public class RawUsageOptimizer {
     }
 
     public RawUsageOptimizerResult getConsumableInArrearUsage(final LocalDate firstEventStartDate, final LocalDate targetDate, final Iterable<InvoiceItem> existingUsageItems, final Map<String, Usage> knownUsage, final InternalCallContext internalCallContext) {
-        final LocalDate targetStartDate = config.getMaxRawUsagePreviousPeriod() > 0 ? getOptimizedRawUsageStartDate(firstEventStartDate, targetDate, existingUsageItems, knownUsage) : firstEventStartDate;
-        log.info("RawUsageOptimizer [accountRecordId = {}]: rawUsageStartDate = {}, (proposed) firstEventStartDate = {}",
-                 new Object[]{internalCallContext.getAccountRecordId(), targetStartDate, firstEventStartDate});
-
+        final LocalDate targetStartDate = config.getMaxRawUsagePreviousPeriod(internalCallContext) > 0 ? getOptimizedRawUsageStartDate(firstEventStartDate, targetDate, existingUsageItems, knownUsage, internalCallContext) : firstEventStartDate;
+        log.info("ConsumableInArrear accountRecordId='{}', rawUsageStartDate='{}', firstEventStartDate='{}'",
+                 internalCallContext.getAccountRecordId(), targetStartDate, firstEventStartDate);
         final List<RawUsage> rawUsageData = usageApi.getRawUsageForAccount(targetStartDate, targetDate, internalCallContext);
         return new RawUsageOptimizerResult(firstEventStartDate, targetStartDate, rawUsageData);
     }
 
     @VisibleForTesting
-    LocalDate getOptimizedRawUsageStartDate(final LocalDate firstEventStartDate, final LocalDate targetDate, final Iterable<InvoiceItem> existingUsageItems, final Map<String, Usage> knownUsage) {
+    LocalDate getOptimizedRawUsageStartDate(final LocalDate firstEventStartDate, final LocalDate targetDate, final Iterable<InvoiceItem> existingUsageItems, final Map<String, Usage> knownUsage, final InternalCallContext internalCallContext) {
 
         if (!existingUsageItems.iterator().hasNext()) {
             return firstEventStartDate;
@@ -101,7 +101,7 @@ public class RawUsageOptimizer {
         int idx = 0;
         for (BillingPeriod bp : BillingPeriod.values()) {
             if (bp != BillingPeriod.NO_BILLING_PERIOD) {
-                final LocalDate makerDateThanCannotBeChosenAsTheMinOfAllDates = targetDate.plusMonths(config.getMaxRawUsagePreviousPeriod() * bp.getNumberOfMonths());
+                final LocalDate makerDateThanCannotBeChosenAsTheMinOfAllDates = InvoiceDateUtils.advanceByNPeriods(targetDate, bp, config.getMaxRawUsagePreviousPeriod(internalCallContext));
                 perBillingPeriodMostRecentConsumableInArrearItemEndDate[idx++] = (knownUsageBillingPeriod.contains(bp)) ? null : makerDateThanCannotBeChosenAsTheMinOfAllDates;
             }
         }
@@ -127,7 +127,7 @@ public class RawUsageOptimizer {
         for (BillingPeriod bp : BillingPeriod.values()) {
             if (bp != BillingPeriod.NO_BILLING_PERIOD) {
                 final LocalDate tmp = perBillingPeriodMostRecentConsumableInArrearItemEndDate[idx];
-                final LocalDate targetBillingPeriodDate = tmp != null ? tmp.minusMonths(config.getMaxRawUsagePreviousPeriod() * bp.getNumberOfMonths()) : null;
+                final LocalDate targetBillingPeriodDate = tmp != null ? InvoiceDateUtils.recedeByNPeriods(tmp, bp, config.getMaxRawUsagePreviousPeriod(internalCallContext)) : null;
                 if (targetStartDate == null || (targetBillingPeriodDate != null && targetBillingPeriodDate.compareTo(targetStartDate) < 0)) {
                     targetStartDate = targetBillingPeriodDate;
                 }
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index b9e4dab..9925595 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -109,10 +109,14 @@ getChargebacksByPaymentId() ::= <<
 
 updateAttempt() ::= <<
     UPDATE <tableName()>
-    SET success = true,
+    SET payment_id := :paymentId,
     payment_date = :paymentDate,
     amount = :amount,
-    processed_currency = :processedCurrency
+    currency = :currency,
+    processed_currency = :processedCurrency,
+    payment_cookie_id = :paymentCookieId,
+    linked_invoice_payment_id := :linkedInvoicePaymentId,
+    success = true
     WHERE record_id = :recordId
     <AND_CHECK_TENANT("")>
     ;
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
index 0af1018..9d791e1 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
@@ -51,15 +51,6 @@ public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB
         verifyRefund(THIRTY, BigDecimal.TEN, BigDecimal.TEN, false, ImmutableMap.<UUID, BigDecimal>of());
     }
 
-    @Test(groups = "slow")
-    public void testFullRefundWithInvoiceAdjustment() throws Exception {
-        verifyRefund(THIRTY, THIRTY, BigDecimal.ZERO, true, ImmutableMap.<UUID, BigDecimal>of());
-    }
-
-    @Test(groups = "slow")
-    public void testPartialRefundWithInvoiceAdjustment() throws Exception {
-        verifyRefund(THIRTY, BigDecimal.TEN, BigDecimal.ZERO, true, ImmutableMap.<UUID, BigDecimal>of());
-    }
 
     @Test(groups = "slow")
     public void testFullRefundWithBothInvoiceItemAdjustments() throws Exception {
@@ -114,7 +105,7 @@ public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB
         Assert.assertEquals(initialInvoiceBalance.compareTo(BigDecimal.ZERO), 0);
 
         // Create a full refund with no adjustment
-        final InvoicePayment refund = invoiceInternalApi.createRefund(payment.getPaymentId(), refundAmount, adjusted, invoiceItemIdsWithAmounts,
+        final InvoicePayment refund = invoiceInternalApi.recordRefund(payment.getPaymentId(), refundAmount, adjusted, invoiceItemIdsWithAmounts,
                                                                       UUID.randomUUID().toString(), internalCallContext);
         Assert.assertEquals(refund.getAmount().compareTo(refundAmount.negate()), 0);
         Assert.assertEquals(refund.getCurrency(), CURRENCY);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index ebd4299..d80a53e 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -280,7 +280,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
         Assert.assertEquals(accountBalance, invoiceBalance);
 
         // Adjust the invoice for a fraction of the balance
-        final BigDecimal adjAmount = invoiceItem.getAmount().divide(BigDecimal.TEN);
+        final BigDecimal adjAmount = invoiceItem.getAmount().divide(BigDecimal.TEN, BigDecimal.ROUND_HALF_UP);
         final InvoiceItem adjInvoiceItem = invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(),
                                                                                       clock.getUTCToday(), adjAmount, accountCurrency,
                                                                                       null, callContext);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
index 12010f6..43875bb 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
@@ -213,7 +213,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     }
 
     @Override
-    public List<InvoicePaymentModelDao> getInvoicePayments(final UUID paymentId, final InternalTenantContext context) {
+    public List<InvoicePaymentModelDao> getInvoicePaymentsByPaymentId(final UUID paymentId, final InternalTenantContext context) {
         final List<InvoicePaymentModelDao> result = new LinkedList<InvoicePaymentModelDao>();
         synchronized (monitor) {
             for (final InvoicePaymentModelDao payment : payments.values()) {
@@ -243,7 +243,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     }
 
     @Override
-    public void notifyOfPayment(final InvoicePaymentModelDao invoicePayment, final InternalCallContext context) {
+    public void notifyOfPaymentCompletion(final InvoicePaymentModelDao invoicePayment, final InternalCallContext context) {
         synchronized (monitor) {
             payments.put(invoicePayment.getId(), invoicePayment);
         }
@@ -387,4 +387,10 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     public void updateInvoiceItemAmount(final UUID invoiceItemId, final BigDecimal amount, final InternalCallContext context) throws InvoiceApiException {
         throw new UnsupportedOperationException();
     }
+
+    public void notifyOfPaymentInit(final InvoicePaymentModelDao invoicePayment, final InternalCallContext context) {
+        synchronized (monitor) {
+            payments.put(invoicePayment.getId(), invoicePayment);
+        }
+    }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
index 8abb9a9..7bb3b93 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
@@ -154,8 +154,8 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal paymentAmount = new BigDecimal("11.00");
         final UUID paymentId = UUID.randomUUID();
 
-        final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, clock.getUTCNow().plusDays(12), paymentAmount, Currency.USD, Currency.USD, true);
-        invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context);
+        final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, clock.getUTCNow().plusDays(12), paymentAmount, Currency.USD, Currency.USD, "cookie", true);
+        invoiceDao.notifyOfPaymentCompletion(new InvoicePaymentModelDao(defaultInvoicePayment), context);
 
         final InvoiceModelDao retrievedInvoice = invoiceDao.getById(invoiceId, context);
         assertNotNull(retrievedInvoice);
@@ -525,7 +525,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         invoiceUtil.createInvoiceItem(item2, context);
 
         final BigDecimal payment1 = new BigDecimal("48.0");
-        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true);
+        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, null, true);
         invoiceUtil.createPayment(payment, context);
 
         final BigDecimal balance = invoiceDao.getAccountBalance(accountId, context);
@@ -590,7 +590,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         invoiceUtil.createInvoice(invoice1, true, context);
 
         final BigDecimal payment1 = new BigDecimal("48.0");
-        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true);
+        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, null, true);
         invoiceUtil.createPayment(payment, context);
 
         final BigDecimal balance = invoiceDao.getAccountBalance(accountId, context);
@@ -602,11 +602,6 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         testAccountBalanceWithRefundInternal(false);
     }
 
-    @Test(groups = "slow")
-    public void testAccountBalanceWithRefundAndAdj() throws InvoiceApiException, EntityPersistenceException {
-        testAccountBalanceWithRefundInternal(true);
-    }
-
     private void testAccountBalanceWithRefundInternal(final boolean withAdjustment) throws InvoiceApiException, EntityPersistenceException {
 
         final UUID accountId = account.getId();
@@ -631,7 +626,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // Pay the whole thing
         final UUID paymentId = UUID.randomUUID();
         final BigDecimal payment1 = rate1;
-        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true);
+        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, null, true);
         invoiceUtil.createPayment(payment, context);
         balance = invoiceDao.getAccountBalance(accountId, context);
         assertEquals(balance.compareTo(new BigDecimal("0.00")), 0);
@@ -680,7 +675,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // Pay the whole thing
         final UUID paymentId = UUID.randomUUID();
         final BigDecimal payment1 = amount;
-        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true);
+        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, null, true);
         invoiceUtil.createPayment(payment, context);
         balancePriorRefund = invoiceDao.getAccountBalance(accountId, context);
         assertEquals(balancePriorRefund.compareTo(new BigDecimal("0.00")), 0);
@@ -746,13 +741,6 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         testAccountBalanceWithRefundAndCBAInternal(false, refundAmount, expectedBalance);
     }
 
-    @Test(groups = "slow")
-    public void testAccountBalanceWithLargeRefundAndCBAWithAdj() throws InvoiceApiException, EntityPersistenceException {
-        final BigDecimal refundAmount = new BigDecimal("20.00");
-        final BigDecimal expectedBalance = new BigDecimal("-10.00");
-        testAccountBalanceWithRefundAndCBAInternal(true, refundAmount, expectedBalance);
-    }
-
     private void testAccountBalanceWithRefundAndCBAInternal(final boolean withAdjustment, final BigDecimal refundAmount, final BigDecimal expectedFinalBalance) throws InvoiceApiException, EntityPersistenceException {
         final UUID accountId = account.getId();
         final UUID bundleId = UUID.randomUUID();
@@ -785,7 +773,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // Pay the whole thing
         final UUID paymentId = UUID.randomUUID();
         final BigDecimal payment1 = amount1.add(rate1);
-        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true);
+        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, null, true);
         invoiceUtil.createPayment(payment, context);
         balance = invoiceDao.getAccountBalance(accountId, context);
         assertEquals(balance.compareTo(new BigDecimal("0.00")), 0);
@@ -808,7 +796,11 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         assertEquals(cba.compareTo(new BigDecimal("10.00")), 0);
 
         // PARTIAL REFUND on the payment
-        invoiceDao.createRefund(paymentId, refundAmount, withAdjustment, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID().toString(), context);
+        final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts = new HashMap<UUID, BigDecimal>();
+        if (withAdjustment) {
+            invoiceItemIdsWithAmounts.put(item2Replace.getId(), refundAmount);
+        }
+        invoiceDao.createRefund(paymentId, refundAmount, withAdjustment, invoiceItemIdsWithAmounts, UUID.randomUUID().toString(), context);
 
         balance = invoiceDao.getAccountBalance(accountId, context);
         assertEquals(balance.compareTo(expectedFinalBalance), 0);
@@ -876,7 +868,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         // Pay the whole thing
         final BigDecimal payment1 = amount1.add(rate1);
-        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, true);
+        final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, null, true);
         invoiceUtil.createPayment(payment, context);
         balance = invoiceDao.getAccountBalance(accountId, context);
         assertEquals(balance.compareTo(new BigDecimal("0.00")), 0);
@@ -1375,8 +1367,8 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // SECOND CREATE THE PAYMENT
         final BigDecimal paymentAmount = new BigDecimal("239.00");
         final UUID paymentId = UUID.randomUUID();
-        final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, clock.getUTCNow(), paymentAmount, Currency.USD, Currency.USD, true);
-        invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context);
+        final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoiceId, clock.getUTCNow(), paymentAmount, Currency.USD, Currency.USD, "cookie", true);
+        invoiceDao.notifyOfPaymentCompletion(new InvoicePaymentModelDao(defaultInvoicePayment), context);
 
         // AND THEN THIRD THE REFUND
         final Map<UUID, BigDecimal> invoiceItemMap = new HashMap<UUID, BigDecimal>();
@@ -1525,9 +1517,9 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         final UUID paymentId = UUID.randomUUID();
         final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), clock.getUTCNow().plusDays(12), new BigDecimal("10.0"),
-                                                                                      Currency.USD, Currency.USD, true);
+                                                                                      Currency.USD, Currency.USD, "cookie", true);
 
-        invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context);
+        invoiceDao.notifyOfPaymentCompletion(new InvoicePaymentModelDao(defaultInvoicePayment), context);
 
         // Create invoice 2
         // Scenario: single item
@@ -1586,8 +1578,8 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final UUID paymentId = UUID.randomUUID();
 
         final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), clock.getUTCNow().plusDays(12), paymentAmount,
-                                                                                      Currency.USD, Currency.USD, true);
-        invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context);
+                                                                                      Currency.USD, Currency.USD, "cookie", true);
+        invoiceDao.notifyOfPaymentCompletion(new InvoicePaymentModelDao(defaultInvoicePayment), context);
 
         // Create invoice 2
         // Scenario: single item
@@ -1687,15 +1679,15 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
 
         final UUID paymentId = UUID.randomUUID();
-        final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), clock.getUTCNow().plusDays(12), BigDecimal.TEN, Currency.USD, Currency.USD, false);
-        invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context);
+        final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), clock.getUTCNow().plusDays(12), BigDecimal.TEN, Currency.USD, Currency.USD, "cookie", false);
+        invoiceDao.notifyOfPaymentCompletion(new InvoicePaymentModelDao(defaultInvoicePayment), context);
 
         final InvoiceModelDao retrievedInvoice1 = invoiceDao.getById(invoice.getId(), context);
         assertEquals(retrievedInvoice1.getInvoicePayments().size(), 1);
         assertEquals(retrievedInvoice1.getInvoicePayments().get(0).getSuccess(), Boolean.FALSE);
 
-        final DefaultInvoicePayment defaultInvoicePayment2 = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), clock.getUTCNow().plusDays(12), BigDecimal.TEN, Currency.USD, Currency.USD, true);
-        invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment2), context);
+        final DefaultInvoicePayment defaultInvoicePayment2 = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), clock.getUTCNow().plusDays(12), BigDecimal.TEN, Currency.USD, Currency.USD, "cookie", true);
+        invoiceDao.notifyOfPaymentCompletion(new InvoicePaymentModelDao(defaultInvoicePayment2), context);
 
         final InvoiceModelDao retrievedInvoice2 = invoiceDao.getById(invoice.getId(), context);
         assertEquals(retrievedInvoice2.getInvoicePayments().size(), 1);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java
index 99e777e..20c682e 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -193,9 +195,8 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testExternalChargeForVariousCurrenciesInvoiceSqlDao() throws Exception {
-        // 1 decimal place
+        // 0 decimal place
         createAndVerifyExternalCharge(new BigDecimal("10"), Currency.VND);
-        createAndVerifyExternalCharge(new BigDecimal("10.1"), Currency.VND);
         // 2 decimal places
         createAndVerifyExternalCharge(new BigDecimal("10"), Currency.USD);
         createAndVerifyExternalCharge(new BigDecimal("10.1"), Currency.USD);
@@ -215,6 +216,8 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
         createAndVerifyExternalCharge(new BigDecimal("10.000001"), Currency.BTC);
         createAndVerifyExternalCharge(new BigDecimal("10.0000001"), Currency.BTC);
         createAndVerifyExternalCharge(new BigDecimal("10.00000001"), Currency.BTC);
+        // Malagasy ariary is subdivided into 5 iraimbilanja
+        createAndVerifyExternalCharge(new BigDecimal("10.2"), Currency.MGA);
     }
 
     private void createAndVerifyExternalCharge(final BigDecimal amount, final Currency currency) throws EntityPersistenceException {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index e9fdfbf..358a78a 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -33,6 +33,7 @@ import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.DefaultPrice;
 import org.killbill.billing.catalog.MockInternationalPrice;
 import org.killbill.billing.catalog.MockPlan;
@@ -63,7 +64,7 @@ import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.mock.MockAccountBuilder;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.billing.util.currency.KillBillMoney;
 import org.killbill.clock.Clock;
 import org.killbill.clock.DefaultClock;
@@ -110,7 +111,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         final Clock clock = new DefaultClock();
         final InvoiceConfig invoiceConfig = new InvoiceConfig() {
             @Override
-            public int getNumberOfMonthsInFuture() {
+            public int getNumberOfMonthsInFuture(final InternalTenantContext context) {
                 return 36;
             }
 
@@ -120,12 +121,12 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
             }
 
             @Override
-            public TimeSpan getDryRunNotificationSchedule() {
+            public TimeSpan getDryRunNotificationSchedule(final InternalTenantContext context) {
                 return new TimeSpan("0s");
             }
 
             @Override
-            public int getMaxRawUsagePreviousPeriod() {
+            public int getMaxRawUsagePreviousPeriod(final InternalTenantContext context) {
                 return -1;
             }
 
@@ -183,6 +184,45 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         assertEquals(invoice.getNumberOfItems(), 2);
         assertEquals(invoice.getBalance(), KillBillMoney.of(TWENTY, invoice.getCurrency()));
         assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
+
+        assertEquals(invoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), new LocalDate(2011, 9, 1));
+        assertEquals(invoice.getInvoiceItems().get(0).getEndDate(), new LocalDate(2011, 10, 1));
+
+        assertEquals(invoice.getInvoiceItems().get(1).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(1).getStartDate(), new LocalDate(2011, 10, 1));
+        assertEquals(invoice.getInvoiceItems().get(1).getEndDate(), new LocalDate(2011, 11, 1));
+    }
+
+    @Test(groups = "fast")
+    public void testWithSingleThirtyDaysEvent() throws InvoiceApiException, CatalogApiException {
+        final BillingEventSet events = new MockBillingEventSet();
+
+        final SubscriptionBase sub = createSubscription();
+        final LocalDate startDate = invoiceUtil.buildDate(2011, 9, 1);
+
+        final Plan plan = new MockPlan();
+        final BigDecimal rate1 = TEN;
+        final PlanPhase phase = createMockThirtyDaysPlanPhase(rate1);
+
+        final BillingEvent event = createBillingEvent(sub.getId(), sub.getBundleId(), startDate, plan, phase, 1);
+        events.add(event);
+
+        final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3);
+        final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+        final Invoice invoice = invoiceWithMetadata.getInvoice();
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 2);
+        assertEquals(invoice.getBalance(), KillBillMoney.of(TWENTY, invoice.getCurrency()));
+        assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
+
+        assertEquals(invoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), new LocalDate(2011, 9, 1));
+        assertEquals(invoice.getInvoiceItems().get(0).getEndDate(), new LocalDate(2011, 10, 1));
+
+        assertEquals(invoice.getInvoiceItems().get(1).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(1).getStartDate(), new LocalDate(2011, 10, 1));
+        assertEquals(invoice.getInvoiceItems().get(1).getEndDate(), new LocalDate(2011, 10, 31));
     }
 
     private SubscriptionBase createSubscription() {
@@ -773,6 +813,11 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
     }
 
+    private MockPlanPhase createMockThirtyDaysPlanPhase(@Nullable final BigDecimal recurringRate) {
+        return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+                                 null, BillingPeriod.THIRTY_DAYS);
+    }
+
     private MockPlanPhase createMockMonthlyPlanPhase() {
         return new MockPlanPhase(null, null, BillingPeriod.MONTHLY);
     }
@@ -919,7 +964,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
 
         // pay the invoice
         invoice1.addPayment(new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), april25.toDateTimeAtCurrentTime(), TEN,
-                                                      Currency.USD, Currency.USD, true));
+                                                      Currency.USD, Currency.USD, null, true));
         assertEquals(invoice1.getBalance().compareTo(ZERO), 0);
 
         // change the plan (i.e. repair) on start date
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java
index dfa95a0..3cd5432 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java
@@ -37,6 +37,7 @@ import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.invoice.InvoiceTestSuiteNoDB;
 import org.killbill.billing.invoice.MockBillingEventSet;
+import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
@@ -160,7 +161,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
     }
 
     @Test(groups = "fast")
-    public void testProcessFixedBillingEventsWithCancellationOnSameDay() {
+    public void testProcessFixedBillingEventsWithCancellationOnSameDay() throws InvoiceApiException {
 
         final LocalDate targetDate = new LocalDate("2016-01-08");
 
@@ -193,7 +194,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
     }
 
     @Test(groups = "fast")
-    public void testProcessFixedBillingEventsWithCancellationOnNextDay() {
+    public void testProcessFixedBillingEventsWithCancellationOnNextDay() throws InvoiceApiException {
 
         final LocalDate targetDate = new LocalDate("2016-01-08");
 
@@ -224,12 +225,12 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
         fixedAndRecurringInvoiceItemGenerator.processFixedBillingEvents(invoiceId, account.getId(), events, targetDate, Currency.USD, proposedItems, internalCallContext);
         assertEquals(proposedItems.size(), 1);
         assertEquals(proposedItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
-        assertEquals(proposedItems.get(0).getAmount(), fixedPriceAmount);
+        assertEquals(proposedItems.get(0).getAmount().compareTo(fixedPriceAmount), 0);
     }
 
 
     @Test(groups = "fast")
-    public void testProcessFixedBillingEventsWithMultipleChangeOnSameDay() {
+    public void testProcessFixedBillingEventsWithMultipleChangeOnSameDay() throws InvoiceApiException {
 
         final LocalDate targetDate = new LocalDate("2016-01-08");
 
@@ -279,7 +280,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
         fixedAndRecurringInvoiceItemGenerator.processFixedBillingEvents(invoiceId, account.getId(), events, targetDate, Currency.USD, proposedItems, internalCallContext);
         assertEquals(proposedItems.size(), 1);
         assertEquals(proposedItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
-        assertEquals(proposedItems.get(0).getAmount(), fixedPriceAmount3);
+        assertEquals(proposedItems.get(0).getAmount().compareTo(fixedPriceAmount3), 0);
     }
 
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java
index 2328f56..121ff05 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -18,11 +20,10 @@ package org.killbill.billing.invoice.generator;
 
 import org.joda.time.LocalDate;
 import org.killbill.billing.catalog.api.BillingMode;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.invoice.InvoiceTestSuiteNoDB;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
 
@@ -35,10 +36,18 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testCalculateFirstBillingCycleDate1() throws Exception {
         final LocalDate from = new LocalDate("2012-01-16");
+        final LocalDate to = null;
+        final LocalDate targetDate = new LocalDate();
         final int bcd = 17;
-        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
-        billingIntervalDetail.calculateFirstBillingCycleDate();
-        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-17"));
+
+        final BillingIntervalDetail annualBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(annualBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-17"));
+
+        final BillingIntervalDetail monthlyBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(monthlyBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-17"));
+
+        final BillingIntervalDetail thirtyDaysBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.THIRTY_DAYS, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(thirtyDaysBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-16"));
     }
 
     /*
@@ -50,16 +59,22 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testCalculateFirstBillingCycleDate2() throws Exception {
         final LocalDate from = new LocalDate("2012-02-16");
+        final LocalDate to = null;
+        final LocalDate targetDate = new LocalDate();
         final int bcd = 30;
-        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
-        billingIntervalDetail.calculateFirstBillingCycleDate();
-        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail annualBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(annualBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail monthlyBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(monthlyBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail thirtyDaysBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.THIRTY_DAYS, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(thirtyDaysBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-16"));
     }
 
     /*
-     * Here the interesting part is that BCD is prior start and
-     *  i) we use MONTHLY billing period
-     * ii) on the next month, there is no such date (2012-02-30 does not exist)
+     * Here the interesting part is that BCD is prior start
      *
      *                                      Start
      *                              BCD     END_MONTH
@@ -67,12 +82,20 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
      *
      */
     @Test(groups = "fast")
-    public void testCalculateFirstBillingCycleDate4() throws Exception {
+    public void testCalculateFirstBillingCycleDate3() throws Exception {
         final LocalDate from = new LocalDate("2012-01-31");
+        final LocalDate to = null;
+        final LocalDate targetDate = new LocalDate();
         final int bcd = 30;
-        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
-        billingIntervalDetail.calculateFirstBillingCycleDate();
-        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail annualBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(annualBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2013-01-30"));
+
+        final BillingIntervalDetail monthlyBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(monthlyBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail thirtyDaysBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.THIRTY_DAYS, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(thirtyDaysBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-31"));
     }
 
     /*
@@ -82,12 +105,20 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
      *
      */
     @Test(groups = "fast")
-    public void testCalculateFirstBillingCycleDate3() throws Exception {
+    public void testCalculateFirstBillingCycleDate4() throws Exception {
         final LocalDate from = new LocalDate("2012-02-16");
+        final LocalDate to = null;
+        final LocalDate targetDate = new LocalDate();
         final int bcd = 14;
-        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
-        billingIntervalDetail.calculateFirstBillingCycleDate();
-        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2013-02-14"));
+
+        final BillingIntervalDetail annualBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(annualBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2013-02-14"));
+
+        final BillingIntervalDetail monthlyBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(monthlyBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-03-14"));
+
+        final BillingIntervalDetail thirtyDaysBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.THIRTY_DAYS, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(thirtyDaysBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-16"));
     }
 
     @Test(groups = "fast")
@@ -165,5 +196,4 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
         final LocalDate effectiveEndDate = billingIntervalDetail.getEffectiveEndDate();
         Assert.assertEquals(effectiveEndDate, new LocalDate("2012-05-31"));
     }
-
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceDateUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceDateUtils.java
index 485329b..67b1f42 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceDateUtils.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceDateUtils.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -18,33 +20,15 @@ package org.killbill.billing.invoice.generator;
 
 import java.math.BigDecimal;
 
-import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.invoice.InvoiceTestSuiteNoDB;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestInvoiceDateUtils extends InvoiceTestSuiteNoDB {
 
     @Test(groups = "fast")
-    public void testLastBCDShouldNotBeBeforePreviousBCD() throws Exception {
-        final LocalDate from = new LocalDate("2012-07-16");
-        final LocalDate previousBCD = new LocalDate("2012-08-15");
-        final int bcdLocal = 15;
-        final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(from, previousBCD, bcdLocal, BillingPeriod.MONTHLY);
-        Assert.assertEquals(lastBCD, new LocalDate("2012-08-15"));
-    }
-
-    @Test(groups = "fast")
-    public void testNextBCDShouldNotBeInThePast() throws Exception {
-        final LocalDate from = new LocalDate("2012-07-16");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 15);
-        Assert.assertEquals(to, new LocalDate("2012-08-15"));
-    }
-
-    @Test(groups = "fast")
     public void testProRationAfterLastBillingCycleDate() throws Exception {
         final LocalDate endDate = new LocalDate("2012-06-02");
         final LocalDate previousBillThroughDate = new LocalDate("2012-03-02");
@@ -53,72 +37,77 @@ public class TestInvoiceDateUtils extends InvoiceTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testBeforeBCDWithAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-02");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-03-03"));
-    }
-
-    @Test(groups = "fast")
-    public void testEqualBCDWithAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-03");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-04-03"));
-    }
-
-    @Test(groups = "fast")
-    public void testAfterBCDWithAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-04");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-04-03"));
-    }
-
-    @Test(groups = "fast")
-    public void testBeforeBCDWithOnOrAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-02");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-03-03"));
-    }
-
-    @Test(groups = "fast")
-    public void testEqualBCDWithOnOrAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-03");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-03-03"));
-    }
+    public void testCalculateNbOfBillingPeriods() throws Exception {
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 15), BillingPeriod.MONTHLY), 1);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 16), BillingPeriod.MONTHLY), 2);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 17), BillingPeriod.MONTHLY), 2);
 
-    @Test(groups = "fast")
-    public void testAfterBCDWithOnOrAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-04");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-04-03"));
-    }
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 13), BillingPeriod.THIRTY_DAYS), 1);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 14), BillingPeriod.THIRTY_DAYS), 2);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 15), BillingPeriod.THIRTY_DAYS), 2);
 
-    @Test(groups = "fast")
-    public void testEffectiveEndDate() throws Exception {
-        final LocalDate firstBCD = new LocalDate(2012, 7, 16);
-        final LocalDate targetDate = new LocalDate(2012, 8, 16);
-        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
-        final LocalDate effectiveEndDate = InvoiceDateUtils.calculateEffectiveEndDate(firstBCD, targetDate, billingPeriod);
-        // TODO should that be 2012-09-15?
-        Assert.assertEquals(effectiveEndDate, new LocalDate(2012, 9, 16));
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 29), BillingPeriod.WEEKLY), 1);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 30), BillingPeriod.WEEKLY), 2);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 31), BillingPeriod.WEEKLY), 2);
     }
 
     @Test(groups = "fast")
-    public void testLastBCD() throws Exception {
-        final LocalDate firstBCD = new LocalDate(2012, 7, 16);
-        final LocalDate effectiveEndDate = new LocalDate(2012, 9, 15);
-        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
-        final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(effectiveEndDate, firstBCD, 16, billingPeriod);
-        Assert.assertEquals(lastBCD, new LocalDate(2012, 8, 16));
+    public void testAdvanceByNPeriods() throws Exception {
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 0), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 1), new LocalDate(2016, 5, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 2), new LocalDate(2016, 6, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 3), new LocalDate(2016, 7, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 4), new LocalDate(2016, 8, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 5), new LocalDate(2016, 9, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 6), new LocalDate(2016, 10, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 7), new LocalDate(2016, 11, 8));
+
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 0), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 1), new LocalDate(2016, 5, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 2), new LocalDate(2016, 6, 7));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 3), new LocalDate(2016, 7, 7));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 4), new LocalDate(2016, 8, 6));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 5), new LocalDate(2016, 9, 5));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 6), new LocalDate(2016, 10, 5));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 7), new LocalDate(2016, 11, 4));
+
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 0), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 1), new LocalDate(2016, 4, 15));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 2), new LocalDate(2016, 4, 22));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 3), new LocalDate(2016, 4, 29));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 4), new LocalDate(2016, 5, 6));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 5), new LocalDate(2016, 5, 13));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 6), new LocalDate(2016, 5, 20));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 7), new LocalDate(2016, 5, 27));
     }
 
     @Test(groups = "fast")
-    public void testCalculateNbOfBillingPeriods() throws Exception {
-        final LocalDate firstBCD = new LocalDate(2012, 7, 16);
-        final LocalDate lastBCD = new LocalDate(2012, 9, 16);
-        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
-        final int numberOfWholeBillingPeriods = InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(firstBCD, lastBCD, billingPeriod);
-        Assert.assertEquals(numberOfWholeBillingPeriods, 2);
+    public void testRecedeByNPeriods() throws Exception {
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 7), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 6), new LocalDate(2016, 5, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 5), new LocalDate(2016, 6, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 4), new LocalDate(2016, 7, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 3), new LocalDate(2016, 8, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 2), new LocalDate(2016, 9, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 1), new LocalDate(2016, 10, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 0), new LocalDate(2016, 11, 8));
+
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 7), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 6), new LocalDate(2016, 5, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 5), new LocalDate(2016, 6, 7));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 4), new LocalDate(2016, 7, 7));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 3), new LocalDate(2016, 8, 6));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 2), new LocalDate(2016, 9, 5));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 1), new LocalDate(2016, 10, 5));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 0), new LocalDate(2016, 11, 4));
+
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 7), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 6), new LocalDate(2016, 4, 15));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 5), new LocalDate(2016, 4, 22));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 4), new LocalDate(2016, 4, 29));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 3), new LocalDate(2016, 5, 6));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 2), new LocalDate(2016, 5, 13));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 1), new LocalDate(2016, 5, 20));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 0), new LocalDate(2016, 5, 27));
     }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java
index a596d64..049446e 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java
@@ -29,6 +29,7 @@ import org.killbill.billing.util.email.EmailModule;
 import org.killbill.billing.util.email.templates.TemplateModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
 import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
 import org.killbill.billing.util.glue.TagStoreModule;
@@ -53,6 +54,7 @@ public class TestInvoiceModule extends DefaultInvoiceModule {
 
         install(new CatalogModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new TemplateModule(configSource));
         install(new EmailModule(configSource));
         install(new MockTenantModule(configSource));
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
index 80552ed..2f6bf60 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
@@ -38,7 +38,7 @@ import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
index 35f50a9..67ff4f8 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -41,7 +43,7 @@ public class TestExternalChargeInvoiceItem extends InvoiceTestSuiteNoDB {
         final ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
                                                                              effectiveDate, amount, currency);
         Assert.assertEquals(item.getAccountId(), accountId);
-        Assert.assertEquals(item.getAmount(), amount);
+        Assert.assertEquals(item.getAmount().compareTo(amount), 0);
         Assert.assertEquals(item.getBundleId(), bundleId);
         Assert.assertEquals(item.getCurrency(), currency);
         Assert.assertEquals(item.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java
index c05b8fe..51647a5 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -53,7 +55,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_OnePeriodLessADayAfterStart() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()).plusDays(-1);
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod()).plusDays(-1);
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, ONE);
     }
@@ -61,7 +63,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_ExactlyOnePeriodAfterStart() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths());
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod());
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO);
     }
@@ -69,7 +71,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_SlightlyMoreThanOnePeriodAfterStart() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()).plusDays(1);
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod()).plusDays(1);
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO);
     }
@@ -77,7 +79,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_CrossingYearBoundary() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 12, 15);
-        final LocalDate oneCycleLater = startDate.plusMonths(getBillingPeriod().getNumberOfMonths());
+        final LocalDate oneCycleLater = startDate.plus(getBillingPeriod().getPeriod());
 
         // test just before the billing cycle day
         testCalculateNumberOfBillingCycles(startDate, oneCycleLater.plusDays(-1), 15, ONE);
@@ -92,7 +94,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_StartingMidFebruary() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths());
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod());
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO);
     }
@@ -100,7 +102,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_StartingMidFebruaryOfLeapYear() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2012, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths());
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod());
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO);
     }
@@ -111,7 +113,10 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
         BigDecimal expectedValue = ONE;
 
         for (int i = 1; i <= 12; i++) {
-            final LocalDate oneCycleLater = startDate.plusMonths(i * getBillingPeriod().getNumberOfMonths());
+            LocalDate oneCycleLater = startDate;
+            for (int j = 0; j < i; j++) {
+                oneCycleLater = oneCycleLater.plus(getBillingPeriod().getPeriod());
+            }
             // test just before the billing cycle day
             testCalculateNumberOfBillingCycles(startDate, oneCycleLater.plusDays(-1), 31, expectedValue);
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java
index 5837359..d526635 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java
@@ -134,7 +134,8 @@ public class InvoiceTestUtils {
         Mockito.when(payment.getProcessedCurrency()).thenReturn(currency);
         Mockito.when(payment.isSuccess()).thenReturn(true);
 
-        invoicePaymentApi.notifyOfPayment(payment, callContext);
+        invoicePaymentApi.recordPaymentAttemptCompletion(payment.getInvoiceId(), payment.getAmount(), payment.getCurrency(), payment.getProcessedCurrency(), payment.getPaymentId(), payment.getPaymentCookieId(),
+                                                         payment.getPaymentDate(), payment.isSuccess(), callContext);
 
         return payment;
     }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
index 1eedaf8..c7beaab 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
@@ -137,9 +137,9 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
         invoice.addInvoiceItem(creditBalanceAdjInvoiceItem2);
         invoice.addInvoiceItem(refundAdjInvoiceItem);
         invoice.addPayment(new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice.getId(), clock.getUTCNow(), BigDecimal.TEN,
-                                                     Currency.USD, Currency.USD, true));
+                                                     Currency.USD, Currency.USD, null, true));
         invoice.addPayment(new DefaultInvoicePayment(InvoicePaymentType.REFUND, UUID.randomUUID(), invoice.getId(), clock.getUTCNow(), BigDecimal.ONE.negate(),
-                                                     Currency.USD, Currency.USD, true));
+                                                     Currency.USD, Currency.USD, null, true));
         // Check the scenario
         Assert.assertEquals(invoice.getBalance().doubleValue(), 0.00);
         Assert.assertEquals(invoice.getCreditedAmount().doubleValue(), 11.00);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
index 079900b..7fbb972 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
@@ -152,7 +152,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
         Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
         Assert.assertEquals(invoiceItems.get(0).getStartDate(), new LocalDate("2012-05-01"));
         Assert.assertNull(invoiceItems.get(0).getEndDate());
-        Assert.assertEquals(invoiceItems.get(0).getAmount(), BigDecimal.ZERO);
+        Assert.assertEquals(invoiceItems.get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
         Assert.assertNull(invoiceItems.get(0).getRate());
 
         Assert.assertEquals(invoiceItems.get(1).getInvoiceItemType(), InvoiceItemType.RECURRING);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index 66ea1e6..8e22146 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -83,7 +83,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.billing.util.currency.KillBillMoney;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.clock.Clock;
@@ -393,7 +393,7 @@ public class TestInvoiceHelper {
             }
 
             @Override
-            public BigDecimal getRecurringPrice() {
+            public BigDecimal getRecurringPrice(DateTime effectiveDate) {
                 return recurringPrice;
             }
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
index f9a86d1..102f9c7 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2014 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -34,7 +34,6 @@ import org.killbill.billing.invoice.model.ItemAdjInvoiceItem;
 import org.killbill.billing.invoice.model.RecurringInvoiceItem;
 import org.killbill.billing.invoice.model.RepairAdjInvoiceItem;
 import org.killbill.billing.util.jackson.ObjectMapper;
-import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.Lists;
@@ -826,8 +825,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void verifyJson() {
-
+    public void verifyJson() throws IOException {
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
         final UUID id1 = UUID.fromString("e8ba6ce7-9bd4-417d-af53-70951ecaa99f");
         final InvoiceItem yearly1 = new RecurringInvoiceItem(id1, new DateTime(), invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, new LocalDate("2014-01-01"), new LocalDate("2015-01-01"), BigDecimal.TEN, BigDecimal.TEN, currency);
@@ -842,17 +840,12 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         tree.addItem(repair);
 
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        try {
-            tree.getRoot().jsonSerializeTree(new ObjectMapper(), outputStream);
-
-            final String json = outputStream.toString("UTF-8");
-            final String expectedJson = "[{\"start\":\"2014-01-01\",\"end\":\"2015-01-01\",\"items\":[{\"id\":\"e8ba6ce7-9bd4-417d-af53-70951ecaa99f\",\"startDate\":\"2014-01-01\",\"endDate\":\"2015-01-01\",\"amount\":10,\"currency\":\"USD\",\"linkedId\":null,\"action\":\"ADD\"}]},[{\"start\":\"2014-08-01\",\"end\":\"2015-01-01\",\"items\":[{\"id\":\"48db1317-9a6e-4666-bcc5-fc7d3d0defc8\",\"startDate\":\"2014-08-01\",\"endDate\":\"2015-01-01\",\"amount\":1,\"currency\":\"USD\",\"linkedId\":null,\"action\":\"ADD\"},{\"id\":\"02ec57f5-2723-478b-86ba-ebeaedacb9db\",\"startDate\":\"2014-08-01\",\"endDate\":\"2015-01-01\",\"amount\":10,\"currency\":\"USD\",\"linkedId\":\"e8ba6ce7-9bd4-417d-af53-70951ecaa99f\",\"action\":\"CANCEL\"}]}]]";
+        tree.getRoot().jsonSerializeTree(new ObjectMapper(), outputStream);
 
-            assertEquals(json, expectedJson);
+        final String json = outputStream.toString("UTF-8");
+        final String expectedJson = "[{\"start\":\"2014-01-01\",\"end\":\"2015-01-01\",\"items\":[{\"id\":\"e8ba6ce7-9bd4-417d-af53-70951ecaa99f\",\"startDate\":\"2014-01-01\",\"endDate\":\"2015-01-01\",\"amount\":10.00,\"currency\":\"USD\",\"linkedId\":null,\"action\":\"ADD\"}]},[{\"start\":\"2014-08-01\",\"end\":\"2015-01-01\",\"items\":[{\"id\":\"48db1317-9a6e-4666-bcc5-fc7d3d0defc8\",\"startDate\":\"2014-08-01\",\"endDate\":\"2015-01-01\",\"amount\":1.00,\"currency\":\"USD\",\"linkedId\":null,\"action\":\"ADD\"},{\"id\":\"02ec57f5-2723-478b-86ba-ebeaedacb9db\",\"startDate\":\"2014-08-01\",\"endDate\":\"2015-01-01\",\"amount\":10.00,\"currency\":\"USD\",\"linkedId\":\"e8ba6ce7-9bd4-417d-af53-70951ecaa99f\",\"action\":\"CANCEL\"}]}]]";
 
-        } catch (final IOException e) {
-            Assert.fail(e.getMessage());
-        }
+        assertEquals(json, expectedJson);
     }
 
     @Test(groups = "fast", description = "https://github.com/killbill/killbill/issues/286")
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
index dd025c0..b7436b8 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -112,7 +112,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         existingUsage.add(ii5);
 
         final BigDecimal result = intervalConsumableInArrear.computeBilledUsage(intervalConsumableInArrear.getBilledItems(startDate, endDate, existingUsage));
-        assertEquals(result, BigDecimal.TEN.add(BigDecimal.TEN));
+        assertEquals(result.compareTo(BigDecimal.TEN.add(BigDecimal.TEN)), 0);
     }
 
     @Test(groups = "fast")
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestRawUsageOptimizer.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestRawUsageOptimizer.java
index eac8b46..292ec21 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestRawUsageOptimizer.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestRawUsageOptimizer.java
@@ -50,7 +50,7 @@ public class TestRawUsageOptimizer extends TestUsageInArrearBase {
         final DefaultUsage usage = createDefaultUsage(usageName, BillingPeriod.MONTHLY, tier);
         knownUsage.put(usageName, usage);
 
-        final LocalDate result = rawUsageOptimizer.getOptimizedRawUsageStartDate(firstEventStartDate, firstEventStartDate.plusDays(1), invoiceItems, knownUsage);
+        final LocalDate result = rawUsageOptimizer.getOptimizedRawUsageStartDate(firstEventStartDate, firstEventStartDate.plusDays(1), invoiceItems, knownUsage, internalCallContext);
         Assert.assertEquals(result.compareTo(firstEventStartDate), 0);
     }
 
@@ -69,7 +69,7 @@ public class TestRawUsageOptimizer extends TestUsageInArrearBase {
         final DefaultUsage usage = createDefaultUsage(usageName, BillingPeriod.MONTHLY, tier);
         knownUsage.put(usageName, usage);
 
-        final LocalDate result = rawUsageOptimizer.getOptimizedRawUsageStartDate(firstEventStartDate, targetDate, invoiceItems, knownUsage);
+        final LocalDate result = rawUsageOptimizer.getOptimizedRawUsageStartDate(firstEventStartDate, targetDate, invoiceItems, knownUsage, internalCallContext);
         // The largest endDate for ii is 2014-04-15, and by default org.killbill.invoice.readMaxRawUsagePreviousPeriod == 2 => targetDate =>  2014-02-15,
         // so we default to firstEventStartDate = 2014-03-15
         Assert.assertEquals(result.compareTo(firstEventStartDate), 0);
@@ -92,7 +92,7 @@ public class TestRawUsageOptimizer extends TestUsageInArrearBase {
         final DefaultUsage usage = createDefaultUsage(usageName, BillingPeriod.MONTHLY, tier);
         knownUsage.put(usageName, usage);
 
-        final LocalDate result = rawUsageOptimizer.getOptimizedRawUsageStartDate(firstEventStartDate, targetDate, invoiceItems, knownUsage);
+        final LocalDate result = rawUsageOptimizer.getOptimizedRawUsageStartDate(firstEventStartDate, targetDate, invoiceItems, knownUsage, internalCallContext);
         // The largest endDate for ii is 2014-08-15, and by default org.killbill.invoice.readMaxRawUsagePreviousPeriod == 2 => targetDate =>  2014-06-15
         Assert.assertEquals(result.compareTo(new LocalDate(2014, 06, 15)), 0, "112 got " + result);
     }
@@ -119,7 +119,7 @@ public class TestRawUsageOptimizer extends TestUsageInArrearBase {
         final DefaultUsage usage2 = createDefaultUsage("usageName2", BillingPeriod.ANNUAL, tier2);
         knownUsage.put("usageName2", usage2);
 
-        final LocalDate result = rawUsageOptimizer.getOptimizedRawUsageStartDate(firstEventStartDate, targetDate, invoiceItems, knownUsage);
+        final LocalDate result = rawUsageOptimizer.getOptimizedRawUsageStartDate(firstEventStartDate, targetDate, invoiceItems, knownUsage, internalCallContext);
         // The same reasoning applies as previously because there is no usage items against the annual and
         // so, the largest endDate for ii is 2014-08-15, and by default org.killbill.invoice.readMaxRawUsagePreviousPeriod == 2 => targetDate =>  2014-06-15
         Assert.assertEquals(result.compareTo(new LocalDate(2014, 06, 15)), 0, "142 got " + result);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java
index f19b31d..da1f233 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java
@@ -21,7 +21,7 @@ import org.killbill.billing.jaxrs.DefaultJaxrsService;
 import org.killbill.billing.jaxrs.JaxrsExecutors;
 import org.killbill.billing.jaxrs.JaxrsService;
 import org.killbill.billing.platform.api.KillbillConfigSource;
-import org.killbill.billing.util.config.JaxrsConfig;
+import org.killbill.billing.util.config.definition.JaxrsConfig;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.skife.config.ConfigurationObjectFactory;
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/JaxrsExecutors.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/JaxrsExecutors.java
index b2625f3..9ec7eea 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/JaxrsExecutors.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/JaxrsExecutors.java
@@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
 
-import org.killbill.billing.util.config.JaxrsConfig;
+import org.killbill.billing.util.config.definition.JaxrsConfig;
 import org.killbill.commons.concurrent.WithProfilingThreadPoolExecutor;
 
 public class JaxrsExecutors {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BlockingStateJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BlockingStateJson.java
index 3d4cd25..90e83df 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BlockingStateJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BlockingStateJson.java
@@ -22,8 +22,11 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
+import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
+import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.util.audit.AccountAuditLogs;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -38,7 +41,7 @@ public class BlockingStateJson extends JsonBase {
     private final Boolean blockChange;
     private final Boolean blockEntitlement;
     private final Boolean blockBilling;
-    private final LocalDate effectiveDate;
+    private final DateTime effectiveDate;
     private final BlockingStateType type;
 
     @JsonCreator
@@ -48,7 +51,7 @@ public class BlockingStateJson extends JsonBase {
                              @JsonProperty("blockChange") final Boolean blockChange,
                              @JsonProperty("blockEntitlement") final Boolean blockEntitlement,
                              @JsonProperty("blockBilling") final Boolean blockBilling,
-                             @JsonProperty("effectiveDate") final LocalDate effectiveDate,
+                             @JsonProperty("effectiveDate") final DateTime effectiveDate,
                              @JsonProperty("type") final BlockingStateType type,
                              @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
         super(auditLogs);
@@ -62,6 +65,19 @@ public class BlockingStateJson extends JsonBase {
         this.type = type;
     }
 
+    public BlockingStateJson(final BlockingState input, final AccountAuditLogs accountAuditLogs) {
+        this(input.getBlockedId().toString(),
+             input.getStateName(),
+             input.getService(),
+             input.isBlockChange(),
+             input.isBlockEntitlement(),
+             input.isBlockBilling(),
+             input.getEffectiveDate(),
+             input.getType(),
+             toAuditLogJson(accountAuditLogs == null ? null : accountAuditLogs.getAuditLogsForBlockingState(input.getId())));
+    }
+
+
     public String getBlockedId() {
         return blockedId;
     }
@@ -86,7 +102,7 @@ public class BlockingStateJson extends JsonBase {
         return blockBilling;
     }
 
-    public LocalDate getEffectiveDate() {
+    public DateTime getEffectiveDate() {
         return effectiveDate;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CreditJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CreditJson.java
index b3fd236..3e29ba3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CreditJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CreditJson.java
@@ -22,6 +22,7 @@ import java.util.List;
 import javax.annotation.Nullable;
 
 import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.util.audit.AuditLog;
@@ -41,10 +42,12 @@ public class CreditJson extends JsonBase {
     @ApiModelProperty(dataType = "java.util.UUID", required = true)
     private final String accountId;
     private final String description;
+    private final String currency;
 
 
     @JsonCreator
     public CreditJson(@JsonProperty("creditAmount") final BigDecimal creditAmount,
+                      @JsonProperty("currency") final String currency,
                       @JsonProperty("invoiceId") final String invoiceId,
                       @JsonProperty("invoiceNumber") final String invoiceNumber,
                       @JsonProperty("effectiveDate") final LocalDate effectiveDate,
@@ -53,6 +56,7 @@ public class CreditJson extends JsonBase {
                       @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
         super(auditLogs);
         this.creditAmount = creditAmount;
+        this.currency = currency;
         this.invoiceId = invoiceId;
         this.invoiceNumber = invoiceNumber;
         this.effectiveDate = effectiveDate;
@@ -64,6 +68,7 @@ public class CreditJson extends JsonBase {
         super(toAuditLogJson(auditLogs));
         this.accountId = toString(credit.getAccountId());
         this.creditAmount = credit.getAmount();
+        this.currency = credit.getCurrency().name();
         this.invoiceId = toString(credit.getInvoiceId());
         this.invoiceNumber = invoice.getInvoiceNumber().toString();
         this.effectiveDate = credit.getStartDate();
@@ -98,11 +103,16 @@ public class CreditJson extends JsonBase {
         return description;
     }
 
+    public String getCurrency() {
+        return currency;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
         sb.append("CreditJson");
         sb.append("{creditAmount=").append(creditAmount);
+        sb.append(", currency=").append(currency);
         sb.append(", invoiceId=").append(invoiceId);
         sb.append(", invoiceNumber='").append(invoiceNumber).append('\'');
         sb.append(", effectiveDate=").append(effectiveDate);
@@ -127,6 +137,9 @@ public class CreditJson extends JsonBase {
               (creditAmount != null && that.creditAmount != null && creditAmount.compareTo(that.creditAmount) == 0))) {
             return false;
         }
+        if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
+            return false;
+        }
         if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
             return false;
         }
@@ -147,6 +160,7 @@ public class CreditJson extends JsonBase {
     @Override
     public int hashCode() {
         int result = creditAmount != null ? creditAmount.hashCode() : 0;
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
         result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
         result = 31 * result + (description != null ? description.hashCode() : 0);
         result = 31 * result + (invoiceNumber != null ? invoiceNumber.hashCode() : 0);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java
index 83eef7c..9267ea9 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java
@@ -57,7 +57,7 @@ public class InvoiceItemJson extends JsonBase {
     private final LocalDate startDate;
     private final LocalDate endDate;
     private final BigDecimal amount;
-    private final Currency currency;
+    private final String currency;
 
     @JsonCreator
     public InvoiceItemJson(@JsonProperty("invoiceItemId") final String invoiceItemId,
@@ -75,7 +75,7 @@ public class InvoiceItemJson extends JsonBase {
                            @JsonProperty("startDate") final LocalDate startDate,
                            @JsonProperty("endDate") final LocalDate endDate,
                            @JsonProperty("amount") final BigDecimal amount,
-                           @JsonProperty("currency") final Currency currency,
+                           @JsonProperty("currency") final String currency,
                            @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
         super(auditLogs);
         this.invoiceItemId = invoiceItemId;
@@ -101,7 +101,7 @@ public class InvoiceItemJson extends JsonBase {
              toString(item.getAccountId()), toString(item.getChildAccountId()), toString(item.getBundleId()), toString(item.getSubscriptionId()),
              item.getPlanName(), item.getPhaseName(), item.getUsageName(), item.getInvoiceItemType().toString(),
              item.getDescription(), item.getStartDate(), item.getEndDate(),
-             item.getAmount(), item.getCurrency(), toAuditLogJson(auditLogs));
+             item.getAmount(), item.getCurrency().name(), toAuditLogJson(auditLogs));
     }
 
     public InvoiceItem toInvoiceItem() {
@@ -143,7 +143,7 @@ public class InvoiceItemJson extends JsonBase {
 
             @Override
             public Currency getCurrency() {
-                return currency;
+                return Currency.valueOf(currency);
             }
 
             @Override
@@ -272,7 +272,7 @@ public class InvoiceItemJson extends JsonBase {
         return amount;
     }
 
-    public Currency getCurrency() {
+    public String getCurrency() {
         return currency;
     }
 
@@ -323,7 +323,7 @@ public class InvoiceItemJson extends JsonBase {
         if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
             return false;
         }
-        if (currency != that.currency) {
+        if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
             return false;
         }
         if (description != null ? !description.equals(that.description) : that.description != null) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueStateJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueStateJson.java
index ed0fd75..a3fe920 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueStateJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueStateJson.java
@@ -21,7 +21,7 @@ import java.util.List;
 import org.joda.time.Period;
 import org.killbill.billing.overdue.api.OverdueApiException;
 import org.killbill.billing.overdue.api.OverdueState;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -56,7 +56,8 @@ public class OverdueStateJson {
     public OverdueStateJson(final OverdueState overdueState, final PaymentConfig paymentConfig) {
         this.name = overdueState.getName();
         this.externalMessage = overdueState.getExternalMessage();
-        this.daysBetweenPaymentRetries = paymentConfig.getPaymentFailureRetryDays();
+        // TODO this is broken if the per tenant system property was updated, but should we really return that in the OverdueState ?
+        this.daysBetweenPaymentRetries = paymentConfig.getPaymentFailureRetryDays(null);
         this.disableEntitlementAndChangesBlocked = overdueState.isDisableEntitlementAndChangesBlocked();
         this.blockChanges = overdueState.isBlockChanges();
         this.isClearState = overdueState.isClearState();
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionUsageRecordJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionUsageRecordJson.java
index 9e961a6..9a38ad5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionUsageRecordJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionUsageRecordJson.java
@@ -28,6 +28,7 @@ import org.killbill.billing.usage.api.UsageRecord;
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Function;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.wordnik.swagger.annotations.ApiModelProperty;
@@ -38,11 +39,15 @@ public class SubscriptionUsageRecordJson {
     private final String subscriptionId;
     @ApiModelProperty(required = true)
     private final List<UnitUsageRecordJson> unitUsageRecords;
+    @ApiModelProperty(required = false)
+    private final String trackingId;
 
     @JsonCreator
     public SubscriptionUsageRecordJson(@JsonProperty("subscriptionId") final String subscriptionId,
+                                       @JsonProperty("trackingId") final String trackingId,
                                        @JsonProperty("unitUsageRecords") final List<UnitUsageRecordJson> unitUsageRecords) {
         this.subscriptionId = subscriptionId;
+        this.trackingId = trackingId;
         this.unitUsageRecords = unitUsageRecords;
     }
 
@@ -54,6 +59,10 @@ public class SubscriptionUsageRecordJson {
         return unitUsageRecords;
     }
 
+    public String getTrackingId() {
+        return trackingId;
+    }
+
     public static class UnitUsageRecordJson {
 
         private final String unitType;
@@ -117,7 +126,7 @@ public class SubscriptionUsageRecordJson {
                 return input.toUnitUsageRecord();
             }
         }));
-        final SubscriptionUsageRecord result = new SubscriptionUsageRecord(UUID.fromString(subscriptionId), tmp);
+        final SubscriptionUsageRecord result = new SubscriptionUsageRecord(UUID.fromString(subscriptionId), trackingId, tmp);
         return result;
     }
-}
+}
\ No newline at end of file
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
index 8794159..35cd19f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -112,55 +114,40 @@ public abstract class ExceptionMapperBase {
     }
 
     protected Response buildConflictingRequestResponse(final Exception e, final UriInfo uriInfo) {
-        // Log the full stacktrace
-        log.warn("Conflicting request", e);
-
         final Response.ResponseBuilder responseBuilder = Response.status(Status.CONFLICT);
         serializeException(e, uriInfo, responseBuilder);
-        return responseBuilder.build();
+        return new LoggingResponse(e, responseBuilder.build());
     }
 
     protected Response buildNotFoundResponse(final Exception e, final UriInfo uriInfo) {
-        // Log the full stacktrace
-        log.info("Not found", e);
-
         final Response.ResponseBuilder responseBuilder = Response.status(Status.NOT_FOUND);
         serializeException(e, uriInfo, responseBuilder);
-        return responseBuilder.build();
+        return new LoggingResponse(e, responseBuilder.build());
     }
 
     protected Response buildBadRequestResponse(final Exception e, final UriInfo uriInfo) {
-        // Log the full stacktrace
-        log.warn("Bad request", e);
-
         final Response.ResponseBuilder responseBuilder = Response.status(Status.BAD_REQUEST);
         serializeException(e, uriInfo, responseBuilder);
-        return responseBuilder.build();
+        return new LoggingResponse(e, responseBuilder.build());
     }
 
     protected Response buildAuthorizationErrorResponse(final Exception e, final UriInfo uriInfo) {
-        // Log the full stacktrace
-        log.warn("Authorization error", e);
-
         // TODO Forbidden?
         final Response.ResponseBuilder responseBuilder = Response.status(Status.UNAUTHORIZED);
         serializeException(e, uriInfo, responseBuilder);
-        return responseBuilder.build();
+        return new LoggingResponse(e, responseBuilder.build());
     }
 
     protected Response buildInternalErrorResponse(final Exception e, final UriInfo uriInfo) {
-        // Log the full stacktrace
-        log.warn("Internal error", e);
-
         final Response.ResponseBuilder responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR);
         serializeException(e, uriInfo, responseBuilder);
-        return responseBuilder.build();
+        return new LoggingResponse(e, responseBuilder.build());
     }
 
     protected Response buildPluginTimeoutResponse(final Exception e, final UriInfo uriInfo) {
         final Response.ResponseBuilder responseBuilder = Response.status(Status.ACCEPTED);
         serializeException(e, uriInfo, responseBuilder);
-        return responseBuilder.build();
+        return new LoggingResponse(e, responseBuilder.build());
     }
 
     private void serializeException(final Exception e, final UriInfo uriInfo, final Response.ResponseBuilder responseBuilder) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/LoggingResponse.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/LoggingResponse.java
new file mode 100644
index 0000000..e3635ca
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/LoggingResponse.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.jaxrs.mappers;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LoggingResponse extends Response {
+
+    private static final Logger log = LoggerFactory.getLogger(LoggingResponse.class);
+
+    private final Exception e;
+    private final Response response;
+
+    public LoggingResponse(final Exception e, final Response response) {
+        this.e = e;
+        this.response = response;
+    }
+
+    @Override
+    public Object getEntity() {
+        // Delay logging until the entity is retrieved: this is to avoid double logging with TimedResourceInterceptor
+        // which needs to access exception mappers to get the response status
+        if (response.getStatus() == Status.CONFLICT.getStatusCode()) {
+            log.warn("Conflicting request", e);
+        } else if (response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
+            log.debug("Not found", e);
+        } else if (response.getStatus() == Status.BAD_REQUEST.getStatusCode()) {
+            log.warn("Bad request", e);
+        } else if (response.getStatus() == Status.UNAUTHORIZED.getStatusCode()) {
+            log.debug("Authorization error", e);
+        } else if (response.getStatus() == Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
+            log.warn("Internal error", e);
+        }
+
+        return response.getEntity();
+    }
+
+    @Override
+    public int getStatus() {
+        return response.getStatus();
+    }
+
+    @Override
+    public MultivaluedMap<String, Object> getMetadata() {
+        return response.getMetadata();
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
index c89369d..efa1828 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
@@ -69,7 +69,7 @@ public class PaymentApiExceptionMapper extends ExceptionMapperBase implements Ex
         } else if (exception.getCode() == ErrorCode.PAYMENT_UPD_PAYMENT_METHOD.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_INVALID_PARAMETER.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
+            return buildBadRequestResponse(exception, uriInfo);
         } else {
             return fallback(exception, uriInfo);
         }
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 fe24c85..0f7e684 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
@@ -51,14 +51,17 @@ import javax.ws.rs.core.UriInfo;
 
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
+import org.killbill.billing.OrderingType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.account.api.AccountEmail;
-import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.account.api.MutableAccountData;
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.entitlement.api.SubscriptionApi;
 import org.killbill.billing.entitlement.api.SubscriptionApiException;
 import org.killbill.billing.entitlement.api.SubscriptionBundle;
@@ -71,6 +74,7 @@ import org.killbill.billing.jaxrs.JaxrsExecutors;
 import org.killbill.billing.jaxrs.json.AccountEmailJson;
 import org.killbill.billing.jaxrs.json.AccountJson;
 import org.killbill.billing.jaxrs.json.AccountTimelineJson;
+import org.killbill.billing.jaxrs.json.BlockingStateJson;
 import org.killbill.billing.jaxrs.json.BundleJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.InvoiceEmailJson;
@@ -83,7 +87,7 @@ import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
 import org.killbill.billing.jaxrs.json.TagJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
-import org.killbill.billing.overdue.OverdueInternalApi;
+import org.killbill.billing.overdue.api.OverdueApi;
 import org.killbill.billing.overdue.api.OverdueApiException;
 import org.killbill.billing.overdue.api.OverdueState;
 import org.killbill.billing.overdue.config.api.OverdueException;
@@ -92,6 +96,7 @@ 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.PaymentTransaction;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.UUIDs;
@@ -105,8 +110,8 @@ import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.audit.AccountAuditLogs;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
-import org.killbill.billing.util.config.JaxrsConfig;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.JaxrsConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
@@ -115,6 +120,7 @@ import org.killbill.commons.metrics.MetricTag;
 import org.killbill.commons.metrics.TimedResource;
 
 import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
@@ -139,7 +145,7 @@ public class AccountResource extends JaxRsResourceBase {
     private final SubscriptionApi subscriptionApi;
     private final InvoiceUserApi invoiceApi;
     private final InvoicePaymentApi invoicePaymentApi;
-    private final OverdueInternalApi overdueApi;
+    private final OverdueApi overdueApi;
     private final PaymentConfig paymentConfig;
     private final JaxrsExecutors jaxrsExecutors;
     private final JaxrsConfig jaxrsConfig;
@@ -154,14 +160,13 @@ public class AccountResource extends JaxRsResourceBase {
                            final AuditUserApi auditUserApi,
                            final CustomFieldUserApi customFieldUserApi,
                            final SubscriptionApi subscriptionApi,
-                           final AccountInternalApi accountInternalApi,
-                           final OverdueInternalApi overdueApi,
+                           final OverdueApi overdueApi,
                            final Clock clock,
                            final PaymentConfig paymentConfig,
                            final JaxrsExecutors jaxrsExecutors,
                            final JaxrsConfig jaxrsConfig,
                            final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountApi, paymentApi, subscriptionApi, clock, context);
         this.subscriptionApi = subscriptionApi;
         this.invoiceApi = invoiceApi;
         this.invoicePaymentApi = invoicePaymentApi;
@@ -370,7 +375,6 @@ public class AccountResource extends JaxRsResourceBase {
         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
     }
 
-
     @TimedResource
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + TIMELINE)
@@ -794,7 +798,6 @@ public class AccountResource extends JaxRsResourceBase {
         return Response.status(Status.OK).build();
     }
 
-
     @TimedResource
     @PUT
     @Consumes(APPLICATION_JSON)
@@ -916,12 +919,28 @@ public class AccountResource extends JaxRsResourceBase {
                              json.getAmount(), "PaymentTransactionJson amount needs to be set");
 
         final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
-        final UUID paymentMethodId = paymentMethodIdStr == null ? account.getPaymentMethodId() : UUID.fromString(paymentMethodIdStr);
         final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency());
         final UUID paymentId = json.getPaymentId() == null ? null : UUID.fromString(json.getPaymentId());
 
+        //
+        // If paymentId was specified, it means we are attempting a payment completion. The preferred way is to use the PaymentResource
+        // (PUT /1.0/kb/payments/{paymentId}/completeTransaction), but for backward compatibility we still allow the call to proceed
+        // as long as the request/existing state is healthy (i.e there is a matching PENDING transaction)
+        //
+        final UUID paymentMethodId;
+        if (paymentId != null) {
+            final Payment initialPayment = paymentApi.getPayment(paymentId, false, pluginProperties, callContext);
+            final PaymentTransaction pendingTransaction = lookupPendingTransaction(initialPayment,
+                                                                                   json != null ? json.getTransactionId() : null,
+                                                                                   json != null ? json.getTransactionExternalKey() : null,
+                                                                                   json != null ? json.getTransactionType() : null);
+            paymentMethodId = initialPayment.getPaymentMethodId();
+        } else {
+            paymentMethodId = paymentMethodIdStr == null ? account.getPaymentMethodId() : UUID.fromString(paymentMethodIdStr);
+        }
         validatePaymentMethodForAccount(account.getId(), paymentMethodId, callContext);
 
+
         final TransactionType transactionType = TransactionType.valueOf(json.getTransactionType());
         final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
         final Payment result;
@@ -966,11 +985,62 @@ public class AccountResource extends JaxRsResourceBase {
         final TenantContext tenantContext = context.createContext(request);
 
         final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), tenantContext);
-        final OverdueState overdueState = overdueApi.getOverdueStateFor(account, tenantContext);
+        final OverdueState overdueState = overdueApi.getOverdueStateFor(account.getId(), tenantContext);
 
         return Response.status(Status.OK).entity(new OverdueStateJson(overdueState, paymentConfig)).build();
     }
 
+
+    /*
+     * *************************      BLOCKING STATE     *****************************
+     */
+
+    @TimedResource
+    @GET
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + BLOCK)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve blocking states for account", response = BlockingStateJson.class, responseContainer = "List")
+    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id supplied")})
+    public Response getBlockingStates(@PathParam(ID_PARAM_NAME) final String id,
+                                      @QueryParam(QUERY_BLOCKING_STATE_TYPES) final List<BlockingStateType> typeFilter,
+                                      @QueryParam(QUERY_BLOCKING_STATE_SVCS) final List<String> svcsFilter,
+                                      @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                      @javax.ws.rs.core.Context final HttpServletRequest request) throws EntitlementApiException {
+
+        final TenantContext tenantContext = this.context.createContext(request);
+        final UUID accountId = UUID.fromString(id);
+        final Iterable<BlockingState> blockingStates = subscriptionApi.getBlockingStates(accountId, typeFilter, svcsFilter, OrderingType.ASCENDING, SubscriptionApi.ALL_EVENTS, tenantContext);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(accountId, auditMode.getLevel(), tenantContext);
+
+        final List<BlockingStateJson> result = ImmutableList.copyOf(Iterables.transform(blockingStates, new Function<BlockingState, BlockingStateJson>() {
+            @Override
+            public BlockingStateJson apply(final BlockingState input) {
+                return new BlockingStateJson(input, accountAuditLogs);
+            }
+        }));
+
+        return Response.status(Status.OK).entity(result).build();
+    }
+
+    @TimedResource
+    @PUT
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + BLOCK)
+    @Consumes(APPLICATION_JSON)
+    @ApiOperation(value = "Block an account")
+    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id supplied"),
+                           @ApiResponse(code = 404, message = "Account not found")})
+    public Response addAccountBlockingState(final BlockingStateJson json,
+                                           @PathParam(ID_PARAM_NAME) final String id,
+                                           @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+                                           @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                           @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                           @HeaderParam(HDR_REASON) final String reason,
+                                           @HeaderParam(HDR_COMMENT) final String comment,
+                                           @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, EntitlementApiException, AccountApiException {
+        return addBlockingState(json, id, BlockingStateType.ACCOUNT, requestedDate, pluginPropertiesString, createdBy, reason, comment, request);
+    }
+
+
     /*
      * *************************      CUSTOM FIELDS     *****************************
      */
@@ -1061,7 +1131,6 @@ public class AccountResource extends JaxRsResourceBase {
         return createTagResponse(accountId, tags, auditMode, tenantContext);
     }
 
-
     @TimedResource
     @POST
     @Path("/{accountId:" + UUID_PATTERN + "}/" + TAGS)
@@ -1171,7 +1240,7 @@ public class AccountResource extends JaxRsResourceBase {
                                                                                }
                                                                            }
                                                                           )
-                                                    .orNull();
+                .orNull();
         if (existingEmail == null) {
             accountUserApi.addEmail(accountId, json.toAccountEmail(UUIDs.randomUUID()), callContext);
         }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
index ed98edb..dbd6cbe 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
@@ -67,7 +67,7 @@ public class AdminResource extends JaxRsResourceBase {
 
     @Inject
     public AdminResource(final JaxrsUriBuilder uriBuilder, final TagUserApi tagUserApi, final CustomFieldUserApi customFieldUserApi, final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final PaymentApi paymentApi, final AdminPaymentApi adminPaymentApi, final Clock clock, final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.adminPaymentApi = adminPaymentApi;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
index 7658730..a09e5db 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
@@ -44,6 +44,7 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.api.EntitlementApi;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.entitlement.api.SubscriptionApi;
@@ -100,7 +101,7 @@ public class BundleResource extends JaxRsResourceBase {
                           final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, subscriptionApi, clock, context);
         this.entitlementApi = entitlementApi;
         this.subscriptionApi = subscriptionApi;
     }
@@ -208,7 +209,7 @@ public class BundleResource extends JaxRsResourceBase {
         final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         final UUID bundleId = UUID.fromString(id);
-        final LocalDate inputLocalDate = toLocalDate(requestedDate, callContext);
+        final LocalDate inputLocalDate = toLocalDate(requestedDate);
         entitlementApi.pause(bundleId, inputLocalDate, pluginProperties, callContext);
         return Response.status(Status.OK).build();
     }
@@ -231,7 +232,7 @@ public class BundleResource extends JaxRsResourceBase {
         final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         final UUID bundleId = UUID.fromString(id);
-        final LocalDate inputLocalDate = toLocalDate(requestedDate, callContext);
+        final LocalDate inputLocalDate = toLocalDate(requestedDate);
         entitlementApi.resume(bundleId, inputLocalDate, pluginProperties, callContext);
         return Response.status(Status.OK).build();
     }
@@ -245,23 +246,13 @@ public class BundleResource extends JaxRsResourceBase {
                            @ApiResponse(code = 404, message = "Bundle not found")})
     public Response addBundleBlockingState(final BlockingStateJson json,
                                            @PathParam(ID_PARAM_NAME) final String id,
+                                           @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
                                            @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                            @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                            @HeaderParam(HDR_REASON) final String reason,
                                            @HeaderParam(HDR_COMMENT) final String comment,
                                            @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, EntitlementApiException, AccountApiException {
-
-        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
-        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
-        final UUID bundleId = UUID.fromString(id);
-
-        final boolean isBlockBilling = (json.isBlockBilling() != null && json.isBlockBilling());
-        final boolean isBlockEntitlement = (json.isBlockEntitlement() != null && json.isBlockEntitlement());
-        final boolean isBlockChange = (json.isBlockChange() != null && json.isBlockChange());
-
-        entitlementApi.setBlockingState(bundleId, json.getStateName(), json.getService(), json.getEffectiveDate(), isBlockBilling, isBlockEntitlement, isBlockChange, pluginProperties, callContext);
-
-        return Response.status(Status.OK).build();
+        return addBlockingState(json, id, BlockingStateType.SUBSCRIPTION_BUNDLE, requestedDate, pluginPropertiesString, createdBy, reason, comment, request);
     }
 
 
@@ -358,7 +349,7 @@ public class BundleResource extends JaxRsResourceBase {
         final UUID bundleId = UUID.fromString(id);
 
         final SubscriptionBundle bundle = subscriptionApi.getSubscriptionBundle(bundleId, callContext);
-        final LocalDate inputLocalDate = toLocalDate(requestedDate, callContext);
+        final LocalDate inputLocalDate = toLocalDate(requestedDate);
 
         final UUID newBundleId = entitlementApi.transferEntitlementsOverrideBillingPolicy(bundle.getAccountId(), UUID.fromString(json.getAccountId()), bundle.getExternalKey(), inputLocalDate, policy, pluginProperties, callContext);
         return uriBuilder.buildResponse(BundleResource.class, "getBundle", newBundleId, uriInfo.getBaseUri().toString());
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
index 35db125..04f7f8b 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
@@ -89,7 +89,7 @@ public class CatalogResource extends JaxRsResourceBase {
                            final CatalogUserApi catalogUserApi,
                            final Clock clock,
                            final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.catalogUserApi = catalogUserApi;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
index e308641..6c87b6c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
@@ -59,7 +59,7 @@ public abstract class ComboPaymentResource extends JaxRsResourceBase {
                                 final PaymentApi paymentApi,
                                 final Clock clock,
                                 final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
     }
 
     protected Account getOrCreateAccount(final AccountJson accountJson, final CallContext callContext) throws AccountApiException {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java
index 0be0e19..91bcb5d 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java
@@ -78,7 +78,7 @@ public class CreditResource extends JaxRsResourceBase {
                           final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.invoiceUserApi = invoiceUserApi;
         this.accountUserApi = accountUserApi;
     }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java
index 488850c..d2b5c80 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java
@@ -69,7 +69,7 @@ public class CustomFieldResource extends JaxRsResourceBase {
                                final PaymentApi paymentApi,
                                final Clock clock,
                                final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
     }
 
     @TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java
index 64d7145..7b7527f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java
@@ -67,7 +67,7 @@ public class ExportResource extends JaxRsResourceBase {
                           final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.exportUserApi = exportUserApi;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
index 9ee85d0..4b36164 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
@@ -98,7 +98,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
                                   final InvoicePaymentApi invoicePaymentApi,
                                   final Clock clock,
                                   final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.invoicePaymentApi = invoicePaymentApi;
     }
 
@@ -125,7 +125,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
         final InvoicePayment invoicePayment = Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
             @Override
             public boolean apply(final InvoicePayment input) {
-                return input.getType() == InvoicePaymentType.ATTEMPT;
+                return input.getType() == InvoicePaymentType.ATTEMPT && input.isSuccess();
             }
         }).orNull();
         final UUID invoiceId = invoicePayment != null ? invoicePayment.getInvoiceId() : null;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index 4a09004..9d6b982 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -158,7 +158,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                            final AuditUserApi auditUserApi,
                            final TenantUserApi tenantApi,
                            final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.invoiceApi = invoiceApi;
         this.invoiceNotifier = invoiceNotifier;
         this.tenantApi = tenantApi;
@@ -300,7 +300,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                                         @javax.ws.rs.core.Context final HttpServletRequest request,
                                         @javax.ws.rs.core.Context final UriInfo uriInfo) throws AccountApiException, InvoiceApiException {
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
-        final LocalDate inputDate = toLocalDate(targetDate, callContext);
+        final LocalDate inputDate = toLocalDate(targetDate);
 
         try {
             final Invoice generatedInvoice = invoiceApi.triggerInvoiceGeneration(UUID.fromString(accountId), inputDate, null,
@@ -362,10 +362,10 @@ public class InvoiceResource extends JaxRsResourceBase {
             } else if (DryRunType.SUBSCRIPTION_ACTION.name().equals(dryRunSubscriptionSpec.getDryRunType()) && dryRunSubscriptionSpec.getEffectiveDate() != null) {
                 inputDate = dryRunSubscriptionSpec.getEffectiveDate();
             } else {
-                inputDate = toLocalDate(targetDate, callContext);
+                inputDate = toLocalDate(targetDate);
             }
         } else {
-            inputDate = toLocalDate(targetDate, callContext);
+            inputDate = toLocalDate(targetDate);
         }
 
         // Passing a null or empty body means we are trying to generate an invoice with a (future) targetDate
@@ -464,7 +464,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                                                                     UUID.fromString(json.getInvoiceItemId()),
                                                                     requestedDate,
                                                                     json.getAmount(),
-                                                                    json.getCurrency(),
+                                                                    Currency.valueOf(json.getCurrency()),
                                                                     json.getDescription(),
                                                                     callContext);
         }
@@ -529,7 +529,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                 @Override
                 public InvoiceItemJson apply(final InvoiceItemJson input) {
                     if (input.getCurrency() != null) {
-                        if (!input.getCurrency().equals(accountCurrency)) {
+                        if (!input.getCurrency().equals(accountCurrency.name())) {
                             throw new IllegalArgumentException(input.getCurrency().toString());
                         }
                         return input;
@@ -549,7 +549,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                                                    input.getStartDate(),
                                                    input.getEndDate(),
                                                    input.getAmount(),
-                                                   accountCurrency,
+                                                   accountCurrency.name(),
                                                    null);
                     }
                 }
@@ -637,9 +637,11 @@ public class InvoiceResource extends JaxRsResourceBase {
                                      (payment.getPaymentMethodId() != null ? UUID.fromString(payment.getPaymentMethodId()) : account.getPaymentMethodId());
 
         final UUID invoiceId = UUID.fromString(payment.getTargetInvoiceId());
+
         final Payment result = createPurchaseForInvoice(account, invoiceId, payment.getPurchasedAmount(), paymentMethodId, externalPayment, pluginProperties, callContext);
-        // STEPH should that live in InvoicePayment instead?
-        return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId());
+        return result != null ?
+               uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId()) :
+               Response.status(Status.NO_CONTENT).build();
     }
 
     @TimedResource
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 227b779..3fdabc3 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
@@ -29,6 +29,7 @@ public interface JaxrsResource {
     public static final String TIMELINE = "timeline";
     public static final String REGISTER_NOTIFICATION_CALLBACK = "registerNotificationCallback";
     public static final String UPLOAD_PLUGIN_CONFIG = "uploadPluginConfig";
+    public static final String UPLOAD_PER_TENANT_CONFIG = "uploadPerTenantConfig";
     public static final String USER_KEY_VALUE = "userKeyValue";
     public static final String SEARCH = "search";
 
@@ -84,6 +85,10 @@ public interface JaxrsResource {
 
     public static final String QUERY_ACCOUNT_ID = "accountId";
 
+    public static final String QUERY_BLOCKING_STATE_TYPES = "blockingStateTypes";
+    public static final String QUERY_BLOCKING_STATE_SVCS = "blockingStateSvcs";
+
+
     public static final String QUERY_INVOICE_WITH_ITEMS = "withItems";
     public static final String QUERY_WITH_MIGRATION_INVOICES = "withMigrationInvoices";
     public static final String QUERY_UNPAID_INVOICES_ONLY = "unpaidInvoicesOnly";
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 82cc906..5699087 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
@@ -30,16 +30,17 @@ import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.StreamingOutput;
 import javax.ws.rs.core.UriInfo;
 
+import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.joda.time.format.DateTimeFormat;
 import org.joda.time.format.DateTimeFormatter;
@@ -49,14 +50,21 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.EntitlementApiException;
+import org.killbill.billing.entitlement.api.SubscriptionApi;
+import org.killbill.billing.entitlement.api.SubscriptionApiException;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.killbill.billing.jaxrs.json.BlockingStateJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.JsonBase;
 import org.killbill.billing.jaxrs.json.PluginPropertyJson;
 import org.killbill.billing.jaxrs.json.TagJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+import org.killbill.billing.junction.DefaultBlockingState;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
@@ -64,6 +72,7 @@ import org.killbill.billing.payment.api.PaymentMethod;
 import org.killbill.billing.payment.api.PaymentOptions;
 import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.api.AuditUserApi;
@@ -110,6 +119,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
     protected final AuditUserApi auditUserApi;
     protected final AccountUserApi accountUserApi;
     protected final PaymentApi paymentApi;
+    protected final SubscriptionApi subscriptionApi;
     protected final Context context;
     protected final Clock clock;
 
@@ -122,6 +132,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
                              final AuditUserApi auditUserApi,
                              final AccountUserApi accountUserApi,
                              final PaymentApi paymentApi,
+                             final SubscriptionApi subscriptionApi,
                              final Clock clock,
                              final Context context) {
         this.uriBuilder = uriBuilder;
@@ -130,6 +141,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         this.auditUserApi = auditUserApi;
         this.accountUserApi = accountUserApi;
         this.paymentApi = paymentApi;
+        this.subscriptionApi = subscriptionApi;
         this.clock = clock;
         this.context = context;
     }
@@ -138,6 +150,33 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         return null;
     }
 
+    public Response addBlockingState(final BlockingStateJson json,
+                                     final String id,
+                                     final BlockingStateType type,
+                                     final String requestedDate,
+                                     final List<String> pluginPropertiesString,
+                                     final String createdBy,
+                                     final String reason,
+                                     final String comment,
+                                     final HttpServletRequest request) throws SubscriptionApiException, EntitlementApiException, AccountApiException {
+
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID blockableId = UUID.fromString(id);
+
+        final boolean isBlockBilling = (json.isBlockBilling() != null && json.isBlockBilling());
+        final boolean isBlockEntitlement = (json.isBlockEntitlement() != null && json.isBlockEntitlement());
+        final boolean isBlockChange = (json.isBlockChange() != null && json.isBlockChange());
+
+        final LocalDate resolvedRequestedDate = toLocalDate(requestedDate);
+        final BlockingState input = new DefaultBlockingState(blockableId, type, json.getStateName(), json.getService(), isBlockChange, isBlockEntitlement, isBlockBilling, null);
+        subscriptionApi.addBlockingState(input, resolvedRequestedDate, pluginProperties, callContext);
+        return Response.status(Status.OK).build();
+    }
+
+
+
+
     protected Response getTags(final UUID accountId, final UUID taggedObjectId, final AuditMode auditMode, final boolean includeDeleted, final TenantContext context) throws TagDefinitionApiException {
         final List<Tag> tags = tagUserApi.getTagsForObject(taggedObjectId, getObjectType(), includeDeleted, context);
         return createTagResponse(accountId, tags, auditMode, context);
@@ -298,6 +337,56 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         }
     }
 
+    protected PaymentTransaction lookupPendingTransaction(final Payment initialPayment, @Nullable final String transactionId, @Nullable final String transactionExternalKey, @Nullable final String transactionType) throws PaymentApiException {
+        final Collection<PaymentTransaction> pendingTransaction  =  Collections2.filter(initialPayment.getTransactions(), new Predicate<PaymentTransaction>() {
+            @Override
+            public boolean apply(final PaymentTransaction input) {
+                if (input.getTransactionStatus() != TransactionStatus.PENDING) {
+                    return false;
+                }
+                if (transactionId != null && !transactionId.equals(input.getId().toString())) {
+                    return false;
+                }
+                if (transactionExternalKey != null && !transactionExternalKey.equals(input.getExternalKey())) {
+                    return false;
+                }
+                if (transactionType != null && !transactionType.equals(input.getTransactionType().name())) {
+                    return false;
+                }
+                //
+                // If we were given a transactionId or a transactionExternalKey or a transactionType we checked there was a match;
+                // In the worst case, if we were given nothing, we return the PENDING transaction for that payment
+                //
+                return true;
+            }
+        });
+        switch (pendingTransaction.size()) {
+            // Nothing: invalid input...
+            case 0:
+                final String parameterType;
+                final String parameterValue;
+                if (transactionId != null) {
+                    parameterType = "transactionId";
+                    parameterValue = transactionId;
+                } else if (transactionExternalKey != null) {
+                    parameterType = "transactionExternalKey";
+                    parameterValue = transactionExternalKey;
+                } else if (transactionType != null) {
+                    parameterType = "transactionType";
+                    parameterValue = transactionType;
+                } else {
+                    parameterType = "paymentId";
+                    parameterValue = initialPayment.getId().toString();
+                }
+                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterType, parameterValue);
+            case 1:
+                return pendingTransaction.iterator().next();
+            default:
+                throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, String.format("Illegal payment state: Found multiple PENDING payment transactions for paymentId='%s'", initialPayment.getId()));
+
+        }
+    }
+
     protected LocalDate toLocalDateDefaultToday(final UUID accountId, @Nullable final String inputDate, final TenantContext context) throws AccountApiException {
         final Account account = accountId != null ? accountUserApi.getAccountById(accountId, context) : null;
         return toLocalDateDefaultToday(account, inputDate, context);
@@ -305,11 +394,11 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
 
     protected LocalDate toLocalDateDefaultToday(final Account account, @Nullable final String inputDate, final TenantContext context) {
         // TODO Switch to cached normalized timezone when available
-        return MoreObjects.firstNonNull(toLocalDate(inputDate, context), clock.getToday(account.getTimeZone()));
+        return MoreObjects.firstNonNull(toLocalDate(inputDate), clock.getToday(account.getTimeZone()));
     }
 
     // API for subscription and invoice generation: keep null, the lower layers will default to now()
-    protected LocalDate toLocalDate(@Nullable final String inputDate, final TenantContext context) {
+    protected LocalDate toLocalDate(@Nullable final String inputDate) {
         return inputDate == null ? null : LocalDate.parse(inputDate, LOCAL_DATE_FORMATTER);
     }
 
@@ -367,9 +456,16 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         final PluginProperty invoiceProperty = new PluginProperty("IPCD_INVOICE_ID" /* InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID (contract with plugin)  */,
                                                                   invoiceId.toString(), false);
         properties.add(invoiceProperty);
-
-        return paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, null, amountToPay, account.getCurrency(), paymentExternalKey, transactionExternalKey,
-                                                           properties, createInvoicePaymentControlPluginApiPaymentOptions(externalPayment), callContext);
+        try {
+            return paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, null, amountToPay, account.getCurrency(), paymentExternalKey, transactionExternalKey,
+                                                               properties, createInvoicePaymentControlPluginApiPaymentOptions(externalPayment), callContext);
+        } catch (final PaymentApiException e) {
+            if (e.getCode() == ErrorCode.PAYMENT_PLUGIN_EXCEPTION.getCode() &&
+                e.getMessage().contains("Aborted Payment for invoice")) {
+                return null;
+            }
+            throw e;
+        }
     }
 
     protected PaymentOptions createInvoicePaymentControlPluginApiPaymentOptions(final boolean isExternalPayment) {
@@ -413,7 +509,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         final InvoicePayment invoicePayment = Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
             @Override
             public boolean apply(final InvoicePayment input) {
-                return input.getPaymentId().equals(payment.getId()) && input.getType() == InvoicePaymentType.ATTEMPT;
+                return input.isSuccess() && input.getPaymentId().equals(payment.getId()) && input.getType() == InvoicePaymentType.ATTEMPT;
             }
         }).orNull();
         return invoicePayment != null ? invoicePayment.getInvoiceId() : null;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/NodesInfoResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/NodesInfoResource.java
index a699485..5376992 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/NodesInfoResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/NodesInfoResource.java
@@ -87,7 +87,7 @@ public class NodesInfoResource extends JaxRsResourceBase {
                              final KillbillNodesApi killbillInfoApi,
                              final Clock clock,
                              final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.killbillInfoApi = killbillInfoApi;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java
index bfe8b11..e71dd16 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java
@@ -73,7 +73,7 @@ public class OverdueResource extends JaxRsResourceBase {
                            final OverdueApi overdueApi,
                            final Clock clock,
                            final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.overdueApi = overdueApi;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
index 07fdd5e..dfcb9ba 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
@@ -89,7 +89,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
                                  final PaymentApi paymentApi,
                                  final Clock clock,
                                  final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
     }
 
     @TimedResource(name = "getPaymentMethod")
@@ -179,7 +179,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
                                                                 account = accountUserApi.getAccountById(paymentMethod.getAccountId(), tenantContext);
                                                                 accounts.put(paymentMethod.getAccountId(), account);
                                                             } catch (final AccountApiException e) {
-                                                                log.warn("Unable to retrieve account", e);
+                                                                log.warn("Error retrieving accountId='{}'", paymentMethod.getAccountId(), e);
                                                                 return null;
                                                             }
                                                         }
@@ -238,7 +238,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
                                                                 account = accountUserApi.getAccountById(paymentMethod.getAccountId(), tenantContext);
                                                                 accounts.put(paymentMethod.getAccountId(), account);
                                                             } catch (final AccountApiException e) {
-                                                                log.warn("Unable to retrieve account", e);
+                                                                log.warn("Error retrieving accountId='{}'", paymentMethod.getAccountId(), e);
                                                                 return null;
                                                             }
                                                         }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
index 57dd579..0978be1 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
@@ -19,7 +19,6 @@ package org.killbill.billing.jaxrs.resources;
 
 import java.math.BigDecimal;
 import java.net.URI;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -73,9 +72,7 @@ import org.killbill.commons.metrics.MetricTag;
 import org.killbill.commons.metrics.TimedResource;
 
 import com.google.common.base.Function;
-import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
-import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableMap;
 import com.wordnik.swagger.annotations.Api;
 import com.wordnik.swagger.annotations.ApiOperation;
@@ -266,6 +263,11 @@ public class PaymentResource extends ComboPaymentResource {
         return completeTransactionInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
+
+
+
+
+
     private Response completeTransactionInternal(final PaymentTransactionJson json,
                                                  @Nullable final String paymentIdStr,
                                                  final List<String> paymentControlPluginNames,
@@ -275,6 +277,7 @@ public class PaymentResource extends ComboPaymentResource {
                                                  final String comment,
                                                  final UriInfo uriInfo,
                                                  final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
         final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json == null ? null : json.getPaymentExternalKey(), pluginProperties, callContext);
@@ -283,92 +286,38 @@ public class PaymentResource extends ComboPaymentResource {
         final BigDecimal amount = json == null ? null : json.getAmount();
         final Currency currency = json == null || json.getCurrency() == null ? null : Currency.valueOf(json.getCurrency());
 
-        final TransactionType transactionType;
-        final String transactionExternalKey;
-        if (json != null && json.getTransactionId() != null) {
-            final Collection<PaymentTransaction> paymentTransactionCandidates = Collections2.<PaymentTransaction>filter(initialPayment.getTransactions(),
-                                                                                                                        new Predicate<PaymentTransaction>() {
-                                                                                                                            @Override
-                                                                                                                            public boolean apply(final PaymentTransaction input) {
-                                                                                                                                return input.getId().toString().equals(json.getTransactionId());
-                                                                                                                            }
-                                                                                                                        });
-            if (paymentTransactionCandidates.size() == 1) {
-                final PaymentTransaction paymentTransaction = paymentTransactionCandidates.iterator().next();
-                transactionType = paymentTransaction.getTransactionType();
-                transactionExternalKey = paymentTransaction.getExternalKey();
-            } else {
-                return Response.status(Status.NOT_FOUND).build();
-            }
-        } else if (json != null && json.getTransactionExternalKey() != null && json.getTransactionType() != null) {
-            transactionType = TransactionType.valueOf(json.getTransactionType());
-            transactionExternalKey = json.getTransactionExternalKey();
-        } else if (json != null && json.getTransactionExternalKey() != null) {
-            final Collection<PaymentTransaction> paymentTransactionCandidates = Collections2.<PaymentTransaction>filter(initialPayment.getTransactions(),
-                                                                                                                        new Predicate<PaymentTransaction>() {
-                                                                                                                            @Override
-                                                                                                                            public boolean apply(final PaymentTransaction input) {
-                                                                                                                                return input.getExternalKey().equals(json.getTransactionExternalKey());
-                                                                                                                            }
-                                                                                                                        });
-            if (paymentTransactionCandidates.size() == 1) {
-                transactionType = paymentTransactionCandidates.iterator().next().getTransactionType();
-                transactionExternalKey = json.getTransactionExternalKey();
-            } else {
-                // Note: we could bit a bit smarter but keep the logic in the payment system
-                verifyNonNullOrEmpty(null, "PaymentTransactionJson transactionType needs to be set");
-                // Never reached
-                return Response.status(Status.PRECONDITION_FAILED).build();
-            }
-        } else if (json != null && json.getTransactionType() != null) {
-            final Collection<PaymentTransaction> paymentTransactionCandidates = Collections2.<PaymentTransaction>filter(initialPayment.getTransactions(),
-                                                                                                                        new Predicate<PaymentTransaction>() {
-                                                                                                                            @Override
-                                                                                                                            public boolean apply(final PaymentTransaction input) {
-                                                                                                                                return input.getTransactionType().toString().equals(json.getTransactionType());
-                                                                                                                            }
-                                                                                                                        });
-            if (paymentTransactionCandidates.size() == 1) {
-                transactionType = TransactionType.valueOf(json.getTransactionType());
-                transactionExternalKey = paymentTransactionCandidates.iterator().next().getExternalKey();
-            } else {
-                verifyNonNullOrEmpty(null, "PaymentTransactionJson externalKey needs to be set");
-                // Never reached
-                return Response.status(Status.PRECONDITION_FAILED).build();
-            }
-        } else if (initialPayment.getTransactions().size() == 1) {
-            final PaymentTransaction paymentTransaction = initialPayment.getTransactions().get(0);
-            transactionType = paymentTransaction.getTransactionType();
-            transactionExternalKey = paymentTransaction.getExternalKey();
-        } else {
-            verifyNonNullOrEmpty(null, "PaymentTransactionJson transactionType and externalKey need to be set");
-            // Never reached
-            return Response.status(Status.PRECONDITION_FAILED).build();
-        }
+            final PaymentTransaction pendingTransaction = lookupPendingTransaction(initialPayment,
+                                                                                   json != null ? json.getTransactionId() : null,
+                                                                                   json != null ? json.getTransactionExternalKey() : null,
+                                                                                   json != null ? json.getTransactionType() : null);
 
-        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
-        switch (transactionType) {
+            final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+            switch (pendingTransaction.getTransactionType()) {
             case AUTHORIZE:
                 paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
-                                                                 initialPayment.getExternalKey(), transactionExternalKey,
+                                                                 initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
                                                                  pluginProperties, paymentOptions, callContext);
                 break;
+            case CAPTURE:
+                paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, pendingTransaction.getExternalKey(),
+                                                           pluginProperties, paymentOptions, callContext);
+                break;
             case PURCHASE:
                 paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
-                                                            initialPayment.getExternalKey(), transactionExternalKey,
+                                                            initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
                                                             pluginProperties, paymentOptions, callContext);
                 break;
             case CREDIT:
                 paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
-                                                          initialPayment.getExternalKey(), transactionExternalKey,
+                                                          initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
                                                           pluginProperties, paymentOptions, callContext);
                 break;
             case REFUND:
                 paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency,
-                                                          transactionExternalKey, pluginProperties, paymentOptions, callContext);
+                                                          pendingTransaction.getExternalKey(), pluginProperties, paymentOptions, callContext);
                 break;
             default:
-                return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " cannot be completed").build();
+                return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + pendingTransaction.getTransactionType() + " cannot be completed").build();
         }
         return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", initialPayment.getId());
     }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginInfoResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginInfoResource.java
index 8c6ce93..1a3f669 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginInfoResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginInfoResource.java
@@ -63,7 +63,7 @@ public class PluginInfoResource extends JaxRsResourceBase {
                               final PluginsInfoApi pluginsInfoApi,
                               final Clock clock,
                               final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.pluginsInfoApi = pluginsInfoApi;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
index 6be2cdb..593dd18 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
@@ -93,7 +93,7 @@ public class PluginResource extends JaxRsResourceBase {
                           final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.osgiServlet = osgiServlet;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
index ac8a283..234af38 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
@@ -80,7 +80,7 @@ public class SecurityResource extends JaxRsResourceBase {
                             final PaymentApi paymentApi,
                             final Clock clock,
                             final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.securityApi = securityApi;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
index d5662dd..a74d09b 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
@@ -52,6 +52,7 @@ import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.api.Entitlement;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
 import org.killbill.billing.entitlement.api.EntitlementApi;
@@ -68,6 +69,7 @@ import org.killbill.billing.events.NullInvoiceInternalEvent;
 import org.killbill.billing.events.PaymentErrorInternalEvent;
 import org.killbill.billing.events.PaymentInfoInternalEvent;
 import org.killbill.billing.events.PaymentPluginErrorInternalEvent;
+import org.killbill.billing.jaxrs.json.BlockingStateJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.PhasePriceOverrideJson;
 import org.killbill.billing.jaxrs.json.SubscriptionJson;
@@ -125,7 +127,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
                                 final PaymentApi paymentApi,
                                 final Clock clock,
                                 final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, subscriptionApi, clock, context);
         this.killbillHandler = killbillHandler;
         this.entitlementApi = entitlementApi;
         this.subscriptionApi = subscriptionApi;
@@ -193,8 +195,8 @@ public class SubscriptionResource extends JaxRsResourceBase {
                                                                        ProductCategory.valueOf(entitlement.getProductCategory()),
                                                                        BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), phaseType);
 
-                final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate, callContext) : toLocalDate(entitlementDate, callContext);
-                final LocalDate resolvedBillingDate = requestedDate != null ? toLocalDate(requestedDate, callContext) : toLocalDate(billingDate, callContext);
+                final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(entitlementDate);
+                final LocalDate resolvedBillingDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(billingDate);
                 final PlanSpecifier planSpec = new PlanSpecifier(entitlement.getProductName(),
                                                                  ProductCategory.valueOf(entitlement.getProductCategory()),
                                                                  BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList());
@@ -326,8 +328,8 @@ public class SubscriptionResource extends JaxRsResourceBase {
                     entitlementSpecifierList.add(specifier);
                 }
 
-                final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate, callContext) : toLocalDate(entitlementDate, callContext);
-                final LocalDate resolvedBillingDate = requestedDate != null ? toLocalDate(requestedDate, callContext) : toLocalDate(billingDate, callContext);
+                final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(entitlementDate);
+                final LocalDate resolvedBillingDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(billingDate);
                 return entitlementApi.createBaseEntitlementWithAddOns(account.getId(), baseEntitlement.getExternalKey(), entitlementSpecifierList,
                                                                       resolvedEntitlementDate, resolvedBillingDate, isMigrated, pluginProperties, callContext);
             }
@@ -405,7 +407,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
                 final UUID uuid = UUID.fromString(subscriptionId);
 
                 final Entitlement current = entitlementApi.getEntitlementForId(uuid, callContext);
-                final LocalDate inputLocalDate = toLocalDate(requestedDate, callContext);
+                final LocalDate inputLocalDate = toLocalDate(requestedDate);
                 final Entitlement newEntitlement;
 
                 final Account account = accountUserApi.getAccountById(current.getAccountId(), callContext);
@@ -446,6 +448,28 @@ public class SubscriptionResource extends JaxRsResourceBase {
         return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
     }
 
+
+    @TimedResource
+    @PUT
+    @Path("/{subscriptionId:" + UUID_PATTERN + "}/" + BLOCK)
+    @Consumes(APPLICATION_JSON)
+    @ApiOperation(value = "Block a subscription")
+    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid subscription id supplied"),
+                           @ApiResponse(code = 404, message = "Subscription not found")})
+    public Response addSubscriptionBlockingState(final BlockingStateJson json,
+                                                 @PathParam(ID_PARAM_NAME) final String id,
+                                                 @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+                                                 @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                                 @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                                 @HeaderParam(HDR_REASON) final String reason,
+                                                 @HeaderParam(HDR_COMMENT) final String comment,
+                                                 @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, EntitlementApiException, AccountApiException {
+
+        return addBlockingState(json, id, BlockingStateType.SUBSCRIPTION, requestedDate, pluginPropertiesString, createdBy, reason, comment, request);
+    }
+
+
+
     @TimedResource
     @DELETE
     @Path("/{subscriptionId:" + UUID_PATTERN + "}")
@@ -480,7 +504,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
                 final UUID uuid = UUID.fromString(subscriptionId);
 
                 final Entitlement current = entitlementApi.getEntitlementForId(uuid, ctx);
-                final LocalDate inputLocalDate = toLocalDate(requestedDate, callContext);
+                final LocalDate inputLocalDate = toLocalDate(requestedDate);
                 final Entitlement newEntitlement;
                 if (billingPolicyString == null && entitlementPolicyString == null) {
                     newEntitlement = current.cancelEntitlementWithDate(inputLocalDate, useRequestedDateForBilling, pluginProperties, ctx);
@@ -527,8 +551,8 @@ public class SubscriptionResource extends JaxRsResourceBase {
 
         @Override
         public void onSubscriptionBaseTransition(final EffectiveSubscriptionInternalEvent event) {
-            log.info(String.format("Got event SubscriptionBaseTransition token = %s, type = %s, remaining = %d ",
-                                   event.getUserToken(), event.getTransitionType(), event.getRemainingEventsForUserOperation()));
+
+            log.info("Got event SubscriptionBaseTransition token='{}', type='{}', remaining='{}'", event.getUserToken(), event.getTransitionType(), event.getRemainingEventsForUserOperation());
         }
 
         @Override
@@ -538,14 +562,14 @@ public class SubscriptionResource extends JaxRsResourceBase {
 
         @Override
         public void onEmptyInvoice(final NullInvoiceInternalEvent event) {
-            log.info(String.format("Got event EmptyInvoiceNotification token = %s ", event.getUserToken()));
+            log.info("Got event EmptyInvoiceNotification token='{}'", event.getUserToken());
             notifyForCompletion();
         }
 
         @Override
         public void onInvoiceCreation(final InvoiceCreationInternalEvent event) {
 
-            log.info(String.format("Got event InvoiceCreationNotification token = %s ", event.getUserToken()));
+            log.info("Got event InvoiceCreationNotification token='{}'", event.getUserToken());
             if (event.getAmountOwed().compareTo(BigDecimal.ZERO) <= 0) {
                 notifyForCompletion();
             }
@@ -553,19 +577,19 @@ public class SubscriptionResource extends JaxRsResourceBase {
 
         @Override
         public void onPaymentInfo(final PaymentInfoInternalEvent event) {
-            log.info(String.format("Got event PaymentInfo token = %s ", event.getUserToken()));
+            log.info("Got event PaymentInfo token='{}'", event.getUserToken());
             notifyForCompletion();
         }
 
         @Override
         public void onPaymentError(final PaymentErrorInternalEvent event) {
-            log.info(String.format("Got event PaymentError token = %s ", event.getUserToken()));
+            log.info("Got event PaymentError token='{}'", event.getUserToken());
             notifyForCompletion();
         }
 
         @Override
         public void onPaymentPluginError(final PaymentPluginErrorInternalEvent event) {
-            log.info(String.format("Got event PaymentPluginError token = %s ", event.getUserToken()));
+            log.info("Got event PaymentPluginError token='{}'", event.getUserToken());
             notifyForCompletion();
         }
     }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
index f5536f2..b4511a1 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
@@ -75,7 +75,7 @@ public class TagDefinitionResource extends JaxRsResourceBase {
                                  final PaymentApi paymentApi,
                                  final Clock clock,
                                  final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
     }
 
     @TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java
index 305aa45..5a5bb6a 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java
@@ -73,7 +73,7 @@ public class TagResource extends JaxRsResourceBase {
                        final PaymentApi paymentApi,
                        final Clock clock,
                        final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
     }
 
     @TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
index 8ff2b14..ff55211 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
@@ -81,7 +81,7 @@ public class TenantResource extends JaxRsResourceBase {
                           final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.tenantApi = tenantApi;
     }
 
@@ -183,6 +183,8 @@ public class TenantResource extends JaxRsResourceBase {
         return insertTenantKey(TenantKey.PLUGIN_CONFIG_, pluginName, pluginConfig, uriInfo, "getPluginConfiguration", createdBy, reason, comment, request);
     }
 
+
+
     @TimedResource
     @GET
     @Path("/" + UPLOAD_PLUGIN_CONFIG + "/{pluginName:" + ANYTHING_PATTERN + "}")
@@ -207,6 +209,45 @@ public class TenantResource extends JaxRsResourceBase {
         return deleteTenantKey(TenantKey.PLUGIN_CONFIG_, pluginName, createdBy, reason, comment, request);
     }
 
+    @TimedResource
+    @POST
+    @Path("/" + UPLOAD_PER_TENANT_CONFIG)
+    @Consumes(TEXT_PLAIN)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Add a per tenant configuration (system properties)")
+    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid tenantId supplied")})
+    public Response uploadPerTenantConfiguration(final String perTenantConfig,
+                                                 @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                                 @HeaderParam(HDR_REASON) final String reason,
+                                                 @HeaderParam(HDR_COMMENT) final String comment,
+                                                 @javax.ws.rs.core.Context final HttpServletRequest request,
+                                                 @javax.ws.rs.core.Context final UriInfo uriInfo) throws TenantApiException {
+        return insertTenantKey(TenantKey.PER_TENANT_CONFIG, null, perTenantConfig, uriInfo, "getPerTenantConfiguration", createdBy, reason, comment, request);
+    }
+
+
+    @TimedResource
+    @GET
+    @Path("/" + UPLOAD_PER_TENANT_CONFIG)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve a per tenant configuration (system properties)", response = TenantKeyJson.class)
+    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid tenantId supplied")})
+    public Response getPerTenantConfiguration(@javax.ws.rs.core.Context final HttpServletRequest request) throws TenantApiException {
+        return getTenantKey(TenantKey.PER_TENANT_CONFIG, null, request);
+    }
+
+    @TimedResource
+    @DELETE
+    @Path("/" + UPLOAD_PER_TENANT_CONFIG)
+    @ApiOperation(value = "Delete a per tenant configuration (system properties)")
+    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid tenantId supplied")})
+    public Response deletePerTenantConfiguration(@HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                              @HeaderParam(HDR_REASON) final String reason,
+                                              @HeaderParam(HDR_COMMENT) final String comment,
+                                              @javax.ws.rs.core.Context final HttpServletRequest request) throws TenantApiException {
+        return deleteTenantKey(TenantKey.PER_TENANT_CONFIG, null, createdBy, reason, comment, request);
+    }
+
 
     @TimedResource
     @POST
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
index ed9e462..a90bae3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
@@ -96,7 +96,7 @@ public class TestResource extends JaxRsResourceBase {
                         final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final RecordIdApi recordIdApi,
                         final PersistentBus persistentBus, final NotificationQueueService notificationQueueService, final PaymentApi paymentApi,
                         final Clock clock, final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.persistentBus = persistentBus;
         this.notificationQueueService = notificationQueueService;
         this.recordIdApi = recordIdApi;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
index 322731d..09ae361 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
@@ -58,7 +58,7 @@ import com.wordnik.swagger.annotations.ApiResponses;
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
 @Path(JaxrsResource.PAYMENT_TRANSACTIONS_PATH)
-@Api(value = JaxrsResource.PAYMENT_TRANSACTIONS, description = "Operations on payment transactions")
+@Api(value = JaxrsResource.PAYMENT_TRANSACTIONS_PATH, description = "Operations on payment transactions")
 public class TransactionResource extends JaxRsResourceBase {
 
     @Inject
@@ -70,7 +70,7 @@ public class TransactionResource extends JaxRsResourceBase {
                                final PaymentApi paymentApi,
                                final Clock clock,
                                final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
     }
 
     @TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java
index b55db89..feb9b52 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java
@@ -42,11 +42,14 @@ import org.killbill.billing.entitlement.api.EntitlementApi;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.jaxrs.json.RolledUpUsageJson;
 import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson;
+import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson.UnitUsageRecordJson;
+import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson.UsageRecordJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.usage.api.RolledUpUsage;
 import org.killbill.billing.usage.api.SubscriptionUsageRecord;
+import org.killbill.billing.usage.api.UsageApiException;
 import org.killbill.billing.usage.api.UsageUserApi;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldUserApi;
@@ -58,6 +61,7 @@ import org.killbill.commons.metrics.TimedResource;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.inject.Singleton;
 import com.wordnik.swagger.annotations.Api;
 import com.wordnik.swagger.annotations.ApiOperation;
@@ -85,7 +89,7 @@ public class UsageResource extends JaxRsResourceBase {
                          final EntitlementApi entitlementApi,
                          final Clock clock,
                          final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.usageUserApi = usageUserApi;
         this.entitlementApi = entitlementApi;
     }
@@ -101,12 +105,22 @@ public class UsageResource extends JaxRsResourceBase {
                                 @HeaderParam(HDR_REASON) final String reason,
                                 @HeaderParam(HDR_COMMENT) final String comment,
                                 @javax.ws.rs.core.Context final HttpServletRequest request,
-                                @javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException, AccountApiException {
+                                @javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException,
+                                                                                        AccountApiException,
+                                                                                        UsageApiException {
         verifyNonNullOrEmpty(json, "SubscriptionUsageRecordJson body should be specified");
         verifyNonNullOrEmpty(json.getSubscriptionId(), "SubscriptionUsageRecordJson subscriptionId needs to be set",
                              json.getUnitUsageRecords(), "SubscriptionUsageRecordJson unitUsageRecords needs to be set");
         Preconditions.checkArgument(!json.getUnitUsageRecords().isEmpty());
-
+        for (final UnitUsageRecordJson unitUsageRecordJson : json.getUnitUsageRecords()) {
+            verifyNonNullOrEmpty(unitUsageRecordJson.getUnitType(), "UnitUsageRecordJson unitType need to be set");
+            Preconditions.checkArgument(Iterables.size(unitUsageRecordJson.getUsageRecords()) > 0,
+                                        "UnitUsageRecordJson usageRecords must have at least one element.");
+            for (final UsageRecordJson usageRecordJson : unitUsageRecordJson.getUsageRecords()) {
+                verifyNonNull(usageRecordJson.getAmount(), "UsageRecordJson amount needs to be set");
+                verifyNonNull(usageRecordJson.getRecordDate(), "UsageRecordJson recordDate needs to be set");
+            }
+        }
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         // Verify subscription exists..
         final Entitlement entitlement = entitlementApi.getEntitlementForId(UUID.fromString(json.getSubscriptionId()), callContext);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/Context.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/Context.java
index b2a67c0..e4118d4 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/Context.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/Context.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -16,6 +18,8 @@
 
 package org.killbill.billing.jaxrs.util;
 
+import java.util.UUID;
+
 import javax.servlet.ServletRequest;
 
 import org.killbill.billing.jaxrs.resources.JaxrsResource;
@@ -24,8 +28,10 @@ import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.CallContextFactory;
 import org.killbill.billing.util.callcontext.CallOrigin;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.commons.request.Request;
 
 import com.google.common.base.Preconditions;
 import com.google.inject.Inject;
@@ -34,13 +40,15 @@ public class Context {
 
     private final CallOrigin origin;
     private final UserType userType;
-    final CallContextFactory contextFactory;
+    private final CallContextFactory contextFactory;
+    private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
-    public Context(final CallContextFactory factory) {
+    public Context(final CallContextFactory factory, final InternalCallContextFactory internalCallContextFactory) {
         this.origin = CallOrigin.EXTERNAL;
         this.userType = UserType.CUSTOMER;
         this.contextFactory = factory;
+        this.internalCallContextFactory = internalCallContextFactory;
     }
 
     public CallContext createContext(final String createdBy, final String reason, final String comment, final ServletRequest request)
@@ -48,21 +56,46 @@ public class Context {
         try {
             Preconditions.checkNotNull(createdBy, String.format("Header %s needs to be set", JaxrsResource.HDR_CREATED_BY));
             final Tenant tenant = getTenantFromRequest(request);
-            return contextFactory.createCallContext(tenant == null ? null : tenant.getId(), createdBy, origin, userType, reason,
-                                                    comment, UUIDs.randomUUID());
+            final CallContext callContext = contextFactory.createCallContext(tenant == null ? null : tenant.getId(), createdBy, origin, userType, reason,
+                                                                             comment, getOrCreateUserToken());
+
+            populateMDCContext(callContext);
+
+            return callContext;
         } catch (final NullPointerException e) {
             throw new IllegalArgumentException(e.getMessage());
         }
     }
 
     public TenantContext createContext(final ServletRequest request) {
+        final TenantContext tenantContext;
+
         final Tenant tenant = getTenantFromRequest(request);
         if (tenant == null) {
             // Multi-tenancy may not have been configured - default to "default" tenant (see InternalCallContextFactory)
-            return contextFactory.createTenantContext(null);
+            tenantContext = contextFactory.createTenantContext(null);
+        } else {
+            tenantContext = contextFactory.createTenantContext(tenant.getId());
+        }
+
+        populateMDCContext(tenantContext);
+
+        return tenantContext;
+    }
+
+    // Use REQUEST_ID_HEADER if this is provided and lloks like a UUID, if not allocate a random one.
+    private UUID getOrCreateUserToken() {
+        UUID userToken;
+        if (Request.getPerThreadRequestData().getRequestId() != null) {
+            try {
+                userToken = UUID.fromString(Request.getPerThreadRequestData().getRequestId());
+            } catch (final IllegalArgumentException ignored) {
+                userToken = UUIDs.randomUUID();
+            }
         } else {
-            return contextFactory.createTenantContext(tenant.getId());
+            userToken = UUIDs.randomUUID();
         }
+        return userToken;
     }
 
     private Tenant getTenantFromRequest(final ServletRequest request) {
@@ -74,4 +107,9 @@ public class Context {
             return (Tenant) tenantObject;
         }
     }
+
+    private void populateMDCContext(final TenantContext tenantContext) {
+        // InternalCallContextFactory will do it for us
+        internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(tenantContext);
+    }
 }
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModuleNoDB.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModuleNoDB.java
index 559d3d4..6fa4edb 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModuleNoDB.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModuleNoDB.java
@@ -25,6 +25,7 @@ import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.tenant.api.TenantInternalApi;
 import org.killbill.billing.util.glue.CacheModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.mockito.Mockito;
 
 public class TestJaxrsModuleNoDB extends TestJaxrsModule {
@@ -41,6 +42,7 @@ public class TestJaxrsModuleNoDB extends TestJaxrsModule {
         install(new MockNonEntityDaoModule(configSource));
         install(new MockAccountModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         bind(TenantInternalApi.class).toInstance(Mockito.mock(TenantInternalApi.class));
         bind(SecurityManager.class).toInstance(Mockito.mock(SecurityManager.class));
     }
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestCreditJson.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestCreditJson.java
index e698ec6..422a0f1 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestCreditJson.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestCreditJson.java
@@ -22,6 +22,7 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.Currency;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -34,12 +35,13 @@ public class TestCreditJson extends JaxrsTestSuiteNoDB {
     @Test(groups = "fast")
     public void testJson() throws Exception {
         final BigDecimal creditAmount = BigDecimal.TEN;
+        final Currency currency = Currency.AED;
         final String invoiceId = UUID.randomUUID().toString();
         final String invoiceNumber = UUID.randomUUID().toString();
         final LocalDate effectiveDate = clock.getUTCToday();
         final String accountId = UUID.randomUUID().toString();
         final List<AuditLogJson> auditLogs = createAuditLogsJson(clock.getUTCNow());
-        final CreditJson creditJson = new CreditJson(creditAmount, invoiceId, invoiceNumber, effectiveDate,
+        final CreditJson creditJson = new CreditJson(creditAmount, currency.name(), invoiceId, invoiceNumber, effectiveDate,
                                                      accountId, null, auditLogs);
         Assert.assertEquals(creditJson.getEffectiveDate(), effectiveDate);
         Assert.assertEquals(creditJson.getCreditAmount(), creditAmount);
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceItemJsonSimple.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
index ef5b359..b4b33f9 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
@@ -55,7 +55,7 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuiteNoDB {
         final List<AuditLogJson> auditLogs = createAuditLogsJson(clock.getUTCNow());
         final InvoiceItemJson invoiceItemJson = new InvoiceItemJson(invoiceItemId, invoiceId, linkedInvoiceItemId, accountId, childAccountId,
                                                                                       bundleId, subscriptionId, planName, phaseName, usageName, type, description,
-                                                                                      startDate, endDate, amount, currency, auditLogs);
+                                                                                      startDate, endDate, amount, currency.name(), auditLogs);
         Assert.assertEquals(invoiceItemJson.getInvoiceItemId(), invoiceItemId);
         Assert.assertEquals(invoiceItemJson.getInvoiceId(), invoiceId);
         Assert.assertEquals(invoiceItemJson.getLinkedInvoiceItemId(), linkedInvoiceItemId);
@@ -71,7 +71,7 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuiteNoDB {
         Assert.assertEquals(invoiceItemJson.getStartDate(), startDate);
         Assert.assertEquals(invoiceItemJson.getEndDate(), endDate);
         Assert.assertEquals(invoiceItemJson.getAmount(), amount);
-        Assert.assertEquals(invoiceItemJson.getCurrency(), currency);
+        Assert.assertEquals(invoiceItemJson.getCurrency(), currency.name());
         Assert.assertEquals(invoiceItemJson.getAuditLogs(), auditLogs);
 
         final String asJson = mapper.writeValueAsString(invoiceItemJson);
@@ -112,6 +112,6 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuiteNoDB {
         Assert.assertEquals(invoiceItemJson.getStartDate(), invoiceItem.getStartDate());
         Assert.assertEquals(invoiceItemJson.getEndDate(), invoiceItem.getEndDate());
         Assert.assertEquals(invoiceItemJson.getAmount(), invoiceItem.getAmount());
-        Assert.assertEquals(invoiceItemJson.getCurrency(), invoiceItem.getCurrency());
+        Assert.assertEquals(invoiceItemJson.getCurrency(), invoiceItem.getCurrency().name());
     }
 }
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
index 0fb96af..6b9a29e 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
@@ -112,10 +112,11 @@ public class TestInvoiceJsonWithBundleKeys extends JaxrsTestSuiteNoDB {
 
     private CreditJson createCreditJson() {
         final BigDecimal creditAmount = BigDecimal.TEN;
+        final Currency currency = Currency.USD;
         final String invoiceId = UUID.randomUUID().toString();
         final String invoiceNumber = UUID.randomUUID().toString();
         final LocalDate effectiveDate = clock.getUTCToday();
         final String accountId = UUID.randomUUID().toString();
-        return new CreditJson(creditAmount, invoiceId, invoiceNumber, effectiveDate,  accountId, null, null);
+        return new CreditJson(creditAmount, currency.name(), invoiceId, invoiceNumber, effectiveDate,  accountId, null, null);
     }
 }
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestSubscriptionUsageRecordJson.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestSubscriptionUsageRecordJson.java
new file mode 100644
index 0000000..95c232b
--- /dev/null
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestSubscriptionUsageRecordJson.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.jaxrs.json;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.jaxrs.JaxrsTestSuiteNoDB;
+import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson.UnitUsageRecordJson;
+import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson.UsageRecordJson;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestSubscriptionUsageRecordJson extends JaxrsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testJson() throws Exception {
+        final LocalDate localDate = new LocalDate();
+        final String subscriptionId = UUID.randomUUID().toString();
+        final String trackingId = UUID.randomUUID().toString();
+        final List<UnitUsageRecordJson> unitUsageRecords = new ArrayList<UnitUsageRecordJson>();
+        final List<UsageRecordJson> usageRecords = new ArrayList<UsageRecordJson>();
+        final UsageRecordJson usageRecordJson = new UsageRecordJson(localDate, 5L);
+        usageRecords.add(usageRecordJson);
+        final UnitUsageRecordJson unitUsageRecordJson = new UnitUsageRecordJson("foo", usageRecords);
+        unitUsageRecords.add(unitUsageRecordJson);
+
+        final SubscriptionUsageRecordJson subscriptionUsageRecordJson = new SubscriptionUsageRecordJson(subscriptionId, trackingId, unitUsageRecords);
+        Assert.assertEquals(subscriptionUsageRecordJson.getSubscriptionId(), subscriptionId);
+        Assert.assertEquals(subscriptionUsageRecordJson.getTrackingId(), trackingId);
+        Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().size(), 1);
+        Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().get(0).getUnitType(), "foo");
+        Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().get(0).getUsageRecords().size(), 1);
+        Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().get(0).getUsageRecords().get(0).getAmount(), new Long(5L));
+        Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().get(0).getUsageRecords().get(0).getRecordDate(), localDate);
+
+        final SubscriptionUsageRecord subscriptionUsageRecord = subscriptionUsageRecordJson.toSubscriptionUsageRecord();
+        Assert.assertEquals(subscriptionUsageRecord.getSubscriptionId().toString(), subscriptionId);
+        Assert.assertEquals(subscriptionUsageRecord.getTrackingId(), trackingId);
+        Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().size(), 1);
+        Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().get(0).getUnitType(), "foo");
+        Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().get(0).getDailyAmount().size(), 1);
+        Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().get(0).getDailyAmount().get(0).getAmount(), new Long(5L));
+        Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().get(0).getDailyAmount().get(0).getDate(), localDate);
+    }
+}
\ No newline at end of file
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/resources/TestJaxRsResourceBase.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/resources/TestJaxRsResourceBase.java
index ecf1d44..85195c0 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/resources/TestJaxRsResourceBase.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/resources/TestJaxRsResourceBase.java
@@ -64,7 +64,7 @@ public class TestJaxRsResourceBase extends JaxrsTestSuiteNoDB {
         private static final class JaxRsResourceBaseTest extends JaxRsResourceBase {
 
         public JaxRsResourceBaseTest() {
-            super(null, null, null, null, null, null, null, null);
+            super(null, null, null, null, null, null, null, null, null);
         }
     }
 }
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/TestDateConversion.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/TestDateConversion.java
index a754a75..f70b3e4 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/TestDateConversion.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/TestDateConversion.java
@@ -38,7 +38,7 @@ import org.killbill.billing.util.UUIDs;
 public class TestDateConversion extends JaxRsResourceBase {
 
     public TestDateConversion() throws AccountApiException {
-        super(null, null, null, null, Mockito.mock(AccountUserApi.class), null, new ClockMock(), null);
+        super(null, null, null, null, Mockito.mock(AccountUserApi.class), null, null, new ClockMock(), null);
     }
 
     public UUID setupAccount(DateTimeZone accountTimeZone) throws AccountApiException {
@@ -62,7 +62,7 @@ public class TestDateConversion extends JaxRsResourceBase {
     public void testNullConversion() throws AccountApiException {
         final String input = null;
 
-        final LocalDate result = toLocalDate(input, null);
+        final LocalDate result = toLocalDate(input);
         Assert.assertNull(result);
 
         final UUID accountId = setupAccount(DateTimeZone.forOffsetHours(-8));
@@ -76,7 +76,7 @@ public class TestDateConversion extends JaxRsResourceBase {
     public void testLocalDateConversion() throws AccountApiException {
         final UUID accountId = setupAccount(DateTimeZone.forOffsetHours(-8));
         final String input = "2013-08-25";
-        final LocalDate result = toLocalDate(input, null);
+        final LocalDate result = toLocalDate(input);
         Assert.assertTrue(result.compareTo(new LocalDate(2013, 8, 25)) == 0);
     }
 
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BillCycleDayCalculator.java
index 4cee36a..dd1a0de 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BillCycleDayCalculator.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BillCycleDayCalculator.java
@@ -145,9 +145,8 @@ public class BillCycleDayCalculator {
 
         final DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate(), initialPhaseType);
         final int bcdLocal = ClockUtil.toDateTime(date, account.getTimeZone()).getDayOfMonth();
-        log.info("Calculated BCD: subscription id {}, subscription start {}, timezone {}, bcd {}",
+        log.info("Calculated BCD: subscriptionId='{}', subscriptionStartDate='{}', accountTimeZone='{}', bcd='{}'",
                  subscription.getId(), date.toDateTimeISO(), account.getTimeZone(), bcdLocal);
-
         return bcdLocal;
     }
 }
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
index b43bd54..37989fa 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
@@ -37,6 +37,9 @@ import org.joda.time.DateTimeZone;
 import org.joda.time.Days;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.Catalog;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
@@ -59,6 +62,7 @@ public class BlockingCalculator {
     private static final AtomicLong globaltotalOrder = new AtomicLong();
 
     private final BlockingInternalApi blockingApi;
+    private final CatalogService catalogService;
 
     protected static class DisabledDuration {
 
@@ -84,8 +88,9 @@ public class BlockingCalculator {
     }
 
     @Inject
-    public BlockingCalculator(final BlockingInternalApi blockingApi) {
+    public BlockingCalculator(final BlockingInternalApi blockingApi, final CatalogService catalogService) {
         this.blockingApi = blockingApi;
+        this.catalogService = catalogService;
     }
 
     /**
@@ -93,7 +98,7 @@ public class BlockingCalculator {
      *
      * @param billingEvents the original list of billing events to update (without overdue events)
      */
-    public void insertBlockingEvents(final SortedSet<BillingEvent> billingEvents, final Set<UUID> skippedSubscriptions, final InternalTenantContext context) {
+    public void insertBlockingEvents(final SortedSet<BillingEvent> billingEvents, final Set<UUID> skippedSubscriptions, final InternalTenantContext context) throws CatalogApiException {
         if (billingEvents.size() <= 0) {
             return;
         }
@@ -129,7 +134,7 @@ public class BlockingCalculator {
                 final List<BlockingState> aggregateSubscriptionBlockingEvents = getAggregateBlockingEventsPerSubscription(subscriptionBlockingEvents, bundleBlockingEvents, accountBlockingEvents);
                 final List<DisabledDuration> accountBlockingDurations = createBlockingDurations(aggregateSubscriptionBlockingEvents);
 
-                billingEventsToAdd.addAll(createNewEvents(accountBlockingDurations, billingEvents, subscription));
+                billingEventsToAdd.addAll(createNewEvents(accountBlockingDurations, billingEvents, subscription, context));
                 billingEventsToRemove.addAll(eventsToRemove(accountBlockingDurations, billingEvents, subscription));
             }
         }
@@ -187,8 +192,11 @@ public class BlockingCalculator {
         return result;
     }
 
-    protected SortedSet<BillingEvent> createNewEvents(final List<DisabledDuration> disabledDuration, final SortedSet<BillingEvent> billingEvents, final SubscriptionBase subscription) {
+    protected SortedSet<BillingEvent> createNewEvents(final List<DisabledDuration> disabledDuration, final SortedSet<BillingEvent> billingEvents, final SubscriptionBase subscription, final InternalTenantContext context) throws CatalogApiException {
+
         final SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+        final Catalog catalog = catalogService.getFullCatalog(context);
+
         for (final DisabledDuration duration : disabledDuration) {
             // The first one before the blocked duration
             final BillingEvent precedingInitialEvent = precedingBillingEventForSubscription(duration.getStart(), billingEvents, subscription);
@@ -196,12 +204,12 @@ public class BlockingCalculator {
             final BillingEvent precedingFinalEvent = precedingBillingEventForSubscription(duration.getEnd(), billingEvents, subscription);
 
             if (precedingInitialEvent != null) { // there is a preceding billing event
-                result.add(createNewDisableEvent(duration.getStart(), precedingInitialEvent));
+                result.add(createNewDisableEvent(duration.getStart(), precedingInitialEvent, catalog, context));
                 if (duration.getEnd() != null) { // no second event in the pair means they are still disabled (no re-enable)
-                    result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent));
+                    result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent, catalog, context));
                 }
             } else if (precedingFinalEvent != null) { // can happen - e.g. phase event
-                result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent));
+                result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent, catalog, context));
             }
             // N.B. if there's no precedingInitial and no precedingFinal then there's nothing to do
         }
@@ -241,7 +249,7 @@ public class BlockingCalculator {
         return result;
     }
 
-    protected BillingEvent createNewDisableEvent(final DateTime odEventTime, final BillingEvent previousEvent) {
+    protected BillingEvent createNewDisableEvent(final DateTime odEventTime, final BillingEvent previousEvent, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
         final int billCycleDay = previousEvent.getBillCycleDayLocal();
         final SubscriptionBase subscription = previousEvent.getSubscription();
         final DateTime effectiveDate = odEventTime;
@@ -251,7 +259,6 @@ public class BlockingCalculator {
         // Make sure to set the fixed price to null and the billing period to NO_BILLING_PERIOD,
         // which makes invoice disregard this event
         final BigDecimal fixedPrice = null;
-        final BigDecimal recurringPrice = null;
         final BillingPeriod billingPeriod = BillingPeriod.NO_BILLING_PERIOD;
 
         final Currency currency = previousEvent.getCurrency();
@@ -260,21 +267,20 @@ public class BlockingCalculator {
         final Long totalOrdering = globaltotalOrder.getAndIncrement();
         final DateTimeZone tz = previousEvent.getTimeZone();
 
-        return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase,
-                                       fixedPrice, recurringPrice, currency,
+        return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase, fixedPrice,
+                                       currency,
                                        billingPeriod, billCycleDay,
-                                       description, totalOrdering, type, tz);
+                                       description, totalOrdering, type, tz, catalog, true);
     }
 
-    protected BillingEvent createNewReenableEvent(final DateTime odEventTime, final BillingEvent previousEvent) {
+    protected BillingEvent createNewReenableEvent(final DateTime odEventTime, final BillingEvent previousEvent, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
         // All fields are populated with the event state from before the blocking period, for invoice to resume invoicing
         final int billCycleDay = previousEvent.getBillCycleDayLocal();
         final SubscriptionBase subscription = previousEvent.getSubscription();
         final DateTime effectiveDate = odEventTime;
         final PlanPhase planPhase = previousEvent.getPlanPhase();
-        final Plan plan = previousEvent.getPlan();
         final BigDecimal fixedPrice = previousEvent.getFixedPrice();
-        final BigDecimal recurringPrice = previousEvent.getRecurringPrice();
+        final Plan plan = previousEvent.getPlan();
         final Currency currency = previousEvent.getCurrency();
         final String description = "";
         final BillingPeriod billingPeriod = previousEvent.getBillingPeriod();
@@ -282,10 +288,10 @@ public class BlockingCalculator {
         final Long totalOrdering = globaltotalOrder.getAndIncrement();
         final DateTimeZone tz = previousEvent.getTimeZone();
 
-        return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase,
-                                       fixedPrice, recurringPrice, currency,
+        return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase, fixedPrice,
+                                       currency,
                                        billingPeriod, billCycleDay,
-                                       description, totalOrdering, type, tz);
+                                       description, totalOrdering, type, tz, catalog, false);
     }
 
     protected Hashtable<UUID, List<SubscriptionBase>> createBundleSubscriptionMap(final SortedSet<BillingEvent> billingEvents) {
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
index cbf1076..b387188 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
@@ -47,7 +47,6 @@ public class DefaultBillingEvent implements BillingEvent {
     private final PlanPhase planPhase;
     private final Plan plan;
     private final BigDecimal fixedPrice;
-    private final BigDecimal recurringPrice;
     private final Currency currency;
     private final String description;
     private final BillingPeriod billingPeriod;
@@ -57,8 +56,14 @@ public class DefaultBillingEvent implements BillingEvent {
 
     private final List<Usage> usages;
 
+    private final Catalog catalog;
+    private final boolean isDisableEvent;
+    private final PlanPhase nextPlanPhase;
+
     public DefaultBillingEvent(final ImmutableAccountData account, final EffectiveSubscriptionInternalEvent transition, final SubscriptionBase subscription, final int billCycleDayLocal, final Currency currency, final Catalog catalog) throws CatalogApiException {
 
+        this.catalog = catalog;
+
         final boolean isActive = transition.getTransitionType() != SubscriptionBaseTransitionType.CANCEL;
 
         this.billCycleDayLocal = billCycleDayLocal;
@@ -71,34 +76,35 @@ public class DefaultBillingEvent implements BillingEvent {
         this.plan = (planName != null) ? catalog.findPlan(planName, transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
 
         final String nextPhaseName = transition.getNextPhase();
-        final PlanPhase nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
+        this.nextPlanPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
 
         final String prevPhaseName = transition.getPreviousPhase();
-        final PlanPhase prevPhase = (prevPhaseName != null) ? catalog.findPhase(prevPhaseName, transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
-
-        this.fixedPrice = getFixedPrice(nextPhase, currency);
-        this.recurringPrice = getRecurringPrice(nextPhase, currency);
+        final PlanPhase prevPlanPhase = (prevPhaseName != null) ? catalog.findPhase(prevPhaseName, transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
 
+        this.fixedPrice = getFixedPrice(nextPlanPhase, currency);
         this.currency = currency;
         this.description = transition.getTransitionType().toString();
-        this.billingPeriod = getRecurringBillingPeriod(isActive ? nextPhase : prevPhase);
+        this.billingPeriod = getRecurringBillingPeriod(isActive ? nextPlanPhase : prevPlanPhase);
         this.type = transition.getTransitionType();
         this.totalOrdering = transition.getTotalOrdering();
         this.timeZone = account.getTimeZone();
         this.usages = initializeUsage(isActive);
+        this.isDisableEvent = false;
     }
 
     public DefaultBillingEvent(final SubscriptionBase subscription, final DateTime effectiveDate, final boolean isActive,
-                               final Plan plan, final PlanPhase planPhase,
-                               final BigDecimal fixedPrice, final BigDecimal recurringPrice, final Currency currency,
+                               final Plan plan, final PlanPhase planPhase, final BigDecimal fixedPrice,
+                               final Currency currency,
                                final BillingPeriod billingPeriod, final int billCycleDayLocal,
-                               final String description, final long totalOrdering, final SubscriptionBaseTransitionType type, final DateTimeZone timeZone) {
+                               final String description, final long totalOrdering, final SubscriptionBaseTransitionType type, final DateTimeZone timeZone,
+                               final Catalog catalog,
+                               final boolean isDisableEvent) {
+        this.catalog = catalog;
         this.subscription = subscription;
         this.effectiveDate = effectiveDate;
         this.plan = plan;
         this.planPhase = planPhase;
         this.fixedPrice = fixedPrice;
-        this.recurringPrice = recurringPrice;
         this.currency = currency;
         this.billingPeriod = billingPeriod;
         this.billCycleDayLocal = billCycleDayLocal;
@@ -107,7 +113,8 @@ public class DefaultBillingEvent implements BillingEvent {
         this.totalOrdering = totalOrdering;
         this.timeZone = timeZone;
         this.usages = initializeUsage(isActive);
-
+        this.isDisableEvent = isDisableEvent;
+        this.nextPlanPhase = isDisableEvent ? null : planPhase;
     }
 
     @Override
@@ -197,8 +204,12 @@ public class DefaultBillingEvent implements BillingEvent {
     }
 
     @Override
-    public BigDecimal getRecurringPrice() {
-        return recurringPrice;
+    public BigDecimal getRecurringPrice(final DateTime effectiveDate) throws CatalogApiException {
+        if (isDisableEvent || nextPlanPhase == null) {
+            return null;
+        }
+        final PlanPhase effectivePlanPhase = effectiveDate != null ? catalog.findPhase(nextPlanPhase.getName(), effectiveDate, subscription.getStartDate()) : nextPlanPhase;
+        return getRecurringPrice(effectivePlanPhase, currency);
     }
 
     @Override
@@ -261,13 +272,13 @@ public class DefaultBillingEvent implements BillingEvent {
         if (currency != that.currency) {
             return false;
         }
-        if (description != null ? !description.equals(that.description) : that.description != null) {
+        if (fixedPrice != null ? !fixedPrice.equals(that.fixedPrice) : that.fixedPrice != null) {
             return false;
         }
-        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+        if (description != null ? !description.equals(that.description) : that.description != null) {
             return false;
         }
-        if (fixedPrice != null ? !fixedPrice.equals(that.fixedPrice) : that.fixedPrice != null) {
+        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
             return false;
         }
         if (plan != null ? !plan.equals(that.plan) : that.plan != null) {
@@ -276,9 +287,6 @@ public class DefaultBillingEvent implements BillingEvent {
         if (planPhase != null ? !planPhase.equals(that.planPhase) : that.planPhase != null) {
             return false;
         }
-        if (recurringPrice != null ? !recurringPrice.equals(that.recurringPrice) : that.recurringPrice != null) {
-            return false;
-        }
         if (subscription != null ? !subscription.equals(that.subscription) : that.subscription != null) {
             return false;
         }
@@ -299,11 +307,10 @@ public class DefaultBillingEvent implements BillingEvent {
     public int hashCode() {
         int result = 31 * billCycleDayLocal;
         result = 31 * result + (subscription != null ? subscription.hashCode() : 0);
+        result = 31 * result + (fixedPrice != null ? fixedPrice.hashCode() : 0);
         result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
         result = 31 * result + (planPhase != null ? planPhase.hashCode() : 0);
         result = 31 * result + (plan != null ? plan.hashCode() : 0);
-        result = 31 * result + (fixedPrice != null ? fixedPrice.hashCode() : 0);
-        result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0);
         result = 31 * result + (currency != null ? currency.hashCode() : 0);
         result = 31 * result + (description != null ? description.hashCode() : 0);
         result = 31 * result + (billingPeriod != null ? billingPeriod.hashCode() : 0);
@@ -333,7 +340,7 @@ public class DefaultBillingEvent implements BillingEvent {
         if (!isActive) {
             return result;
         }
-        if (planPhase != null) {
+        if (planPhase != null && planPhase.getUsages() != null) {
             result = Lists.newArrayList();
             for (Usage usage : planPhase.getUsages()) {
                 result.add(usage);
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index ede4a6d..7ec0b85 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -111,7 +111,7 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
         }
 
         // Pretty-print the events, before and after the blocking calculator does its magic
-        final StringBuilder logStringBuilder = new StringBuilder("Computed billing events for accountId ").append(accountId);
+        final StringBuilder logStringBuilder = new StringBuilder("Computed billing events for accountId='").append(accountId).append("'");
         eventsToString(logStringBuilder, result, "\nBilling Events Raw");
         blockCalculator.insertBlockingEvents(result, skippedSubscriptions, context);
         eventsToString(logStringBuilder, result, "\nBilling Events After Blocking");
diff --git a/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModule.java b/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModule.java
index d2bb7aa..803853a 100644
--- a/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModule.java
+++ b/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModule.java
@@ -29,6 +29,7 @@ import org.killbill.billing.mock.glue.MockTenantModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.KillBillShiroAopModule;
 import org.killbill.billing.util.glue.KillBillShiroModule;
 import org.killbill.billing.util.glue.SecurityModule;
@@ -44,6 +45,7 @@ public class TestJunctionModule extends DefaultJunctionModule {
         super.configure();
 
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new CallContextModule(configSource));
         install(new MockTenantModule(configSource));
         // Needed because Entitlement depends on Security
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
index 5b4748b..df07cbc 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
@@ -247,9 +247,9 @@ public class TestBillingApi extends JunctionTestSuiteNoDB {
         }
 
         if (recurringPrice != null) {
-            Assert.assertEquals(recurringPrice.getPrice(Currency.USD), event.getRecurringPrice());
+            Assert.assertEquals(recurringPrice.getPrice(Currency.USD), event.getRecurringPrice(null));
         } else {
-            assertNull(event.getRecurringPrice());
+            assertNull(event.getRecurringPrice(null));
         }
 
         Assert.assertEquals(BCD, event.getBillCycleDayLocal());
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
index 8c26b09..ebb438e 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -33,11 +33,13 @@ import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.MockPlan;
-import org.killbill.billing.catalog.MockPlanPhase;
 import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.InternationalPrice;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.Recurring;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.dao.MockBlockingStateDao;
@@ -48,6 +50,7 @@ import org.killbill.billing.junction.plumbing.billing.BlockingCalculator.Disable
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
 import org.mockito.Mockito;
+import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -104,7 +107,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
     // S2 --B--[-------]--------------------------
     // S3 ------------------D---------------------
     @Test(groups = "fast")
-    public void testInsertBlockingEventsForBundle() {
+    public void testInsertBlockingEventsForBundle() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
 
         final BillingEvent A = createRealEvent(now.minusDays(1).minusHours(1), subscription1);
@@ -319,7 +322,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
     // Open ended duration with a previous event
     // --X--[----------------------------------
     @Test(groups = "fast")
-    public void testCreateNewEventsOpenPrev() {
+    public void testCreateNewEventsOpenPrev() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
         final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
@@ -327,12 +330,12 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         disabledDuration.add(new DisabledDuration(now, null));
         billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
 
-        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1);
+        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
 
         assertEquals(results.size(), 1);
         assertEquals(results.first().getEffectiveDate(), now);
         assertNull(results.first().getFixedPrice());
-        assertNull(results.first().getRecurringPrice());
+        assertNull(results.first().getRecurringPrice(null));
         assertEquals(results.first().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
         assertEquals(results.first().getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
     }
@@ -340,7 +343,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
     // Open with previous and following events
     // --X--[----Y-----------------------------
     @Test(groups = "fast")
-    public void testCreateNewEventsOpenPrevFollow() {
+    public void testCreateNewEventsOpenPrevFollow() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
         final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
@@ -349,12 +352,12 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
         billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
 
-        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1);
+        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
 
         assertEquals(results.size(), 1);
         assertEquals(results.first().getEffectiveDate(), now);
         assertNull(results.first().getFixedPrice());
-        assertNull(results.first().getRecurringPrice());
+        assertNull(results.first().getRecurringPrice(null));
         assertEquals(results.first().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
         assertEquals(results.first().getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
     }
@@ -362,7 +365,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
     // Open with no previous event (only following)
     // -----[----X-----------------------------
     @Test(groups = "fast")
-    public void testCreateNewEventsOpenFollow() {
+    public void testCreateNewEventsOpenFollow() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
         final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
@@ -370,7 +373,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         disabledDuration.add(new DisabledDuration(now, null));
         billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
 
-        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1);
+        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
 
         assertEquals(results.size(), 0);
     }
@@ -378,7 +381,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
     // Closed duration with a single previous event
     // --X--[------------]---------------------
     @Test(groups = "fast")
-    public void testCreateNewEventsClosedPrev() {
+    public void testCreateNewEventsClosedPrev() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
         final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
@@ -386,23 +389,24 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
         billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
 
-        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1);
+        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+
 
         assertEquals(results.size(), 2);
         assertEquals(results.first().getEffectiveDate(), now);
         assertNull(results.first().getFixedPrice());
-        assertNull(results.first().getRecurringPrice());
+        assertNull(results.first().getRecurringPrice(null));
         assertEquals(results.first().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
         assertEquals(results.first().getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
         assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
-        assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+        assertEquals(results.last().getRecurringPrice(null), billingEvents.first().getRecurringPrice(null));
         assertEquals(results.last().getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED);
     }
 
     // Closed duration with a previous event and in-between event
     // --X--[------Y-----]---------------------
     @Test(groups = "fast")
-    public void testCreateNewEventsClosedPrevBetw() {
+    public void testCreateNewEventsClosedPrevBetw() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
         final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
@@ -411,23 +415,23 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
         billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
 
-        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1);
+        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
 
         assertEquals(results.size(), 2);
         assertEquals(results.first().getEffectiveDate(), now);
         assertNull(results.first().getFixedPrice());
-        assertNull(results.first().getRecurringPrice());
+        assertNull(results.first().getRecurringPrice(null));
         assertEquals(results.first().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
         assertEquals(results.first().getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
         assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
-        assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+        assertEquals(results.last().getRecurringPrice(null), billingEvents.first().getRecurringPrice(null));
         assertEquals(results.last().getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED);
     }
 
     // Closed duration with a previous event and in-between event and following
     // --X--[------Y-----]-------Z-------------
     @Test(groups = "fast")
-    public void testCreateNewEventsClosedPrevBetwNext() {
+    public void testCreateNewEventsClosedPrevBetwNext() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
         final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
@@ -437,23 +441,23 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
         billingEvents.add(createRealEvent(now.plusDays(3), subscription1));
 
-        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1);
+        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
 
         assertEquals(results.size(), 2);
         assertEquals(results.first().getEffectiveDate(), now);
         assertNull(results.first().getFixedPrice());
-        assertNull(results.first().getRecurringPrice());
+        assertNull(results.first().getRecurringPrice(null));
         assertEquals(results.first().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
         assertEquals(results.first().getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
         assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
-        assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+        assertEquals(results.last().getRecurringPrice(null), billingEvents.first().getRecurringPrice(null));
         assertEquals(results.last().getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED);
     }
 
     // Closed with no previous event but in-between events
     // -----[------Y-----]---------------------
     @Test(groups = "fast")
-    public void testCreateNewEventsClosedBetwn() {
+    public void testCreateNewEventsClosedBetwn() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
         final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
@@ -461,18 +465,18 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
         billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
 
-        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1);
+        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
 
         assertEquals(results.size(), 1);
         assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
-        assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+        assertEquals(results.last().getRecurringPrice(null), billingEvents.first().getRecurringPrice(null));
         assertEquals(results.last().getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED);
     }
 
     // Closed with no previous event but in-between events and following
     // -----[------Y-----]-------Z-------------
     @Test(groups = "fast")
-    public void testCreateNewEventsClosedBetweenFollow() {
+    public void testCreateNewEventsClosedBetweenFollow() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
         final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
@@ -480,18 +484,18 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
         billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
 
-        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1);
+        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
 
         assertEquals(results.size(), 1);
         assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
-        assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+        assertEquals(results.last().getRecurringPrice(null), billingEvents.first().getRecurringPrice(null));
         assertEquals(results.last().getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED);
     }
 
     // Closed duration with only following
     // -----[------------]-------Z-------------
     @Test(groups = "fast")
-    public void testCreateNewEventsClosedFollow() {
+    public void testCreateNewEventsClosedFollow() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
         final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
@@ -499,7 +503,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
         billingEvents.add(createRealEvent(now.plusDays(3), subscription1));
 
-        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1);
+        final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
 
         assertEquals(results.size(), 0);
     }
@@ -528,23 +532,38 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
         return createRealEvent(effectiveDate, subscription, SubscriptionBaseTransitionType.CHANGE);
     }
 
-    protected BillingEvent createRealEvent(final DateTime effectiveDate, final SubscriptionBase subscription, final SubscriptionBaseTransitionType type) {
-        final Account account = this.account;
-        final Integer billCycleDay = 1;
-        final PlanPhase planPhase = new MockPlanPhase();
-        final Plan plan = new MockPlan();
-        final BigDecimal fixedPrice = BigDecimal.TEN;
-        final BigDecimal recurringPrice = BigDecimal.TEN;
-        final Currency currency = Currency.USD;
-        final String description = "";
-        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
-        final Long totalOrdering = 0L;
-        final DateTimeZone tz = DateTimeZone.UTC;
-
-        return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase,
-                                       fixedPrice, recurringPrice, currency,
-                                       billingPeriod, billCycleDay,
-                                       description, totalOrdering, type, tz);
+    protected BillingEvent createRealEvent(final DateTime effectiveDate, final SubscriptionBase subscription, final SubscriptionBaseTransitionType type)  {
+        try {
+
+            final Integer billCycleDay = 1;
+            final Plan plan = new MockPlan();
+            final Currency currency = Currency.USD;
+            final String description = "";
+            final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+            final Long totalOrdering = 0L;
+            final DateTimeZone tz = DateTimeZone.UTC;
+
+
+            final PlanPhase planPhase = Mockito.mock(PlanPhase.class);
+
+            final InternationalPrice recurringPrice = Mockito.mock(InternationalPrice.class);
+
+            Mockito.when(recurringPrice.getPrice(Mockito.<Currency>any())).thenReturn(BigDecimal.TEN);
+            final Recurring recurring = Mockito.mock(Recurring.class);
+            Mockito.when(recurring.getRecurringPrice()).thenReturn(recurringPrice);
+            Mockito.when(planPhase.getRecurring()).thenReturn(recurring);
+
+            final BigDecimal fixedPrice = BigDecimal.TEN;
+
+            return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase, fixedPrice,
+                                           currency,
+                                           billingPeriod, billCycleDay,
+                                           description, totalOrdering, type, tz, null, false);
+
+        } catch (final CatalogApiException e) {
+            Assert.fail("", e);
+        }
+        throw new IllegalStateException();
     }
 
     @Test(groups = "fast")
@@ -569,17 +588,17 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testCreateNewDisableEvent() {
+    public void testCreateNewDisableEvent() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final BillingEvent event = new MockBillingEvent();
 
-        final BillingEvent result = blockingCalculator.createNewDisableEvent(now, event);
+        final BillingEvent result = blockingCalculator.createNewDisableEvent(now, event, null, internalCallContext);
         assertEquals(result.getBillCycleDayLocal(), event.getBillCycleDayLocal());
         assertEquals(result.getEffectiveDate(), now);
         assertEquals(result.getPlanPhase(), event.getPlanPhase());
         assertEquals(result.getPlan(), event.getPlan());
         assertNull(result.getFixedPrice());
-        assertNull(result.getRecurringPrice());
+        assertNull(result.getRecurringPrice(null));
         assertEquals(result.getCurrency(), event.getCurrency());
         assertEquals(result.getDescription(), "");
         assertEquals(result.getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
@@ -589,17 +608,17 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testCreateNewReenableEvent() {
+    public void testCreateNewReenableEvent() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final BillingEvent event = new MockBillingEvent();
 
-        final BillingEvent result = blockingCalculator.createNewReenableEvent(now, event);
+        final BillingEvent result = blockingCalculator.createNewReenableEvent(now, event, null, internalCallContext);
         assertEquals(result.getBillCycleDayLocal(), event.getBillCycleDayLocal());
         assertEquals(result.getEffectiveDate(), now);
         assertEquals(result.getPlanPhase(), event.getPlanPhase());
         assertEquals(result.getPlan(), event.getPlan());
         assertEquals(result.getFixedPrice(), event.getFixedPrice());
-        assertEquals(result.getRecurringPrice(), event.getRecurringPrice());
+        assertEquals(result.getRecurringPrice(null), event.getRecurringPrice(null));
         assertEquals(result.getCurrency(), event.getCurrency());
         assertEquals(result.getDescription(), "");
         assertEquals(result.getBillingPeriod(), event.getBillingPeriod());
@@ -611,8 +630,8 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
     private class MockBillingEvent extends DefaultBillingEvent {
 
         public MockBillingEvent() {
-            super(subscription1, clock.getUTCNow(), true, null, null, BigDecimal.ZERO, BigDecimal.TEN, Currency.USD, BillingPeriod.ANNUAL,
-                  4, "", 3L, SubscriptionBaseTransitionType.CREATE, DateTimeZone.UTC);
+            super(subscription1, clock.getUTCNow(), true, null, null, BigDecimal.ZERO, Currency.USD, BillingPeriod.ANNUAL,
+                  4, "", 3L, SubscriptionBaseTransitionType.CREATE, DateTimeZone.UTC, null, false);
         }
     }
 
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java
index fbd53bf..98252a9 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultBillingEvent.java
@@ -187,9 +187,9 @@ public class TestDefaultBillingEvent extends JunctionTestSuiteNoDB {
 
         final Account account = new MockAccountBuilder().build();
         return new DefaultBillingEvent(sub, effectiveDate, true,
-                                       shotgun, shotgunMonthly,
-                                       BigDecimal.ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, billCycleDay,
-                                       "Test Event 1", totalOrdering, type, DateTimeZone.UTC);
+                                       shotgun, shotgunMonthly, BigDecimal.ZERO,
+                                       Currency.USD, BillingPeriod.NO_BILLING_PERIOD, billCycleDay,
+                                       "Test Event 1", totalOrdering, type, DateTimeZone.UTC, null, false);
     }
 
     private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate,

NEWS 6(+6 -0)

diff --git a/NEWS b/NEWS
index 3201911..ad12397 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,9 @@
+0.16.4
+    See https://github.com/killbill/killbill/releases/tag/killbill-0.16.4
+
+0.16.3
+    See https://github.com/killbill/killbill/releases/tag/killbill-0.16.3
+
 0.16.2
     See https://github.com/killbill/killbill/releases/tag/killbill-0.16.2
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueApi.java b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueApi.java
index 8540b0e..580e4f9 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueApi.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueApi.java
@@ -17,10 +17,19 @@
 
 package org.killbill.billing.overdue.api;
 
+import java.util.UUID;
+
 import javax.inject.Inject;
 
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.overdue.OverdueService;
 import org.killbill.billing.overdue.caching.OverdueConfigCache;
+import org.killbill.billing.overdue.config.DefaultOverdueConfig;
+import org.killbill.billing.overdue.config.api.OverdueStateSet;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
 import org.killbill.billing.tenant.api.TenantApiException;
 import org.killbill.billing.tenant.api.TenantKV.TenantKey;
 import org.killbill.billing.tenant.api.TenantUserApi;
@@ -31,15 +40,18 @@ import org.killbill.billing.util.callcontext.TenantContext;
 public class DefaultOverdueApi implements OverdueApi {
 
     private final OverdueConfigCache overdueConfigCache;
+    private final BlockingInternalApi blockingInternalApi;
     private final InternalCallContextFactory internalCallContextFactory;
     private final TenantUserApi tenantApi;
 
     @Inject
     public DefaultOverdueApi(final OverdueConfigCache overdueConfigCache,
                              final TenantUserApi tenantApi,
+                             final BlockingInternalApi blockingInternalApi,
                              final InternalCallContextFactory internalCallContextFactory) {
         this.overdueConfigCache = overdueConfigCache;
         this.tenantApi = tenantApi;
+        this.blockingInternalApi = blockingInternalApi;
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
@@ -64,6 +76,16 @@ public class DefaultOverdueApi implements OverdueApi {
         }
     }
 
+    @Override
+    public OverdueState getOverdueStateFor(final UUID accountId, final TenantContext tenantContext) throws OverdueApiException {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(accountId, tenantContext);
+        final BlockingState blockingStateForService = blockingInternalApi.getBlockingStateForService(accountId, BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalTenantContext);
+        final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
+        final OverdueConfig overdueConfig = overdueConfigCache.getOverdueConfig(internalTenantContext);
+        final OverdueStateSet states = ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount();
+        return states.findState(stateName);
+    }
+
     private InternalTenantContext createInternalTenantContext(final TenantContext tenantContext) {
         // Only tenantRecordId will be populated -- this is important to always create the (ehcache) key the same way
         return internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(tenantContext);
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
index 91a0e03..22d0865 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
@@ -27,7 +27,6 @@ import java.util.UUID;
 import javax.inject.Named;
 
 import org.joda.time.DateTime;
-import org.joda.time.LocalDate;
 import org.joda.time.Period;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
@@ -171,11 +170,20 @@ public class OverdueStateApplicator {
         } catch (final AccountApiException e) {
             throw new OverdueException(e);
         }
+
+        final OverdueChangeInternalEvent event;
+        try {
+            event = createOverdueEvent(account, previousOverdueState.getName(), nextOverdueState.getName(), isBlockBillingTransition(previousOverdueState, nextOverdueState),
+                                       isUnblockBillingTransition(previousOverdueState, nextOverdueState), context);
+        } catch (final BlockingApiException e) {
+            log.warn("Failed to create OverdueChangeInternalEvent for accountId='{}'", account.getId(), e);
+            return;
+        }
+
         try {
-            bus.post(createOverdueEvent(account, previousOverdueState.getName(), nextOverdueState.getName(), isBlockBillingTransition(previousOverdueState, nextOverdueState),
-                                        isUnblockBillingTransition(previousOverdueState, nextOverdueState), context));
+            bus.post(event);
         } catch (final Exception e) {
-            log.error("Error posting overdue change event to bus", e);
+            log.warn("Failed to post event {}", event, e);
         }
     }
 
@@ -218,11 +226,19 @@ public class OverdueStateApplicator {
             throw new OverdueException(e);
         }
 
+        final OverdueChangeInternalEvent event;
+        try {
+            event = createOverdueEvent(account, previousOverdueState.getName(), clearState.getName(), isBlockBillingTransition(previousOverdueState, clearState),
+                                       isUnblockBillingTransition(previousOverdueState, clearState), context);
+        } catch (final BlockingApiException e) {
+            log.warn("Failed to create OverdueChangeInternalEvent for accountId='{}'", account.getId(), e);
+            return;
+        }
+
         try {
-            bus.post(createOverdueEvent(account, previousOverdueState.getName(), clearState.getName(), isBlockBillingTransition(previousOverdueState, clearState),
-                                        isUnblockBillingTransition(previousOverdueState, clearState), context));
+            bus.post(event);
         } catch (final Exception e) {
-            log.error("Error posting overdue change event to bus", e);
+            log.warn("Failed to post event {}", event, e);
         }
     }
 
@@ -372,11 +388,11 @@ public class OverdueStateApplicator {
                 emailSender.sendPlainTextEmail(to, cc, subject, emailBody);
             }
         } catch (final IOException e) {
-            log.warn(String.format("Unable to generate or send overdue notification email for account %s and overdueable %s", account.getId(), account.getId()), e);
+            log.warn("Unable to generate or send overdue notification email for accountId='{}'", account.getId(), e);
         } catch (final EmailApiException e) {
-            log.warn(String.format("Unable to send overdue notification email for account %s and overdueable %s", account.getId(), account.getId()), e);
+            log.warn("Unable to send overdue notification email for accountId='{}'", account.getId(), e);
         } catch (final MustacheException e) {
-            log.warn(String.format("Unable to generate overdue notification email for account %s and overdueable %s", account.getId(), account.getId()), e);
+            log.warn("Unable to generate overdue notification email for accountId='{}'", account.getId(), e);
         }
     }
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java b/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
index d52470c..7285e87 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
@@ -60,7 +60,7 @@ public class EhCacheOverdueConfigCache implements OverdueConfigCache {
             defaultOverdueConfig = XMLLoader.getObjectFromUri(noOverdueConfigURI, DefaultOverdueConfig.class);
         } catch (final Exception e) {
             defaultOverdueConfig = new DefaultOverdueConfig();
-            log.warn("Exception loading NoOverdueConfig - should never happen!", e);
+            log.error("Exception loading NoOverdueConfig - should never happen!", e);
         }
     }
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/caching/OverdueCacheInvalidationCallback.java b/overdue/src/main/java/org/killbill/billing/overdue/caching/OverdueCacheInvalidationCallback.java
index e7fb85e..4a19678 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/caching/OverdueCacheInvalidationCallback.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/caching/OverdueCacheInvalidationCallback.java
@@ -38,7 +38,7 @@ public class OverdueCacheInvalidationCallback implements CacheInvalidationCallba
 
     @Override
     public void invalidateCache(TenantKey key, final Object cookie, final InternalTenantContext tenantContext) {
-        log.info("Invalidate overdue cache for tenant {} ", tenantContext.getTenantRecordId());
+        log.info("Invalidate overdue cache for tenantRecordId='{}'", tenantContext.getTenantRecordId());
         overdueConfigCache.clearOverdueConfig(tenantContext);
     }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
index eb71506..af4a0cd 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
@@ -48,7 +48,7 @@ public abstract class DefaultOverdueStateSet extends ValidatingConfig<DefaultOve
                 return state;
             }
         }
-        throw new OverdueApiException(ErrorCode.CAT_NO_SUCH_OVEDUE_STATE, stateName);
+        throw new OverdueApiException(ErrorCode.CAT_NO_SUCH_OVERDUE_STATE, stateName);
     }
 
     /* (non-Javadoc)
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
index 9c613e5..930c774 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
@@ -19,11 +19,9 @@
 package org.killbill.billing.overdue.glue;
 
 import org.killbill.billing.glue.OverdueModule;
-import org.killbill.billing.overdue.OverdueInternalApi;
 import org.killbill.billing.overdue.OverdueProperties;
 import org.killbill.billing.overdue.OverdueService;
 import org.killbill.billing.overdue.api.DefaultOverdueApi;
-import org.killbill.billing.overdue.api.DefaultOverdueInternalApi;
 import org.killbill.billing.overdue.api.OverdueApi;
 import org.killbill.billing.overdue.applicator.OverdueEmailGenerator;
 import org.killbill.billing.overdue.applicator.formatters.DefaultOverdueEmailFormatterFactory;
@@ -95,7 +93,6 @@ public class DefaultOverdueModule extends KillBillModule implements OverdueModul
 
     @Override
     public void installOverdueUserApi() {
-        bind(OverdueInternalApi.class).to(DefaultOverdueInternalApi.class).asEagerSingleton();
         bind(OverdueApi.class).to(DefaultOverdueApi.class).asEagerSingleton();
     }
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueDispatcher.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueDispatcher.java
index 58653d2..c631b72 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueDispatcher.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueDispatcher.java
@@ -50,7 +50,7 @@ public class OverdueDispatcher {
         try {
             factory.createOverdueWrapperFor(accountId, context).refresh(context);
         } catch (BillingExceptionBase e) {
-            log.error(String.format("Error processing Overdue for blockable %s", accountId), e);
+            log.warn("Error processing Overdue for accountId='{}'", accountId, e);
         }
     }
 
@@ -58,7 +58,7 @@ public class OverdueDispatcher {
         try {
             factory.createOverdueWrapperFor(accountId, context).clear(context);
         } catch (BillingExceptionBase e) {
-            log.error(String.format("Error processing Overdue for blockable %s (type %s)", accountId), e);
+            log.warn("Error processing Overdue for accountId='{}'", accountId, e);
         }
     }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index 47b4071..867b81f 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -20,14 +20,26 @@ package org.killbill.billing.overdue.listener;
 
 import java.util.UUID;
 
+import javax.inject.Named;
+
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.events.ControlTagCreationInternalEvent;
 import org.killbill.billing.events.ControlTagDeletionInternalEvent;
 import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
 import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
 import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
-import org.killbill.billing.overdue.OverdueInternalApi;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
+import org.killbill.billing.overdue.caching.OverdueConfigCache;
+import org.killbill.billing.overdue.config.DefaultOverdueConfig;
+import org.killbill.billing.overdue.config.DefaultOverdueState;
+import org.killbill.billing.overdue.glue.DefaultOverdueModule;
+import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey;
+import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey.OverdueAsyncBusNotificationAction;
+import org.killbill.billing.overdue.notification.OverdueAsyncBusNotifier;
+import org.killbill.billing.overdue.notification.OverduePoster;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.CallOrigin;
@@ -36,6 +48,7 @@ import org.killbill.billing.util.callcontext.UserType;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.bus.api.BusEvent;
+import org.killbill.clock.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,39 +60,40 @@ public class OverdueListener {
 
     private static final Logger log = LoggerFactory.getLogger(OverdueListener.class);
 
-    private final OverdueInternalApi overdueInternalApi;
     private final InternalCallContextFactory internalCallContextFactory;
     private final CacheControllerDispatcher cacheControllerDispatcher;
-
+    private final Clock clock;
+    private final OverduePoster asyncPoster;
+    private final OverdueConfigCache overdueConfigCache;
     private final NonEntityDao nonEntityDao;
 
     @Inject
-    public OverdueListener(final OverdueInternalApi overdueInternalApi,
-                           final NonEntityDao nonEntityDao,
+    public OverdueListener(final NonEntityDao nonEntityDao,
                            final CacheControllerDispatcher cacheControllerDispatcher,
+                           final Clock clock,
+                           @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED)  final OverduePoster asyncPoster,
+                           final OverdueConfigCache overdueConfigCache,
                            final InternalCallContextFactory internalCallContextFactory) {
-        this.overdueInternalApi = overdueInternalApi;
         this.nonEntityDao = nonEntityDao;
+        this.clock = clock;
+        this.asyncPoster = asyncPoster;
+        this.overdueConfigCache = overdueConfigCache;
         this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
-
-
-
     @AllowConcurrentEvents
     @Subscribe
     public void handleTagInsert(final ControlTagCreationInternalEvent event) {
         if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
-            final InternalCallContext callContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
-            overdueInternalApi.scheduleOverdueClear(event.getObjectId(), callContext);
+            final InternalCallContext internalCallContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
+            insertBusEventIntoNotificationQueue(event.getObjectId(), OverdueAsyncBusNotificationAction.CLEAR, internalCallContext);
         } else if (event.getTagDefinition().getName().equals(ControlTagType.WRITTEN_OFF.toString()) && event.getObjectType() == ObjectType.INVOICE) {
             final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
             insertBusEventIntoNotificationQueue(accountId, event);
         }
     }
 
-
     @AllowConcurrentEvents
     @Subscribe
     public void handleTagRemoval(final ControlTagDeletionInternalEvent event) {
@@ -113,11 +127,42 @@ public class OverdueListener {
     }
 
     private void insertBusEventIntoNotificationQueue(final UUID accountId, final BusEvent event) {
-        final InternalCallContext callContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
-        overdueInternalApi.scheduleOverdueRefresh(accountId, callContext);
+        final InternalCallContext internalCallContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
+        insertBusEventIntoNotificationQueue(accountId, OverdueAsyncBusNotificationAction.REFRESH, internalCallContext);
     }
 
     private InternalCallContext createCallContext(final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
         return internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "OverdueService", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
     }
+
+    private void insertBusEventIntoNotificationQueue(final UUID accountId, final OverdueAsyncBusNotificationAction action, final InternalCallContext callContext) {
+        final boolean shouldInsertNotification = shouldInsertNotification(callContext);
+
+        if (shouldInsertNotification) {
+            final OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
+            asyncPoster.insertOverdueNotification(accountId, clock.getUTCNow(), OverdueAsyncBusNotifier.OVERDUE_ASYNC_BUS_NOTIFIER_QUEUE, notificationKey, callContext);
+        }
+    }
+
+    // Optimization: don't bother running the Overdue machinery if it's disabled
+    private boolean shouldInsertNotification(final InternalTenantContext internalTenantContext) {
+        OverdueConfig overdueConfig;
+        try {
+            overdueConfig = overdueConfigCache.getOverdueConfig(internalTenantContext);
+        } catch (final OverdueApiException e) {
+            log.warn("Failed to extract overdue config for tenant " + internalTenantContext.getTenantRecordId());
+            overdueConfig = null;
+        }
+        if (overdueConfig == null || overdueConfig.getOverdueStatesAccount() == null || overdueConfig.getOverdueStatesAccount().getStates() == null) {
+            return false;
+        }
+
+        for (final DefaultOverdueState state : ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount().getStates()) {
+            if (state.getConditionEvaluation() != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/OverdueProperties.java b/overdue/src/main/java/org/killbill/billing/overdue/OverdueProperties.java
index 0957140..4b59b8b 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/OverdueProperties.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/OverdueProperties.java
@@ -20,7 +20,7 @@ import org.skife.config.Config;
 import org.skife.config.Default;
 import org.skife.config.Description;
 
-import org.killbill.billing.util.config.KillbillConfig;
+import org.killbill.billing.util.config.definition.KillbillConfig;
 
 public interface OverdueProperties extends KillbillConfig {
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java b/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
index 87cc174..7aa0bed 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
@@ -91,8 +91,7 @@ public class DefaultOverdueService implements OverdueService {
                 overdueConfigCache.loadDefaultOverdueConfig(properties.getConfigURI());
                 isConfigLoaded = true;
             } catch (OverdueApiException e) {
-                log.warn("Overdue system disabled: unable to loadDefaultCatalog the overdue config from " + properties.getConfigURI(), e);
-                e.printStackTrace();
+                log.warn("Overdue system disabled: unable to load the overdue config from uri='{}'", properties.getConfigURI(), e);
             }
         }
     }
@@ -109,7 +108,7 @@ public class DefaultOverdueService implements OverdueService {
         try {
             busService.getBus().register(listener);
         } catch (final EventBusException e) {
-            log.error("Problem encountered registering OverdueListener on the Event Bus", e);
+            log.error("Failed to register OverdueListener", e);
         }
     }
 
@@ -124,7 +123,7 @@ public class DefaultOverdueService implements OverdueService {
         try {
             busService.getBus().unregister(listener);
         } catch (final EventBusException e) {
-            log.error("Problem encountered registering OverdueListener on the Event Bus", e);
+            log.error("Failed to unregister OverdueListener", e);
         }
         checkNotifier.stop();
         asyncNotifier.stop();
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
index 95a2b7d..7743e65 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
@@ -87,8 +87,7 @@ public class OverdueWrapper {
 
             return refreshWithLock(context);
         } catch (final LockFailedException e) {
-            // Not good!
-            log.error(String.format("Failed to process overdue for account %s", overdueable.getId()), e);
+            log.warn("Failed to process overdue for accountId='{}'", overdueable.getId(), e);
         } finally {
             if (lock != null) {
                 lock.release();
@@ -116,8 +115,7 @@ public class OverdueWrapper {
 
             clearWithLock(context);
         } catch (final LockFailedException e) {
-            // Not good!
-            log.error(String.format("Failed to clear overdue for account %s", overdueable.getId()), e);
+            log.warn("Failed to clear overdue for accountId='{}'", overdueable.getId(), e);
         } finally {
             if (lock != null) {
                 lock.release();
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
index df5a469..39ead21 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
@@ -46,6 +46,7 @@ import org.killbill.billing.util.email.templates.TemplateModule;
 import org.killbill.billing.util.glue.AuditModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
 import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
 import org.killbill.clock.Clock;
@@ -65,6 +66,7 @@ public class TestOverdueModule extends DefaultOverdueModule {
 
         install(new AuditModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new CallContextModule(configSource));
         install(new CustomFieldModule(configSource));
         install(new EmailModule(configSource));
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java
index 8e90891..0e1f5e5 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java
@@ -25,6 +25,7 @@ import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.junction.BlockingInternalApi;
 import org.killbill.billing.lifecycle.api.BusService;
+import org.killbill.billing.overdue.api.OverdueApi;
 import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
 import org.killbill.billing.overdue.applicator.OverdueStateApplicator;
 import org.killbill.billing.overdue.caching.OverdueCacheInvalidationCallback;
@@ -86,7 +87,7 @@ public abstract class OverdueTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
     @Inject
     protected OverdueStateApplicator applicator;
     @Inject
-    protected OverdueInternalApi overdueApi;
+    protected OverdueApi overdueApi;
     @Inject
     protected OverdueProperties overdueProperties;
     @Inject
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
index 1253410..f2b590d 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
@@ -25,6 +25,7 @@ import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.junction.BlockingInternalApi;
 import org.killbill.billing.lifecycle.api.BusService;
+import org.killbill.billing.overdue.api.OverdueApi;
 import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
 import org.killbill.billing.overdue.applicator.OverdueStateApplicator;
 import org.killbill.billing.overdue.caching.OverdueConfigCache;
@@ -87,7 +88,7 @@ public abstract class OverdueTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @Inject
     protected OverdueStateApplicator applicator;
     @Inject
-    protected OverdueInternalApi overdueApi;
+    protected OverdueApi overdueApi;
     @Inject
     protected OverdueProperties overdueProperties;
     @Inject
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
index cd916d9..be60575 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -26,76 +26,115 @@ import javax.annotation.Nullable;
 
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 
 public class DefaultApiBase {
 
     private static final Logger log = LoggerFactory.getLogger(DefaultApiBase.class);
+    private static final Joiner JOINER = Joiner.on(",");
 
-    private final PaymentConfig paymentConfig;
+    protected final PaymentConfig paymentConfig;
+    protected final InternalCallContextFactory internalCallContextFactory;
 
-    public DefaultApiBase(final PaymentConfig paymentConfig) {
+    public DefaultApiBase(final PaymentConfig paymentConfig, final InternalCallContextFactory internalCallContextFactory) {
         this.paymentConfig = paymentConfig;
+        this.internalCallContextFactory = internalCallContextFactory;
     }
 
-    protected void logAPICall(final String transactionType, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey) {
+    protected void logAPICall(final String transactionType,
+                              final Account account,
+                              final UUID paymentMethodId,
+                              @Nullable final UUID paymentId,
+                              @Nullable final UUID transactionId,
+                              @Nullable final BigDecimal amount,
+                              @Nullable final Currency currency,
+                              @Nullable final String paymentExternalKey,
+                              @Nullable final String paymentTransactionExternalKey,
+                              @Nullable final TransactionStatus transactionStatus,
+                              @Nullable final List<String> paymentControlPluginNames) {
         if (log.isInfoEnabled()) {
             final StringBuilder logLine = new StringBuilder();
-            logLine.append("PaymentApi : ")
+            logLine.append("PaymentApi: transactionType='")
                    .append(transactionType)
-                   .append(", account = ")
-                   .append(account.getId());
+                   .append("', accountId='")
+                   .append(account.getId())
+                   .append("'");
             if (paymentMethodId != null) {
-                logLine.append(", paymentMethodId = ")
-                       .append(paymentMethodId);
+                logLine.append(", paymentMethodId='")
+                       .append(paymentMethodId)
+                       .append("'");
             }
             if (paymentExternalKey != null) {
-                logLine.append(", paymentExternalKey = ")
-                       .append(paymentExternalKey);
+                logLine.append(", paymentExternalKey='")
+                       .append(paymentExternalKey)
+                       .append("'");
             }
             if (paymentTransactionExternalKey != null) {
-                logLine.append(", paymentTransactionExternalKey = ")
-                       .append(paymentTransactionExternalKey);
+                logLine.append(", paymentTransactionExternalKey='")
+                       .append(paymentTransactionExternalKey)
+                       .append("'");
             }
             if (paymentId != null) {
-                logLine.append(", paymentId = ")
-                       .append(paymentId);
+                logLine.append(", paymentId='")
+                       .append(paymentId)
+                       .append("'");
             }
             if (transactionId != null) {
-                logLine.append(", transactionId = ")
-                       .append(transactionId);
+                logLine.append(", transactionId='")
+                       .append(transactionId)
+                       .append("'");
             }
             if (amount != null) {
-                logLine.append(", amount = ")
-                       .append(amount);
+                logLine.append(", amount='")
+                       .append(amount)
+                       .append("'");
             }
             if (currency != null) {
-                logLine.append(", currency = ")
-                       .append(currency);
+                logLine.append(", currency='")
+                       .append(currency)
+                       .append("'");
+            }
+            if (transactionStatus != null) {
+                logLine.append(", transactionStatus='")
+                       .append(transactionStatus)
+                       .append("'");
+            }
+            if (paymentControlPluginNames != null) {
+                logLine.append(", paymentControlPluginNames='")
+                       .append(JOINER.join(paymentControlPluginNames))
+                       .append("'");
             }
             log.info(logLine.toString());
         }
     }
 
-    protected List<String> toPaymentControlPluginNames(final PaymentOptions paymentOptions) {
+    protected List<String> toPaymentControlPluginNames(final PaymentOptions paymentOptions, final CallContext callContext) {
+
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(callContext);
+
         // Special path for JAX-RS InvoicePayment endpoints (see JaxRsResourceBase)
-        if (paymentConfig.getPaymentControlPluginNames() != null &&
+        final List<String> controlPluginNames = paymentConfig.getPaymentControlPluginNames(internalTenantContext);
+        if (controlPluginNames != null &&
             paymentOptions.getPaymentControlPluginNames() != null &&
             paymentOptions.getPaymentControlPluginNames().size() == 1 &&
             InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentOptions.getPaymentControlPluginNames().get(0))) {
             final List<String> paymentControlPluginNames = new LinkedList<String>(paymentOptions.getPaymentControlPluginNames());
-            paymentControlPluginNames.addAll(paymentConfig.getPaymentControlPluginNames());
+            paymentControlPluginNames.addAll(controlPluginNames);
             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 if (controlPluginNames != null && !controlPluginNames.isEmpty()) {
+            return controlPluginNames;
         } else {
             return ImmutableList.<String>of();
         }
@@ -106,11 +145,4 @@ public class DefaultApiBase {
             throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null");
         }
     }
-
-    protected void checkPositiveAmount(final BigDecimal amount) throws PaymentApiException {
-        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "amount", "should be greater than 0");
-        }
-    }
-
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
index 14e7ab0..1e7962a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
@@ -1,7 +1,8 @@
 /*
- * Copyright 2014 Groupon, Inc
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Groupon licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -17,6 +18,8 @@
 package org.killbill.billing.payment.api;
 
 import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
@@ -28,6 +31,7 @@ import org.killbill.billing.entity.EntityBase;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 
 public class DefaultPayment extends EntityBase implements Payment {
 
@@ -40,7 +44,7 @@ public class DefaultPayment extends EntityBase implements Payment {
     private final BigDecimal purchasedAmount;
     private final BigDecimal creditAmount;
     private final BigDecimal refundAmount;
-    private final Boolean isVoided;
+    private final Boolean isAuthVoided;
 
     private final Currency currency;
     private final List<PaymentTransaction> transactions;
@@ -56,18 +60,40 @@ public class DefaultPayment extends EntityBase implements Payment {
         this.paymentNumber = paymentNumber;
         this.externalKey = externalKey;
         this.transactions = transactions;
-        this.authAmount = getAmountForType(transactions, TransactionType.AUTHORIZE);
-        this.captureAmount = getAmountForType(transactions, TransactionType.CAPTURE);
-        this.purchasedAmount = getAmountForType(transactions, TransactionType.PURCHASE);
-        this.creditAmount = getAmountForType(transactions, TransactionType.CREDIT);
-        this.refundAmount = getAmountForType(transactions, TransactionType.REFUND);
-        this.isVoided = Iterables.filter(transactions, new Predicate<PaymentTransaction>() {
-            @Override
-            public boolean apply(final PaymentTransaction input) {
-                return input.getTransactionType() == TransactionType.VOID && TransactionStatus.SUCCESS.equals(input.getTransactionStatus());
+
+        final Collection<PaymentTransaction> voidedTransactions = new LinkedList<PaymentTransaction>();
+        final Collection<PaymentTransaction> nonVoidedTransactions = new LinkedList<PaymentTransaction>();
+        int nvTxToVoid = 0;
+        for (final PaymentTransaction paymentTransaction : Lists.<PaymentTransaction>reverse(transactions)) {
+            if (TransactionStatus.SUCCESS.equals(paymentTransaction.getTransactionStatus())) {
+                if (paymentTransaction.getTransactionType() == TransactionType.VOID) {
+                    nvTxToVoid++;
+                } else {
+                    if (nvTxToVoid > 0) {
+                        nvTxToVoid--;
+                        voidedTransactions.add(paymentTransaction);
+                    } else {
+                        nonVoidedTransactions.add(paymentTransaction);
+                    }
+                }
             }
-        }).iterator().hasNext();
-        this.currency = (transactions != null && !transactions.isEmpty()) ? transactions.get(0).getCurrency() : null;
+        }
+
+        this.authAmount = getAmountForType(nonVoidedTransactions, TransactionType.AUTHORIZE);
+        this.captureAmount = getAmountForType(nonVoidedTransactions, TransactionType.CAPTURE);
+        this.purchasedAmount = getAmountForType(nonVoidedTransactions, TransactionType.PURCHASE);
+        this.creditAmount = getAmountForType(nonVoidedTransactions, TransactionType.CREDIT);
+        this.refundAmount = getAmountForType(nonVoidedTransactions, TransactionType.REFUND);
+
+        this.isAuthVoided = Iterables.<PaymentTransaction>tryFind(voidedTransactions,
+                                                                  new Predicate<PaymentTransaction>() {
+                                                                      @Override
+                                                                      public boolean apply(final PaymentTransaction input) {
+                                                                          return input.getTransactionType() == TransactionType.AUTHORIZE && TransactionStatus.SUCCESS.equals(input.getTransactionStatus());
+                                                                      }
+                                                                  }).isPresent();
+
+        this.currency = !transactions.isEmpty() ? transactions.get(0).getCurrency() : null;
     }
 
     private static BigDecimal getAmountForType(final Iterable<PaymentTransaction> transactions, final TransactionType transactiontype) {
@@ -141,7 +167,7 @@ public class DefaultPayment extends EntityBase implements Payment {
 
     @Override
     public Boolean isAuthVoided() {
-        return isVoided;
+        return isAuthVoided;
     }
 
     @Override
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 406e8d8..a28b6e6 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
@@ -37,11 +37,14 @@ import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.entity.Pagination;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
 public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
 
     private static final boolean SHOULD_LOCK_ACCOUNT = true;
@@ -53,15 +56,13 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
     private final PaymentProcessor paymentProcessor;
     private final PaymentMethodProcessor paymentMethodProcessor;
     private final PluginControlPaymentProcessor pluginControlPaymentProcessor;
-    private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
     public DefaultPaymentApi(final PaymentConfig paymentConfig, final PaymentProcessor paymentProcessor, final PaymentMethodProcessor paymentMethodProcessor, final PluginControlPaymentProcessor pluginControlPaymentProcessor, final InternalCallContextFactory internalCallContextFactory) {
-        super(paymentConfig);
+        super(paymentConfig, internalCallContextFactory);
         this.paymentProcessor = paymentProcessor;
         this.paymentMethodProcessor = paymentMethodProcessor;
         this.pluginControlPaymentProcessor = pluginControlPaymentProcessor;
-        this.internalCallContextFactory = internalCallContextFactory;
     }
 
     @Override
@@ -71,23 +72,38 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentMethodId, "paymentMethodId");
         if (paymentId == null) {
             checkNotNullParameter(amount, "amount");
-            checkPositiveAmount(amount);
             checkNotNullParameter(currency, "currency");
         }
         checkNotNullParameter(properties, "plugin properties");
 
-        logAPICall(TransactionType.AUTHORIZE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.AUTHORIZE.name();
+        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return paymentProcessor.createAuthorization(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                    SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+        final Payment payment = paymentProcessor.createAuthorization(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                     SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   null);
+
+        return payment;
     }
 
     @Override
     public Payment createAuthorizationWithPaymentControl(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);
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
         if (paymentControlPluginNames.isEmpty()) {
             return createAuthorization(account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
         }
@@ -96,16 +112,31 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentMethodId, "paymentMethodId");
         if (paymentId == null) {
             checkNotNullParameter(amount, "amount");
-            checkPositiveAmount(amount);
             checkNotNullParameter(currency, "currency");
         }
         checkNotNullParameter(properties, "plugin properties");
 
-        logAPICall(TransactionType.AUTHORIZE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.AUTHORIZE.name();
+        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return pluginControlPaymentProcessor.createAuthorization(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                                 properties, paymentControlPluginNames, callContext, internalCallContext);
+        final Payment payment = pluginControlPaymentProcessor.createAuthorization(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                                  properties, paymentControlPluginNames, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   paymentControlPluginNames);
+
+        return payment;
     }
 
     @Override
@@ -114,34 +145,68 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
 
         checkNotNullParameter(account, "account");
         checkNotNullParameter(paymentId, "paymentId");
+        checkNotNullParameter(amount, "amount");
         checkNotNullParameter(currency, "currency");
         checkNotNullParameter(properties, "plugin properties");
-        checkPositiveAmount(amount);
 
-        logAPICall(TransactionType.CAPTURE.name(), account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.CAPTURE.name();
+        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return paymentProcessor.createCapture(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
-                                              SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+        final Payment payment = paymentProcessor.createCapture(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                                               SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   null);
+
+        return payment;
     }
 
     @Override
     public Payment createCaptureWithPaymentControl(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentTransactionExternalKey,
                                                    final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
-        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
         if (paymentControlPluginNames.isEmpty()) {
             return createCapture(account, paymentId, amount, currency, paymentTransactionExternalKey, properties, callContext);
         }
 
         checkNotNullParameter(account, "account");
         checkNotNullParameter(paymentId, "paymentId");
+        checkNotNullParameter(amount, "amount");
         checkNotNullParameter(currency, "currency");
         checkNotNullParameter(properties, "plugin properties");
-        checkPositiveAmount(amount);
+
+        final String transactionType = TransactionType.CAPTURE.name();
+        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return pluginControlPaymentProcessor.createCapture(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
-                                                           properties, paymentControlPluginNames, callContext, internalCallContext);
+        final Payment payment = pluginControlPaymentProcessor.createCapture(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                                                            properties, paymentControlPluginNames, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   paymentControlPluginNames);
+
+        return payment;
     }
 
     @Override
@@ -151,22 +216,37 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentMethodId, "paymentMethodId");
         if (paymentId == null) {
             checkNotNullParameter(amount, "amount");
-            checkPositiveAmount(amount);
             checkNotNullParameter(currency, "currency");
         }
         checkNotNullParameter(properties, "plugin properties");
 
-        logAPICall(TransactionType.PURCHASE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.PURCHASE.name();
+        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return paymentProcessor.createPurchase(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                               SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+        final Payment payment = paymentProcessor.createPurchase(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   null);
+
+        return payment;
     }
 
     @Override
-    public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable  final String paymentExternalKey, final String paymentTransactionExternalKey,
+    public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
                                                     final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
-        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
         if (paymentControlPluginNames.isEmpty()) {
             return createPurchase(account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
         }
@@ -174,26 +254,39 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(account, "account");
         if (paymentId == null) {
             checkNotNullParameter(amount, "amount");
-            checkPositiveAmount(amount);
             checkNotNullParameter(currency, "currency");
         }
         checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey");
         checkNotNullParameter(properties, "plugin properties");
 
-        logAPICall(TransactionType.PURCHASE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
-
         if (paymentMethodId == null && !paymentOptions.isExternalPayment()) {
             throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "paymentMethodId", "should not be null");
         }
-
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-
         final UUID nonNulPaymentMethodId = (paymentMethodId != null) ?
                                            paymentMethodId :
                                            paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
-        return pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                            properties, paymentControlPluginNames, callContext, internalCallContext);
 
+        final String transactionType = TransactionType.PURCHASE.name();
+        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
+
+        final Payment payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                             properties, paymentControlPluginNames, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   paymentControlPluginNames);
+
+        return payment;
     }
 
     @Override
@@ -204,17 +297,33 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentId, "paymentId");
         checkNotNullParameter(properties, "plugin properties");
 
-        logAPICall(TransactionType.VOID.name(), account, null, paymentId, null, null, null, null, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.VOID.name();
+        logAPICall(transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, null);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return paymentProcessor.createVoid(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey,
-                                           SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+        final Payment payment = paymentProcessor.createVoid(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey,
+                                                            SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   null);
+
+        return payment;
 
     }
 
     @Override
     public Payment createVoidWithPaymentControl(final Account account, final UUID paymentId, final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
-        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
         if (paymentControlPluginNames.isEmpty()) {
             return createVoid(account, paymentId, paymentTransactionExternalKey, properties, callContext);
         }
@@ -223,11 +332,27 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentId, "paymentId");
         checkNotNullParameter(properties, "plugin properties");
 
-        logAPICall(TransactionType.VOID.name(), account, null, paymentId, null, null, null, null, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.VOID.name();
+        logAPICall(transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return pluginControlPaymentProcessor.createVoid(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey,
-                                                        properties, paymentControlPluginNames, callContext, internalCallContext);
+        final Payment payment = pluginControlPaymentProcessor.createVoid(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey,
+                                                                         properties, paymentControlPluginNames, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   paymentControlPluginNames);
+
+        return payment;
     }
 
     @Override
@@ -239,21 +364,34 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         }
         checkNotNullParameter(paymentId, "paymentId");
         checkNotNullParameter(properties, "plugin properties");
-        if (amount != null) {
-            checkPositiveAmount(amount);
-        }
 
-        logAPICall(TransactionType.REFUND.name(), account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.REFUND.name();
+        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return paymentProcessor.createRefund(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
-                                             SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+        final Payment payment = paymentProcessor.createRefund(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                                              SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   null);
+
+        return payment;
     }
 
     @Override
     public Payment createRefundWithPaymentControl(final Account account, @Nullable 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);
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
         if (paymentControlPluginNames.isEmpty()) {
             return createRefund(account, paymentId, amount, currency, paymentTransactionExternalKey, properties, callContext);
         }
@@ -265,16 +403,28 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentId, "paymentId");
         checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey");
         checkNotNullParameter(properties, "plugin properties");
-        if (amount != null) {
-            checkPositiveAmount(amount);
-        }
 
-        logAPICall(TransactionType.REFUND.name(), account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.REFUND.name();
+        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return pluginControlPaymentProcessor.createRefund(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
-                                                          properties, paymentControlPluginNames, callContext, internalCallContext);
+        final Payment payment = pluginControlPaymentProcessor.createRefund(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                                                           properties, paymentControlPluginNames, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   paymentControlPluginNames);
 
+        return payment;
     }
 
     @Override
@@ -285,24 +435,38 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentMethodId, "paymentMethodId");
         if (paymentId == null) {
             checkNotNullParameter(amount, "amount");
-            checkPositiveAmount(amount);
             checkNotNullParameter(currency, "currency");
         }
         checkNotNullParameter(properties, "plugin properties");
 
-        logAPICall(TransactionType.CREDIT.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.CREDIT.name();
+        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                             SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+        final Payment payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                              SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   null);
 
+        return payment;
     }
 
     @Override
     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);
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
         if (paymentControlPluginNames.isEmpty()) {
             return createCredit(account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
         }
@@ -311,16 +475,31 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentMethodId, "paymentMethodId");
         if (paymentId == null) {
             checkNotNullParameter(amount, "amount");
-            checkPositiveAmount(amount);
             checkNotNullParameter(currency, "currency");
         }
         checkNotNullParameter(properties, "plugin properties");
 
-        logAPICall(TransactionType.CREDIT.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.CREDIT.name();
+        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                          properties, paymentControlPluginNames, callContext, internalCallContext);
+        final Payment payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                           properties, paymentControlPluginNames, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   paymentControlPluginNames);
+
+        return payment;
     }
 
     @Override
@@ -328,10 +507,32 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(account, "account");
         checkNotNullParameter(paymentTransactionId, "paymentTransactionId");
 
-        logAPICall("NOTIFY_STATE_CHANGE", account, null, null, paymentTransactionId, null, null, null, null);
+        final String transactionType = "NOTIFY_STATE_CHANGE";
+        logAPICall(transactionType, account, null, null, paymentTransactionId, null, null, null, null, null, null);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return paymentProcessor.notifyPendingPaymentOfStateChanged(account, paymentTransactionId, isSuccess, callContext, internalCallContext);
+        final Payment payment = paymentProcessor.notifyPendingPaymentOfStateChanged(account, paymentTransactionId, isSuccess, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = Iterables.<PaymentTransaction>tryFind(payment.getTransactions(),
+                                                                                            new Predicate<PaymentTransaction>() {
+                                                                                                @Override
+                                                                                                public boolean apply(final PaymentTransaction transaction) {
+                                                                                                    return transaction.getId().equals(paymentTransactionId);
+                                                                                                }
+                                                                                            }).orNull();
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction == null ? null : paymentTransaction.getId(),
+                   paymentTransaction == null ? null : paymentTransaction.getProcessedAmount(),
+                   paymentTransaction == null ? null : paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction == null ? null : paymentTransaction.getExternalKey(),
+                   paymentTransaction == null ? null : paymentTransaction.getTransactionStatus(),
+                   null);
+
+        return payment;
     }
 
     @Override
@@ -345,19 +546,33 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(amount, "amount");
         checkNotNullParameter(currency, "currency");
         checkNotNullParameter(paymentId, "paymentId");
-        checkPositiveAmount(amount);
 
-        logAPICall(TransactionType.CHARGEBACK.name(), account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey);
+        final String transactionType = TransactionType.CHARGEBACK.name();
+        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return paymentProcessor.createChargeback(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, amount, currency, true,
-                                                 callContext, internalCallContext);
+        final Payment payment = paymentProcessor.createChargeback(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, amount, currency, true,
+                                                                  callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   null);
 
+        return payment;
     }
 
     @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);
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
         if (paymentControlPluginNames.isEmpty()) {
             return createChargeback(account, paymentId, amount, currency, paymentTransactionExternalKey, callContext);
         }
@@ -366,11 +581,28 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(amount, "amount");
         checkNotNullParameter(currency, "currency");
         checkNotNullParameter(paymentId, "paymentId");
-        checkPositiveAmount(amount);
+
+        final String transactionType = TransactionType.CHARGEBACK.name();
+        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return pluginControlPaymentProcessor.createChargeback(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey, amount, currency,
-                                                              paymentControlPluginNames, callContext, internalCallContext);
+        final Payment payment = pluginControlPaymentProcessor.createChargeback(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey, amount, currency,
+                                                                               paymentControlPluginNames, callContext, internalCallContext);
+
+        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+        logAPICall(transactionType,
+                   account,
+                   payment.getPaymentMethodId(),
+                   payment.getId(),
+                   paymentTransaction.getId(),
+                   paymentTransaction.getProcessedAmount(),
+                   paymentTransaction.getProcessedCurrency(),
+                   payment.getExternalKey(),
+                   paymentTransaction.getExternalKey(),
+                   paymentTransaction.getTransactionStatus(),
+                   paymentControlPluginNames);
+
+        return payment;
     }
 
     @Override
@@ -429,7 +661,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
     @Override
     public List<PaymentMethod> getAccountPaymentMethods(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context)
             throws PaymentApiException {
-        return paymentMethodProcessor.getPaymentMethods(accountId, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(accountId, context));
+        return paymentMethodProcessor.getPaymentMethods(withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(accountId, context));
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
index ec17b19..997b015 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
@@ -41,7 +41,7 @@ import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
 import org.killbill.billing.util.PluginProperties;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 
 import com.google.common.base.Joiner;
 
@@ -55,7 +55,6 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
     private final ControlPluginRunner controlPluginRunner;
     private final PluginDispatcher<HostedPaymentPageFormDescriptor> paymentPluginFormDispatcher;
     private final PluginDispatcher<GatewayNotification> paymentPluginNotificationDispatcher;
-    private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
     public DefaultPaymentGatewayApi(final PaymentConfig paymentConfig,
@@ -63,13 +62,12 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
                                     final ControlPluginRunner controlPluginRunner,
                                     final PaymentExecutors executors,
                                     final InternalCallContextFactory internalCallContextFactory) {
-        super(paymentConfig);
+        super(paymentConfig, internalCallContextFactory);
         this.paymentGatewayProcessor = paymentGatewayProcessor;
         this.controlPluginRunner = controlPluginRunner;
         final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
         this.paymentPluginFormDispatcher = new PluginDispatcher<HostedPaymentPageFormDescriptor>(paymentPluginTimeoutSec, executors);
         this.paymentPluginNotificationDispatcher = new PluginDispatcher<GatewayNotification>(paymentPluginTimeoutSec, executors);
-        this.internalCallContextFactory = internalCallContextFactory;
     }
 
     @Override
@@ -128,7 +126,7 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
                                             final CallContext callContext,
                                             final PluginDispatcher<T> pluginDispatcher,
                                             final WithPaymentControlCallback<T> callback) throws PaymentApiException {
-        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+        final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
         if (paymentControlPluginNames.isEmpty()) {
             return callback.doPaymentGatewayApiOperation(paymentMethodId, properties);
         }
diff --git a/payment/src/main/java/org/killbill/billing/payment/bus/PaymentBusEventHandler.java b/payment/src/main/java/org/killbill/billing/payment/bus/PaymentBusEventHandler.java
index c7705ec..1c1d8dc 100644
--- a/payment/src/main/java/org/killbill/billing/payment/bus/PaymentBusEventHandler.java
+++ b/payment/src/main/java/org/killbill/billing/payment/bus/PaymentBusEventHandler.java
@@ -40,7 +40,7 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.CallOrigin;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.UserType;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -80,8 +80,7 @@ public class PaymentBusEventHandler {
     @AllowConcurrentEvents
     @Subscribe
     public void processInvoiceEvent(final InvoiceCreationInternalEvent event) {
-        log.info("Received invoice creation notification for account {} and invoice {}",
-                 event.getAccountId(), event.getInvoiceId());
+        log.info("Received invoice creation notification for accountId='{}', invoiceId='{}'", event.getAccountId(), event.getInvoiceId());
 
         final Account account;
         try {
@@ -95,12 +94,12 @@ public class PaymentBusEventHandler {
             final CallContext callContext = internalCallContextFactory.createCallContext(internalContext);
 
             final BigDecimal amountToBePaid = null; // We let the plugin compute how much should be paid
-            final List<String> paymentControlPluginNames = paymentConfig.getPaymentControlPluginNames() != null ? new LinkedList<String>(paymentConfig.getPaymentControlPluginNames()) : new LinkedList<String>();
+            final List<String> paymentControlPluginNames = paymentConfig.getPaymentControlPluginNames(internalContext) != null ? new LinkedList<String>(paymentConfig.getPaymentControlPluginNames(internalContext)) : new LinkedList<String>();
             paymentControlPluginNames.add(InvoicePaymentControlPluginApi.PLUGIN_NAME);
             pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amountToBePaid, account.getCurrency(), UUIDs.randomUUID().toString(), UUIDs.randomUUID().toString(),
                                                          properties, paymentControlPluginNames, callContext, internalContext);
         } catch (final AccountApiException e) {
-            log.error("Failed to process invoice payment", e);
+            log.warn("Failed to process invoice payment", e);
         } catch (final PaymentApiException e) {
             // Log as error unless:
             if (e.getCode() != ErrorCode.PAYMENT_NULL_INVOICE.getCode() /* Nothing left to be paid */ &&
diff --git a/payment/src/main/java/org/killbill/billing/payment/config/MultiTenantPaymentConfig.java b/payment/src/main/java/org/killbill/billing/payment/config/MultiTenantPaymentConfig.java
new file mode 100644
index 0000000..2c0e421
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/config/MultiTenantPaymentConfig.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.config;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.util.config.definition.PaymentConfig;
+import org.killbill.billing.util.config.tenant.CacheConfig;
+import org.killbill.billing.util.config.tenant.MultiTenantConfigBase;
+import org.skife.config.Param;
+import org.skife.config.TimeSpan;
+
+public class MultiTenantPaymentConfig extends MultiTenantConfigBase implements PaymentConfig {
+
+    private final PaymentConfig staticConfig;
+
+    @Inject
+    public MultiTenantPaymentConfig(@Named(PaymentModule.STATIC_CONFIG) final PaymentConfig staticConfig, final CacheConfig cacheConfig) {
+        super(cacheConfig);
+        this.staticConfig = staticConfig;
+    }
+
+    @Override
+    public List<Integer> getPaymentFailureRetryDays(@Param("dummy") final InternalTenantContext tenantContext) {
+        // There is no good way to achieve that in java; this solution is expensive (we could consider hardcoding the method name each time instead)
+        final Method method = new Object() {}.getClass().getEnclosingMethod();
+        final String result = getStringTenantConfig(method.getName(), tenantContext);
+        if (result != null) {
+            return convertToListInteger(result, method.getName());
+        }
+        return staticConfig.getPaymentFailureRetryDays(tenantContext);
+    }
+
+    @Override
+    public int getPluginFailureInitialRetryInSec(@Param("dummy") final InternalTenantContext tenantContext) {
+        final Method method = new Object() {}.getClass().getEnclosingMethod();
+        final String result = getStringTenantConfig(method.getName(), tenantContext);
+        if (result != null) {
+            return Integer.parseInt(result);
+        }
+        return staticConfig.getPluginFailureInitialRetryInSec(tenantContext);
+    }
+
+    @Override
+    public int getPluginFailureRetryMultiplier(@Param("dummy") final InternalTenantContext tenantContext) {
+        final Method method = new Object() {}.getClass().getEnclosingMethod();
+
+        final String result = getStringTenantConfig(method.getName(), tenantContext);
+        if (result != null) {
+            return Integer.parseInt(result);
+        }
+        return staticConfig.getPluginFailureRetryMultiplier(tenantContext);
+    }
+
+    @Override
+    public List<TimeSpan> getIncompleteTransactionsRetries(@Param("dummy") final InternalTenantContext tenantContext) {
+        final Method method = new Object() {}.getClass().getEnclosingMethod();
+
+        final String result = getStringTenantConfig(method.getName(), tenantContext);
+        if (result != null) {
+            return convertToListTimeSpan(result, method.getName());
+        }
+        return staticConfig.getIncompleteTransactionsRetries(tenantContext);
+    }
+
+    @Override
+    public int getPluginFailureRetryMaxAttempts(@Param("dummy") final InternalTenantContext tenantContext) {
+
+        final Method method = new Object() {}.getClass().getEnclosingMethod();
+
+        final String result = getStringTenantConfig(method.getName(), tenantContext);
+        if (result != null) {
+            return Integer.parseInt(result);
+        }
+        return staticConfig.getPluginFailureRetryMaxAttempts(tenantContext);
+    }
+
+    @Override
+    public List<String> getPaymentControlPluginNames(@Param("dummy") final InternalTenantContext tenantContext) {
+
+        final Method method = new Object() {}.getClass().getEnclosingMethod();
+
+        final String result = getStringTenantConfig(method.getName(), tenantContext);
+        if (result != null) {
+            return convertToListString(result, method.getName());
+        }
+        return staticConfig.getPaymentControlPluginNames(tenantContext);
+    }
+
+    @Override
+    public TimeSpan getJanitorRunningRate() {
+        return staticConfig.getJanitorRunningRate();
+    }
+
+    @Override
+    public TimeSpan getIncompleteAttemptsTimeSpanDelay() {
+        return staticConfig.getIncompleteAttemptsTimeSpanDelay();
+    }
+
+    @Override
+    public String getDefaultPaymentProvider() {
+        return staticConfig.getDefaultPaymentProvider();
+    }
+
+    @Override
+    public TimeSpan getPaymentPluginTimeout() {
+        return staticConfig.getPaymentPluginTimeout();
+    }
+
+    @Override
+    public int getPaymentPluginThreadNb() {
+        return staticConfig.getPaymentPluginThreadNb();
+    }
+
+    @Override
+    public int getMaxGlobalLockRetries() {
+        return staticConfig.getMaxGlobalLockRetries();
+    }
+
+    @Override
+    protected Method getConfigStaticMethod(final String methodName) {
+        try {
+            return PaymentConfig.class.getMethod(methodName, InternalTenantContext.class);
+        } catch (final NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
index 4803871..a6b0780 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -36,7 +36,7 @@ import org.killbill.billing.util.callcontext.CallOrigin;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.callcontext.UserType;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLock;
@@ -48,7 +48,7 @@ import org.slf4j.LoggerFactory;
 
 abstract class CompletionTaskBase<T> implements Runnable {
 
-    protected Logger log = LoggerFactory.getLogger(CompletionTaskBase.class);
+    private static final Logger log = LoggerFactory.getLogger(CompletionTaskBase.class);
 
     protected final PaymentConfig paymentConfig;
     protected final Clock clock;
@@ -125,9 +125,9 @@ abstract class CompletionTaskBase<T> implements Runnable {
             lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), account.getExternalKey(), paymentConfig.getMaxGlobalLockRetries());
             return callback.doIteration();
         } catch (AccountApiException e) {
-            log.warn(String.format("Janitor failed to retrieve account with recordId %s", internalTenantContext.getAccountRecordId()), e);
+            log.warn("Error retrieving accountRecordId='{}'", internalTenantContext.getAccountRecordId(), e);
         } catch (LockFailedException e) {
-            log.warn(String.format("Janitor failed to lock account with recordId %s", internalTenantContext.getAccountRecordId()), e);
+            log.warn("Error locking accountRecordId='{}'", internalTenantContext.getAccountRecordId(), e);
         } finally {
             if (lock != null) {
                 lock.release();
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
index 9851afb..a33e4e0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -43,11 +43,13 @@ import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertyS
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.notificationq.api.NotificationQueue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
@@ -61,6 +63,8 @@ import com.google.common.collect.Iterables;
  */
 public class IncompletePaymentAttemptTask extends CompletionTaskBase<PaymentAttemptModelDao> {
 
+    private static final Logger log = LoggerFactory.getLogger(IncompletePaymentAttemptTask.class);
+
     //
     // Each paymentAttempt *should* transition to a new state, so fetching a limited size will still allow us to progress (as opposed to fetching the same entries over and over)
     // We also don't expect to see too many entries in the INIT state.
@@ -124,7 +128,7 @@ public class IncompletePaymentAttemptTask extends CompletionTaskBase<PaymentAtte
         if (transaction == null ||
             transaction.getTransactionStatus() == TransactionStatus.PLUGIN_FAILURE ||
             transaction.getTransactionStatus() == TransactionStatus.PAYMENT_FAILURE) {
-            log.info("Janitor AttemptCompletionTask moving attempt " + attempt.getId() + " -> ABORTED");
+            log.info("Moving attemptId='{}' to ABORTED", attempt.getId());
             paymentDao.updatePaymentAttempt(attempt.getId(), attempt.getTransactionId(), "ABORTED", internalCallContext);
             return;
         }
@@ -140,7 +144,7 @@ public class IncompletePaymentAttemptTask extends CompletionTaskBase<PaymentAtte
             transaction.getTransactionStatus() == TransactionStatus.PENDING) {
 
             try {
-                log.info("Janitor AttemptCompletionTask completing attempt " + attempt.getId() + " -> SUCCESS");
+                log.info("Moving attemptId='{}' to SUCCESS", attempt.getId());
 
                 final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
                 final boolean isApiPayment = true; // unclear
@@ -166,11 +170,11 @@ public class IncompletePaymentAttemptTask extends CompletionTaskBase<PaymentAtte
                 //
                 pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
             } catch (final AccountApiException e) {
-                log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+                log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
             } catch (final PluginPropertySerializerException e) {
-                log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+                log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
             } catch (final PaymentApiException e) {
-                log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+                log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
             }
         }
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
index c71407d..5c6d3d8 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -48,12 +48,14 @@ import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.notificationq.api.NotificationEvent;
 import org.killbill.notificationq.api.NotificationQueue;
 import org.skife.config.TimeSpan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
@@ -64,6 +66,8 @@ import com.google.common.collect.Iterables;
 
 public class IncompletePaymentTransactionTask extends CompletionTaskBase<PaymentTransactionModelDao> {
 
+    private static final Logger log = LoggerFactory.getLogger(IncompletePaymentTransactionTask.class);
+
     private static final ImmutableList<TransactionStatus> TRANSACTION_STATUSES_TO_CONSIDER = ImmutableList.<TransactionStatus>builder()
                                                                                                           .add(TransactionStatus.PENDING)
                                                                                                           .add(TransactionStatus.UNKNOWN)
@@ -192,7 +196,7 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
             case UNKNOWN:
             default:
                 if (transactionStatus != paymentTransaction.getTransactionStatus()) {
-                    log.info("Janitor IncompletePaymentTransactionTask unable to repair payment {}, transaction {}: {} -> {}",
+                    log.info("Unable to repair paymentId='{}', paymentTransactionId='{}', currentTransactionStatus='{}', newTransactionStatus='{}'",
                              payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
                 }
                 // We can't get anything interesting from the plugin...
@@ -233,8 +237,7 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
         final String gatewayErrorCode = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayErrorCode() : paymentTransaction.getGatewayErrorCode();
         final String gatewayError = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayError() : paymentTransaction.getGatewayErrorMsg();
 
-
-        log.info("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}",
+        log.info("Repairing paymentId='{}', paymentTransactionId='{}', currentTransactionStatus='{}', newTransactionStatus='{}'",
                  payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(payment.getAccountId(), callContext);
@@ -258,9 +261,9 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
     }
 
     @VisibleForTesting
-    DateTime getNextNotificationTime(final Integer attemptNumber) {
+    DateTime getNextNotificationTime(final Integer attemptNumber, final InternalTenantContext tenantContext) {
 
-        final List<TimeSpan> retries = paymentConfig.getIncompleteTransactionsRetries();
+        final List<TimeSpan> retries = paymentConfig.getIncompleteTransactionsRetries(tenantContext);
         if (attemptNumber > retries.size()) {
             return null;
         }
@@ -274,10 +277,12 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
             return;
         }
 
+        final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(tenantRecordId, accountRecordId);
+
         // Increment value before we insert
         final Integer newAttemptNumber = attemptNumber.intValue() + 1;
         final NotificationEvent key = new JanitorNotificationKey(paymentTransactionId, IncompletePaymentTransactionTask.class.toString(), newAttemptNumber);
-        final DateTime notificationTime = getNextNotificationTime(newAttemptNumber);
+        final DateTime notificationTime = getNextNotificationTime(newAttemptNumber, tenantContext);
         // Will be null in the GET path or when we run out opf attempts..
         if (notificationTime != null) {
             try {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java
index fbac8e0..cdb152d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java
@@ -35,7 +35,7 @@ import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.glue.DefaultPaymentService;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.notificationq.api.NotificationEvent;
@@ -135,7 +135,7 @@ public class Janitor {
                                                                             @Override
                                                                             public void handleReadyNotification(final NotificationEvent notificationKey, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
                                                                                 if (!(notificationKey instanceof JanitorNotificationKey)) {
-                                                                                    log.error("Janitor service received an unexpected event type {}" + notificationKey.getClass().getName());
+                                                                                    log.error("Janitor service received an unexpected event className='{}", notificationKey.getClass());
                                                                                     return;
 
                                                                                 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java
index 18fcb1e..afef161 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentExecutors.java
@@ -21,11 +21,12 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
 
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.concurrent.Executors;
 import org.killbill.commons.concurrent.WithProfilingThreadPoolExecutor;
 
@@ -43,7 +44,7 @@ public class PaymentExecutors {
 
     private final PaymentConfig paymentConfig;
 
-    private volatile ExecutorService pluginExecutorService;
+    private volatile ThreadPoolExecutor pluginExecutorService;
     private volatile ScheduledExecutorService janitorExecutorService;
 
     @Inject
@@ -54,6 +55,7 @@ public class PaymentExecutors {
 
     public void initialize() {
         this.pluginExecutorService = createPluginExecutorService();
+        this.pluginExecutorService.prestartAllCoreThreads();
         this.janitorExecutorService = createJanitorExecutorService();
     }
 
@@ -77,7 +79,7 @@ public class PaymentExecutors {
         return janitorExecutorService;
     }
 
-    private ExecutorService createPluginExecutorService() {
+    private ThreadPoolExecutor createPluginExecutorService() {
         final int minThreadNb = DEFAULT_MIN_PLUGIN_THREADS < paymentConfig.getPaymentPluginThreadNb() ? DEFAULT_MIN_PLUGIN_THREADS : paymentConfig.getPaymentPluginThreadNb();
         return new WithProfilingThreadPoolExecutor(minThreadNb,
                                                    paymentConfig.getPaymentPluginThreadNb(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
index 53ffd63..ea40106 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
@@ -44,7 +44,7 @@ import org.killbill.billing.payment.provider.DefaultNoOpHostedPaymentPageFormDes
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
index 0429c46..3048539 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
@@ -55,7 +55,7 @@ import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.entity.DefaultPagination;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.EntityPaginationBuilder;
@@ -139,7 +139,6 @@ public class PaymentMethodProcessor extends ProcessorBase {
                                                                                                                     accountInternalApi.updatePaymentMethod(account.getId(), pm.getId(), context);
                                                                                                                 }
                                                                                                             } catch (final PaymentPluginApiException e) {
-                                                                                                                log.warn("Error adding payment method " + pm.getId() + " for plugin " + paymentPluginServiceName, e);
                                                                                                                 throw new PaymentApiException(ErrorCode.PAYMENT_ADD_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
                                                                                                             } catch (final AccountApiException e) {
                                                                                                                 throw new PaymentApiException(e);
@@ -150,7 +149,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
 
                                                                                                         private void validateUniqueExternalPaymentMethod(final UUID accountId, final String pluginName) throws PaymentApiException {
                                                                                                             if (ExternalPaymentProviderPlugin.PLUGIN_NAME.equals(pluginName)) {
-                                                                                                                final List<PaymentMethodModelDao> accountPaymentMethods = paymentDao.getPaymentMethods(accountId, context);
+                                                                                                                final List<PaymentMethodModelDao> accountPaymentMethods = paymentDao.getPaymentMethods(context);
                                                                                                                 if (Iterables.any(accountPaymentMethods, new Predicate<PaymentMethodModelDao>() {
                                                                                                                     @Override
                                                                                                                     public boolean apply(final PaymentMethodModelDao input) {
@@ -176,7 +175,11 @@ public class PaymentMethodProcessor extends ProcessorBase {
         try {
             paymentMethodPlugin = pluginApi.getPaymentMethodDetail(account.getId(), pm.getId(), properties, callContext);
         } catch (final PaymentPluginApiException e) {
-            log.warn("Error retrieving payment method " + pm.getId() + " from plugin " + pm.getPluginName(), e);
+            if (e.getCause() == null) {
+                log.warn("Error retrieving paymentMethodId='{}', plugin='{}', errorMessage='{}', errorType='{}'", pm.getId(), pm.getPluginName(), e.getErrorMessage(), e.getErrorType());
+            } else {
+                log.warn("Error retrieving paymentMethodId='{}', plugin='{}', errorMessage='{}', errorType='{}'", pm.getId(), pm.getPluginName(), e.getErrorMessage(), e.getErrorType(), e);
+            }
             return null;
         }
 
@@ -189,12 +192,12 @@ public class PaymentMethodProcessor extends ProcessorBase {
         }
     }
 
-    public List<PaymentMethod> getPaymentMethods(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final InternalTenantContext context) throws PaymentApiException {
-        return getPaymentMethods(accountId, withPluginInfo, properties, buildTenantContext(context), context);
+    public List<PaymentMethod> getPaymentMethods(final boolean withPluginInfo, final Iterable<PluginProperty> properties, final InternalTenantContext context) throws PaymentApiException {
+        return getPaymentMethods(withPluginInfo, properties, buildTenantContext(context), context);
     }
 
-    public List<PaymentMethod> getPaymentMethods(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
-        final List<PaymentMethodModelDao> paymentMethodModels = paymentDao.getPaymentMethods(accountId, context);
+    public List<PaymentMethod> getPaymentMethods(final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
+        final List<PaymentMethodModelDao> paymentMethodModels = paymentDao.getPaymentMethods(context);
         if (paymentMethodModels.isEmpty()) {
             return Collections.emptyList();
         }
@@ -227,7 +230,6 @@ public class PaymentMethodProcessor extends ProcessorBase {
                 final PaymentPluginApi pluginApi = getPaymentPluginApi(paymentMethodModelDao.getPluginName());
                 paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), properties, tenantContext);
             } catch (final PaymentPluginApiException e) {
-                log.warn("Error retrieving payment method " + paymentMethodModelDao.getId() + " from plugin " + paymentMethodModelDao.getPluginName(), e);
                 throw new PaymentApiException(ErrorCode.PAYMENT_GET_PAYMENT_METHODS, paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId());
             }
         } else {
@@ -270,7 +272,11 @@ public class PaymentMethodProcessor extends ProcessorBase {
                                                try {
                                                    paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), properties, tenantContext);
                                                } catch (final PaymentPluginApiException e) {
-                                                   log.warn("Unable to find payment method id " + paymentMethodModelDao.getId() + " in plugin " + pluginName);
+                                                   if (e.getCause() == null) {
+                                                       log.warn("Error retrieving paymentMethodId='{}', plugin='{}', errorMessage='{}', errorType='{}'", paymentMethodModelDao.getId(), pluginName, e.getErrorMessage(), e.getErrorType());
+                                                   } else {
+                                                       log.warn("Error retrieving paymentMethodId='{}', plugin='{}', errorMessage='{}', errorType='{}'", paymentMethodModelDao.getId(), pluginName, e.getErrorMessage(), e.getErrorType(), e);
+                                                   }
                                                    // We still want to return a payment method object, even though the plugin details are missing
                                                }
                                            }
@@ -353,8 +359,8 @@ public class PaymentMethodProcessor extends ProcessorBase {
                                   );
     }
 
-    public PaymentMethod getExternalPaymentMethod(final UUID accountId, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
-        final List<PaymentMethod> paymentMethods = getPaymentMethods(accountId, false, properties, tenantContext, context);
+    public PaymentMethod getExternalPaymentMethod(final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
+        final List<PaymentMethod> paymentMethods = getPaymentMethods(false, properties, tenantContext, context);
         for (final PaymentMethod paymentMethod : paymentMethods) {
             if (ExternalPaymentProviderPlugin.PLUGIN_NAME.equals(paymentMethod.getPluginName())) {
                 return paymentMethod;
@@ -366,7 +372,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
     public UUID createOrGetExternalPaymentMethod(final String paymentMethodExternalKey, final Account account, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context) throws PaymentApiException {
         // Check if this account has already used the external payment plugin
         // If not, it's the first time - add a payment method for it
-        final PaymentMethod externalPaymentMethod = getExternalPaymentMethod(account.getId(), properties, callContext, context);
+        final PaymentMethod externalPaymentMethod = getExternalPaymentMethod(properties, callContext, context);
         if (externalPaymentMethod != null) {
             return externalPaymentMethod.getId();
         }
@@ -413,7 +419,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
                             } else {
                                 final boolean isAccountAutoPayOff = isAccountAutoPayOff(account.getId(), context);
                                 if (!isAccountAutoPayOff) {
-                                    log.info("Setting account {} to AUTO_PAY_OFF because of default payment method deletion", account.getId());
+                                    log.info("Setting AUTO_PAY_OFF on accountId='{}' because of default payment method deletion", account.getId());
                                     setAccountAutoPayOff(account.getId(), context);
                                 }
                                 accountInternalApi.removePaymentMethod(account.getId(), context);
@@ -424,7 +430,6 @@ public class PaymentMethodProcessor extends ProcessorBase {
                         paymentDao.deletedPaymentMethod(paymentMethodId, context);
                         return PluginDispatcher.createPluginDispatcherReturnType(null);
                     } catch (final PaymentPluginApiException e) {
-                        log.warn("Error deleting payment method " + paymentMethodId, e);
                         throw new PaymentApiException(ErrorCode.PAYMENT_DEL_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
                     } catch (final AccountApiException e) {
                         throw new PaymentApiException(e);
@@ -502,7 +507,6 @@ public class PaymentMethodProcessor extends ProcessorBase {
                 return ImmutableList.<PaymentMethod>of();
             }
         } catch (final PaymentPluginApiException e) {
-            log.warn("Error refreshing payment methods for account " + account.getId() + " and plugin " + pluginName, e);
             throw new PaymentApiException(ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
         }
 
@@ -534,15 +538,13 @@ public class PaymentMethodProcessor extends ProcessorBase {
                         }
                     }
 
-                    final List<PaymentMethodModelDao> refreshedPaymentMethods = paymentDao.refreshPaymentMethods(account.getId(),
-                                                                                                                 pluginName,
+                    final List<PaymentMethodModelDao> refreshedPaymentMethods = paymentDao.refreshPaymentMethods(pluginName,
                                                                                                                  finalPaymentMethods,
                                                                                                                  context);
 
                     try {
                         pluginApi.resetPaymentMethods(account.getId(), pluginPmsWithId, properties, callContext);
                     } catch (final PaymentPluginApiException e) {
-                        log.warn("Error resetting payment methods for account " + account.getId() + " and plugin " + pluginName, e);
                         throw new PaymentApiException(ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
                     }
                     try {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
index 85ba858..17e87d8 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
@@ -51,12 +51,16 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 
 public class PluginControlPaymentProcessor extends ProcessorBase {
 
+    private static final Logger log = LoggerFactory.getLogger(PluginControlPaymentProcessor.class);
+
     private static final Joiner JOINER = Joiner.on(", ");
 
     private final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner;
@@ -225,20 +229,20 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
                                                        internalCallContext);
 
         } catch (final AccountApiException e) {
-            log.warn("Failed to retry attempt " + attemptId + toPluginNamesOnError(" for plugins ", paymentControlPluginNames), e);
+            log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
         } catch (final PaymentApiException e) {
-            log.warn("Failed to retry attempt " + attemptId + toPluginNamesOnError(" for plugins ", paymentControlPluginNames), e);
+            log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
         } catch (final PluginPropertySerializerException e) {
-            log.warn("Failed to retry attempt " + attemptId + toPluginNamesOnError(" for plugins ", paymentControlPluginNames), e);
+            log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
         } catch (final MissingEntryException e) {
-            log.warn("Failed to retry attempt " + attemptId + toPluginNamesOnError(" for plugins ", paymentControlPluginNames), e);
+            log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
         }
     }
 
-    private String toPluginNamesOnError(final String prefixMessage, final Collection<String> paymentControlPluginNames) {
+    private String toPluginNamesOnError(final Collection<String> paymentControlPluginNames) {
         if (paymentControlPluginNames == null || paymentControlPluginNames.isEmpty()) {
             return "";
         }
-        return prefixMessage + "(" + JOINER.join(paymentControlPluginNames) + ")";
+        return JOINER.join(paymentControlPluginNames);
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
index dd41668..a18340b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
@@ -43,7 +43,7 @@ import org.killbill.billing.util.api.TagApiException;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
@@ -59,6 +59,8 @@ import com.google.common.collect.Collections2;
 
 public abstract class ProcessorBase {
 
+    private static final Logger log = LoggerFactory.getLogger(ProcessorBase.class);
+
     protected final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
     protected final AccountInternalApi accountInternalApi;
     protected final GlobalLocker locker;
@@ -66,8 +68,6 @@ public abstract class ProcessorBase {
     protected final InternalCallContextFactory internalCallContextFactory;
     protected final TagInternalApi tagInternalApi;
     protected final Clock clock;
-
-    protected static final Logger log = LoggerFactory.getLogger(ProcessorBase.class);
     protected final InvoiceInternalApi invoiceApi;
 
     public ProcessorBase(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
index 4f05f5e..8da4e82 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
@@ -17,13 +17,11 @@
 package org.killbill.billing.payment.core.sm.control;
 
 import org.killbill.automaton.OperationResult;
-import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class AuthorizeControlOperation extends OperationControlCallback {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
index 54f133f..36776b3 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
@@ -17,13 +17,11 @@
 package org.killbill.billing.payment.core.sm.control;
 
 import org.killbill.automaton.OperationResult;
-import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class CaptureControlOperation extends OperationControlCallback {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
index 4836a2b..6837d0f 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
@@ -18,13 +18,11 @@
 package org.killbill.billing.payment.core.sm.control;
 
 import org.killbill.automaton.OperationResult;
-import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class ChargebackControlOperation extends OperationControlCallback {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
index c8bbf8f..fd8eed7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
@@ -31,7 +31,7 @@ import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.control.plugin.api.PaymentControlContext;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 import com.google.common.base.Joiner;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
index 7d4e20b..7f0ba27 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
@@ -187,9 +187,9 @@ public class ControlPluginRunner {
                     }
                     // Exceptions from the control plugins are ignored (and logged) because the semantics on what to do are undefined.
                 } catch (final PaymentControlApiException e) {
-                    log.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + inputPaymentControlContext.getPaymentExternalKey(), e);
+                    log.warn("Error during onSuccessCall for plugin='{}', paymentExternalKey='{}'", pluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
                 } catch (final RuntimeException e) {
-                    log.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + inputPaymentControlContext.getPaymentExternalKey(), e);
+                    log.warn("Error during onSuccessCall for plugin='{}', paymentExternalKey='{}'", pluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
                 }
             }
         }
@@ -247,7 +247,7 @@ public class ControlPluginRunner {
                     }
 
                 } catch (final PaymentControlApiException e) {
-                    log.warn("Plugin " + pluginName + " failed to return next retryDate for payment " + inputPaymentControlContext.getPaymentExternalKey(), e);
+                    log.warn("Error during onFailureCall for plugin='{}', paymentExternalKey='{}'", pluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
                     return new DefaultFailureCallResult(candidate, inputPluginProperties);
                 }
             }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
index 5d2c643..9e91396 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
@@ -17,13 +17,11 @@
 package org.killbill.billing.payment.core.sm.control;
 
 import org.killbill.automaton.OperationResult;
-import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class CreditControlOperation extends OperationControlCallback {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
index a463954..22f53e1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
@@ -45,7 +45,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.LockFailedException;
 import org.slf4j.Logger;
@@ -150,31 +150,32 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
                 } catch (final RuntimeException e) {
                     // Attempts to set the retry date in context if needed.
                     executePluginOnFailureCallsAndSetRetryDate(paymentControlContext);
-                    throw e;
+                    throw new OperationException(e, OperationResult.EXCEPTION);
                 }
             }
         });
     }
 
     @Override
-    protected OperationException unwrapExceptionFromDispatchedTask(final PaymentStateContext paymentStateContext, final Exception e) {
-
+    protected OperationException unwrapExceptionFromDispatchedTask(final Exception e) {
         // If this is an ExecutionException we attempt to extract the cause first
-        final Throwable originalExceptionOrCause = e instanceof ExecutionException ? MoreObjects.firstNonNull(e.getCause(), e) : e;
+        final Throwable originalExceptionOrCausePossiblyOperationException = e instanceof ExecutionException ? MoreObjects.firstNonNull(e.getCause(), e) : e;
+
+        // Unwrap OperationException too (doOperationCallback wraps exceptions in OperationException)
+        final Throwable originalExceptionOrCause = originalExceptionOrCausePossiblyOperationException instanceof OperationException ? MoreObjects.firstNonNull(originalExceptionOrCausePossiblyOperationException.getCause(), originalExceptionOrCausePossiblyOperationException) : originalExceptionOrCausePossiblyOperationException;
 
         if (originalExceptionOrCause instanceof OperationException) {
             return (OperationException) originalExceptionOrCause;
         } else if (originalExceptionOrCause instanceof LockFailedException) {
-            final String format = String.format("Failed to lock account %s", paymentStateContext.getAccount().getExternalKey());
-            logger.error(String.format(format));
+            logger.warn("Failed to lock accountId='{}'", paymentStateContext.getAccount().getId());
         } else if (originalExceptionOrCause instanceof TimeoutException) {
-            logger.warn("RetryOperationCallback call TIMEOUT for account {}", paymentStateContext.getAccount().getExternalKey());
+            logger.warn("Call TIMEOUT for accountId='{}'", paymentStateContext.getAccount().getId());
         } else if (originalExceptionOrCause instanceof InterruptedException) {
-            logger.error("RetryOperationCallback call was interrupted for account {}", paymentStateContext.getAccount().getExternalKey());
-        } else /* most probably RuntimeException */ {
-            logger.warn("RetryOperationCallback failed for account {}", paymentStateContext.getAccount().getExternalKey(), e);
+            logger.warn("Call was interrupted for accountId='{}'", paymentStateContext.getAccount().getId());
+        } else {
+            logger.warn("Operation failed for accountId='{}'", paymentStateContext.getAccount().getId(), e);
         }
-        return new OperationException(e, getOperationResultOnException(paymentStateContext));
+        return new OperationException(originalExceptionOrCause, getOperationResultOnException(paymentStateContext));
     }
 
     private OperationResult getOperationResultOnException(final PaymentStateContext paymentStateContext) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
index 4a766c7..401b780 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
@@ -17,13 +17,11 @@
 package org.killbill.billing.payment.core.sm.control;
 
 import org.killbill.automaton.OperationResult;
-import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class PurchaseControlOperation extends OperationControlCallback {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
index 4c60dd3..64fda3f 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
@@ -17,13 +17,11 @@
 package org.killbill.billing.payment.core.sm.control;
 
 import org.killbill.automaton.OperationResult;
-import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class RefundControlOperation extends OperationControlCallback {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
index db74fa9..f600e8b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
@@ -17,13 +17,11 @@
 package org.killbill.billing.payment.core.sm.control;
 
 import org.killbill.automaton.OperationResult;
-import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 
 public class VoidControlOperation extends OperationControlCallback {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
index f5c542d..5547c22 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
@@ -28,14 +28,14 @@ import org.killbill.billing.payment.core.ProcessorBase.CallableWithAccountLock;
 import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOperationException extends Exception> {
 
-    protected final Logger logger = LoggerFactory.getLogger(OperationCallbackBase.class);
+    private final Logger logger = LoggerFactory.getLogger(OperationCallbackBase.class);
 
     private final GlobalLocker locker;
     private final PluginDispatcher<OperationResult> paymentPluginDispatcher;
@@ -72,14 +72,14 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
             logger.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, account.getExternalKey(), operationResult);
             return operationResult;
         } catch (final ExecutionException e) {
-            throw unwrapExceptionFromDispatchedTask(paymentStateContext, e);
+            throw unwrapExceptionFromDispatchedTask(e);
         } catch (final TimeoutException e) {
             logger.warn("TimeoutException while executing the plugin(s) {}", pluginNames);
-            throw unwrapExceptionFromDispatchedTask(paymentStateContext, e);
+            throw unwrapExceptionFromDispatchedTask(e);
         } catch (final InterruptedException e) {
             Thread.currentThread().interrupt();
             logger.warn("InterruptedException while executing the following plugin(s): {}", pluginNames);
-            throw unwrapExceptionFromDispatchedTask(paymentStateContext, e);
+            throw unwrapExceptionFromDispatchedTask(e);
         }
     }
 
@@ -91,5 +91,5 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
     //
     protected abstract CallbackOperationResult doCallSpecificOperationCallback() throws CallbackOperationException;
 
-    protected abstract OperationException unwrapExceptionFromDispatchedTask(final PaymentStateContext paymentStateContext, final Exception e);
+    protected abstract OperationException unwrapExceptionFromDispatchedTask(final Exception e);
 }
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 4587b28..f9742b1 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
@@ -71,7 +71,7 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.util.callcontext.CallContext;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java
index d8eb405..fdbe229 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java
@@ -24,7 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java
index e6b67c5..cbafd42 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java
@@ -24,7 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java
index cf477d5..230e73a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java
@@ -27,7 +27,7 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java
index 53b5634..2d26268 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java
@@ -24,7 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentEnteringStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentEnteringStateCallback.java
index f5dd2f2..d336109 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentEnteringStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentEnteringStateCallback.java
@@ -76,7 +76,7 @@ public abstract class PaymentEnteringStateCallback implements EnteringStateCallb
             try {
                 daoHelper.getEventBus().post(event);
             } catch (EventBusException e) {
-                logger.error("Failed to post Payment event event for account {} ", paymentStateContext.getAccount().getId(), e);
+                logger.warn("Failed to post event {}", event, e);
             }
         }
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java
index edc6f70..831b349 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java
@@ -18,6 +18,9 @@
 package org.killbill.billing.payment.core.sm.payments;
 
 import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
 
 import org.killbill.automaton.OperationException;
 import org.killbill.automaton.State;
@@ -25,6 +28,7 @@ import org.killbill.automaton.State.LeavingStateCallback;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper;
 import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
@@ -58,29 +62,38 @@ public abstract class PaymentLeavingStateCallback implements LeavingStateCallbac
                 throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, paymentStateContext.getAccount().getId());
             }
 
+            // If we were given a paymentId (or existing paymentExternalId -> effectivePaymentId) we first fetch existing transactions (required for sanity and handling PENDING transactions)
+            final List<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment = paymentStateContext.getPaymentId() != null ?
+                                                                                          daoHelper.getPaymentDao().getTransactionsForPayment(paymentStateContext.getPaymentId(), paymentStateContext.getInternalCallContext()) :
+                                                                                          ImmutableList.<PaymentTransactionModelDao>of();
+
             //
             // Extract existing transaction matching the transactionId if specified (for e.g notifyPendingTransactionOfStateChanged), or based on transactionExternalKey
             //
-            final List<PaymentTransactionModelDao> existingPaymentTransactions;
-            if (paymentStateContext.getTransactionId() != null) {
-                final PaymentTransactionModelDao transactionModelDao = daoHelper.getPaymentDao().getPaymentTransaction(paymentStateContext.getTransactionId(), paymentStateContext.getInternalCallContext());
-                existingPaymentTransactions = ImmutableList.of(transactionModelDao);
-            } else if (paymentStateContext.getPaymentTransactionExternalKey() != null) {
-                existingPaymentTransactions = daoHelper.getPaymentDao().getPaymentTransactionsByExternalKey(paymentStateContext.getPaymentTransactionExternalKey(), paymentStateContext.getInternalCallContext());
-            } else {
-                existingPaymentTransactions = ImmutableList.of();
-            }
+            final Iterable<PaymentTransactionModelDao> existingPaymentTransactionsForTransactionIdOrKey = filterExistingPaymentTransactionsForTransactionIdOrKey(paymentTransactionsForCurrentPayment, paymentStateContext.getTransactionId(), paymentStateContext.getPaymentTransactionExternalKey());
 
             // Validate the payment transactions belong to the right payment
-            validatePaymentId(existingPaymentTransactions);
+            validatePaymentIdAndTransactionType(existingPaymentTransactionsForTransactionIdOrKey);
 
             // Validate some constraints on the unicity of that paymentTransactionExternalKey
-            validateUniqueTransactionExternalKey(existingPaymentTransactions);
+            validateUniqueTransactionExternalKey(existingPaymentTransactionsForTransactionIdOrKey);
+
+            //
+            // Handle PENDING case:
+            // a) If we have a PENDING transaction for the same (payment transaction) key, this is a completion and we want to re-use the same transaction
+            // b) If we have a PENDING transaction for a different (payment transaction) key, and for an initial request (AUTH, PURCHASE, CREDIT), we FAIL the request
+            //   (unfortunately this cannot be caught by the state machine because the transition XXX_PENDING -> _SUCCESS needs to be allowed and this is irrespective of the keys)
+            // c) If we have a PENDING transaction for a different (payment transaction) key, and for other follow-up request  (CAPTURE, REFUND, ..), we ignore it and create a new transaction
+            //
+            final Iterable<PaymentTransactionModelDao> pendingTransactionsForPaymentAndTransactionType = filterPendingTransactionsForPaymentAndTransactionType(paymentTransactionsForCurrentPayment, paymentStateContext.getTransactionType());
 
-            // Handle PENDING cases, where we want to re-use the same transaction
-            final PaymentTransactionModelDao pendingPaymentTransaction = getPendingPaymentTransaction(existingPaymentTransactions);
+            // Case b)
+            validateUniqueInitialPendingTransaction(pendingTransactionsForPaymentAndTransactionType, paymentStateContext.getTransactionType(), paymentStateContext.getPaymentTransactionExternalKey());
+
+
+            final PaymentTransactionModelDao pendingPaymentTransaction = filterPendingTransactionsForTransactionKey(pendingTransactionsForPaymentAndTransactionType, paymentStateContext.getPaymentTransactionExternalKey());
             if (pendingPaymentTransaction != null) {
-                // Set the current paymentTransaction in the context (needed for the state machine logic)
+                // Case a) Set the current paymentTransaction in the context (needed for the state machine logic)
                 paymentStateContext.setPaymentTransactionModelDao(pendingPaymentTransaction);
                 return;
             }
@@ -93,25 +106,61 @@ public abstract class PaymentLeavingStateCallback implements LeavingStateCallbac
         }
     }
 
-    protected PaymentTransactionModelDao getUnknownPaymentTransaction(final List<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
-        return Iterables.tryFind(existingPaymentTransactions, new Predicate<PaymentTransactionModelDao>() {
+    private void validateUniqueInitialPendingTransaction(final Iterable<PaymentTransactionModelDao> pendingTransactionsForPaymentAndTransactionType, final TransactionType transactionType, final String paymentTransactionExternalKey) {
+        if (transactionType != TransactionType.AUTHORIZE &&
+            transactionType != TransactionType.PURCHASE &&
+            transactionType != TransactionType.CREDIT) {
+            return;
+        }
+
+        final PaymentTransactionModelDao existingPendingTransactionForDifferentKey = Iterables.tryFind(pendingTransactionsForPaymentAndTransactionType, new Predicate<PaymentTransactionModelDao>() {
             @Override
             public boolean apply(final PaymentTransactionModelDao input) {
-                return input.getTransactionStatus() == TransactionStatus.UNKNOWN;
+                return !input.getTransactionExternalKey().equals(paymentTransactionExternalKey);
             }
         }).orNull();
+        if (existingPendingTransactionForDifferentKey !=  null) {
+            // We are missing ErrorCode PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS (should be fixed in 0.17.0. See #525)
+            throw new RuntimeException(String.format("Failed to create another initial transaction for paymentId='%s' : Existing PENDING transactionId='%s'",
+                                                          existingPendingTransactionForDifferentKey.getPaymentId(), existingPendingTransactionForDifferentKey.getId()));
+        }
     }
 
-    protected PaymentTransactionModelDao getPendingPaymentTransaction(final List<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
-        return Iterables.tryFind(existingPaymentTransactions, new Predicate<PaymentTransactionModelDao>() {
+    protected Iterable<PaymentTransactionModelDao> filterExistingPaymentTransactionsForTransactionIdOrKey(final List<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment, @Nullable final UUID paymentTransactionId, @Nullable final String paymentTransactionExternalKey) throws PaymentApiException {
+        return Iterables.filter(paymentTransactionsForCurrentPayment, new Predicate<PaymentTransactionModelDao>() {
             @Override
             public boolean apply(final PaymentTransactionModelDao input) {
-                return input.getTransactionStatus() == TransactionStatus.PENDING;
+                if (paymentTransactionId != null && input.getId().equals(paymentTransactionId)) {
+                    return true;
+                }
+                if (paymentTransactionExternalKey != null && input.getTransactionExternalKey().equals(paymentTransactionExternalKey)) {
+                    return true;
+                }
+                return false;
+            }
+        });
+    }
+
+    protected Iterable<PaymentTransactionModelDao> filterPendingTransactionsForPaymentAndTransactionType(final Iterable<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment, final TransactionType transactionType) throws PaymentApiException {
+        return Iterables.filter(paymentTransactionsForCurrentPayment, new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao input) {
+                return input.getTransactionStatus() == TransactionStatus.PENDING &&
+                       input.getTransactionType() == transactionType;
+            }
+        });
+    }
+
+    protected PaymentTransactionModelDao filterPendingTransactionsForTransactionKey(final Iterable<PaymentTransactionModelDao> existingPendingPaymentTransactions, final String paymentTransactionExternalKey) throws PaymentApiException {
+        return Iterables.tryFind(existingPendingPaymentTransactions, new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao input) {
+                return input.getTransactionExternalKey().equals(paymentTransactionExternalKey);
             }
         }).orNull();
     }
 
-    protected void validateUniqueTransactionExternalKey(final List<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
+    protected void validateUniqueTransactionExternalKey(final Iterable<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
         // If no key specified, system will allocate a unique one later, there is nothing to check
         if (paymentStateContext.getPaymentTransactionExternalKey() == null) {
             return;
@@ -134,11 +183,15 @@ public abstract class PaymentLeavingStateCallback implements LeavingStateCallbac
     }
 
     // At this point, the payment id should have been populated for follow-up transactions (see PaymentAutomationRunner#run)
-    protected void validatePaymentId(final List<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
+    protected void validatePaymentIdAndTransactionType(final Iterable<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
         for (final PaymentTransactionModelDao paymentTransactionModelDao : existingPaymentTransactions) {
             if (!paymentTransactionModelDao.getPaymentId().equals(paymentStateContext.getPaymentId())) {
                 throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, paymentTransactionModelDao.getId(), "does not belong to payment " + paymentStateContext.getPaymentId());
             }
+            if (paymentStateContext.getTransactionType() != null && paymentTransactionModelDao.getTransactionType() != paymentStateContext.getTransactionType()) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, paymentTransactionModelDao.getId(), "has a transaction type of " + paymentTransactionModelDao.getTransactionType() +
+                                                                                                                       " instead of requested " + paymentStateContext.getTransactionType());
+            }
         }
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
index 4b11c6b..4f16b73 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
@@ -42,9 +42,11 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.LockFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicate;
@@ -54,6 +56,8 @@ import com.google.common.collect.Iterables;
 // Encapsulates the payment specific logic
 public abstract class PaymentOperation extends OperationCallbackBase<PaymentTransactionInfoPlugin, PaymentPluginApiException> implements OperationCallback {
 
+    private final Logger logger = LoggerFactory.getLogger(PaymentOperation.class);
+
     protected final PaymentAutomatonDAOHelper daoHelper;
     protected PaymentPluginApi plugin;
 
@@ -68,41 +72,49 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
 
     @Override
     public OperationResult doOperationCallback() throws OperationException {
+        final String pluginName;
         try {
-            final String pluginName = daoHelper.getPaymentProviderPluginName();
+            pluginName = daoHelper.getPaymentProviderPluginName();
             this.plugin = daoHelper.getPaymentPluginApi(pluginName);
+        } catch (final PaymentApiException e) {
+            throw convertToUnknownTransactionStatusAndErroredPaymentState(e);
+        }
 
-            if (paymentStateContext.shouldLockAccountAndDispatch()) {
-                return doOperationCallbackWithDispatchAndAccountLock(pluginName);
-            } else {
+        if (paymentStateContext.shouldLockAccountAndDispatch()) {
+            // This will already call unwrapExceptionFromDispatchedTask
+            return doOperationCallbackWithDispatchAndAccountLock(pluginName);
+        } else {
+            try {
                 return doSimpleOperationCallback();
+            } catch (final Exception e) {
+                // We need to unwrap OperationException (see doSimpleOperationCallback below)
+                throw unwrapExceptionFromDispatchedTask(e);
             }
-        } catch (final Exception e) {
-            throw convertToUnknownTransactionStatusAndErroredPaymentState(e);
         }
     }
 
     @Override
-    protected OperationException unwrapExceptionFromDispatchedTask(final PaymentStateContext paymentStateContext, final Exception e) {
-
+    protected OperationException unwrapExceptionFromDispatchedTask(final Exception e) {
         // If this is an ExecutionException we attempt to extract the cause first
-        final Throwable originalExceptionOrCause = e instanceof ExecutionException ? MoreObjects.firstNonNull(e.getCause(), e) : e;
+        final Throwable originalExceptionOrCausePossiblyOperationException = e instanceof ExecutionException ? MoreObjects.firstNonNull(e.getCause(), e) : e;
+
+        // Unwrap OperationException too (doOperationCallback wraps exceptions in OperationException)
+        final Throwable originalExceptionOrCause = originalExceptionOrCausePossiblyOperationException instanceof OperationException ? MoreObjects.firstNonNull(originalExceptionOrCausePossiblyOperationException.getCause(), originalExceptionOrCausePossiblyOperationException) : originalExceptionOrCausePossiblyOperationException;
 
         //
         // Any case of exception (checked or runtime) should lead to a TransactionStatus.UNKNOWN (and a XXX_ERRORED payment state).
         // In order to reach that state we create PaymentTransactionInfoPlugin with an PaymentPluginStatus.UNDEFINED status (and an OperationResult.EXCEPTION).
         //
         if (originalExceptionOrCause instanceof LockFailedException) {
-            logger.warn("Failed to lock account {}", paymentStateContext.getAccount().getExternalKey());
+            logger.warn("Failed to lock accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey());
         } else if (originalExceptionOrCause instanceof TimeoutException) {
-            logger.error("Plugin call TIMEOUT for account {}", paymentStateContext.getAccount().getExternalKey());
+            logger.warn("Plugin call TIMEOUT for accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey());
         } else if (originalExceptionOrCause instanceof InterruptedException) {
-            logger.error("Plugin call was interrupted for account {}", paymentStateContext.getAccount().getExternalKey());
+            logger.warn("Plugin call was interrupted for accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey());
         } else {
-            logger.warn("Payment plugin call threw an exception for account {}", paymentStateContext.getAccount().getExternalKey(), originalExceptionOrCause);
+            logger.warn("Payment plugin call threw an exception for accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey(), originalExceptionOrCause);
         }
         return convertToUnknownTransactionStatusAndErroredPaymentState(originalExceptionOrCause);
-
     }
 
     //
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java
index 1ddf4b1..7fab5e1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java
@@ -24,7 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java
index 22d9239..b236ac0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java
@@ -24,7 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java
index 0d776b6..b6542d9 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java
@@ -24,7 +24,7 @@ import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
index 5d95ed7..f8e27a7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
@@ -31,7 +31,6 @@ import org.killbill.automaton.OperationException;
 import org.killbill.automaton.State;
 import org.killbill.automaton.State.EnteringStateCallback;
 import org.killbill.automaton.State.LeavingStateCallback;
-import org.killbill.billing.BillingExceptionBase;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalCallContext;
@@ -62,7 +61,7 @@ import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
 import org.killbill.billing.util.callcontext.CallContext;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
index 2c849ca..2053ba1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
@@ -444,11 +444,11 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    public List<PaymentMethodModelDao> getPaymentMethods(final UUID accountId, final InternalTenantContext context) {
+    public List<PaymentMethodModelDao> getPaymentMethods(final InternalTenantContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentMethodModelDao>>() {
             @Override
             public List<PaymentMethodModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getByAccountId(accountId.toString(), context);
+                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getForAccount(context);
             }
         });
     }
@@ -513,8 +513,7 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    public List<PaymentMethodModelDao> refreshPaymentMethods(final UUID accountId, final String pluginName,
-                                                             final List<PaymentMethodModelDao> newPaymentMethods, final InternalCallContext context) {
+    public List<PaymentMethodModelDao> refreshPaymentMethods(final String pluginName, final List<PaymentMethodModelDao> newPaymentMethods, final InternalCallContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentMethodModelDao>>() {
 
             @Override
@@ -523,7 +522,7 @@ public class DefaultPaymentDao implements PaymentDao {
                 // Look at all payment methods, including deleted ones. We assume that newPaymentMethods (payment methods returned by the plugin)
                 // is the full set of non-deleted payment methods in the plugin. If a payment method was marked as deleted on our side,
                 // but is still existing in the plugin, we will un-delete it.
-                final List<PaymentMethodModelDao> allPaymentMethodsForAccount = transactional.getByAccountIdIncludedDelete(accountId.toString(), context);
+                final List<PaymentMethodModelDao> allPaymentMethodsForAccount = transactional.getForAccountIncludedDelete(context);
 
                 // Consider only the payment methods for the plugin we are refreshing
                 final Collection<PaymentMethodModelDao> existingPaymentMethods = Collections2.filter(allPaymentMethodsForAccount,
@@ -567,7 +566,7 @@ public class DefaultPaymentDao implements PaymentDao {
                         deletedPaymentMethodInTransaction(entitySqlDaoWrapperFactory, existingPaymentMethod.getId(), context);
                     }
                 }
-                return transactional.getByAccountId(accountId.toString(), context);
+                return transactional.getForAccount(context);
             }
         });
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
index 69b6941..4c3eeee 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
@@ -84,7 +84,7 @@ public interface PaymentDao {
 
     public PaymentMethodModelDao getPaymentMethodByExternalKeyIncludedDeleted(String paymentMethodExternalKey, InternalTenantContext context);
 
-    public List<PaymentMethodModelDao> getPaymentMethods(UUID accountId, InternalTenantContext context);
+    public List<PaymentMethodModelDao> getPaymentMethods(InternalTenantContext context);
 
     public Pagination<PaymentMethodModelDao> getPaymentMethods(String pluginName, Long offset, Long limit, InternalTenantContext context);
 
@@ -92,5 +92,5 @@ public interface PaymentDao {
 
     public void deletedPaymentMethod(UUID paymentMethodId, InternalCallContext context);
 
-    public List<PaymentMethodModelDao> refreshPaymentMethods(UUID accountId, String pluginName, List<PaymentMethodModelDao> paymentMethods, InternalCallContext context);
+    public List<PaymentMethodModelDao> refreshPaymentMethods(String pluginName, List<PaymentMethodModelDao> paymentMethods, InternalCallContext context);
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
index 0db4703..ec7cc3d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -57,10 +59,10 @@ public interface PaymentMethodSqlDao extends EntitySqlDao<PaymentMethodModelDao,
                                                          @BindBean final InternalTenantContext context);
 
     @SqlQuery
-    List<PaymentMethodModelDao> getByAccountId(@Bind("accountId") final String accountId, @BindBean final InternalTenantContext context);
+    List<PaymentMethodModelDao> getForAccount(@BindBean final InternalTenantContext context);
 
     @SqlQuery
-    List<PaymentMethodModelDao> getByAccountIdIncludedDelete(@Bind("accountId") final String accountId, @BindBean final InternalTenantContext context);
+    List<PaymentMethodModelDao> getForAccountIncludedDelete(@BindBean final InternalTenantContext context);
 
     @SqlQuery
     @SmartFetchSize(shouldStream = true)
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
index d8012d2..01b15f1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
@@ -17,6 +17,7 @@
 
 package org.killbill.billing.payment.dispatcher;
 
+import java.util.Map;
 import java.util.Random;
 import java.util.concurrent.Callable;
 
@@ -26,6 +27,7 @@ import org.apache.shiro.util.ThreadContext;
 import org.killbill.billing.util.UUIDs;
 import org.killbill.commons.request.Request;
 import org.killbill.commons.request.RequestData;
+import org.slf4j.MDC;
 
 public class CallableWithRequestData<T> implements Callable<T> {
 
@@ -33,13 +35,20 @@ public class CallableWithRequestData<T> implements Callable<T> {
     private final Random random;
     private final SecurityManager securityManager;
     private final Subject subject;
+    private final Map<String, String> mdcContextMap;
     private final Callable<T> delegate;
 
-    public CallableWithRequestData(final RequestData requestData, final Random random, final SecurityManager securityManager, final Subject subject, final Callable<T> delegate) {
+    public CallableWithRequestData(final RequestData requestData,
+                                   final Random random,
+                                   final SecurityManager securityManager,
+                                   final Subject subject,
+                                   final Map<String, String> mdcContextMap,
+                                   final Callable<T> delegate) {
         this.requestData = requestData;
         this.random = random;
         this.securityManager = securityManager;
         this.subject = subject;
+        this.mdcContextMap = mdcContextMap;
         this.delegate = delegate;
     }
 
@@ -50,12 +59,14 @@ public class CallableWithRequestData<T> implements Callable<T> {
             UUIDs.setRandom(random);
             ThreadContext.bind(securityManager);
             ThreadContext.bind(subject);
+            MDC.setContextMap(mdcContextMap);
             return delegate.call();
         } finally {
             Request.resetPerThreadRequestData();
             UUIDs.setRandom(null);
             ThreadContext.unbindSecurityManager();
             ThreadContext.unbindSubject();
+            MDC.clear();
         }
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
index 8454892..fec3c2e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -48,20 +48,20 @@ public class PaymentPluginDispatcher {
             log.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, accountId, result);
             return result;
         } catch (final TimeoutException e) {
-            final String errorMessage = String.format("TimeoutException while executing the plugin(s) %s", pluginNames);
-            log.warn(errorMessage, e);
+            final String errorMessage = String.format("TimeoutException while executing plugin='%s'", pluginNames);
+            log.warn(errorMessage);
             throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, errorMessage);
         } catch (final InterruptedException e) {
             Thread.currentThread().interrupt();
-            final String errorMessage = String.format("InterruptedException while executing the following plugin(s): %s", pluginNames);
+            final String errorMessage = String.format("InterruptedException while executing plugin='%s'", pluginNames);
             log.warn(errorMessage, e);
             throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), errorMessage));
         } catch (final ExecutionException e) {
             if (e.getCause() instanceof PaymentApiException) {
                 throw (PaymentApiException) e.getCause();
             } else if (e.getCause() instanceof LockFailedException) {
-                final String format = String.format("Failed to lock account %s", accountExternalKey);
-                log.error(format, e);
+                final String format = String.format("Failed to lock accountExternalKey='%s'", accountExternalKey);
+                log.warn(format);
                 throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, format);
             } else {
                 throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
index fa65e1b..c31bbbe 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
@@ -31,6 +31,7 @@ import org.killbill.billing.util.UUIDs;
 import org.killbill.commons.profiling.Profiling;
 import org.killbill.commons.profiling.ProfilingData;
 import org.killbill.commons.request.Request;
+import org.slf4j.MDC;
 
 import com.google.common.annotations.VisibleForTesting;
 
@@ -62,6 +63,7 @@ public class PluginDispatcher<ReturnType> {
                                                                                                                      UUIDs.getRandom(),
                                                                                                                      ThreadContext.getSecurityManager(),
                                                                                                                      ThreadContext.getSubject(),
+                                                                                                                     MDC.getCopyOfContextMap(),
                                                                                                                      task);
 
         final Future<PluginDispatcherReturnType<ReturnType>> future = pluginExecutor.submit(callableWithRequestData);
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentProviderPluginRegistryProvider.java b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentProviderPluginRegistryProvider.java
index dc4cd18..91d7de3 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentProviderPluginRegistryProvider.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentProviderPluginRegistryProvider.java
@@ -19,7 +19,7 @@ package org.killbill.billing.payment.glue;
 import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.payment.provider.DefaultPaymentProviderPluginRegistry;
 import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
index 93603b1..e27a8b2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
@@ -77,7 +77,7 @@ public class DefaultPaymentService implements PaymentService {
             eventBus.register(paymentBusEventHandler);
             eventBus.register(tagHandler);
         } catch (final PersistentBus.EventBusException e) {
-            log.error("Unable to register with the EventBus!", e);
+            log.error("Failed to register bus handlers", e);
         }
         paymentExecutors.initialize();
         retryService.initialize();
@@ -96,7 +96,7 @@ public class DefaultPaymentService implements PaymentService {
             eventBus.unregister(paymentBusEventHandler);
             eventBus.unregister(tagHandler);
         } catch (final PersistentBus.EventBusException e) {
-            throw new RuntimeException("Unable to unregister to the EventBus!", e);
+            throw new RuntimeException("Failed to unregister bus handlers", e);
         }
         retryService.stop();
         janitor.stop();
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 c9a0c74..fc0e8f7 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
@@ -32,13 +32,12 @@ import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentGatewayApi;
 import org.killbill.billing.payment.api.PaymentService;
 import org.killbill.billing.payment.bus.PaymentBusEventHandler;
+import org.killbill.billing.payment.config.MultiTenantPaymentConfig;
 import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.PaymentGatewayProcessor;
 import org.killbill.billing.payment.core.PaymentMethodProcessor;
 import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.PluginControlPaymentProcessor;
-import org.killbill.billing.payment.core.janitor.IncompletePaymentAttemptTask;
-import org.killbill.billing.payment.core.janitor.IncompletePaymentTransactionTask;
 import org.killbill.billing.payment.core.janitor.Janitor;
 import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper;
 import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
@@ -54,7 +53,7 @@ import org.killbill.billing.payment.retry.DefaultRetryService;
 import org.killbill.billing.payment.retry.DefaultRetryService.DefaultRetryServiceScheduler;
 import org.killbill.billing.payment.retry.RetryService;
 import org.killbill.billing.platform.api.KillbillConfigSource;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.xmlloader.XMLLoader;
 import org.skife.config.ConfigurationObjectFactory;
@@ -67,6 +66,7 @@ import com.google.inject.name.Names;
 
 public class PaymentModule extends KillBillModule {
 
+    public static final String STATIC_CONFIG = "StaticConfig";
 
     public static final String RETRYABLE_NAMED = "Retryable";
 
@@ -131,8 +131,9 @@ public class PaymentModule extends KillBillModule {
     protected void configure() {
         final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(skifeConfigSource);
         final PaymentConfig paymentConfig = factory.build(PaymentConfig.class);
+        bind(PaymentConfig.class).annotatedWith(Names.named(STATIC_CONFIG)).toInstance(paymentConfig);
+        bind(PaymentConfig.class).to(MultiTenantPaymentConfig.class).asEagerSingleton();
 
-        bind(PaymentConfig.class).toInstance(paymentConfig);
         bind(new TypeLiteral<OSGIServiceRegistration<PaymentPluginApi>>() {}).toProvider(DefaultPaymentProviderPluginRegistryProvider.class).asEagerSingleton();
         bind(new TypeLiteral<OSGIServiceRegistration<PaymentControlPluginApi>>() {}).toProvider(DefaultPaymentControlProviderPluginRegistryProvider.class).asEagerSingleton();
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
index e0ea154..5c14fce 100644
--- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
@@ -49,6 +49,7 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.invoice.api.InvoiceStatus;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
@@ -67,7 +68,7 @@ import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
 import org.killbill.clock.Clock;
@@ -161,24 +162,25 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                     existingInvoicePayment = invoiceApi.getInvoicePaymentForAttempt(paymentControlContext.getPaymentId(), internalContext);
                     if (existingInvoicePayment != null && existingInvoicePayment.isSuccess()) {
                         // Only one successful purchase per payment (the invoice could be linked to multiple successful payments though)
-                        log.info("onSuccessCall was already completed for payment purchase: " + paymentControlContext.getPaymentId());
+                        log.info("onSuccessCall was already completed for purchase paymentId='{}'", paymentControlContext.getPaymentId());
                     } else {
                         final BigDecimal invoicePaymentAmount;
                         if (paymentControlContext.getCurrency() == paymentControlContext.getProcessedCurrency()) {
                             invoicePaymentAmount = paymentControlContext.getProcessedAmount();
                         } else {
-                            log.warn("Currency {} of invoice payment {} doesn't match invoice currency {}, assuming it is a full payment" , paymentControlContext.getProcessedCurrency(), paymentControlContext.getPaymentId(), paymentControlContext.getCurrency());
+                            log.warn("processedCurrency='{}' of invoice paymentId='{}' doesn't match invoice currency='{}', assuming it is a full payment", paymentControlContext.getProcessedCurrency(), paymentControlContext.getPaymentId(), paymentControlContext.getCurrency());
                             invoicePaymentAmount = paymentControlContext.getAmount();
                         }
-                        log.debug("Notifying invoice of successful payment: id={}, amount={}, currency={}, invoiceId={}", paymentControlContext.getPaymentId(), invoicePaymentAmount, paymentControlContext.getCurrency(), invoiceId);
-                        invoiceApi.notifyOfPayment(invoiceId,
-                                                   invoicePaymentAmount,
-                                                   paymentControlContext.getCurrency(),
-                                                   paymentControlContext.getProcessedCurrency(),
-                                                   paymentControlContext.getPaymentId(),
-                                                   paymentControlContext.getCreatedDate(),
-                                                   true,
-                                                   internalContext);
+                        log.debug("Notifying invoice of successful paymentId='{}', amount='{}', currency='{}', invoiceId='{}'", paymentControlContext.getPaymentId(), invoicePaymentAmount, paymentControlContext.getCurrency(), invoiceId);
+                        invoiceApi.recordPaymentAttemptCompletion(invoiceId,
+                                                                  invoicePaymentAmount,
+                                                                  paymentControlContext.getCurrency(),
+                                                                  paymentControlContext.getProcessedCurrency(),
+                                                                  paymentControlContext.getPaymentId(),
+                                                                  paymentControlContext.getTransactionExternalKey(),
+                                                                  paymentControlContext.getCreatedDate(),
+                                                                  true,
+                                                                  internalContext);
                     }
                     break;
 
@@ -186,14 +188,14 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                     final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(pluginProperties);
                     final PluginProperty prop = getPluginProperty(pluginProperties, PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
                     final boolean isAdjusted = prop != null ? Boolean.valueOf((String) prop.getValue()) : false;
-                    invoiceApi.createRefund(paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), isAdjusted, idWithAmount, paymentControlContext.getTransactionExternalKey(), internalContext);
+                    invoiceApi.recordRefund(paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), isAdjusted, idWithAmount, paymentControlContext.getTransactionExternalKey(), internalContext);
                     break;
 
                 case CHARGEBACK:
                     existingInvoicePayment = invoiceApi.getInvoicePaymentForChargeback(paymentControlContext.getPaymentId(), internalContext);
                     if (existingInvoicePayment != null) {
                         // We don't support partial chargebacks (yet?)
-                        log.info("onSuccessCall was already completed for payment chargeback: " + paymentControlContext.getPaymentId());
+                        log.info("onSuccessCall was already completed for chargeback paymentId='{}'", paymentControlContext.getPaymentId());
                     } else {
                         final InvoicePayment linkedInvoicePayment = invoiceApi.getInvoicePaymentForAttempt(paymentControlContext.getPaymentId(), internalContext);
 
@@ -210,7 +212,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                             currency = linkedInvoicePayment.getCurrency();
                         }
 
-                        invoiceApi.createChargeback(paymentControlContext.getPaymentId(), amount, currency, internalContext);
+                        invoiceApi.recordChargeback(paymentControlContext.getPaymentId(), amount, currency, internalContext);
                     }
                     break;
 
@@ -218,7 +220,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                     throw new IllegalStateException("Unexpected transactionType " + transactionType);
             }
         } catch (final InvoiceApiException e) {
-            log.error("InvoicePaymentControlPluginApi onSuccessCall failed for attemptId = " + paymentControlContext.getAttemptPaymentId() + ", transactionType  = " + transactionType, e);
+            log.warn("onSuccessCall failed for attemptId='{}', transactionType='{}'", paymentControlContext.getAttemptPaymentId(), transactionType, e);
         }
 
         return new DefaultOnSuccessPaymentControlResult();
@@ -235,15 +237,16 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                 final UUID invoiceId = getInvoiceId(pluginProperties);
                 try {
                     log.debug("Notifying invoice of failed payment: id={}, amount={}, currency={}, invoiceId={}", paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), paymentControlContext.getCurrency(), invoiceId);
-                    invoiceApi.notifyOfPayment(invoiceId,
-                                               BigDecimal.ZERO,
-                                               paymentControlContext.getCurrency(),
-                                               // processed currency may be null so we use currency; processed currency will be updated if/when payment succeeds
-                                               paymentControlContext.getCurrency(),
-                                               paymentControlContext.getPaymentId(),
-                                               paymentControlContext.getCreatedDate(),
-                                               false,
-                                               internalContext);
+                    invoiceApi.recordPaymentAttemptCompletion(invoiceId,
+                                                              BigDecimal.ZERO,
+                                                              paymentControlContext.getCurrency(),
+                                                              // processed currency may be null so we use currency; processed currency will be updated if/when payment succeeds
+                                                              paymentControlContext.getCurrency(),
+                                                              paymentControlContext.getPaymentId(),
+                                                              paymentControlContext.getTransactionExternalKey(),
+                                                              paymentControlContext.getCreatedDate(),
+                                                              false,
+                                                              internalContext);
                 } catch (final InvoiceApiException e) {
                     log.error("InvoicePaymentControlPluginApi onFailureCall failed ton update invoice for attemptId = " + paymentControlContext.getAttemptPaymentId() + ", transactionType  = " + transactionType, e);
                 }
@@ -282,7 +285,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
     private PriorPaymentControlResult getPluginPurchaseResult(final PaymentControlContext paymentControlPluginContext, final Iterable<PluginProperty> pluginProperties, final InternalCallContext internalContext) throws PaymentControlApiException {
         try {
             final UUID invoiceId = getInvoiceId(pluginProperties);
-            final Invoice invoice = rebalanceAndGetInvoice(invoiceId, internalContext);
+            final Invoice invoice = getAndSanitizeInvoice(invoiceId, internalContext);
 
             if (!InvoiceStatus.COMMITTED.equals(invoice.getStatus())) {
                 // abort payment if the invoice status is not COMMITTED
@@ -294,7 +297,6 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
             if ((accountData != null) && (accountData.getParentAccountId() != null) && accountData.isPaymentDelegatedToParent()) {
                 return new DefaultPriorPaymentControlResult(true);
             }
-
             final BigDecimal requestedAmount = validateAndComputePaymentAmount(invoice, paymentControlPluginContext.getAmount(), paymentControlPluginContext.isApiPayment());
 
             final boolean isAborted = requestedAmount.compareTo(BigDecimal.ZERO) == 0;
@@ -304,11 +306,27 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
 
             if (paymentControlPluginContext.isApiPayment() && isAborted) {
                 throw new PaymentControlApiException("Abort purchase call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION,
-                                                                                                     String.format("Payment for invoice %s aborted : invoice balance is = %s, requested payment amount is = %s",
-                                                                                                                   invoice.getId(),
-                                                                                                                   invoice.getBalance(),
-                                                                                                                   paymentControlPluginContext.getAmount())));
+                                                                                                      String.format("Aborted Payment for invoice %s : invoice balance is = %s, requested payment amount is = %s",
+                                                                                                                    invoice.getId(),
+                                                                                                                    invoice.getBalance(),
+                                                                                                                    paymentControlPluginContext.getAmount())));
             } else {
+
+                //
+                // Insert attempt row with a success = false status to implement a two-phase commit strategy and guard against scenario where payment would go through
+                // but onSuccessCall callback never gets called (leaving the place for a double payment if user retries the operation)
+                //
+                invoiceApi.recordPaymentAttemptInit(invoice.getId(),
+                                                    BigDecimal.ZERO,
+                                                    paymentControlPluginContext.getCurrency(),
+                                                    paymentControlPluginContext.getCurrency(),
+                                                    // Likely to be null, but we don't care as we use the transactionExternalKey
+                                                    // to match the operation in the checkForIncompleteInvoicePaymentAndRepair logic below
+                                                    paymentControlPluginContext.getPaymentId(),
+                                                    paymentControlPluginContext.getTransactionExternalKey(),
+                                                    paymentControlPluginContext.getCreatedDate(),
+                                                    internalContext);
+
                 return new DefaultPriorPaymentControlResult(isAborted, requestedAmount);
             }
         } catch (final InvoiceApiException e) {
@@ -325,9 +343,9 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         if ((paymentControlPluginContext.getAmount() == null || paymentControlPluginContext.getAmount().compareTo(BigDecimal.ZERO) == 0) &&
             idWithAmount.size() == 0) {
             throw new PaymentControlApiException("Abort refund call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION,
-                                                                                                  String.format("Refund for payment, key = %s, aborted: requested refund amount is = %s",
-                                                                                                                paymentControlPluginContext.getPaymentExternalKey(),
-                                                                                                                paymentControlPluginContext.getAmount())));
+                                                                                                String.format("Refund for payment, key = %s, aborted: requested refund amount is = %s",
+                                                                                                              paymentControlPluginContext.getPaymentExternalKey(),
+                                                                                                              paymentControlPluginContext.getAmount())));
         }
 
         final PaymentModelDao payment = paymentDao.getPayment(paymentControlPluginContext.getPaymentId(), internalContext);
@@ -431,10 +449,10 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         final PaymentTransactionModelDao lastTransaction = purchasedTransactions.get(purchasedTransactions.size() - 1);
         switch (lastTransaction.getTransactionStatus()) {
             case PAYMENT_FAILURE:
-                return getNextRetryDateForPaymentFailure(purchasedTransactions);
+                return getNextRetryDateForPaymentFailure(purchasedTransactions, internalContext);
 
             case PLUGIN_FAILURE:
-                return getNextRetryDateForPluginFailure(purchasedTransactions);
+                return getNextRetryDateForPluginFailure(purchasedTransactions, internalContext);
 
             case UNKNOWN:
             default:
@@ -442,10 +460,10 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         }
     }
 
-    private DateTime getNextRetryDateForPaymentFailure(final List<PaymentTransactionModelDao> purchasedTransactions) {
+    private DateTime getNextRetryDateForPaymentFailure(final List<PaymentTransactionModelDao> purchasedTransactions, final InternalCallContext internalContext) {
 
         DateTime result = null;
-        final List<Integer> retryDays = paymentConfig.getPaymentFailureRetryDays();
+        final List<Integer> retryDays = paymentConfig.getPaymentFailureRetryDays(internalContext);
         final int attemptsInState = getNumberAttemptsInState(purchasedTransactions, TransactionStatus.PAYMENT_FAILURE);
         final int retryCount = (attemptsInState - 1) >= 0 ? (attemptsInState - 1) : 0;
         if (retryCount < retryDays.size()) {
@@ -462,17 +480,17 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         return result;
     }
 
-    private DateTime getNextRetryDateForPluginFailure(final List<PaymentTransactionModelDao> purchasedTransactions) {
+    private DateTime getNextRetryDateForPluginFailure(final List<PaymentTransactionModelDao> purchasedTransactions, final InternalCallContext internalContext) {
 
         DateTime result = null;
         final int attemptsInState = getNumberAttemptsInState(purchasedTransactions, TransactionStatus.PLUGIN_FAILURE);
         final int retryAttempt = (attemptsInState - 1) >= 0 ? (attemptsInState - 1) : 0;
 
-        if (retryAttempt < paymentConfig.getPluginFailureRetryMaxAttempts()) {
-            int nbSec = paymentConfig.getPluginFailureInitialRetryInSec();
+        if (retryAttempt < paymentConfig.getPluginFailureRetryMaxAttempts(internalContext)) {
+            int nbSec = paymentConfig.getPluginFailureInitialRetryInSec(internalContext);
             int remainingAttempts = retryAttempt;
             while (--remainingAttempts > 0) {
-                nbSec = nbSec * paymentConfig.getPluginFailureRetryMultiplier();
+                nbSec = nbSec * paymentConfig.getPluginFailureRetryMultiplier(internalContext);
             }
             result = clock.getUTCNow().plusSeconds(nbSec);
             log.debug("Next retryDate={}, retryAttempt={}, now={}", result, retryAttempt, clock.getUTCNow());
@@ -514,25 +532,76 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         }));
     }
 
-    private Invoice rebalanceAndGetInvoice(final UUID invoiceId, final InternalCallContext context) throws InvoiceApiException {
+    private Invoice getAndSanitizeInvoice(final UUID invoiceId, final InternalCallContext context) throws InvoiceApiException {
         final Invoice invoicePriorRebalancing = invoiceApi.getInvoiceById(invoiceId, context);
         invoiceApi.consumeExistingCBAOnAccountWithUnpaidInvoices(invoicePriorRebalancing.getAccountId(), context);
         final Invoice invoice = invoiceApi.getInvoiceById(invoiceId, context);
-        return invoice;
+
+        if (checkForIncompleteInvoicePaymentAndRepair(invoice, context)) {
+            // Fetch new repaired 'invoice'
+            return invoiceApi.getInvoiceById(invoiceId, context);
+        } else {
+            return invoice;
+        }
+    }
+
+    private boolean checkForIncompleteInvoicePaymentAndRepair(final Invoice invoice, final InternalCallContext internalContext) throws InvoiceApiException {
+
+        final List<InvoicePayment> invoicePayments = invoice.getPayments();
+
+        // Look for ATTEMPT matching that invoiceId that are not successful and extract matching paymentTransaction
+        final InvoicePayment incompleteInvoicePayment = Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
+            @Override
+            public boolean apply(final InvoicePayment input) {
+                return input.getType() == InvoicePaymentType.ATTEMPT && !input.isSuccess();
+            }
+        }).orNull();
+
+        // If such (incomplete) paymentTransaction exists, verify the state of the payment transaction
+        if (incompleteInvoicePayment != null) {
+            final String transactionExternalKey = incompleteInvoicePayment.getPaymentCookieId();
+            final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(transactionExternalKey, internalContext);
+            final PaymentTransactionModelDao successfulTransaction = Iterables.tryFind(transactions, new Predicate<PaymentTransactionModelDao>() {
+                @Override
+                public boolean apply(final PaymentTransactionModelDao input) {
+                    //
+                    // In reality this is more tricky because the matching transaction could be an UNKNOWN or PENDING (unsupported by the plugin) state
+                    // In case of UNKNOWN, we don't know what to do: fixing it could result in not paying, and not fixing it could result in double payment
+                    // Current code ignores it, which means we might end up in doing a double payment in that very edgy scenario, and customer would have to request a refund.
+                    //
+                    return input.getTransactionStatus() == TransactionStatus.SUCCESS;
+                }
+            }).orNull();
+
+            if (successfulTransaction != null) {
+                log.info(String.format("Detected an incomplete invoicePayment row for invoiceId='%s' and transactionExternalKey='%s', will correct status", invoice.getId(), successfulTransaction.getTransactionExternalKey()));
+
+                invoiceApi.recordPaymentAttemptCompletion(invoice.getId(),
+                                                          successfulTransaction.getAmount(),
+                                                          successfulTransaction.getCurrency(),
+                                                          successfulTransaction.getProcessedCurrency(),
+                                                          successfulTransaction.getPaymentId(),
+                                                          successfulTransaction.getTransactionExternalKey(),
+                                                          successfulTransaction.getCreatedDate(),
+                                                          true,
+                                                          internalContext);
+                return true;
+
+            }
+        }
+        return false;
     }
 
     private BigDecimal validateAndComputePaymentAmount(final Invoice invoice, @Nullable final BigDecimal inputAmount, final boolean isApiPayment) {
 
         if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
-            log.info("Invoice " + invoice.getId() + " has already been paid");
+            log.info("invoiceId='{}' has already been paid", invoice.getId());
             return BigDecimal.ZERO;
         }
         if (isApiPayment &&
             inputAmount != null &&
             invoice.getBalance().compareTo(inputAmount) < 0) {
-            log.info("Invoice " + invoice.getId() +
-                     " has a balance of " + invoice.getBalance().floatValue() +
-                     " less than retry payment amount of " + inputAmount.floatValue());
+            log.info("invoiceId='{}' has a balance='{}' < retry paymentAmount='{}'", invoice.getId(), invoice.getBalance().floatValue(), inputAmount.floatValue());
             return BigDecimal.ZERO;
         }
         if (inputAmount == null) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentControlProviderPluginRegistry.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentControlProviderPluginRegistry.java
index 40a3ad5..d8472a5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentControlProviderPluginRegistry.java
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentControlProviderPluginRegistry.java
@@ -23,7 +23,6 @@ import java.util.concurrent.ConcurrentHashMap;
 import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -41,13 +40,13 @@ public class DefaultPaymentControlProviderPluginRegistry implements OSGIServiceR
 
     @Override
     public void registerService(final OSGIServiceDescriptor desc, final PaymentControlPluginApi service) {
-        log.info("DefaultPaymentControlProviderPluginRegistry registering service " + desc.getRegistrationName());
+        log.info("Registering service='{}'", desc.getRegistrationName());
         pluginsByName.put(desc.getRegistrationName(), service);
     }
 
     @Override
     public void unregisterService(final String serviceName) {
-        log.info("DefaultPaymentControlProviderPluginRegistry unregistering service " + serviceName);
+        log.info("Unregistering service='{}'", serviceName);
         pluginsByName.remove(serviceName);
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentProviderPluginRegistry.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentProviderPluginRegistry.java
index 5d9c44e..475b46d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentProviderPluginRegistry.java
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentProviderPluginRegistry.java
@@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory;
 import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 
 import com.google.inject.Inject;
 
@@ -45,13 +45,13 @@ public class DefaultPaymentProviderPluginRegistry implements OSGIServiceRegistra
 
     @Override
     public void registerService(final OSGIServiceDescriptor desc, final PaymentPluginApi service) {
-        log.info("DefaultPaymentProviderPluginRegistry registering service " + desc.getRegistrationName());
+        log.info("Registering service='{}'", desc.getRegistrationName());
         pluginsByName.put(desc.getRegistrationName(), service);
     }
 
     @Override
     public void unregisterService(final String serviceName) {
-        log.info("DefaultPaymentProviderPluginRegistry unregistering service " + serviceName);
+        log.info("Unregistering service='{}'", serviceName);
         pluginsByName.remove(serviceName);
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java b/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
index 7757b65..ded591c 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
@@ -124,10 +124,10 @@ public abstract class BaseRetryService implements RetryService {
                     }
                 }
             } catch (final NoSuchNotificationQueue e) {
-                log.error(String.format("Failed to retrieve notification queue %s:%s", DefaultPaymentService.SERVICE_NAME, getQueueName()));
+                log.error("Failed to retrieve notification queue='{}', service='{}'", getQueueName(), DefaultPaymentService.SERVICE_NAME);
                 return false;
             } catch (final IOException e) {
-                log.error(String.format("Failed to serialize notificationQueue event for objectId %s", objectId));
+                log.error("Failed to serialize notificationQueue event for objectId='{}'", objectId);
                 return false;
             }
             return true;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
index aeccb57..7d2d59c 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
@@ -75,21 +75,21 @@ where id = :id
 ;
 >>
 
-getByAccountId(accountId) ::= <<
+getForAccount() ::= <<
 select
 <allTableFields()>
 from <tableName()>
-where account_id = :accountId
+where <accountRecordIdField()> = :accountRecordId
 <andCheckSoftDeletionWithComma()>
 <AND_CHECK_TENANT()>
 ;
 >>
 
-getByAccountIdIncludedDelete(accountId) ::= <<
+getForAccountIncludedDelete() ::= <<
 select
 <allTableFields()>
 from <tableName()>
-where account_id = :accountId
+where <accountRecordIdField()> = :accountRecordId
 <AND_CHECK_TENANT()>
 ;
 >>
diff --git a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
index 98b3c0a..fef6bd7 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
+++ b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
@@ -77,7 +77,6 @@ CREATE TABLE payment_methods (
 CREATE UNIQUE INDEX payment_methods_id ON payment_methods(id);
 CREATE UNIQUE INDEX payment_methods_external_key ON payment_methods(external_key, tenant_record_id);
 CREATE INDEX payment_methods_plugin_name ON payment_methods(plugin_name);
-CREATE INDEX payment_methods_active_accnt ON payment_methods(is_active, account_id);
 CREATE INDEX payment_methods_tenant_account_record_id ON payment_methods(tenant_record_id, account_record_id);
 
 DROP TABLE IF EXISTS payment_method_history;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/migration/V20160324060345__revisit_payment_methods_indexes_509.sql b/payment/src/main/resources/org/killbill/billing/payment/migration/V20160324060345__revisit_payment_methods_indexes_509.sql
new file mode 100644
index 0000000..3b74b7a
--- /dev/null
+++ b/payment/src/main/resources/org/killbill/billing/payment/migration/V20160324060345__revisit_payment_methods_indexes_509.sql
@@ -0,0 +1 @@
+drop index payment_methods_active_accnt on payment_methods;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
index ebfa2d2..7453caf 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
+++ b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
@@ -456,12 +456,6 @@
         <linkStateMachine>
             <initialStateMachine>AUTHORIZE</initialStateMachine>
             <initialState>AUTH_SUCCESS</initialState>
-            <finalStateMachine>AUTHORIZE</finalStateMachine>
-            <finalState>AUTH_INIT</finalState>
-        </linkStateMachine>
-        <linkStateMachine>
-            <initialStateMachine>AUTHORIZE</initialStateMachine>
-            <initialState>AUTH_SUCCESS</initialState>
             <finalStateMachine>CAPTURE</finalStateMachine>
             <finalState>CAPTURE_INIT</finalState>
         </linkStateMachine>
@@ -472,6 +466,30 @@
             <finalState>VOID_INIT</finalState>
         </linkStateMachine>
         <linkStateMachine>
+            <initialStateMachine>VOID</initialStateMachine>
+            <initialState>VOID_SUCCESS</initialState>
+            <finalStateMachine>VOID</finalStateMachine>
+            <finalState>VOID_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>VOID</initialStateMachine>
+            <initialState>VOID_SUCCESS</initialState>
+            <finalStateMachine>CAPTURE</finalStateMachine>
+            <finalState>CAPTURE_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>VOID</initialStateMachine>
+            <initialState>VOID_SUCCESS</initialState>
+            <finalStateMachine>REFUND</finalStateMachine>
+            <finalState>REFUND_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>VOID</initialStateMachine>
+            <initialState>VOID_SUCCESS</initialState>
+            <finalStateMachine>CREDIT</finalStateMachine>
+            <finalState>CREDIT_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
             <initialStateMachine>CAPTURE</initialStateMachine>
             <initialState>CAPTURE_SUCCESS</initialState>
             <finalStateMachine>REFUND</finalStateMachine>
@@ -490,6 +508,12 @@
             <finalState>CHARGEBACK_INIT</finalState>
         </linkStateMachine>
         <linkStateMachine>
+            <initialStateMachine>CAPTURE</initialStateMachine>
+            <initialState>CAPTURE_SUCCESS</initialState>
+            <finalStateMachine>VOID</finalStateMachine>
+            <finalState>VOID_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
             <initialStateMachine>REFUND</initialStateMachine>
             <initialState>REFUND_SUCCESS</initialState>
             <finalStateMachine>REFUND</finalStateMachine>
@@ -502,6 +526,12 @@
             <finalState>CHARGEBACK_INIT</finalState>
         </linkStateMachine>
         <linkStateMachine>
+            <initialStateMachine>REFUND</initialStateMachine>
+            <initialState>REFUND_SUCCESS</initialState>
+            <finalStateMachine>VOID</finalStateMachine>
+            <finalState>VOID_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
             <initialStateMachine>PURCHASE</initialStateMachine>
             <initialState>PURCHASE_SUCCESS</initialState>
             <finalStateMachine>REFUND</finalStateMachine>
@@ -514,6 +544,12 @@
             <finalState>CHARGEBACK_INIT</finalState>
         </linkStateMachine>
         <linkStateMachine>
+            <initialStateMachine>CREDIT</initialStateMachine>
+            <initialState>CREDIT_SUCCESS</initialState>
+            <finalStateMachine>VOID</finalStateMachine>
+            <finalState>VOID_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
             <initialStateMachine>CHARGEBACK</initialStateMachine>
             <initialState>CHARGEBACK_SUCCESS</initialState>
             <finalStateMachine>CHARGEBACK</finalStateMachine>
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 d6e293e..bb8c247 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
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -59,6 +59,7 @@ import com.google.common.collect.ImmutableList;
 
 import static org.killbill.billing.payment.logging.TestLoggingHelper.withSpyLogger;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
@@ -189,19 +190,265 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
         assertNotNull(payment.getTransactions().get(0).getGatewayErrorMsg());
         assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
+
+        final Payment payment2 = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+                                                           ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment2.getExternalKey(), paymentExternalKey);
+        assertEquals(payment2.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        assertEquals(payment2.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+
+        assertEquals(payment2.getTransactions().get(1).getExternalKey(), transactionExternalKey);
+        assertEquals(payment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment2.getTransactions().get(1).getTransactionType(), TransactionType.PURCHASE);
+
     }
 
+
     @Test(groups = "slow")
-    public void testCreateSuccessAuthCapture() throws PaymentApiException {
+    public void testCreateCancelledPurchase() throws PaymentApiException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+
+        final String paymentExternalKey = "hgh3";
+        final String transactionExternalKey = "hgh3sss";
+
+        mockPaymentProviderPlugin.makeNextPaymentFailWithCancellation();
+
+        final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+                                                          ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment.getExternalKey(), paymentExternalKey);
+        assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment.getAccountId(), account.getId());
+        assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
+        assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.AED);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PLUGIN_FAILURE);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+        assertNotNull(payment.getTransactions().get(0).getGatewayErrorMsg());
+        assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
+
+        final Payment payment2 = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+                                                           ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment2.getExternalKey(), paymentExternalKey);
+        assertEquals(payment2.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PLUGIN_FAILURE);
+        assertEquals(payment2.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+
+        assertEquals(payment2.getTransactions().get(1).getExternalKey(), transactionExternalKey);
+        assertEquals(payment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment2.getTransactions().get(1).getTransactionType(), TransactionType.PURCHASE);
+
+    }
+
 
+    @Test(groups = "slow")
+    public void testCreateSuccessAuthVoid() throws PaymentApiException {
+        final BigDecimal authAmount = BigDecimal.TEN;
+
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String transactionExternalKey = UUID.randomUUID().toString();
+        final String transactionExternalKey2 = UUID.randomUUID().toString();
+
+        final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED,
+                                                               paymentExternalKey, transactionExternalKey,
+                                                               ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment.getExternalKey(), paymentExternalKey);
+        assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment.getAccountId(), account.getId());
+        assertEquals(payment.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCurrency(), Currency.AED);
+        assertFalse(payment.isAuthVoided());
+
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
+        assertEquals(payment.getTransactions().get(0).getAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.AED);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.AUTHORIZE);
+        assertNotNull(payment.getTransactions().get(0).getGatewayErrorMsg());
+        assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
+
+        // Void the authorization
+        final Payment payment2 = paymentApi.createVoid(account, payment.getId(), transactionExternalKey2, ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment2.getExternalKey(), paymentExternalKey);
+        assertEquals(payment2.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment2.getAccountId(), account.getId());
+        assertEquals(payment2.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getCurrency(), Currency.AED);
+        assertTrue(payment2.isAuthVoided());
+
+        assertEquals(payment2.getTransactions().size(), 2);
+        assertEquals(payment2.getTransactions().get(1).getExternalKey(), transactionExternalKey2);
+        assertEquals(payment2.getTransactions().get(1).getPaymentId(), payment.getId());
+        assertNull(payment2.getTransactions().get(1).getAmount());
+        assertNull(payment2.getTransactions().get(1).getCurrency());
+        assertNull(payment2.getTransactions().get(1).getProcessedAmount());
+        assertNull(payment2.getTransactions().get(1).getProcessedCurrency());
+
+        assertEquals(payment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment2.getTransactions().get(1).getTransactionType(), TransactionType.VOID);
+        assertNotNull(payment2.getTransactions().get(1).getGatewayErrorMsg());
+        assertNotNull(payment2.getTransactions().get(1).getGatewayErrorCode());
+    }
+
+    @Test(groups = "slow")
+    public void testCreateSuccessAuthCaptureVoidCapture() throws PaymentApiException {
         final BigDecimal authAmount = BigDecimal.TEN;
         final BigDecimal captureAmount = BigDecimal.ONE;
 
-        final String paymentExternalKey = "bouzou";
-        final String transactionExternalKey = "kaput";
-        final String transactionExternalKey2 = "kapu2t";
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String transactionExternalKey = UUID.randomUUID().toString();
+        final String transactionExternalKey2 = UUID.randomUUID().toString();
+        final String transactionExternalKey3 = UUID.randomUUID().toString();
+        final String transactionExternalKey4 = UUID.randomUUID().toString();
+
+        final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED,
+                                                               paymentExternalKey, transactionExternalKey,
+                                                               ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment.getExternalKey(), paymentExternalKey);
+        assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment.getAccountId(), account.getId());
+        assertEquals(payment.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCurrency(), Currency.AED);
+        assertFalse(payment.isAuthVoided());
+
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
+        assertEquals(payment.getTransactions().get(0).getAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.AED);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.AUTHORIZE);
+        assertNotNull(payment.getTransactions().get(0).getGatewayErrorMsg());
+        assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
+
+        final Payment payment2 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, transactionExternalKey2,
+                                                          ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment2.getExternalKey(), paymentExternalKey);
+        assertEquals(payment2.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment2.getAccountId(), account.getId());
+        assertEquals(payment2.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment2.getCapturedAmount().compareTo(captureAmount), 0);
+        assertEquals(payment2.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getCurrency(), Currency.AED);
+        assertFalse(payment2.isAuthVoided());
+
+        assertEquals(payment2.getTransactions().size(), 2);
+        assertEquals(payment2.getTransactions().get(1).getExternalKey(), transactionExternalKey2);
+        assertEquals(payment2.getTransactions().get(1).getPaymentId(), payment.getId());
+        assertEquals(payment2.getTransactions().get(1).getAmount().compareTo(captureAmount), 0);
+        assertEquals(payment2.getTransactions().get(1).getCurrency(), Currency.AED);
+        assertEquals(payment2.getTransactions().get(1).getProcessedAmount().compareTo(captureAmount), 0);
+        assertEquals(payment2.getTransactions().get(1).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment2.getTransactions().get(1).getTransactionType(), TransactionType.CAPTURE);
+        assertNotNull(payment2.getTransactions().get(1).getGatewayErrorMsg());
+        assertNotNull(payment2.getTransactions().get(1).getGatewayErrorCode());
+
+        // Void the capture
+        final Payment payment3 = paymentApi.createVoid(account, payment.getId(), transactionExternalKey3, ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment3.getExternalKey(), paymentExternalKey);
+        assertEquals(payment3.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment3.getAccountId(), account.getId());
+        assertEquals(payment3.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment3.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getCurrency(), Currency.AED);
+        assertFalse(payment3.isAuthVoided());
+
+        assertEquals(payment3.getTransactions().size(), 3);
+        assertEquals(payment3.getTransactions().get(2).getExternalKey(), transactionExternalKey3);
+        assertEquals(payment3.getTransactions().get(2).getPaymentId(), payment.getId());
+        assertNull(payment3.getTransactions().get(2).getAmount());
+        assertNull(payment3.getTransactions().get(2).getCurrency());
+        assertNull(payment3.getTransactions().get(2).getProcessedAmount());
+        assertNull(payment3.getTransactions().get(2).getProcessedCurrency());
+
+        assertEquals(payment3.getTransactions().get(2).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment3.getTransactions().get(2).getTransactionType(), TransactionType.VOID);
+        assertNotNull(payment3.getTransactions().get(2).getGatewayErrorMsg());
+        assertNotNull(payment3.getTransactions().get(2).getGatewayErrorCode());
+
+        // Capture again
+        final Payment payment4 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, transactionExternalKey4,
+                                                          ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment4.getExternalKey(), paymentExternalKey);
+        assertEquals(payment4.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment4.getAccountId(), account.getId());
+        assertEquals(payment4.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment4.getCapturedAmount().compareTo(captureAmount), 0);
+        assertEquals(payment4.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment4.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment4.getCurrency(), Currency.AED);
+        assertFalse(payment4.isAuthVoided());
+
+        assertEquals(payment4.getTransactions().size(), 4);
+        assertEquals(payment4.getTransactions().get(3).getExternalKey(), transactionExternalKey4);
+        assertEquals(payment4.getTransactions().get(3).getPaymentId(), payment.getId());
+        assertEquals(payment4.getTransactions().get(3).getAmount().compareTo(captureAmount), 0);
+        assertEquals(payment4.getTransactions().get(3).getCurrency(), Currency.AED);
+        assertEquals(payment4.getTransactions().get(3).getProcessedAmount().compareTo(captureAmount), 0);
+        assertEquals(payment4.getTransactions().get(3).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment4.getTransactions().get(3).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment4.getTransactions().get(3).getTransactionType(), TransactionType.CAPTURE);
+        assertNotNull(payment4.getTransactions().get(3).getGatewayErrorMsg());
+        assertNotNull(payment4.getTransactions().get(3).getGatewayErrorCode());
+    }
+
+    @Test(groups = "slow")
+    public void testCreateSuccessAuthCaptureVoidVoid() throws PaymentApiException {
+        final BigDecimal authAmount = BigDecimal.TEN;
+        final BigDecimal captureAmount = BigDecimal.ONE;
+
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String transactionExternalKey = UUID.randomUUID().toString();
+        final String transactionExternalKey2 = UUID.randomUUID().toString();
+        final String transactionExternalKey3 = UUID.randomUUID().toString();
+        final String transactionExternalKey4 = UUID.randomUUID().toString();
 
-        final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+        final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED,
+                                                               paymentExternalKey, transactionExternalKey,
                                                                ImmutableList.<PluginProperty>of(), callContext);
 
         assertEquals(payment.getExternalKey(), paymentExternalKey);
@@ -212,6 +459,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment.getCurrency(), Currency.AED);
+        assertFalse(payment.isAuthVoided());
 
         assertEquals(payment.getTransactions().size(), 1);
         assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
@@ -237,6 +485,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(payment2.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment2.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment2.getCurrency(), Currency.AED);
+        assertFalse(payment2.isAuthVoided());
 
         assertEquals(payment2.getTransactions().size(), 2);
         assertEquals(payment2.getTransactions().get(1).getExternalKey(), transactionExternalKey2);
@@ -250,6 +499,58 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(payment2.getTransactions().get(1).getTransactionType(), TransactionType.CAPTURE);
         assertNotNull(payment2.getTransactions().get(1).getGatewayErrorMsg());
         assertNotNull(payment2.getTransactions().get(1).getGatewayErrorCode());
+
+        // Void the capture
+        final Payment payment3 = paymentApi.createVoid(account, payment.getId(), transactionExternalKey3, ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment3.getExternalKey(), paymentExternalKey);
+        assertEquals(payment3.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment3.getAccountId(), account.getId());
+        assertEquals(payment3.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment3.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getCurrency(), Currency.AED);
+        assertFalse(payment3.isAuthVoided());
+
+        assertEquals(payment3.getTransactions().size(), 3);
+        assertEquals(payment3.getTransactions().get(2).getExternalKey(), transactionExternalKey3);
+        assertEquals(payment3.getTransactions().get(2).getPaymentId(), payment.getId());
+        assertNull(payment3.getTransactions().get(2).getAmount());
+        assertNull(payment3.getTransactions().get(2).getCurrency());
+        assertNull(payment3.getTransactions().get(2).getProcessedAmount());
+        assertNull(payment3.getTransactions().get(2).getProcessedCurrency());
+
+        assertEquals(payment3.getTransactions().get(2).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment3.getTransactions().get(2).getTransactionType(), TransactionType.VOID);
+        assertNotNull(payment3.getTransactions().get(2).getGatewayErrorMsg());
+        assertNotNull(payment3.getTransactions().get(2).getGatewayErrorCode());
+
+        // Void the authorization
+        final Payment payment4 = paymentApi.createVoid(account, payment.getId(), transactionExternalKey4, ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment4.getExternalKey(), paymentExternalKey);
+        assertEquals(payment4.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment4.getAccountId(), account.getId());
+        assertEquals(payment4.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment4.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment4.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment4.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment4.getCurrency(), Currency.AED);
+        assertTrue(payment4.isAuthVoided());
+
+        assertEquals(payment4.getTransactions().size(), 4);
+        assertEquals(payment4.getTransactions().get(3).getExternalKey(), transactionExternalKey4);
+        assertEquals(payment4.getTransactions().get(3).getPaymentId(), payment.getId());
+        assertNull(payment4.getTransactions().get(3).getAmount());
+        assertNull(payment4.getTransactions().get(3).getCurrency());
+        assertNull(payment4.getTransactions().get(3).getProcessedAmount());
+        assertNull(payment4.getTransactions().get(3).getProcessedCurrency());
+
+        assertEquals(payment4.getTransactions().get(3).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment4.getTransactions().get(3).getTransactionType(), TransactionType.VOID);
+        assertNotNull(payment4.getTransactions().get(3).getGatewayErrorMsg());
+        assertNotNull(payment4.getTransactions().get(3).getGatewayErrorCode());
     }
 
     @Test(groups = "slow")
@@ -414,6 +715,82 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
 
     @Test(groups = "slow")
+    public void testCreateCancelledPurchaseWithPaymentControl() throws PaymentApiException, InvoiceApiException, EventBusException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final LocalDate now = clock.getUTCToday();
+
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+
+        final String paymentExternalKey = invoice.getId().toString();
+        final String transactionExternalKey = "hgjhgjgjhg33";
+
+        mockPaymentProviderPlugin.makeNextPaymentFailWithCancellation();
+
+        invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
+                                                            subscriptionId,
+                                                            bundleId,
+                                                            "test plan", "test phase", null,
+                                                            now,
+                                                            now.plusMonths(1),
+                                                            requestedAmount,
+                                                            new BigDecimal("1.0"),
+                                                            Currency.USD));
+        try {
+            paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                        createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+        } catch (final PaymentApiException expected) {
+            assertTrue(true);
+        }
+
+
+        final List<Payment> accountPayments = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+        assertEquals(accountPayments.size(), 1);
+        final Payment payment = accountPayments.get(0);
+        assertEquals(payment.getExternalKey(), paymentExternalKey);
+        assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment.getAccountId(), account.getId());
+        assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCurrency(), Currency.USD);
+
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
+        assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.USD);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.USD);
+
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PLUGIN_FAILURE);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+
+        // Make sure we can retry and that works
+        paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                    createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+
+
+        final List<Payment> accountPayments2 = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+        assertEquals(accountPayments2.size(), 1);
+        final Payment payment2 = accountPayments2.get(0);
+        assertEquals(payment2.getTransactions().size(), 2);
+
+        assertEquals(payment2.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PLUGIN_FAILURE);
+        assertEquals(payment2.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+
+        assertEquals(payment2.getTransactions().get(1).getExternalKey(), transactionExternalKey);
+        assertEquals(payment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment2.getTransactions().get(1).getTransactionType(), TransactionType.PURCHASE);
+
+    }
+
+
+    @Test(groups = "slow")
     public void testCreateAbortedPurchaseWithPaymentControl() throws InvoiceApiException, EventBusException {
 
         final BigDecimal requestedAmount = BigDecimal.TEN;
@@ -722,74 +1099,6 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         }
     }
 
-    @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/371")
-    public void testApiWithDuplicatePendingPaymentTransaction() throws Exception {
-        final BigDecimal requestedAmount = BigDecimal.TEN;
-
-        for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
-            final String payment1ExternalKey = UUID.randomUUID().toString();
-            final String payment1TransactionExternalKey = UUID.randomUUID().toString();
-            final String payment2ExternalKey = UUID.randomUUID().toString();
-            final String payment2TransactionExternalKey = UUID.randomUUID().toString();
-            final String payment3TransactionExternalKey = UUID.randomUUID().toString();
-
-            final Payment pendingPayment1 = createPayment(transactionType, null, payment1ExternalKey, payment1TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
-            assertNotNull(pendingPayment1);
-            Assert.assertEquals(pendingPayment1.getExternalKey(), payment1ExternalKey);
-            Assert.assertEquals(pendingPayment1.getTransactions().size(), 1);
-            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
-            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
-            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getCurrency(), account.getCurrency());
-            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getExternalKey(), payment1TransactionExternalKey);
-            Assert.assertEquals(pendingPayment1.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
-
-            // Attempt to create a second transaction for the same payment, but with a different transaction external key
-            final Payment pendingPayment2 = createPayment(transactionType, null, payment1ExternalKey, payment2TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
-            assertNotNull(pendingPayment2);
-            Assert.assertEquals(pendingPayment2.getId(), pendingPayment1.getId());
-            Assert.assertEquals(pendingPayment2.getExternalKey(), payment1ExternalKey);
-            Assert.assertEquals(pendingPayment2.getTransactions().size(), 2);
-            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
-            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
-            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getCurrency(), account.getCurrency());
-            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getExternalKey(), payment1TransactionExternalKey);
-            Assert.assertEquals(pendingPayment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
-            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getAmount().compareTo(requestedAmount), 0);
-            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getProcessedAmount().compareTo(requestedAmount), 0);
-            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getCurrency(), account.getCurrency());
-            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getExternalKey(), payment2TransactionExternalKey);
-            Assert.assertEquals(pendingPayment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.PENDING);
-
-            try {
-                // Verify we cannot use the same transaction external key on a different payment if the payment id isn't specified
-                createPayment(transactionType, null, payment2ExternalKey, payment1TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
-                Assert.fail();
-            } catch (final PaymentApiException e) {
-                Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
-            }
-
-            try {
-                // Verify we cannot use the same transaction external key on a different payment if the payment id isn't specified
-                createPayment(transactionType, null, payment2ExternalKey, payment2TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
-                Assert.fail();
-            } catch (final PaymentApiException e) {
-                Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
-            }
-
-            // Attempt to create a second transaction for a different payment
-            final Payment pendingPayment3 = createPayment(transactionType, null, payment2ExternalKey, payment3TransactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
-            assertNotNull(pendingPayment3);
-            Assert.assertNotEquals(pendingPayment3.getId(), pendingPayment1.getId());
-            Assert.assertEquals(pendingPayment3.getExternalKey(), payment2ExternalKey);
-            Assert.assertEquals(pendingPayment3.getTransactions().size(), 1);
-            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
-            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
-            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getCurrency(), account.getCurrency());
-            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getExternalKey(), payment3TransactionExternalKey);
-            Assert.assertEquals(pendingPayment3.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
-        }
-    }
-
     @Test(groups = "slow")
     public void testApiWithPendingPaymentTransaction() throws Exception {
         for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
@@ -893,11 +1202,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
                 assertNotNull(thrownException);
 
-                Throwable operationException = thrownException.getCause();
-                assertNotNull(operationException);
-                assertTrue(operationException instanceof OperationException);
-
-                Throwable timeoutException = operationException.getCause();
+                Throwable timeoutException = thrownException.getCause();
                 assertNotNull(timeoutException);
                 assertTrue(timeoutException instanceof TimeoutException);
 
@@ -909,6 +1214,147 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertTrue(spyLogger.contains("TimeoutException.*" + pluginName, Optional.of(SpyLogger.LOG_LEVEL_WARN)));
     }
 
+
+    @Test(groups = "slow")
+    public void testSanityAcrossTransactionTypes() throws PaymentApiException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final String paymentExternalKey = "ahhhhhhhh";
+        final String transactionExternalKey = "okkkkkkk";
+
+        final Payment pendingPayment = createPayment(TransactionType.AUTHORIZE, null, paymentExternalKey, transactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
+        assertNotNull(pendingPayment);
+        Assert.assertEquals(pendingPayment.getExternalKey(), paymentExternalKey);
+        Assert.assertEquals(pendingPayment.getTransactions().size(), 1);
+        Assert.assertEquals(pendingPayment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
+        Assert.assertEquals(pendingPayment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
+        Assert.assertEquals(pendingPayment.getTransactions().get(0).getCurrency(), account.getCurrency());
+        Assert.assertEquals(pendingPayment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        Assert.assertEquals(pendingPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
+
+
+        try {
+            createPayment(TransactionType.PURCHASE, null, paymentExternalKey, transactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
+            Assert.fail("PURCHASE transaction with same key should have failed");
+        } catch (final PaymentApiException expected) {
+            Assert.assertEquals(expected.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testSuccessfulInitialTransactionToSameTransaction() throws Exception {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
+
+            final String paymentExternalKey = UUID.randomUUID().toString();
+            final String keyA = UUID.randomUUID().toString();
+
+            final Payment processedPayment = createPayment(transactionType, null, paymentExternalKey, keyA, requestedAmount, PaymentPluginStatus.PROCESSED);
+            assertNotNull(processedPayment);
+            Assert.assertEquals(processedPayment.getTransactions().size(), 1);
+            Assert.assertEquals(processedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+
+            // Attempt to create another {AUTH, PURCHASE, CREDIT} with different key => KB state machine should make the request fail as we don't allow
+            // multiple SUCCESS {AUTH, PURCHASE, CREDIT}
+            final String keyB = UUID.randomUUID().toString();
+            try {
+                createPayment(transactionType, processedPayment.getId(), paymentExternalKey, keyB, requestedAmount, PaymentPluginStatus.PROCESSED);
+                Assert.fail("Retrying initial successful transaction (AUTHORIZE, PURCHASE, CREDIT) with same different key should fail");
+            } catch (final PaymentApiException e) {
+            }
+
+            // Attempt to create another {AUTH, PURCHASE, CREDIT} with same key => key constraint should make the request fail
+            try {
+                createPayment(transactionType, processedPayment.getId(), paymentExternalKey, keyA, requestedAmount, PaymentPluginStatus.PROCESSED);
+                Assert.fail("Retrying initial successful transaction (AUTHORIZE, PURCHASE, CREDIT) with same transaction key should fail");
+            } catch (final PaymentApiException e) {
+            }
+        }
+    }
+
+
+    @Test(groups = "slow")
+    public void testPendingInitialTransactionToSameTransaction() throws Exception {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
+
+            final String paymentExternalKey = UUID.randomUUID().toString();
+            final String keyA = UUID.randomUUID().toString();
+
+            final Payment pendingPayment = createPayment(transactionType, null, paymentExternalKey, keyA, requestedAmount, PaymentPluginStatus.PENDING);
+            assertNotNull(pendingPayment);
+            Assert.assertEquals(pendingPayment.getTransactions().size(), 1);
+            Assert.assertEquals(pendingPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
+
+            // Attempt to create another {AUTH, PURCHASE, CREDIT} with different key => KB state machine should make the request fail as we don't allow
+            // multiple SUCCESS {AUTH, PURCHASE, CREDIT}
+            final String keyB = UUID.randomUUID().toString();
+            try {
+                createPayment(transactionType, pendingPayment.getId(), paymentExternalKey, keyB, requestedAmount, PaymentPluginStatus.PROCESSED);
+                Assert.fail("Retrying initial successful transaction (AUTHORIZE, PURCHASE, CREDIT) with same different key should fail");
+            } catch (final PaymentApiException e) {
+            }
+
+            // Attempt to create another {AUTH, PURCHASE, CREDIT} with same key => That should work because we are completing the payment
+            final Payment completedPayment = createPayment(transactionType, pendingPayment.getId(), paymentExternalKey, keyA, requestedAmount, PaymentPluginStatus.PROCESSED);
+            assertNotNull(completedPayment);
+            Assert.assertEquals(completedPayment.getId(), pendingPayment.getId());
+            Assert.assertEquals(completedPayment.getTransactions().size(), 1);
+        }
+    }
+
+
+    @Test(groups = "slow")
+    public void testFailedInitialTransactionToSameTransactionWithSameKey() throws Exception {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
+
+            final String paymentExternalKey = UUID.randomUUID().toString();
+            final String keyA = UUID.randomUUID().toString();
+
+            final Payment errorPayment = createPayment(transactionType, null, paymentExternalKey, keyA, requestedAmount, PaymentPluginStatus.ERROR);
+            assertNotNull(errorPayment);
+            Assert.assertEquals(errorPayment.getTransactions().size(), 1);
+            Assert.assertEquals(errorPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+
+            // Attempt to create another {AUTH, PURCHASE, CREDIT} with same key => That should work because we are completing the payment
+            final Payment successfulPayment = createPayment(transactionType, errorPayment.getId(), paymentExternalKey, keyA, requestedAmount, PaymentPluginStatus.PROCESSED);
+            assertNotNull(successfulPayment);
+            Assert.assertEquals(successfulPayment.getId(), errorPayment.getId());
+            Assert.assertEquals(successfulPayment.getTransactions().size(), 2);
+        }
+    }
+
+
+    @Test(groups = "slow")
+    public void testFailedInitialTransactionToSameTransactionWithDifferentKey() throws Exception {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
+
+            final String paymentExternalKey = UUID.randomUUID().toString();
+            final String keyA = UUID.randomUUID().toString();
+
+            final Payment errorPayment = createPayment(transactionType, null, paymentExternalKey, keyA, requestedAmount, PaymentPluginStatus.ERROR);
+            assertNotNull(errorPayment);
+            Assert.assertEquals(errorPayment.getTransactions().size(), 1);
+            Assert.assertEquals(errorPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+
+            // Attempt to create another {AUTH, PURCHASE, CREDIT} with different key => KB state machine should make the request fail as we don't allow
+            // multiple SUCCESS {AUTH, PURCHASE, CREDIT}
+            final String keyB = UUID.randomUUID().toString();
+            final Payment successfulPayment = createPayment(transactionType, errorPayment.getId(), paymentExternalKey, keyB, requestedAmount, PaymentPluginStatus.PROCESSED);
+            assertNotNull(successfulPayment);
+            Assert.assertEquals(successfulPayment.getId(), errorPayment.getId());
+            Assert.assertEquals(successfulPayment.getTransactions().size(), 2);
+        }
+    }
+
+
+
     private void verifyRefund(final Payment refund, final String paymentExternalKey, final String paymentTransactionExternalKey, final String refundTransactionExternalKey, final BigDecimal requestedAmount, final BigDecimal refundAmount, final TransactionStatus transactionStatus) {
         Assert.assertEquals(refund.getExternalKey(), paymentExternalKey);
         Assert.assertEquals(refund.getTransactions().size(), 2);
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java
index 9e81218..a3d260c 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java
@@ -19,7 +19,6 @@ package org.killbill.billing.payment.core.janitor;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.payment.PaymentTestSuiteNoDB;
-import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.inject.Inject;
@@ -39,7 +38,7 @@ public class TestIncompletePaymentTransactionTask extends PaymentTestSuiteNoDB {
 
         // Based on config "15s,1m,3m,1h,1d,1d,1d,1d,1d"
         for (int i = 1; i < 10; i++) {
-            final DateTime nextTime = incompletePaymentTransactionTask.getNextNotificationTime(i);
+            final DateTime nextTime = incompletePaymentTransactionTask.getNextNotificationTime(i, internalCallContext);
             assertNotNull(nextTime);
             assertTrue(nextTime.compareTo(initTime) > 0);
             if (i == 0) {
@@ -62,6 +61,6 @@ public class TestIncompletePaymentTransactionTask extends PaymentTestSuiteNoDB {
                 assertTrue(nextTime.compareTo(initTime.plusDays(1).plusSeconds(1)) < 0);
             }
         }
-        assertNull(incompletePaymentTransactionTask.getNextNotificationTime(10));
+        assertNull(incompletePaymentTransactionTask.getNextNotificationTime(10, internalCallContext));
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
index 07c8031..6b3a0df 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
@@ -45,7 +45,7 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.callcontext.CallContext;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
index 2a3a677..9b1c710 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
@@ -19,7 +19,6 @@ package org.killbill.billing.payment.core.sm;
 import java.util.Collections;
 
 import org.killbill.automaton.OperationResult;
-import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.DefaultPayment;
 import org.killbill.billing.payment.api.DefaultPaymentTransaction;
 import org.killbill.billing.payment.api.Payment;
@@ -34,8 +33,7 @@ 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.dispatcher.PluginDispatcher;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
index 1765bef..a6f0f73 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
@@ -38,7 +38,7 @@ import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.memory.MemoryGlobalLocker;
 import org.mockito.Mockito;
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
index 41155e8..94463d3 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
@@ -42,7 +42,7 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.memory.MemoryGlobalLocker;
 import org.mockito.Mockito;
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
index 5b74e38..5a50314 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -44,7 +44,6 @@ import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.PluginControlPaymentProcessor;
 import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
 import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext;
-import org.killbill.billing.payment.dao.MockPaymentDao;
 import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
@@ -157,7 +156,6 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
-        ((MockPaymentDao) paymentDao).reset();
         this.utcNow = clock.getUTCNow();
 
         runner = new MockRetryablePaymentAutomatonRunner(
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java
index 5da756d..226a34b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java
@@ -59,11 +59,11 @@ public class TestPaymentMethodProcessorNoDB extends PaymentTestSuiteNoDB {
         Mockito.when(account.getId()).thenReturn(accountId);
         Mockito.when(account.getExternalKey()).thenReturn(accountId.toString());
 
-        Assert.assertEquals(paymentMethodProcessor.getPaymentMethods(account.getId(), false, properties, internalCallContext).size(), 0);
+        Assert.assertEquals(paymentMethodProcessor.getPaymentMethods(false, properties, internalCallContext).size(), 0);
 
         // The first call should create the payment method
         final ExternalPaymentProviderPlugin providerPlugin = paymentMethodProcessor.createPaymentMethodAndGetExternalPaymentProviderPlugin(UUID.randomUUID().toString(), account, properties, callContext, internalCallContext);
-        final List<PaymentMethod> paymentMethods = paymentMethodProcessor.getPaymentMethods(account.getId(), false, properties, internalCallContext);
+        final List<PaymentMethod> paymentMethods = paymentMethodProcessor.getPaymentMethods(false, properties, internalCallContext);
         Assert.assertEquals(paymentMethods.size(), 1);
         Assert.assertEquals(paymentMethods.get(0).getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
         Assert.assertEquals(paymentMethods.get(0).getAccountId(), account.getId());
@@ -74,7 +74,7 @@ public class TestPaymentMethodProcessorNoDB extends PaymentTestSuiteNoDB {
             final ExternalPaymentProviderPlugin foundProviderPlugin = paymentMethodProcessor.createPaymentMethodAndGetExternalPaymentProviderPlugin(UUID.randomUUID().toString(), account, properties, callContext, internalCallContext);
             Assert.assertNotNull(foundProviderPlugin);
 
-            final List<PaymentMethod> foundPaymentMethods = paymentMethodProcessor.getPaymentMethods(account.getId(), false, properties, internalCallContext);
+            final List<PaymentMethod> foundPaymentMethods = paymentMethodProcessor.getPaymentMethods(false, properties, internalCallContext);
             Assert.assertEquals(foundPaymentMethods.size(), 1);
             Assert.assertEquals(foundPaymentMethods.get(0).getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
             Assert.assertEquals(foundPaymentMethods.get(0).getAccountId(), account.getId());
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
index 19f10b9..d60f90b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
@@ -38,6 +38,8 @@ import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -71,54 +73,56 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
     public void testClassicFlow() throws Exception {
         final String paymentExternalKey = UUID.randomUUID().toString();
 
+
+        final Iterable<PluginProperty> pluginPropertiesToDriveTransationToPending = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PENDING, false));
+
         // AUTH pre-3DS
         final String authorizationKey = UUID.randomUUID().toString();
         final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, authorizationKey,
-                                                                           SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
-        verifyPayment(authorization, paymentExternalKey, TEN, ZERO, ZERO, 1);
+                                                                           SHOULD_LOCK_ACCOUNT, pluginPropertiesToDriveTransationToPending, callContext, internalCallContext);
+        verifyPayment(authorization, paymentExternalKey, ZERO, ZERO, ZERO, 1);
         final UUID paymentId = authorization.getId();
         verifyPaymentTransaction(authorization.getTransactions().get(0), authorizationKey, TransactionType.AUTHORIZE, TEN, paymentId);
-        paymentBusListener.verify(1, account.getId(), paymentId, TEN);
+        paymentBusListener.verify(1, account.getId(), paymentId, TEN, TransactionStatus.PENDING);
 
         // AUTH post-3DS
-        final String authorizationPost3DSKey = UUID.randomUUID().toString();
-        final Payment authorizationPost3DS = paymentProcessor.createAuthorization(true, null, account, null, paymentId, TEN, CURRENCY, paymentExternalKey, authorizationPost3DSKey,
+        final Payment authorizationPost3DS = paymentProcessor.createAuthorization(true, null, account, null, paymentId, TEN, CURRENCY, paymentExternalKey, authorizationKey,
                                                                                   SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
-        verifyPayment(authorizationPost3DS, paymentExternalKey, TEN, ZERO, ZERO, 2);
-        verifyPaymentTransaction(authorizationPost3DS.getTransactions().get(1), authorizationPost3DSKey, TransactionType.AUTHORIZE, TEN, paymentId);
-        paymentBusListener.verify(2, account.getId(), paymentId, TEN);
+        verifyPayment(authorizationPost3DS, paymentExternalKey, TEN, ZERO, ZERO, 1);
+        verifyPaymentTransaction(authorizationPost3DS.getTransactions().get(0), authorizationKey, TransactionType.AUTHORIZE, TEN, paymentId);
+        paymentBusListener.verify(2, account.getId(), paymentId, TEN, TransactionStatus.SUCCESS);
 
         // CAPTURE
         final String capture1Key = UUID.randomUUID().toString();
         final Payment partialCapture1 = paymentProcessor.createCapture(true, null, account, paymentId, FIVE, CURRENCY, capture1Key,
                                                                        SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
-        verifyPayment(partialCapture1, paymentExternalKey, TEN, FIVE, ZERO, 3);
-        verifyPaymentTransaction(partialCapture1.getTransactions().get(2), capture1Key, TransactionType.CAPTURE, FIVE, paymentId);
-        paymentBusListener.verify(3, account.getId(), paymentId, FIVE);
+        verifyPayment(partialCapture1, paymentExternalKey, TEN, FIVE, ZERO, 2);
+        verifyPaymentTransaction(partialCapture1.getTransactions().get(1), capture1Key, TransactionType.CAPTURE, FIVE, paymentId);
+        paymentBusListener.verify(3, account.getId(), paymentId, FIVE, TransactionStatus.SUCCESS);
 
         // CAPTURE
         final String capture2Key = UUID.randomUUID().toString();
         final Payment partialCapture2 = paymentProcessor.createCapture(true, null, account, paymentId, FIVE, CURRENCY, capture2Key,
                                                                        SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
-        verifyPayment(partialCapture2, paymentExternalKey, TEN, TEN, ZERO, 4);
-        verifyPaymentTransaction(partialCapture2.getTransactions().get(3), capture2Key, TransactionType.CAPTURE, FIVE, paymentId);
-        paymentBusListener.verify(4, account.getId(), paymentId, FIVE);
+        verifyPayment(partialCapture2, paymentExternalKey, TEN, TEN, ZERO, 3);
+        verifyPaymentTransaction(partialCapture2.getTransactions().get(2), capture2Key, TransactionType.CAPTURE, FIVE, paymentId);
+        paymentBusListener.verify(4, account.getId(), paymentId, FIVE, TransactionStatus.SUCCESS);
 
         // REFUND
         final String refund1Key = UUID.randomUUID().toString();
         final Payment partialRefund1 = paymentProcessor.createRefund(true, null, account, paymentId, FIVE, CURRENCY, refund1Key,
                                                                      SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
-        verifyPayment(partialRefund1, paymentExternalKey, TEN, TEN, FIVE, 5);
-        verifyPaymentTransaction(partialRefund1.getTransactions().get(4), refund1Key, TransactionType.REFUND, FIVE, paymentId);
-        paymentBusListener.verify(5, account.getId(), paymentId, FIVE);
+        verifyPayment(partialRefund1, paymentExternalKey, TEN, TEN, FIVE, 4);
+        verifyPaymentTransaction(partialRefund1.getTransactions().get(3), refund1Key, TransactionType.REFUND, FIVE, paymentId);
+        paymentBusListener.verify(5, account.getId(), paymentId, FIVE, TransactionStatus.SUCCESS);
 
         // REFUND
         final String refund2Key = UUID.randomUUID().toString();
         final Payment partialRefund2 = paymentProcessor.createRefund(true, null, account, paymentId, FIVE, CURRENCY, refund2Key,
                                                                      SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
-        verifyPayment(partialRefund2, paymentExternalKey, TEN, TEN, TEN, 6);
-        verifyPaymentTransaction(partialRefund2.getTransactions().get(5), refund2Key, TransactionType.REFUND, FIVE, paymentId);
-        paymentBusListener.verify(6, account.getId(), paymentId, FIVE);
+        verifyPayment(partialRefund2, paymentExternalKey, TEN, TEN, TEN, 5);
+        verifyPaymentTransaction(partialRefund2.getTransactions().get(4), refund2Key, TransactionType.REFUND, FIVE, paymentId);
+        paymentBusListener.verify(6, account.getId(), paymentId, FIVE, TransactionStatus.SUCCESS);
     }
 
     @Test(groups = "slow")
@@ -132,15 +136,15 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
         verifyPayment(authorization, paymentExternalKey, TEN, ZERO, ZERO, 1);
         final UUID paymentId = authorization.getId();
         verifyPaymentTransaction(authorization.getTransactions().get(0), authorizationKey, TransactionType.AUTHORIZE, TEN, paymentId);
-        paymentBusListener.verify(1, account.getId(), paymentId, TEN);
+        paymentBusListener.verify(1, account.getId(), paymentId, TEN, TransactionStatus.SUCCESS);
 
         // VOID
         final String voidKey = UUID.randomUUID().toString();
         final Payment voidTransaction = paymentProcessor.createVoid(true, null, account, paymentId, voidKey,
                                                                     SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
-        verifyPayment(voidTransaction, paymentExternalKey, TEN, ZERO, ZERO, 2);
+        verifyPayment(voidTransaction, paymentExternalKey, ZERO, ZERO, ZERO, 2);
         verifyPaymentTransaction(voidTransaction.getTransactions().get(1), voidKey, TransactionType.VOID, null, paymentId);
-        paymentBusListener.verify(2, account.getId(), paymentId, null);
+        paymentBusListener.verify(2, account.getId(), paymentId, null, TransactionStatus.SUCCESS);
     }
 
     @Test(groups = "slow")
@@ -154,7 +158,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
         verifyPayment(purchase, paymentExternalKey, ZERO, ZERO, ZERO, 1);
         final UUID paymentId = purchase.getId();
         verifyPaymentTransaction(purchase.getTransactions().get(0), purchaseKey, TransactionType.PURCHASE, TEN, paymentId);
-        paymentBusListener.verify(1, account.getId(), paymentId, TEN);
+        paymentBusListener.verify(1, account.getId(), paymentId, TEN, TransactionStatus.SUCCESS);
     }
 
     @Test(groups = "slow")
@@ -168,7 +172,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
         verifyPayment(purchase, paymentExternalKey, ZERO, ZERO, ZERO, 1);
         final UUID paymentId = purchase.getId();
         verifyPaymentTransaction(purchase.getTransactions().get(0), creditKey, TransactionType.CREDIT, TEN, paymentId);
-        paymentBusListener.verify(1, account.getId(), paymentId, TEN);
+        paymentBusListener.verify(1, account.getId(), paymentId, TEN, TransactionStatus.SUCCESS);
     }
 
     private void verifyPayment(final Payment payment, final String paymentExternalKey,
@@ -223,7 +227,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
             paymentPluginErrorEvents.add(event);
         }
 
-        public void verify(final int eventNb, final UUID accountId, final UUID paymentId, final BigDecimal amount) throws Exception {
+        private void verify(final int eventNb, final UUID accountId, final UUID paymentId, final BigDecimal amount, final TransactionStatus transactionStatus) throws Exception {
             Awaitility.await()
                       .until(new Callable<Boolean>() {
                           @Override
@@ -234,18 +238,18 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
             Assert.assertEquals(paymentErrorEvents.size(), 0);
             Assert.assertEquals(paymentPluginErrorEvents.size(), 0);
 
-            verify(paymentInfoEvents.get(eventNb - 1), accountId, paymentId, amount);
+            verify(paymentInfoEvents.get(eventNb - 1), accountId, paymentId, amount, transactionStatus);
         }
 
-        private void verify(final PaymentInfoInternalEvent event, final UUID accountId, final UUID paymentId, @Nullable final BigDecimal amount) {
+        private void verify(final PaymentInfoInternalEvent event, final UUID accountId, final UUID paymentId, @Nullable final BigDecimal amount, final TransactionStatus transactionStatus) {
             Assert.assertEquals(event.getPaymentId(), paymentId);
             Assert.assertEquals(event.getAccountId(), accountId);
             if (amount == null) {
-                Assert.assertEquals(event.getAmount().compareTo(BigDecimal.ZERO), 0);
+                Assert.assertNull(event.getAmount());
             } else {
                 Assert.assertEquals(event.getAmount().compareTo(amount), 0);
             }
-            Assert.assertEquals(event.getStatus(), TransactionStatus.SUCCESS);
+            Assert.assertEquals(event.getStatus(), transactionStatus);
         }
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
index d479336..3f314c8 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -50,6 +50,7 @@ public class MockPaymentDao implements PaymentDao {
     private final Map<UUID, PaymentAttemptModelDao> attempts = new HashMap<UUID, PaymentAttemptModelDao>();
 
     private final MockNonEntityDao mockNonEntityDao;
+    private final List<PaymentMethodModelDao> paymentMethods = new LinkedList<PaymentMethodModelDao>();
 
     @Inject
     public MockPaymentDao(final MockNonEntityDao mockNonEntityDao) {
@@ -59,6 +60,7 @@ public class MockPaymentDao implements PaymentDao {
     public void reset() {
         synchronized (this) {
             payments.clear();
+            paymentMethods.clear();
             transactions.clear();
             attempts.clear();
         }
@@ -66,7 +68,7 @@ public class MockPaymentDao implements PaymentDao {
 
     @Override
     public Pagination<PaymentTransactionModelDao> getByTransactionStatusAcrossTenants(final Iterable<TransactionStatus> transactionStatuses, DateTime createdBeforeDate, DateTime createdAfterDate, Long offset, Long limit) {
-        final List<PaymentTransactionModelDao> result=  ImmutableList.copyOf(Iterables.filter(transactions.values(), new Predicate<PaymentTransactionModelDao>() {
+        final List<PaymentTransactionModelDao> result = ImmutableList.copyOf(Iterables.filter(transactions.values(), new Predicate<PaymentTransactionModelDao>() {
             @Override
             public boolean apply(final PaymentTransactionModelDao input) {
                 return Iterables.any(transactionStatuses, new Predicate<TransactionStatus>() {
@@ -311,11 +313,11 @@ public class MockPaymentDao implements PaymentDao {
         }
     }
 
-    private final List<PaymentMethodModelDao> paymentMethods = new LinkedList<PaymentMethodModelDao>();
-
     @Override
     public PaymentMethodModelDao insertPaymentMethod(final PaymentMethodModelDao paymentMethod, final InternalCallContext context) {
         synchronized (this) {
+            paymentMethod.setAccountRecordId(context.getAccountRecordId());
+            paymentMethod.setTenantRecordId(context.getTenantRecordId());
             paymentMethods.add(paymentMethod);
             return paymentMethod;
         }
@@ -346,11 +348,11 @@ public class MockPaymentDao implements PaymentDao {
     }
 
     @Override
-    public List<PaymentMethodModelDao> getPaymentMethods(final UUID accountId, final InternalTenantContext context) {
+    public List<PaymentMethodModelDao> getPaymentMethods(final InternalTenantContext context) {
         synchronized (this) {
             final List<PaymentMethodModelDao> result = new ArrayList<PaymentMethodModelDao>();
             for (final PaymentMethodModelDao cur : paymentMethods) {
-                if (cur.getAccountId().equals(accountId)) {
+                if (cur.getAccountRecordId().equals(context.getAccountRecordId())) {
                     result.add(cur);
                 }
             }
@@ -383,7 +385,7 @@ public class MockPaymentDao implements PaymentDao {
     }
 
     @Override
-    public List<PaymentMethodModelDao> refreshPaymentMethods(final UUID accountId, final String pluginName, final List<PaymentMethodModelDao> paymentMethods, final InternalCallContext context) {
+    public List<PaymentMethodModelDao> refreshPaymentMethods(final String pluginName, final List<PaymentMethodModelDao> paymentMethods, final InternalCallContext context) {
         return ImmutableList.<PaymentMethodModelDao>of();
     }
 
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index 8bc6634..91a98e1 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -228,7 +228,7 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(savedMethod.getPluginName(), pluginName);
         assertEquals(savedMethod.isActive(), isActive);
 
-        final List<PaymentMethodModelDao> result = paymentDao.getPaymentMethods(accountId, internalCallContext);
+        final List<PaymentMethodModelDao> result = paymentDao.getPaymentMethods(internalCallContext);
         assertEquals(result.size(), 1);
         savedMethod = result.get(0);
         assertEquals(savedMethod.getId(), paymentMethodId);
diff --git a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
index f2cd9c8..b582f33 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
@@ -30,6 +30,7 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcher
 import org.killbill.billing.util.UUIDs;
 import org.killbill.commons.request.Request;
 import org.killbill.commons.request.RequestData;
+import org.slf4j.MDC;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -140,6 +141,7 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
                                                                                                                                                      UUIDs.getRandom(),
                                                                                                                                                      null,
                                                                                                                                                      null,
+                                                                                                                                                     MDC.getCopyOfContextMap(),
                                                                                                                                                      delegate);
 
         final String actualRequestId = stringPluginDispatcher.dispatchWithTimeout(callable, 100, TimeUnit.MILLISECONDS);
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
index c5d16c5..a4bc6e9 100644
--- a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
@@ -31,9 +31,10 @@ import org.killbill.billing.payment.provider.MockPaymentProviderPluginModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.api.TagUserApi;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
 import org.killbill.billing.util.tag.Tag;
 import org.killbill.clock.Clock;
@@ -72,6 +73,7 @@ public class TestPaymentModule extends PaymentModule {
         install(new MemoryGlobalLockerModule(configSource));
         install(new MockTenantModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new CallContextModule(configSource));
 
         installExternalApis();
diff --git a/payment/src/test/java/org/killbill/billing/payment/logging/SpyLogger.java b/payment/src/test/java/org/killbill/billing/payment/logging/SpyLogger.java
index 62efbe4..56270cd 100644
--- a/payment/src/test/java/org/killbill/billing/payment/logging/SpyLogger.java
+++ b/payment/src/test/java/org/killbill/billing/payment/logging/SpyLogger.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -14,12 +14,15 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package org.killbill.billing.payment.logging;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.regex.Pattern;
 
+import org.slf4j.event.SubstituteLoggingEvent;
 import org.slf4j.helpers.FormattingTuple;
 import org.slf4j.helpers.MessageFormatter;
 import org.slf4j.helpers.SubstituteLogger;
@@ -28,10 +31,10 @@ import com.google.common.base.Optional;
 
 public class SpyLogger extends SubstituteLogger {
 
-    private List<LogMessage> logMessageList = new ArrayList<LogMessage>();
+    private final List<LogMessage> logMessageList = new ArrayList<LogMessage>();
 
     public SpyLogger(String loggerName) {
-        super(loggerName);
+        super(loggerName, new LinkedBlockingQueue<SubstituteLoggingEvent>(), false);
     }
 
     public static final String LOG_LEVEL_TRACE = "TRACE";
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
index 9af2ee5..1b8eecb 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
@@ -30,6 +30,7 @@ import org.killbill.billing.payment.core.PaymentProcessor;
 import org.killbill.billing.payment.core.PluginControlPaymentProcessor;
 import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
 import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner;
+import org.killbill.billing.payment.dao.MockPaymentDao;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.glue.TestPaymentModuleNoDB;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
@@ -37,7 +38,7 @@ import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.killbill.billing.payment.retry.DefaultRetryService;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.commons.profiling.Profiling;
 import org.testng.annotations.AfterMethod;
@@ -104,6 +105,7 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
     public void beforeMethod() throws Exception {
         eventBus.start();
         paymentExecutors.initialize();
+        ((MockPaymentDao) paymentDao).reset();
         Profiling.resetPerThreadProfilingData();
     }
 
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
index dc7b36f..4d3a3d0 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
@@ -33,7 +33,7 @@ import org.killbill.billing.payment.glue.TestPaymentModuleWithEmbeddedDB;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.killbill.billing.platform.api.KillbillConfigSource;
-import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.commons.profiling.Profiling;
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
index cc8c6e1..d6fb5f3 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -68,9 +68,10 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
 
     public static final String PLUGIN_NAME = "__NO_OP__";
 
-    private final AtomicBoolean makeNextInvoiceFailWithError = new AtomicBoolean(false);
-    private final AtomicBoolean makeNextInvoiceFailWithException = new AtomicBoolean(false);
-    private final AtomicBoolean makeAllInvoicesFailWithError = new AtomicBoolean(false);
+    private final AtomicBoolean makeNextPaymentFailWithError = new AtomicBoolean(false);
+    private final AtomicBoolean makeNextPaymentFailWithCancellation = new AtomicBoolean(false);
+    private final AtomicBoolean makeNextPaymentFailWithException = new AtomicBoolean(false);
+    private final AtomicBoolean makeAllPaymentsFailWithError = new AtomicBoolean(false);
     private final AtomicInteger makePluginWaitSomeMilliseconds = new AtomicInteger(0);
     private final AtomicReference<BigDecimal> overrideNextProcessedAmount = new AtomicReference<BigDecimal>();
     private final AtomicReference<Currency> overrideNextProcessedCurrency = new AtomicReference<Currency>();
@@ -194,9 +195,10 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
     }
 
     public void clear() {
-        makeNextInvoiceFailWithException.set(false);
-        makeAllInvoicesFailWithError.set(false);
-        makeNextInvoiceFailWithError.set(false);
+        makeNextPaymentFailWithException.set(false);
+        makeAllPaymentsFailWithError.set(false);
+        makeNextPaymentFailWithError.set(false);
+        makeNextPaymentFailWithCancellation.set(false);
         makePluginWaitSomeMilliseconds.set(0);
         overrideNextProcessedAmount.set(null);
         paymentMethods.clear();
@@ -206,15 +208,19 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
     }
 
     public void makeNextPaymentFailWithError() {
-        makeNextInvoiceFailWithError.set(true);
+        makeNextPaymentFailWithError.set(true);
+    }
+
+    public void makeNextPaymentFailWithCancellation() {
+        makeNextPaymentFailWithCancellation.set(true);
     }
 
     public void makeNextPaymentFailWithException() {
-        makeNextInvoiceFailWithException.set(true);
+        makeNextPaymentFailWithException.set(true);
     }
 
     public void makeAllInvoicesFailWithError(final boolean failure) {
-        makeAllInvoicesFailWithError.set(failure);
+        makeAllPaymentsFailWithError.set(failure);
     }
 
     public void makePluginWaitSomeMilliseconds(final int milliseconds) {
@@ -255,7 +261,7 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
     @Override
     public PaymentTransactionInfoPlugin voidPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentPluginApiException {
-        return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.VOID, BigDecimal.ZERO, null, properties);
+        return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.VOID, null, null, properties);
     }
 
     @Override
@@ -358,7 +364,7 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
         return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency, properties);
     }
 
-    private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType type, final BigDecimal amount, @Nullable final Currency currency, final Iterable<PluginProperty> pluginProperties) throws PaymentPluginApiException {
+    private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType type, @Nullable final BigDecimal amount, @Nullable final Currency currency, final Iterable<PluginProperty> pluginProperties) throws PaymentPluginApiException {
         if (makePluginWaitSomeMilliseconds.get() > 0) {
             try {
                 Thread.sleep(makePluginWaitSomeMilliseconds.get());
@@ -368,7 +374,7 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
             }
         }
 
-        if (makeNextInvoiceFailWithException.getAndSet(false)) {
+        if (makeNextPaymentFailWithException.getAndSet(false)) {
             throw new PaymentPluginApiException("", "test error");
         }
 
@@ -382,8 +388,12 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
         final PaymentPluginStatus status;
         if (paymentPluginStatusOverride != null && paymentPluginStatusOverride.getValue() != null) {
             status = PaymentPluginStatus.valueOf(paymentPluginStatusOverride.getValue().toString());
+        } else if (makeAllPaymentsFailWithError.get() || makeNextPaymentFailWithError.getAndSet(false)) {
+            status = PaymentPluginStatus.ERROR;
+        } else if (makeNextPaymentFailWithCancellation.getAndSet(false)) {
+            status = PaymentPluginStatus.CANCELED;
         } else {
-            status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED;
+            status = PaymentPluginStatus.PROCESSED;
         }
         final String errorCode = status == PaymentPluginStatus.PROCESSED ? "" : GATEWAY_ERROR_CODE;
         final String error = status == PaymentPluginStatus.PROCESSED ? "" : GATEWAY_ERROR;
@@ -394,7 +404,8 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
             payments.put(kbPaymentId.toString(), info);
         }
 
-        final BigDecimal processedAmount = MoreObjects.firstNonNull(overrideNextProcessedAmount.getAndSet(null), amount);
+        final BigDecimal overrideNextProcessedAmount = this.overrideNextProcessedAmount.getAndSet(null);
+        final BigDecimal processedAmount = overrideNextProcessedAmount != null ? overrideNextProcessedAmount : amount;
         Currency processedCurrency = overrideNextProcessedCurrency.getAndSet(null);
         if (processedCurrency == null) {
             processedCurrency = currency;
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index ccf0a1c..aad5f13 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -445,7 +445,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
         testListener.assertListenerStatus();
 
         // 15s,1m,3m,1h,1d,1d,1d,1d,1d
-        for (final TimeSpan cur : paymentConfig.getIncompleteTransactionsRetries()) {
+        for (final TimeSpan cur : paymentConfig.getIncompleteTransactionsRetries(internalCallContext)) {
             // Verify there is a notification to retry updating the value
             assertEquals(getPendingNotificationCnt(internalCallContext), 1);
 
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
index b5462f5..521911c 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
@@ -34,7 +34,6 @@ import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
-import org.killbill.billing.payment.dao.MockPaymentDao;
 import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
@@ -70,7 +69,6 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
         setDefaultPollInterval(Duration.ONE_HUNDRED_MILLISECONDS);
         Awaitility.setDefaultPollDelay(Duration.SAME_AS_POLL_INTERVAL);
 
-        ((MockPaymentDao) paymentDao).reset();
         mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME);
         mockPaymentProviderPlugin.clear();
         retryService.initialize();
@@ -249,7 +247,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
         final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(payment.getId(), internalCallContext);
         assertEquals(transactions.size(), 1);
 
-        int maxTries = paymentConfig.getPaymentFailureRetryDays().size();
+        int maxTries = paymentConfig.getPaymentFailureRetryDays(internalCallContext).size();
         for (int curFailure = 0; curFailure < maxTries; curFailure++) {
 
             // Set plugin to fail with specific type unless this is the last attempt and we want a success
@@ -336,7 +334,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
         final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(payment.getId(), internalCallContext);
         assertEquals(transactions.size(), 1);
 
-        int maxTries = paymentConfig.getPaymentFailureRetryDays().size();
+        int maxTries = paymentConfig.getPaymentFailureRetryDays(internalCallContext).size();
         for (int curFailure = 0; curFailure < maxTries; curFailure++) {
 
             // Set plugin to fail with specific type unless this is the last attempt and we want a success
@@ -396,7 +394,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
     private void moveClockForFailureType(final FailureType failureType, final int curFailure) throws InterruptedException {
         final int nbDays;
         if (failureType == FailureType.PAYMENT_FAILURE) {
-            nbDays = paymentConfig.getPaymentFailureRetryDays().get(curFailure) + 1;
+            nbDays = paymentConfig.getPaymentFailureRetryDays(internalCallContext).get(curFailure) + 1;
         } else {
             nbDays = 1;
         }
@@ -405,7 +403,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
 
     private int getMaxRetrySizeForFailureType(final FailureType failureType) {
         if (failureType == FailureType.PAYMENT_FAILURE) {
-            return paymentConfig.getPaymentFailureRetryDays().size();
+            return paymentConfig.getPaymentFailureRetryDays(internalCallContext).size();
         } else {
             return 0;
         }

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 32e5f74..f73a959 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.80.ha-SNAPSHOT</version>
+        <version>0.93.ha-SNAPSHOT</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.17.0.ha-SNAPSHOT</version>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/DefaultServerService.java b/profiles/killbill/src/main/java/org/killbill/billing/server/DefaultServerService.java
index 0306248..a085bfa 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/DefaultServerService.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/DefaultServerService.java
@@ -55,7 +55,7 @@ public class DefaultServerService implements ServerService {
         try {
             bus.register(pushNotificationListener);
         } catch (final EventBusException e) {
-            log.warn("Failed to initialize Server service :", e);
+            log.warn("Failed to register PushNotificationListener", e);
         }
     }
 
@@ -64,7 +64,7 @@ public class DefaultServerService implements ServerService {
         try {
             bus.unregister(pushNotificationListener);
         } catch (final EventBusException e) {
-            log.warn("Failed to stop Server service :", e);
+            log.warn("Failed to unregister PushNotificationListener", e);
         }
     }
 }
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/KillbillMDCInsertingServletFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/KillbillMDCInsertingServletFilter.java
new file mode 100644
index 0000000..7782a51
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/KillbillMDCInsertingServletFilter.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.filters;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.killbill.commons.request.Request;
+import org.killbill.commons.request.RequestData;
+import org.slf4j.MDC;
+
+import com.google.inject.Singleton;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponse;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ContainerResponseWriter;
+
+import static org.killbill.billing.util.callcontext.InternalCallContextFactory.MDC_KB_ACCOUNT_RECORD_ID;
+import static org.killbill.billing.util.callcontext.InternalCallContextFactory.MDC_KB_TENANT_RECORD_ID;
+
+@Singleton
+public class KillbillMDCInsertingServletFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+    private static final String MDC_REQUEST_ID = "req.requestId";
+
+    @Override
+    public ContainerRequest filter(final ContainerRequest request) {
+        final RequestData perThreadRequestData = Request.getPerThreadRequestData();
+        if (perThreadRequestData != null) {
+            MDC.put(MDC_REQUEST_ID, perThreadRequestData.getRequestId());
+        }
+
+        return request;
+    }
+
+    @Override
+    public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response) {
+        response.setContainerResponseWriter(new Adapter(response.getContainerResponseWriter()));
+        return response;
+    }
+
+    private static final class Adapter implements ContainerResponseWriter {
+
+        private final ContainerResponseWriter crw;
+
+        Adapter(final ContainerResponseWriter containerResponseWriter) {
+            this.crw = containerResponseWriter;
+        }
+
+        @Override
+        public OutputStream writeStatusAndHeaders(final long contentLength, final ContainerResponse response) throws IOException {
+            return crw.writeStatusAndHeaders(contentLength, response);
+        }
+
+        @Override
+        public void finish() throws IOException {
+            crw.finish();
+
+            // Removing possibly inexistent item is OK
+            MDC.remove(MDC_REQUEST_ID);
+
+            // Cleanup
+            MDC.remove(MDC_KB_ACCOUNT_RECORD_ID);
+            MDC.remove(MDC_KB_TENANT_RECORD_ID);
+        }
+    }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ProfilingContainerResponseFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ProfilingContainerResponseFilter.java
index 38402b5..9f7e05f 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ProfilingContainerResponseFilter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ProfilingContainerResponseFilter.java
@@ -63,7 +63,7 @@ public class ProfilingContainerResponseFilter implements ContainerRequestFilter,
                     profilingData.addStart(ProfilingFeatureType.JAXRS, request.getPath());
                 }
             } catch (IllegalArgumentException e) {
-                log.info("Profiling data output " + profilingHeaderRequest + " is not supported, profiling NOT enabled");
+                log.info("Profiling data output {} is not supported, profiling NOT enabled", profilingHeaderRequest);
             }
         }
         return request;
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java
index 02fa786..33a7879 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestDataFilter.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -17,6 +17,8 @@
 
 package org.killbill.billing.server.filters;
 
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.List;
 
 import javax.ws.rs.core.HttpHeaders;
@@ -30,6 +32,7 @@ import com.sun.jersey.spi.container.ContainerRequest;
 import com.sun.jersey.spi.container.ContainerRequestFilter;
 import com.sun.jersey.spi.container.ContainerResponse;
 import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ContainerResponseWriter;
 
 @Singleton
 public class RequestDataFilter implements ContainerRequestFilter, ContainerResponseFilter {
@@ -48,7 +51,7 @@ public class RequestDataFilter implements ContainerRequestFilter, ContainerRespo
 
     @Override
     public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response) {
-        Request.resetPerThreadRequestData();
+        response.setContainerResponseWriter(new Adapter(response.getContainerResponseWriter()));
         return response;
     }
 
@@ -59,4 +62,26 @@ public class RequestDataFilter implements ContainerRequestFilter, ContainerRespo
         }
         return requestIds;
     }
+
+    private static final class Adapter implements ContainerResponseWriter {
+
+        private final ContainerResponseWriter crw;
+
+        Adapter(final ContainerResponseWriter containerResponseWriter) {
+            this.crw = containerResponseWriter;
+        }
+
+        @Override
+        public OutputStream writeStatusAndHeaders(final long contentLength, final ContainerResponse response) throws IOException {
+            return crw.writeStatusAndHeaders(contentLength, response);
+        }
+
+        @Override
+        public void finish() throws IOException {
+            crw.finish();
+
+            // Reset the per-thread RequestData last
+            Request.resetPerThreadRequestData();
+        }
+    }
 }
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ResponseCorsFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ResponseCorsFilter.java
index f67843c..f8b0c37 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ResponseCorsFilter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ResponseCorsFilter.java
@@ -63,6 +63,7 @@ public class ResponseCorsFilter implements Filter {
         res.addHeader("Access-Control-Allow-Origin", "*");
         res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS");
         res.addHeader("Access-Control-Allow-Headers", allowedHeaders);
+        res.addHeader("Access-Control-Expose-Headers", allowedHeaders);
         chain.doFilter(request, response);
     }
 
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
index e031a5d..51a2a75 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
@@ -27,6 +27,7 @@ import org.killbill.billing.jaxrs.resources.JaxRsResourceBase;
 import org.killbill.billing.jaxrs.util.KillbillEventHandler;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.platform.config.DefaultKillbillConfigSource;
+import org.killbill.billing.server.filters.KillbillMDCInsertingServletFilter;
 import org.killbill.billing.server.filters.ProfilingContainerResponseFilter;
 import org.killbill.billing.server.filters.RequestDataFilter;
 import org.killbill.billing.server.filters.ResponseCorsFilter;
@@ -34,11 +35,10 @@ import org.killbill.billing.server.modules.KillbillServerModule;
 import org.killbill.billing.server.security.TenantFilter;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.commons.skeleton.modules.BaseServerModuleBuilder;
-import org.slf4j.ILoggerFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.helpers.MDCInsertingServletFilter;
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Module;
 import com.google.inject.servlet.ServletModule;
@@ -63,10 +63,14 @@ public class KillbillGuiceListener extends KillbillPlatformGuiceListener {
                                                                              // Swagger integration
                                                                              .addJaxrsResource("com.wordnik.swagger.jersey.listing");
 
-        //
-        // Add jersey filters which are executed prior jersey write the output stream
-        //
-        builder.addJerseyFilter("com.sun.jersey.api.container.filter.LoggingFilter");
+        // Set the per-thread RequestData first
+        builder.addJerseyFilter(RequestDataFilter.class.getName());
+
+        // Logback default MDC
+        builder.addFilter("/*", MDCInsertingServletFilter.class);
+
+        // Kill Bill specific MDC
+        builder.addJerseyFilter(KillbillMDCInsertingServletFilter.class.getName());
 
         // Disable WADL - it generates noisy log messages, such as:
         // c.s.j.s.w.g.AbstractWadlGeneratorGrammarGenerator - Couldn't find grammar element for class javax.ws.rs.core.Response
@@ -79,16 +83,19 @@ public class KillbillGuiceListener extends KillbillPlatformGuiceListener {
             builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName());
         }
         builder.addJerseyFilter(ProfilingContainerResponseFilter.class.getName());
-        builder.addJerseyFilter(RequestDataFilter.class.getName());
 
         // Broader, to support the "Try it out!" feature
         //builder.addFilter("/" + SWAGGER_PATH + "*", ResponseCorsFilter.class);
         builder.addFilter("/*", ResponseCorsFilter.class);
 
-        // Add TenantFilter right after is multi-tenancy has been configured.
+        // Add TenantFilter right after if multi-tenancy has been configured.
         if (config.isMultiTenancyEnabled()) {
             builder.addFilter("/*", TenantFilter.class);
         }
+
+        // Finally, just before the request starts, enable the LoggingFilter
+        builder.addJerseyFilter("com.sun.jersey.api.container.filter.LoggingFilter");
+
         return builder.build();
     }
 
@@ -105,18 +112,6 @@ public class KillbillGuiceListener extends KillbillPlatformGuiceListener {
     }
 
     @Override
-    protected void startLifecycleStage1() {
-        super.startLifecycleStage1();
-
-        // Work-around for http://jira.qos.ch/browse/LOGBACK-730
-        final ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory();
-        if (iLoggerFactory instanceof LoggerContext) {
-            final LoggerContext lc = (LoggerContext) iLoggerFactory;
-            lc.setPackagingDataEnabled(false);
-        }
-    }
-
-    @Override
     protected void startLifecycleStage2() {
         killbilleventHandler = injector.getInstance(KillbillEventHandler.class);
 
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java
index 2fb4840..d53b571 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java
@@ -22,7 +22,7 @@ import javax.sql.DataSource;
 
 import org.apache.shiro.cache.CacheManager;
 import org.killbill.billing.server.security.KillbillJdbcTenantRealm;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.killbill.billing.util.glue.ShiroEhCacheInstrumentor;
 
 import com.google.inject.Inject;
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
index 9b2a102..8c32108 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
@@ -72,6 +72,7 @@ import org.killbill.billing.util.glue.BroadcastModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
 import org.killbill.billing.util.glue.ClockModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
 import org.killbill.billing.util.glue.ExportModule;
 import org.killbill.billing.util.glue.GlobalLockerModule;
@@ -90,6 +91,7 @@ import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
 import org.skife.jdbi.v2.ResultSetMapperFactory;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
+import ch.qos.logback.classic.helpers.MDCInsertingServletFilter;
 import com.google.inject.multibindings.Multibinder;
 
 public class KillbillServerModule extends KillbillPlatformModule {
@@ -144,6 +146,7 @@ public class KillbillServerModule extends KillbillPlatformModule {
         install(new BroadcastModule(configSource));
         install(new BeatrixModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new CallContextModule(configSource));
         install(new CatalogModule(configSource));
         install(new CurrencyModule(configSource));
@@ -200,6 +203,7 @@ public class KillbillServerModule extends KillbillPlatformModule {
 
     protected void configureFilters() {
         bind(ResponseCorsFilter.class).asEagerSingleton();
+        bind(MDCInsertingServletFilter.class).asEagerSingleton();
     }
 
     protected void configurePushNotification() {
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index 721c7a7..a623ff3 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -38,7 +38,7 @@ import org.apache.shiro.web.util.WebUtils;
 import org.killbill.billing.jaxrs.resources.JaxrsResource;
 import org.killbill.billing.server.security.FirstSuccessfulStrategyWith540;
 import org.killbill.billing.server.security.KillbillJdbcTenantRealm;
-import org.killbill.billing.util.config.RbacConfig;
+import org.killbill.billing.util.config.definition.RbacConfig;
 import org.killbill.billing.util.glue.EhCacheManagerProvider;
 import org.killbill.billing.util.glue.IniRealmProvider;
 import org.killbill.billing.util.glue.JDBCSessionDaoProvider;
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationListener.java
index d821177..7e3a993 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationListener.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationListener.java
@@ -113,7 +113,7 @@ public class PushNotificationListener {
                     });
             response = futureStatus.get(timeoutSec, TimeUnit.SECONDS);
         } catch (final Exception e) {
-            log.warn(String.format("Failed to push notification %s for the tenant %s", url, tenantId), e);
+            log.warn("Failed to push notification url='{}', tenantId='{}'", url, tenantId, e);
             return false;
         }
         return response.getStatusCode() >= 200 && response.getStatusCode() < 300;
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
index 20b2d7a..aa2e377 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
@@ -27,7 +27,7 @@ import org.apache.shiro.authc.SimpleAuthenticationInfo;
 import org.apache.shiro.codec.Base64;
 import org.apache.shiro.realm.jdbc.JdbcRealm;
 import org.apache.shiro.util.ByteSource;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
 
 /**
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
index 7f2a2bb..8ce5b3e 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
@@ -37,6 +37,7 @@ import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
 import org.apache.shiro.realm.Realm;
 import org.killbill.billing.jaxrs.resources.JaxrsResource;
+import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.server.listeners.KillbillGuiceListener;
 import org.killbill.billing.tenant.api.Tenant;
 import org.killbill.billing.tenant.api.TenantApiException;
@@ -55,6 +56,8 @@ public class TenantFilter implements Filter {
     private static final Logger log = LoggerFactory.getLogger(TenantFilter.class);
 
     @Inject
+    protected Context context;
+    @Inject
     protected TenantUserApi tenantUserApi;
     @Inject
     protected KillbillJdbcTenantRealm killbillJdbcTenantRealm;
@@ -101,10 +104,13 @@ public class TenantFilter implements Filter {
             final Tenant tenant = tenantUserApi.getTenantByApiKey(apiKey);
             request.setAttribute(TENANT, tenant);
 
+            // Create a dummy context, to set the MDC very early for LoggingFilter
+            context.createContext(request);
+
             chain.doFilter(request, response);
         } catch (final TenantApiException e) {
             // Should never happen since Shiro validated the credentials?
-            log.warn("Couldn't find the tenant?", e);
+            log.error("Couldn't find the tenant? - should never happen!", e);
         }
     }
 
diff --git a/profiles/killbill/src/main/resources/logback.xml b/profiles/killbill/src/main/resources/logback.xml
index 3e86876..f2cdef8 100644
--- a/profiles/killbill/src/main/resources/logback.xml
+++ b/profiles/killbill/src/main/resources/logback.xml
@@ -21,7 +21,8 @@
 
     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
         <encoder>
-            <pattern>%date [%thread] %-5level %logger{36} - %maskedMsg%n%ex</pattern>
+            <!-- See http://jira.qos.ch/browse/LOGBACK-262 -->
+            <pattern>%date{"yyyy-MM-dd'T'HH:mm:ss,SSSZ", UTC} lvl='%level', log='%logger{0}', th='%thread', xff='%X{req.xForwardedFor}', rId='%X{req.requestId}', aRId='%X{kb.accountRecordId}', tRId='%X{kb.tenantRecordId}', %maskedMsg%n</pattern>
         </encoder>
     </appender>
 
@@ -42,7 +43,7 @@
                     </timeBasedFileNamingAndTriggeringPolicy>
                 </rollingPolicy>
                 <encoder>
-                    <pattern>%date [%thread] %maskedMsg%n%ex</pattern>
+                    <pattern>%date [%thread] %maskedMsg%n</pattern>
                 </encoder>
             </appender>
         </sift>
@@ -63,7 +64,7 @@
                     </timeBasedFileNamingAndTriggeringPolicy>
                 </rollingPolicy>
                 <encoder>
-                    <pattern>%date [%thread] %maskedMsg%n%ex</pattern>
+                    <pattern>%date [%thread] %maskedMsg%n</pattern>
                 </encoder>
             </appender>
         </sift>
@@ -84,7 +85,7 @@
                     </timeBasedFileNamingAndTriggeringPolicy>
                 </rollingPolicy>
                 <encoder>
-                    <pattern>%date [%thread] %maskedMsg%n%ex</pattern>
+                    <pattern>%date [%thread] %maskedMsg%n</pattern>
                 </encoder>
             </appender>
         </sift>
@@ -105,7 +106,7 @@
                     </timeBasedFileNamingAndTriggeringPolicy>
                 </rollingPolicy>
                 <encoder>
-                    <pattern>%date [%thread] %maskedMsg%n%ex</pattern>
+                    <pattern>%date [%thread] %maskedMsg%n</pattern>
                 </encoder>
             </appender>
         </sift>
@@ -126,7 +127,7 @@
                     </timeBasedFileNamingAndTriggeringPolicy>
                 </rollingPolicy>
                 <encoder>
-                    <pattern>%date [%thread] %maskedMsg%n%ex</pattern>
+                    <pattern>%date [%thread] %maskedMsg%n</pattern>
                 </encoder>
             </appender>
         </sift>
@@ -147,7 +148,7 @@
                     </timeBasedFileNamingAndTriggeringPolicy>
                 </rollingPolicy>
                 <encoder>
-                    <pattern>%date [%thread] %maskedMsg%n%ex</pattern>
+                    <pattern>%date [%thread] %maskedMsg%n</pattern>
                 </encoder>
             </appender>
         </sift>
@@ -178,6 +179,9 @@
         <appender-ref ref="SIFT-jdbc-connection"/>
     </logger>
 
+    <!-- See https://github.com/jOOQ/jOOQ/issues/4019 -->
+    <logger name="org.jooq.Constants" level="OFF"/>
+
     <!-- Silence verbose loggers in DEBUG mode -->
     <logger name="com.dmurph" level="OFF"/>
     <logger name="org.killbill.billing.notificationq" level="INFO"/>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
index d8ed6a8..b2fb830 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
@@ -182,6 +182,11 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
      * but until we have a strong need for it, this is in the TODO list...
      */
     protected void crappyWaitForLackOfProperSynchonization() throws Exception {
-        Thread.sleep(5000);
+        crappyWaitForLackOfProperSynchonization(5000);
+    }
+
+
+    protected void crappyWaitForLackOfProperSynchonization(int sleepValueMSec) throws Exception {
+        Thread.sleep(sleepValueMSec);
     }
 }
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBundle.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBundle.java
index fbc8a31..7a1f6af 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBundle.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBundle.java
@@ -25,17 +25,22 @@ import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.billing.client.KillBillClientException;
 import org.killbill.billing.client.model.Account;
 import org.killbill.billing.client.model.BlockingState;
+import org.killbill.billing.client.model.BlockingStates;
 import org.killbill.billing.client.model.Bundle;
 import org.killbill.billing.client.model.Bundles;
+import org.killbill.billing.client.model.PluginProperty;
 import org.killbill.billing.client.model.Subscription;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
+import org.killbill.billing.util.api.AuditLevel;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotEquals;
 
@@ -134,19 +139,30 @@ public class TestBundle extends TestJaxrsBase {
         assertEquals(bundle.getAccountId(), accountJson.getAccountId());
         assertEquals(bundle.getExternalKey(), bundleExternalKey);
 
-        final BlockingState blockingState = new BlockingState(bundle.getBundleId(), "block", "service", false, true, true, clock.getToday(DateTimeZone.forID(accountJson.getTimeZone())), BlockingStateType.SUBSCRIPTION_BUNDLE, null);
-        killBillClient.setBlockingState(bundle.getBundleId(), blockingState, createdBy, reason, comment);
+        final BlockingState blockingState = new BlockingState(bundle.getBundleId(), "block", "service", false, true, true, null, BlockingStateType.SUBSCRIPTION_BUNDLE, null);
+        killBillClient.setBlockingState(bundle.getBundleId(), blockingState, clock.getToday(DateTimeZone.forID(accountJson.getTimeZone())), ImmutableMap.<String, String>of(), createdBy, reason, comment);
 
         final Subscription subscription = killBillClient.getSubscription(entitlement.getSubscriptionId());
         assertEquals(subscription.getState(), EntitlementState.BLOCKED);
 
         clock.addDays(1);
 
-        final BlockingState unblockingState = new BlockingState(bundle.getBundleId(), "unblock", "service", false, false, false, clock.getToday(DateTimeZone.forID(accountJson.getTimeZone())), BlockingStateType.SUBSCRIPTION_BUNDLE, null);
-        killBillClient.setBlockingState(bundle.getBundleId(), unblockingState, createdBy, reason, comment);
+        final BlockingState unblockingState = new BlockingState(bundle.getBundleId(), "unblock", "service", false, false, false, null, BlockingStateType.SUBSCRIPTION_BUNDLE, null);
+        killBillClient.setBlockingState(bundle.getBundleId(), unblockingState, clock.getToday(DateTimeZone.forID(accountJson.getTimeZone())), ImmutableMap.<String, String>of(), createdBy, reason, comment);
 
         final Subscription subscription2 = killBillClient.getSubscription(entitlement.getSubscriptionId());
         assertEquals(subscription2.getState(), EntitlementState.ACTIVE);
+
+        final BlockingStates blockingStates = killBillClient.getBlockingStates(accountJson.getAccountId(), null, ImmutableList.<String>of("service"), AuditLevel.FULL);
+        Assert.assertEquals(blockingStates.size(), 2);
+
+
+        final BlockingStates blockingStates2 = killBillClient.getBlockingStates(accountJson.getAccountId(), ImmutableList.<BlockingStateType>of(BlockingStateType.SUBSCRIPTION_BUNDLE), null, AuditLevel.FULL);
+        Assert.assertEquals(blockingStates2.size(), 2);
+
+
+        final BlockingStates blockingStates3 = killBillClient.getBlockingStates(accountJson.getAccountId(), null, null, AuditLevel.FULL);
+        Assert.assertEquals(blockingStates3.size(), 3);
     }
 
 
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java
index 50e2a2d..a8eb3bd 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java
@@ -123,7 +123,6 @@ public class TestChargeback extends TestJaxrsBase {
 
         final InvoicePaymentTransaction input = new InvoicePaymentTransaction();
         input.setPaymentId(payment.getPaymentId());
-        input.setAmount(BigDecimal.TEN.negate());
 
         try {
             killBillClient.createInvoicePaymentChargeback(input, createdBy, reason, comment);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
index a9c6129..1170aee 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
@@ -120,7 +120,7 @@ public class TestInvoice extends TestJaxrsBase {
                                   "            <tr>\n" +
                                   "                <td />\n" +
                                   "                <td align=right>invoiceNumber</td>\n" +
-                                  "                <td>1</td>\n" +
+                                  "                <td>" + invoiceJson.getInvoiceNumber() + "</td>\n" +
                                   "            </tr>\n" +
                                   "            <tr>\n" +
                                   "                <td>companyName</td>\n" +
@@ -168,7 +168,7 @@ public class TestInvoice extends TestJaxrsBase {
                                   "                <td>shotgun-monthly-trial</td>\n" +
                                   "                <td>Monthly shotgun plan</td>\n" +
                                   "                <td>25 avr. 2012</td>\n" +
-                                  "                <td>USD 0E-9</td>\n" +
+                                  "                <td>USD 0.00</td>\n" +
                                   "            </tr>\n" +
                                   "            \n" +
                                   "            <tr>\n" +
@@ -182,7 +182,7 @@ public class TestInvoice extends TestJaxrsBase {
                                   "            <tr>\n" +
                                   "                <td colspan=2 />\n" +
                                   "                <td align=right><strong>invoiceAmountPaid</strong></td>\n" +
-                                  "                <td align=right><strong>0</strong></td>\n" +
+                                  "                <td align=right><strong>0.00</strong></td>\n" +
                                   "            </tr>\n" +
                                   "            <tr>\n" +
                                   "                <td colspan=2 />\n" +
@@ -414,13 +414,13 @@ public class TestInvoice extends TestJaxrsBase {
         assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
 
         // Adjust partially the item
-        final BigDecimal adjustedAmount = invoiceItem.getAmount().divide(BigDecimal.TEN);
+        final BigDecimal adjustedAmount = invoiceItem.getAmount().divide(BigDecimal.TEN, BigDecimal.ROUND_HALF_UP);
         final InvoiceItem adjustmentInvoiceItem = new InvoiceItem();
         adjustmentInvoiceItem.setAccountId(accountJson.getAccountId());
         adjustmentInvoiceItem.setInvoiceId(invoice.getInvoiceId());
         adjustmentInvoiceItem.setInvoiceItemId(invoiceItem.getInvoiceItemId());
         adjustmentInvoiceItem.setAmount(adjustedAmount);
-        adjustmentInvoiceItem.setCurrency(invoice.getCurrency());
+        adjustmentInvoiceItem.setCurrency(invoice.getCurrency().name());
         killBillClient.adjustInvoiceItem(adjustmentInvoiceItem, createdBy, reason, comment);
 
         // Verify the new invoice balance
@@ -441,7 +441,7 @@ public class TestInvoice extends TestJaxrsBase {
         final InvoiceItem externalCharge = new InvoiceItem();
         externalCharge.setAccountId(accountJson.getAccountId());
         externalCharge.setAmount(chargeAmount);
-        externalCharge.setCurrency(Currency.valueOf(accountJson.getCurrency()));
+        externalCharge.setCurrency(accountJson.getCurrency());
         externalCharge.setDescription(UUID.randomUUID().toString());
         final InvoiceItem createdExternalCharge = killBillClient.createExternalCharge(externalCharge, clock.getUTCToday(), false, true, createdBy, reason, comment);
         final Invoice invoiceWithItems = killBillClient.getInvoice(createdExternalCharge.getInvoiceId(), true);
@@ -476,7 +476,7 @@ public class TestInvoice extends TestJaxrsBase {
         final InvoiceItem externalCharge2 = new InvoiceItem();
         externalCharge2.setAccountId(accountJson.getAccountId());
         externalCharge2.setAmount(chargeAmount);
-        externalCharge2.setCurrency(Currency.valueOf(accountJson.getCurrency()));
+        externalCharge2.setCurrency(accountJson.getCurrency());
         externalCharge2.setDescription(UUID.randomUUID().toString());
         externalCharges.add(externalCharge2);
 
@@ -501,8 +501,13 @@ public class TestInvoice extends TestJaxrsBase {
         final InvoiceItem externalCharge = new InvoiceItem();
         externalCharge.setAccountId(accountJson.getAccountId());
         externalCharge.setAmount(chargeAmount);
+<<<<<<< HEAD
         externalCharge.setCurrency(Currency.valueOf(accountJson.getCurrency()));
         final InvoiceItem createdExternalCharge = killBillClient.createExternalCharge(externalCharge, clock.getUTCToday(), true, true, createdBy, reason, comment);
+=======
+        externalCharge.setCurrency(accountJson.getCurrency());
+        final InvoiceItem createdExternalCharge = killBillClient.createExternalCharge(externalCharge, clock.getUTCToday(), true, createdBy, reason, comment);
+>>>>>>> work-for-release-0.17.0
         final Invoice invoiceWithItems = killBillClient.getInvoice(createdExternalCharge.getInvoiceId(), true);
         assertEquals(invoiceWithItems.getBalance().compareTo(BigDecimal.ZERO), 0);
         assertEquals(invoiceWithItems.getItems().size(), 1);
@@ -525,7 +530,7 @@ public class TestInvoice extends TestJaxrsBase {
         final InvoiceItem externalCharge = new InvoiceItem();
         externalCharge.setAccountId(accountJson.getAccountId());
         externalCharge.setAmount(chargeAmount);
-        externalCharge.setCurrency(Currency.valueOf(accountJson.getCurrency()));
+        externalCharge.setCurrency(accountJson.getCurrency());
         externalCharge.setBundleId(bundleId);
         final InvoiceItem createdExternalCharge = killBillClient.createExternalCharge(externalCharge, clock.getUTCToday(), false, true, createdBy, reason, comment);
         final Invoice invoiceWithItems = killBillClient.getInvoice(createdExternalCharge.getInvoiceId(), true);
@@ -554,7 +559,7 @@ public class TestInvoice extends TestJaxrsBase {
         final InvoiceItem externalCharge = new InvoiceItem();
         externalCharge.setAccountId(accountJson.getAccountId());
         externalCharge.setAmount(chargeAmount);
-        externalCharge.setCurrency(Currency.valueOf(accountJson.getCurrency()));
+        externalCharge.setCurrency(accountJson.getCurrency());
         externalCharge.setInvoiceId(invoiceId);
         final InvoiceItem createdExternalCharge = killBillClient.createExternalCharge(externalCharge, clock.getUTCToday(), false, true, createdBy, reason, comment);
         final Invoice invoiceWithItems = killBillClient.getInvoice(createdExternalCharge.getInvoiceId(), true);
@@ -584,7 +589,7 @@ public class TestInvoice extends TestJaxrsBase {
         final InvoiceItem externalCharge = new InvoiceItem();
         externalCharge.setAccountId(accountJson.getAccountId());
         externalCharge.setAmount(chargeAmount);
-        externalCharge.setCurrency(Currency.valueOf(accountJson.getCurrency()));
+        externalCharge.setCurrency(accountJson.getCurrency());
         externalCharge.setInvoiceId(invoiceId);
         final InvoiceItem createdExternalCharge = killBillClient.createExternalCharge(externalCharge, clock.getUTCToday(), true, true, createdBy, reason, comment);
         final Invoice invoiceWithItems = killBillClient.getInvoice(createdExternalCharge.getInvoiceId(), true);
@@ -614,7 +619,7 @@ public class TestInvoice extends TestJaxrsBase {
         final InvoiceItem externalCharge = new InvoiceItem();
         externalCharge.setAccountId(accountJson.getAccountId());
         externalCharge.setAmount(chargeAmount);
-        externalCharge.setCurrency(Currency.valueOf(accountJson.getCurrency()));
+        externalCharge.setCurrency(accountJson.getCurrency());
         externalCharge.setInvoiceId(invoiceId);
         externalCharge.setBundleId(bundleId);
         final InvoiceItem createdExternalCharge = killBillClient.createExternalCharge(externalCharge, clock.getUTCToday(), false, true, createdBy, reason, comment);
@@ -693,7 +698,7 @@ public class TestInvoice extends TestJaxrsBase {
         externalCharge.setAccountId(accountJson.getAccountId());
         externalCharge.setAmount(chargeAmount);
         externalCharge.setItemType(InvoiceItemType.EXTERNAL_CHARGE.toString());
-        externalCharge.setCurrency(Currency.valueOf(accountJson.getCurrency()));
+        externalCharge.setCurrency(accountJson.getCurrency());
 
         final Account accountWithBalance = killBillClient.getAccount(accountJson.getAccountId(), true, true);
 
@@ -701,7 +706,7 @@ public class TestInvoice extends TestJaxrsBase {
         assertEquals(migrationInvoice.getBalance(), BigDecimal.ZERO);
         assertEquals(migrationInvoice.getItems().size(), 1);
         assertEquals(migrationInvoice.getItems().get(0).getAmount().compareTo(chargeAmount), 0);
-        assertEquals(migrationInvoice.getItems().get(0).getCurrency(), Currency.valueOf(accountJson.getCurrency()));
+        assertEquals(migrationInvoice.getItems().get(0).getCurrency(), accountJson.getCurrency());
 
 
         final List<Invoice> invoicesWithMigration = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, true);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java
index bf11eaa..fbb1e9c 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java
@@ -88,52 +88,6 @@ public class TestInvoicePayment extends TestJaxrsBase {
         verifyInvoice(paymentJson, expectedInvoiceBalance);
     }
 
-    @Test(groups = "slow", description = "Can create a full refund with invoice adjustment")
-    public void testFullRefundWithInvoiceAdjustment() throws Exception {
-        final InvoicePayment paymentJson = setupScenarioWithPayment();
-
-        // Issue a refund for the full amount
-        final BigDecimal refundAmount = paymentJson.getPurchasedAmount();
-        final BigDecimal expectedInvoiceBalance = BigDecimal.ZERO;
-
-        final InvoicePayments invoicePayments = killBillClient.getInvoicePayment(paymentJson.getTargetInvoiceId());
-        Assert.assertEquals(invoicePayments.size(), 1);
-
-        // Post and verify the refund
-        final InvoicePaymentTransaction refund = new InvoicePaymentTransaction();
-        refund.setPaymentId(paymentJson.getPaymentId());
-        refund.setAmount(refundAmount);
-        refund.setIsAdjusted(true);
-        final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, createdBy, reason, comment);
-        verifyRefund(paymentJson, paymentAfterRefundJson, refundAmount);
-
-        // Verify the invoice balance
-        verifyInvoice(paymentJson, expectedInvoiceBalance);
-
-        final InvoicePayments invoicePaymentsAfterRefund = killBillClient.getInvoicePayment(paymentJson.getTargetInvoiceId());
-        Assert.assertEquals(invoicePaymentsAfterRefund.size(), 1);
-
-    }
-
-    @Test(groups = "slow", description = "Can create a partial refund with invoice adjustment")
-    public void testPartialRefundWithInvoiceAdjustment() throws Exception {
-        final InvoicePayment paymentJson = setupScenarioWithPayment();
-
-        // Issue a refund for a fraction of the amount
-        final BigDecimal refundAmount = getFractionOfAmount(paymentJson.getPurchasedAmount());
-        final BigDecimal expectedInvoiceBalance = BigDecimal.ZERO;
-
-        // Post and verify the refund
-        final InvoicePaymentTransaction refund = new InvoicePaymentTransaction();
-        refund.setPaymentId(paymentJson.getPaymentId());
-        refund.setAmount(refundAmount);
-        refund.setIsAdjusted(true);
-        final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, createdBy, reason, comment);
-        verifyRefund(paymentJson, paymentAfterRefundJson, refundAmount);
-
-        // Verify the invoice balance
-        verifyInvoice(paymentJson, expectedInvoiceBalance);
-    }
 
     @Test(groups = "slow", description = "Can create a full refund with invoice item adjustment")
     public void testRefundWithFullInvoiceItemAdjustment() throws Exception {
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
index 5044598..7184aae 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
@@ -54,8 +54,8 @@ import org.killbill.billing.server.config.KillbillServerConfig;
 import org.killbill.billing.server.listeners.KillbillGuiceListener;
 import org.killbill.billing.server.modules.KillbillServerModule;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
-import org.killbill.billing.util.config.PaymentConfig;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.PaymentConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.commons.jdbi.guice.DaoConfig;
 import org.skife.config.ConfigurationObjectFactory;
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
index b76434b..b46915f 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
@@ -33,6 +33,7 @@ import org.killbill.billing.client.model.PaymentTransaction;
 import org.killbill.billing.client.model.Payments;
 import org.killbill.billing.client.model.PluginProperty;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
@@ -47,6 +48,7 @@ import com.google.common.collect.ImmutableMap;
 import com.google.inject.Inject;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
 
 public class TestPayment extends TestJaxrsBase {
 
@@ -139,6 +141,185 @@ public class TestPayment extends TestJaxrsBase {
     }
 
     @Test(groups = "slow")
+    public void testAuthorizeCompletionUsingPaymentId() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+        final UUID paymentMethodId = account.getPaymentMethodId();
+        final BigDecimal amount = BigDecimal.TEN;
+
+        final String pending = PaymentPluginStatus.PENDING.toString();
+        final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+        final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+        TransactionType transactionType = TransactionType.AUTHORIZE;
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+        final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+        // Complete operation: first, only specify the payment id
+        final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction();
+        completeTransactionByPaymentId.setPaymentId(initialPayment.getPaymentId());
+        final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment);
+        verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+    }
+
+
+    @Test(groups = "slow")
+    public void testAuthorizeCompletionUsingPaymentIdAndTransactionId() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+        final UUID paymentMethodId = account.getPaymentMethodId();
+        final BigDecimal amount = BigDecimal.TEN;
+
+        final String pending = PaymentPluginStatus.PENDING.toString();
+        final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+        final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+        TransactionType transactionType = TransactionType.AUTHORIZE;
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+        final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+
+        final PaymentTransaction completeTransactionByPaymentIdAndInvalidTransactionId = new PaymentTransaction();
+        completeTransactionByPaymentIdAndInvalidTransactionId.setPaymentId(initialPayment.getPaymentId());
+        completeTransactionByPaymentIdAndInvalidTransactionId.setTransactionId(UUID.randomUUID());
+        try {
+            killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionId, pluginProperties, createdBy, reason, comment);
+            fail("Payment completion should fail when invalid transaction id has been provided" );
+        } catch (final KillBillClientException expected) {
+        }
+
+        final PaymentTransaction completeTransactionByPaymentIdAndTransactionId = new PaymentTransaction();
+        completeTransactionByPaymentIdAndTransactionId.setPaymentId(initialPayment.getPaymentId());
+        completeTransactionByPaymentIdAndTransactionId.setTransactionId(initialPayment.getTransactions().get(0).getTransactionId());
+        final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionId, pluginProperties, createdBy, reason, comment);
+        verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+    }
+
+    @Test(groups = "slow")
+    public void testAuthorizeCompletionUsingPaymentIdAndTransactionExternalKey() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+        final UUID paymentMethodId = account.getPaymentMethodId();
+        final BigDecimal amount = BigDecimal.TEN;
+
+        final String pending = PaymentPluginStatus.PENDING.toString();
+        final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+        final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+        TransactionType transactionType = TransactionType.AUTHORIZE;
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+        final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+        final PaymentTransaction completeTransactionByPaymentIdAndInvalidTransactionExternalKey = new PaymentTransaction();
+        completeTransactionByPaymentIdAndInvalidTransactionExternalKey.setPaymentId(initialPayment.getPaymentId());
+        completeTransactionByPaymentIdAndInvalidTransactionExternalKey.setTransactionExternalKey("bozo");
+        try {
+            killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionExternalKey, pluginProperties, createdBy, reason, comment);
+            fail("Payment completion should fail when invalid transaction externalKey has been provided" );
+        } catch (final KillBillClientException expected) {
+        }
+
+        final PaymentTransaction completeTransactionByPaymentIdAndTransactionExternalKey = new PaymentTransaction();
+        completeTransactionByPaymentIdAndTransactionExternalKey.setPaymentId(initialPayment.getPaymentId());
+        completeTransactionByPaymentIdAndTransactionExternalKey.setTransactionExternalKey(authTransactionExternalKey);
+        final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionExternalKey, pluginProperties, createdBy, reason, comment);
+        verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+    }
+
+
+    @Test(groups = "slow")
+    public void testAuthorizeCompletionUsingPaymentIdAndTransactionType() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+        final UUID paymentMethodId = account.getPaymentMethodId();
+        final BigDecimal amount = BigDecimal.TEN;
+
+        final String pending = PaymentPluginStatus.PENDING.toString();
+        final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+        final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+        TransactionType transactionType = TransactionType.AUTHORIZE;
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+        final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+
+        final PaymentTransaction completeTransactionByPaymentIdAndInvalidTransactionType = new PaymentTransaction();
+        completeTransactionByPaymentIdAndInvalidTransactionType.setPaymentId(initialPayment.getPaymentId());
+        completeTransactionByPaymentIdAndInvalidTransactionType.setTransactionType(TransactionType.CAPTURE.name());
+        try {
+            killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionType, pluginProperties, createdBy, reason, comment);
+            fail("Payment completion should fail when invalid transaction type has been provided" );
+        } catch (final KillBillClientException expected) {
+        }
+
+        final PaymentTransaction completeTransactionByPaymentIdAndTransactionType = new PaymentTransaction();
+        completeTransactionByPaymentIdAndTransactionType.setPaymentId(initialPayment.getPaymentId());
+        completeTransactionByPaymentIdAndTransactionType.setTransactionType(transactionType.name());
+        final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionType, pluginProperties, createdBy, reason, comment);
+        verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+    }
+
+    @Test(groups = "slow")
+    public void testAuthorizeCompletionUsingExternalKey() throws Exception {
+
+        final Account account = createAccountWithDefaultPaymentMethod();
+        final UUID paymentMethodId = account.getPaymentMethodId();
+        final BigDecimal amount = BigDecimal.TEN;
+
+        final String pending = PaymentPluginStatus.PENDING.toString();
+        final ImmutableMap<String, String> pendingPluginProperties = ImmutableMap.<String, String>of(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, pending);
+
+        final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+        TransactionType transactionType = TransactionType.AUTHORIZE;
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+        final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, pending, amount, BigDecimal.ZERO, pendingPluginProperties, 1);
+
+        final PaymentTransaction completeTransactionWithTypeAndKey = new PaymentTransaction();
+        completeTransactionWithTypeAndKey.setPaymentId(initialPayment.getPaymentId());
+        completeTransactionWithTypeAndKey.setTransactionExternalKey(authTransactionExternalKey);
+        final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, createdBy, reason, comment);
+        verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+    }
+
+
+    @Test(groups = "slow")
+    public void testAuthorizeInvalidCompletionUsingPaymentId() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+        final UUID paymentMethodId = account.getPaymentMethodId();
+        final BigDecimal amount = BigDecimal.TEN;
+
+        final ImmutableMap<String, String> pluginProperties = ImmutableMap.of();
+
+        TransactionType transactionType = TransactionType.AUTHORIZE;
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String authTransactionExternalKey = UUID.randomUUID().toString();
+
+        final Payment initialPayment = createVerifyTransaction(account, paymentMethodId, paymentExternalKey, authTransactionExternalKey, transactionType, TransactionStatus.SUCCESS.name(), amount, amount, pluginProperties, 1);
+
+        // The payment was already completed
+        final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction();
+        completeTransactionByPaymentId.setPaymentId(initialPayment.getPaymentId());
+        try {
+            killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment);
+            fail("Completion should not succeed, there is no PENDING payment transaction");
+        } catch (final KillBillClientException expected) {
+            // Invalid parameter paymentId: XXXX
+        }
+    }
+
+
+    @Test(groups = "slow")
     public void testCompletionForSubsequentTransaction() throws Exception {
         final Account account = createAccountWithDefaultPaymentMethod();
         final UUID paymentMethodId = account.getPaymentMethodId();
@@ -163,27 +344,7 @@ public class TestPayment extends TestJaxrsBase {
         final Payment refundPayment = killBillClient.refundPayment(refundTransaction, null, pluginProperties, createdBy, reason, comment);
         verifyPaymentWithPendingRefund(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, purchaseAmount, refundTransactionExternalKey, refundPayment);
 
-        // We cannot complete using just the payment id as JAX-RS doesn't know which transaction to complete
-        try {
-            final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction();
-            completeTransactionByPaymentId.setPaymentId(refundPayment.getPaymentId());
-            killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, createdBy, reason, comment);
-            Assert.fail();
-        } catch (final KillBillClientException e) {
-            assertEquals(e.getMessage(), "PaymentTransactionJson transactionType and externalKey need to be set");
-        }
-
-        // We cannot complete using just the payment external key as JAX-RS doesn't know which transaction to complete
-        try {
-            final PaymentTransaction completeTransactionByPaymentExternalKey = new PaymentTransaction();
-            completeTransactionByPaymentExternalKey.setPaymentExternalKey(refundPayment.getPaymentExternalKey());
-            killBillClient.completePayment(completeTransactionByPaymentExternalKey, pluginProperties, createdBy, reason, comment);
-            Assert.fail();
-        } catch (final KillBillClientException e) {
-            assertEquals(e.getMessage(), "PaymentTransactionJson transactionType and externalKey need to be set");
-        }
 
-        // Finally, it should work if we specify the payment id and transaction external key
         final PaymentTransaction completeTransactionWithTypeAndKey = new PaymentTransaction();
         completeTransactionWithTypeAndKey.setPaymentId(refundPayment.getPaymentId());
         completeTransactionWithTypeAndKey.setTransactionExternalKey(refundTransactionExternalKey);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPerTenantConfig.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPerTenantConfig.java
new file mode 100644
index 0000000..d07d474
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPerTenantConfig.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.jaxrs;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.ComboPaymentTransaction;
+import org.killbill.billing.client.model.Payment;
+import org.killbill.billing.client.model.PaymentMethod;
+import org.killbill.billing.client.model.PaymentMethodPluginDetail;
+import org.killbill.billing.client.model.PaymentTransaction;
+import org.killbill.billing.client.model.Payments;
+import org.killbill.billing.client.model.PluginProperty;
+import org.killbill.billing.client.model.Tenant;
+import org.killbill.billing.client.model.TenantKey;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
+import org.killbill.billing.util.jackson.ObjectMapper;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestPerTenantConfig extends TestJaxrsBase {
+
+    @Inject
+    protected OSGIServiceRegistration<PaymentPluginApi> registry;
+
+    private MockPaymentProviderPlugin mockPaymentProviderPlugin;
+
+    @BeforeMethod(groups = "slow")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(PLUGIN_NAME);
+        mockPaymentProviderPlugin.clear();
+    }
+
+    @Test(groups = "slow")
+    public void testFailedPaymentWithPerTenantRetryConfig() throws Exception {
+
+        // Create the tenant
+        final String apiKeyTenant1 = "tenantSuperTuned";
+        final String apiSecretTenant1 = "2367$$ffr79";
+        loginTenant(apiKeyTenant1, apiSecretTenant1);
+        final Tenant tenant1 = new Tenant();
+        tenant1.setApiKey(apiKeyTenant1);
+        tenant1.setApiSecret(apiSecretTenant1);
+        killBillClient.createTenant(tenant1, createdBy, reason, comment);
+
+        // Configure our plugin to fail
+        mockPaymentProviderPlugin.makeNextPaymentFailWithError();
+
+        // Upload the config
+        final ObjectMapper mapper = new ObjectMapper();
+        final HashMap<String, String> perTenantProperties = new HashMap<String, String>();
+        perTenantProperties.put("org.killbill.payment.retry.days", "1,1,1");
+        final String perTenantConfig = mapper.writeValueAsString(perTenantProperties);
+
+        final TenantKey tenantKey = killBillClient.postConfigurationPropertiesForTenant(perTenantConfig, createdBy, reason, comment);
+
+        final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        final Payments payments = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
+        Assert.assertEquals(payments.size(), 1);
+        Assert.assertEquals(payments.get(0).getTransactions().size(), 1);
+
+        //
+        // Because we have specified a retry interval of one day we should see the new attempt after moving clock 1 day (and not 8 days which is default)
+        //
+
+        // Configure our plugin to fail AGAIN
+        mockPaymentProviderPlugin.makeNextPaymentFailWithError();
+
+
+        //
+        // Now unregister special per tenant config and we the first retry occurs one day after (and still fails), it now sets a retry date of 8 days
+        //
+        killBillClient.unregisterConfigurationForTenant(createdBy, reason, comment);
+        // org.killbill.tenant.broadcast.rate has been set to 1s
+        crappyWaitForLackOfProperSynchonization(2000);
+
+
+        clock.addDays(1);
+        crappyWaitForLackOfProperSynchonization(3000);
+
+        final Payments payments2 = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
+        Assert.assertEquals(payments2.size(), 1);
+        Assert.assertEquals(payments2.get(0).getTransactions().size(), 2);
+        Assert.assertEquals(payments2.get(0).getTransactions().get(0).getStatus(), TransactionStatus.PAYMENT_FAILURE.name());
+        Assert.assertEquals(payments2.get(0).getTransactions().get(1).getStatus(), TransactionStatus.PAYMENT_FAILURE.name());
+
+
+        clock.addDays(1);
+        crappyWaitForLackOfProperSynchonization(3000);
+
+        // No retry with default config
+        final Payments payments3 = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
+        Assert.assertEquals(payments3.size(), 1);
+        Assert.assertEquals(payments3.get(0).getTransactions().size(), 2);
+
+        clock.addDays(7);
+        crappyWaitForLackOfProperSynchonization(3000);
+
+        final Payments payments4 = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
+        Assert.assertEquals(payments4.size(), 1);
+        Assert.assertEquals(payments4.get(0).getTransactions().size(), 3);
+        Assert.assertEquals(payments4.get(0).getTransactions().get(0).getStatus(), TransactionStatus.PAYMENT_FAILURE.name());
+        Assert.assertEquals(payments4.get(0).getTransactions().get(1).getStatus(), TransactionStatus.PAYMENT_FAILURE.name());
+        Assert.assertEquals(payments4.get(0).getTransactions().get(2).getStatus(), TransactionStatus.SUCCESS.name());
+
+    }
+
+
+}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java
index 2e9dd6a..dfc49e3 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java
@@ -19,9 +19,11 @@ package org.killbill.billing.jaxrs;
 
 import java.util.UUID;
 
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.client.KillBillClientException;
 import org.killbill.billing.client.model.Account;
 import org.killbill.billing.client.model.Bundle;
 import org.killbill.billing.client.model.RolledUpUsage;
@@ -115,4 +117,62 @@ public class TestUsage extends TestJaxrsBase {
         Assert.assertEquals(retrievedUsage4.getRolledUpUnits().get(0).getUnitType(), "bullets");
         Assert.assertEquals((long) retrievedUsage4.getRolledUpUnits().get(0).getAmount(), 5);
     }
+
+    @Test(groups = "slow", description = "Test tracking ID already exists")
+    public void testRecordUsageTrackingIdExists() throws Exception {
+
+        final Account accountJson = createAccountWithDefaultPaymentMethod();
+
+        final Subscription base = new Subscription();
+        base.setAccountId(accountJson.getAccountId());
+        base.setProductName("Pistol");
+        base.setProductCategory(ProductCategory.BASE);
+        base.setBillingPeriod(BillingPeriod.MONTHLY);
+        base.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        final Subscription addOn = new Subscription();
+        addOn.setAccountId(accountJson.getAccountId());
+        addOn.setProductName("Bullets");
+        addOn.setProductCategory(ProductCategory.ADD_ON);
+        addOn.setBillingPeriod(BillingPeriod.NO_BILLING_PERIOD);
+        addOn.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        final Bundle bundle = killBillClient.createSubscriptionWithAddOns(ImmutableList.<Subscription>of(base, addOn),
+                                                                          null,
+                                                                          DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC,
+                                                                          createdBy,
+                                                                          reason,
+                                                                          comment);
+        final UUID addOnSubscriptionId = Iterables.<Subscription>find(bundle.getSubscriptions(),
+                                                                      new Predicate<Subscription>() {
+                                                                          @Override
+                                                                          public boolean apply(final Subscription input) {
+                                                                              return ProductCategory.ADD_ON.equals(input.getProductCategory());
+                                                                          }
+                                                                      }).getSubscriptionId();
+
+        final UsageRecord usageRecord1 = new UsageRecord();
+        usageRecord1.setAmount(10L);
+        usageRecord1.setRecordDate(clock.getUTCToday().minusDays(1));
+
+        final UnitUsageRecord unitUsageRecord = new UnitUsageRecord();
+        unitUsageRecord.setUnitType("bullets");
+        unitUsageRecord.setUsageRecords(ImmutableList.<UsageRecord>of(usageRecord1));
+
+        final SubscriptionUsageRecord usage = new SubscriptionUsageRecord();
+        usage.setSubscriptionId(addOnSubscriptionId);
+        usage.setTrackingId(UUID.randomUUID().toString());
+        usage.setUnitUsageRecords(ImmutableList.<UnitUsageRecord>of(unitUsageRecord));
+
+        killBillClient.createSubscriptionUsageRecord(usage, createdBy, reason, comment);
+
+        try {
+            killBillClient.createSubscriptionUsageRecord(usage, createdBy, reason, comment);
+            Assert.fail();
+        }
+        catch (final KillBillClientException e) {
+            Assert.assertEquals(e.getBillingException().getCode(), (Integer) ErrorCode.USAGE_RECORD_TRACKING_ID_ALREADY_EXISTS.getCode());
+        }
+
+    }
 }
diff --git a/profiles/killbill/src/test/resources/killbill.properties b/profiles/killbill/src/test/resources/killbill.properties
index 08ec536..71e091f 100644
--- a/profiles/killbill/src/test/resources/killbill.properties
+++ b/profiles/killbill/src/test/resources/killbill.properties
@@ -28,3 +28,6 @@ org.killbill.osgi.bundle.install.dir=/var/tmp/somethingthatdoesnotexist
 
 # Speed up from the (more secure) default
 org.killbill.security.shiroNbHashIterations=10
+
+org.killbill.tenant.broadcast.rate=1s
+
diff --git a/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java b/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java
index 389b1fb..a617d2e 100644
--- a/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java
+++ b/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java
@@ -54,6 +54,7 @@ import org.killbill.billing.util.glue.AuditModule;
 import org.killbill.billing.util.glue.BroadcastModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
 import org.killbill.billing.util.glue.ExportModule;
 import org.killbill.billing.util.glue.GlobalLockerModule;
@@ -78,6 +79,7 @@ public class KillpayServerModule extends KillbillServerModule {
         install(new BroadcastModule(configSource));
         install(new BeatrixModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new CallContextModule(configSource));
         install(new CurrencyModule(configSource));
         install(new CustomFieldModule(configSource));
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/alignment/BaseAligner.java b/subscription/src/main/java/org/killbill/billing/subscription/alignment/BaseAligner.java
index 57ce150..e0a1128 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/alignment/BaseAligner.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/alignment/BaseAligner.java
@@ -1,11 +1,13 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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
+ *    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
@@ -17,7 +19,6 @@
 package org.killbill.billing.subscription.alignment;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.catalog.api.Duration;
 
 public class BaseAligner {
@@ -30,25 +31,7 @@ public class BaseAligner {
         return addOrRemoveDuration(input, duration, false);
     }
 
-    private DateTime addOrRemoveDuration(final DateTime input, final Duration duration, boolean add) {
-        DateTime result = input;
-        switch (duration.getUnit()) {
-            case DAYS:
-                result = add ? result.plusDays(duration.getNumber()) : result.minusDays(duration.getNumber());
-                ;
-                break;
-
-            case MONTHS:
-                result = add ? result.plusMonths(duration.getNumber()) : result.minusMonths(duration.getNumber());
-                break;
-
-            case YEARS:
-                result = add ? result.plusYears(duration.getNumber()) : result.minusYears(duration.getNumber());
-                break;
-            case UNLIMITED:
-            default:
-                throw new RuntimeException("Trying to move to unlimited time period");
-        }
-        return result;
+    private DateTime addOrRemoveDuration(final DateTime input, final Duration duration, final boolean add) {
+        return add ? input.plus(duration.toJodaPeriod()) : input.minus(duration.toJodaPeriod());
     }
 }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index 5ef474f..5f4ef51 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -323,7 +323,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                     }
                 }
             } catch (final CatalogApiException e) {
-                log.warn("Failed to get subscriptions, ", e);
+                log.warn("Failed to get subscriptions for bundleId='{}'", cur.getId(), e);
                 return null;
             }
         }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
index 5a40d15..bf05324 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
@@ -102,7 +102,7 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
                 @Override
                 public void handleReadyNotification(final NotificationEvent inputKey, final DateTime eventDateTime, final UUID fromNotificationQueueUserToken, final Long accountRecordId, final Long tenantRecordId) {
                     if (!(inputKey instanceof SubscriptionNotificationKey)) {
-                        log.error("SubscriptionBase service received an unexpected event type {}" + inputKey.getClass().getName());
+                        log.error("SubscriptionBase service received an unexpected event className='{}'", inputKey.getClass().getName());
                         return;
                     }
 
@@ -110,7 +110,7 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
                     final SubscriptionBaseEvent event = dao.getEventById(key.getEventId(), internalCallContextFactory.createInternalTenantContext(tenantRecordId, accountRecordId));
                     if (event == null) {
                         // This can be expected if the event is soft deleted (is_active = 0)
-                        log.info("Failed to extract event for notification key {}", inputKey);
+                        log.debug("Failed to extract event for notification key {}", inputKey);
                         return;
                     }
 
@@ -149,7 +149,7 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
         try {
             final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(event.getSubscriptionId(), context);
             if (subscription == null) {
-                log.warn("Failed to retrieve subscription for id %s", event.getSubscriptionId());
+                log.warn("Error retrieving subscriptionId='{}'", event.getSubscriptionId());
                 return;
             }
 
@@ -172,9 +172,9 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
                 eventBus.post(busEvent);
             }
         } catch (final EventBusException e) {
-            log.warn("Failed to post subscription event " + event, e);
+            log.warn("Failed to post event {}", event, e);
         } catch (final CatalogApiException e) {
-            log.warn("Failed to post subscription event " + event, e);
+            log.warn("Failed to post event {}", event, e);
         }
     }
 
@@ -191,7 +191,7 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
                 return true;
             }
         } catch (final SubscriptionBaseError e) {
-            log.error(String.format("Failed to insert next phase for subscription %s", subscription.getId()), e);
+            log.warn("Error inserting next phase for subscriptionId='{}'", subscription.getId(), e);
         }
 
         return false;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 71b5af6..d7cc375 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -269,19 +269,19 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
             public UUID inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
                 final SubscriptionModelDao subscriptionModel = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getById(subscriptionId.toString(), context);
                 if (subscriptionModel == null) {
-                    log.error(String.format(ErrorCode.SUB_INVALID_SUBSCRIPTION_ID.getFormat(), subscriptionId.toString()));
+                    log.warn(String.format(ErrorCode.SUB_INVALID_SUBSCRIPTION_ID.getFormat(), subscriptionId.toString()));
                     return null;
                 }
 
                 final UUID bundleId = subscriptionModel.getBundleId();
                 if (bundleId == null) {
-                    log.error(String.format(ErrorCode.SUB_GET_NO_BUNDLE_FOR_SUBSCRIPTION.getFormat(), subscriptionId.toString()));
+                    log.warn(String.format(ErrorCode.SUB_GET_NO_BUNDLE_FOR_SUBSCRIPTION.getFormat(), subscriptionId.toString()));
                     return null;
                 }
 
                 final SubscriptionBundleModelDao bundleModel = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getById(bundleId.toString(), context);
                 if (bundleModel == null) {
-                    log.error(String.format(ErrorCode.SUB_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
+                    log.warn(String.format(ErrorCode.SUB_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
                     return null;
                 }
                 return bundleModel.getAccountId();
@@ -952,7 +952,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
             final DefaultSubscriptionBase upToDateSubscription = createSubscriptionWithNewEvent(subscription, immediateEvent, context);
             notifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, upToDateSubscription, immediateEvent, seqId, context);
         } catch (final CatalogApiException e) {
-            log.warn("Failed to post effective event for subscription " + subscription.getId(), e);
+            log.warn("Failed to post effective event for subscriptionId='{}'", subscription.getId(), e);
         }
     }
 
@@ -968,7 +968,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
             eventBus.postFromTransaction(busEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
         } catch (final EventBusException e) {
-            log.warn("Failed to post effective event for subscription " + subscription.getId(), e);
+            log.warn("Failed to post effective event for subscriptionId='{}'", subscription.getId(), e);
         }
     }
 
@@ -977,7 +977,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         try {
             eventBus.postFromTransaction(new DefaultRequestedSubscriptionEvent(subscription, nextEvent, transitionType, context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()), entitySqlDaoWrapperFactory.getHandle().getConnection());
         } catch (final EventBusException e) {
-            log.warn("Failed to post requested change event for subscription " + subscription.getId(), e);
+            log.warn("Failed to post requested change event for subscriptionId='{}'", subscription.getId(), e);
         }
     }
 
@@ -1004,7 +1004,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
         final List<SubscriptionBundleModelDao> existingBundleModels = transBundleDao.getBundlesFromAccountAndKey(bundleData.getAccountId().toString(), bundleData.getExternalKey(), context);
         if (existingBundleModels.size() != 0) {
-            log.error(String.format("Attempted to create a bundle for account %s and key %s that already existed, skip...", bundleData.getAccountId().toString(), bundleData.getExternalKey()));
+            log.warn("Bundle already exists for accountId='{}', bundleExternalKey='{}'", bundleData.getAccountId(), bundleData.getExternalKey());
             return;
         }
 
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java b/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java
index f5ac919..16dde8b 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java
@@ -34,7 +34,7 @@ import org.killbill.billing.subscription.engine.addon.AddonUtils;
 import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseService;
 import org.killbill.billing.subscription.engine.dao.DefaultSubscriptionDao;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
-import org.killbill.billing.util.config.SubscriptionConfig;
+import org.killbill.billing.util.config.definition.SubscriptionConfig;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.skife.config.ConfigurationObjectFactory;
 
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModule.java b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModule.java
index 25984ce..fe9ba7c 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModule.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModule.java
@@ -27,6 +27,7 @@ import org.killbill.billing.subscription.SubscriptionTestInitializer;
 import org.killbill.billing.subscription.api.user.TestSubscriptionHelper;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 
 public class TestDefaultSubscriptionModule extends DefaultSubscriptionModule {
 
@@ -40,6 +41,7 @@ public class TestDefaultSubscriptionModule extends DefaultSubscriptionModule {
         install(new CatalogModule(configSource));
         install(new CallContextModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new MockTenantModule(configSource));
 
         bind(TestSubscriptionHelper.class).asEagerSingleton();
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
index 0148bc4..0ace9f9 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
@@ -41,7 +41,7 @@ import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.glue.TestDefaultSubscriptionModuleNoDB;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.SubscriptionConfig;
+import org.killbill.billing.util.config.definition.SubscriptionConfig;
 import org.killbill.clock.ClockMock;
 import org.mockito.Mockito;
 import org.skife.jdbi.v2.IDBI;
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
index 59b5209..e97f3e0 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
@@ -38,7 +38,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
 import org.killbill.billing.subscription.api.user.TestSubscriptionHelper;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.glue.TestDefaultSubscriptionModuleWithEmbeddedDB;
-import org.killbill.billing.util.config.SubscriptionConfig;
+import org.killbill.billing.util.config.definition.SubscriptionConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.clock.ClockMock;
 import org.slf4j.Logger;
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenantInternalApi.java b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenantInternalApi.java
index be25ccd..50923c6 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenantInternalApi.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenantInternalApi.java
@@ -72,6 +72,12 @@ public class DefaultTenantInternalApi implements TenantInternalApi {
     }
 
     @Override
+    public String getTenantConfig(final InternalTenantContext tenantContext) {
+        final List<String> values = tenantDao.getTenantValueForKey(TenantKey.PER_TENANT_CONFIG.toString(), tenantContext);
+        return getUniqueValue(values, "per tenant config", tenantContext);
+    }
+
+    @Override
     public String getInvoiceTemplate(final Locale locale, final InternalTenantContext tenantContext) {
         final List<String> values = tenantDao.getTenantValueForKey(LocaleUtils.localeString(locale, TenantKey.INVOICE_TEMPLATE.toString()), tenantContext);
         return getUniqueValue(values, "invoice template", tenantContext);
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java
index a49f041..8bae33c 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidation.java
@@ -38,7 +38,7 @@ import org.killbill.billing.tenant.dao.TenantBroadcastModelDao;
 import org.killbill.billing.tenant.dao.TenantDao;
 import org.killbill.billing.tenant.dao.TenantKVModelDao;
 import org.killbill.billing.tenant.glue.DefaultTenantModule;
-import org.killbill.billing.util.config.TenantConfig;
+import org.killbill.billing.util.config.definition.TenantConfig;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.commons.concurrent.Executors;
@@ -198,7 +198,7 @@ public class TenantCacheInvalidation {
                             try {
                                 parent.getEventBus().post(event);
                             } catch (final EventBusException e) {
-                                logger.warn("Failed post bus event " + event, e);
+                                logger.warn("Failed to post event {}", event, e);
                             }
                         }
                     } else {
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
index 1e12715..fa33b37 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
@@ -38,7 +38,7 @@ import org.killbill.billing.tenant.api.TenantKV.TenantKey;
 import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.entity.dao.EntityDaoBase;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java b/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java
index c85a96f..b95e741 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/glue/DefaultTenantModule.java
@@ -35,7 +35,7 @@ import org.killbill.billing.tenant.dao.NoCachingTenantDao;
 import org.killbill.billing.tenant.dao.TenantBroadcastDao;
 import org.killbill.billing.tenant.dao.TenantDao;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.TenantConfig;
+import org.killbill.billing.util.config.definition.TenantConfig;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.billing.util.glue.NoCachingInternalCallContextFactoryProvider;
 import org.skife.config.ConfigurationObjectFactory;
diff --git a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModule.java b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModule.java
index 3fba86d..89a6621 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModule.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModule.java
@@ -21,6 +21,7 @@ package org.killbill.billing.tenant.glue;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.ConfigModule;
 
 public class TestTenantModule extends DefaultTenantModule {
 
@@ -33,6 +34,7 @@ public class TestTenantModule extends DefaultTenantModule {
         super.configure();
 
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new CallContextModule(configSource));
     }
 }
diff --git a/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java b/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java
index 2c4f78f..060a087 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java
@@ -26,7 +26,7 @@ import org.killbill.billing.tenant.dao.DefaultTenantDao;
 import org.killbill.billing.tenant.dao.TenantBroadcastDao;
 import org.killbill.billing.tenant.glue.DefaultTenantModule;
 import org.killbill.billing.tenant.glue.TestTenantModuleWithEmbeddedDB;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
diff --git a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
index 45b0146..74baa77 100644
--- a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
+++ b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
@@ -25,6 +25,7 @@ import java.util.UUID;
 import javax.inject.Inject;
 
 import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
@@ -32,6 +33,7 @@ import org.killbill.billing.usage.api.RolledUpUnit;
 import org.killbill.billing.usage.api.RolledUpUsage;
 import org.killbill.billing.usage.api.SubscriptionUsageRecord;
 import org.killbill.billing.usage.api.UnitUsageRecord;
+import org.killbill.billing.usage.api.UsageApiException;
 import org.killbill.billing.usage.api.UsageRecord;
 import org.killbill.billing.usage.api.UsageUserApi;
 import org.killbill.billing.usage.dao.RolledUpUsageDao;
@@ -40,6 +42,8 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
 
+import com.google.common.base.Strings;
+
 public class DefaultUsageUserApi implements UsageUserApi {
 
     private final RolledUpUsageDao rolledUpUsageDao;
@@ -53,13 +57,21 @@ public class DefaultUsageUserApi implements UsageUserApi {
     }
 
     @Override
-    public void recordRolledUpUsage(final SubscriptionUsageRecord record, final CallContext callContext) {
+    public void recordRolledUpUsage(final SubscriptionUsageRecord record, final CallContext callContext) throws UsageApiException {
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(record.getSubscriptionId(), ObjectType.SUBSCRIPTION, callContext);
-        for (UnitUsageRecord unitUsageRecord : record.getUnitUsageRecord()) {
-            for (UsageRecord usageRecord : unitUsageRecord.getDailyAmount()) {
-                rolledUpUsageDao.record(record.getSubscriptionId(), unitUsageRecord.getUnitType(), usageRecord.getDate(), usageRecord.getAmount(), internalCallContext);
+
+        // check if we have (at least) one row with the supplied tracking id
+        if(!Strings.isNullOrEmpty(record.getTrackingId()) && recordsWithTrackingIdExist(record, internalCallContext)){
+            throw new UsageApiException(ErrorCode.USAGE_RECORD_TRACKING_ID_ALREADY_EXISTS, record.getTrackingId());
+        }
+
+        final List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+        for (final UnitUsageRecord unitUsageRecord : record.getUnitUsageRecord()) {
+            for (final UsageRecord usageRecord : unitUsageRecord.getDailyAmount()) {
+                usages.add(new RolledUpUsageModelDao(record.getSubscriptionId(), unitUsageRecord.getUnitType(), usageRecord.getDate(), usageRecord.getAmount(), record.getTrackingId()));
             }
         }
+        rolledUpUsageDao.record(usages, internalCallContext);
     }
 
     @Override
@@ -98,4 +110,8 @@ public class DefaultUsageUserApi implements UsageUserApi {
         }
         return result;
     }
+
+    private boolean recordsWithTrackingIdExist(SubscriptionUsageRecord record, InternalCallContext context){
+        return rolledUpUsageDao.recordsWithTrackingIdExist(record.getSubscriptionId(), record.getTrackingId(), context);
+    }
 }
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java
index 3ab2973..4d3b4c5 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java
@@ -16,7 +16,6 @@
 
 package org.killbill.billing.usage.dao;
 
-import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
@@ -37,9 +36,13 @@ public class DefaultRolledUpUsageDao implements RolledUpUsageDao {
     }
 
     @Override
-    public void record(final UUID subscriptionId, final String unitType, final LocalDate date, final Long amount, final InternalCallContext context) {
-        final RolledUpUsageModelDao rolledUpUsageModelDao = new RolledUpUsageModelDao(subscriptionId, unitType, date, amount);
-        rolledUpUsageSqlDao.create(rolledUpUsageModelDao, context);
+    public void record(final Iterable<RolledUpUsageModelDao> usages, final InternalCallContext context){
+        rolledUpUsageSqlDao.create(usages, context);
+    }
+
+    @Override
+    public Boolean recordsWithTrackingIdExist(final UUID subscriptionId, final String trackingId, final InternalTenantContext context){
+        return rolledUpUsageSqlDao.recordsWithTrackingIdExist(subscriptionId, trackingId, context) != null ;
     }
 
     @Override
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java
index e458635..9f6be8e 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java
@@ -25,13 +25,13 @@ import org.killbill.billing.callcontext.InternalTenantContext;
 
 public interface RolledUpUsageDao {
 
-    void record(UUID subscriptionId, String unitType, LocalDate date,
-                Long amount, InternalCallContext context);
+    void record(Iterable<RolledUpUsageModelDao> usages, InternalCallContext context);
+
+    Boolean recordsWithTrackingIdExist(UUID subscriptionId, String trackingId, InternalTenantContext context);
 
     List<RolledUpUsageModelDao> getUsageForSubscription(UUID subscriptionId, LocalDate startDate, LocalDate endDate, String unitType, InternalTenantContext context);
 
     List<RolledUpUsageModelDao> getAllUsageForSubscription(UUID subscriptionId, LocalDate startDate, LocalDate endDate, InternalTenantContext context);
 
-
     List<RolledUpUsageModelDao> getRawUsageForAccount(LocalDate startDate, LocalDate endDate, InternalTenantContext context);
 }
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageModelDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageModelDao.java
index 411765a..5cd4eb1 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageModelDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageModelDao.java
@@ -26,25 +26,35 @@ import org.killbill.billing.util.entity.Entity;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
 import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
+import com.google.common.base.Strings;
+
 public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityModelDao<Entity> {
 
     private UUID subscriptionId;
     private String unitType;
     private LocalDate recordDate;
     private Long amount;
+    private String trackingId;
 
     public RolledUpUsageModelDao() { /* For the DAO mapper */ }
 
-    public RolledUpUsageModelDao(final UUID id, final DateTime createdDate, final DateTime updatedDate, final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount) {
+    public RolledUpUsageModelDao(final UUID id, final DateTime createdDate, final DateTime updatedDate, final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount, final String trackingId) {
         super(id, createdDate, updatedDate);
         this.subscriptionId = subscriptionId;
         this.unitType = unitType;
         this.recordDate = recordDate;
         this.amount = amount;
+
+        if(Strings.isNullOrEmpty(trackingId)){
+            this.trackingId = UUIDs.randomUUID().toString();
+        }
+        else {
+            this.trackingId = trackingId;
+        }
     }
 
-    public RolledUpUsageModelDao(final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount) {
-        this(UUIDs.randomUUID(), null, null, subscriptionId, unitType, recordDate, amount);
+    public RolledUpUsageModelDao(final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount, final String trackingId) {
+        this(UUIDs.randomUUID(), null, null, subscriptionId, unitType, recordDate, amount, trackingId);
     }
 
     public UUID getSubscriptionId() {
@@ -79,6 +89,14 @@ public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityM
         this.amount = amount;
     }
 
+    public String getTrackingId() {
+        return trackingId;
+    }
+
+    public void setTrackingId(final String trackingId) {
+        this.trackingId = trackingId;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
@@ -88,6 +106,7 @@ public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityM
         sb.append(", unitType='").append(unitType).append('\'');
         sb.append(", recordDate=").append(recordDate);
         sb.append(", amount=").append(amount);
+        sb.append(", trackingId=").append(trackingId);
         sb.append('}');
         return sb.toString();
     }
@@ -118,7 +137,9 @@ public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityM
         if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
             return false;
         }
-
+        if (trackingId != null ? !trackingId.equals(that.trackingId) : that.trackingId != null) {
+            return false;
+        }
         return true;
     }
 
@@ -129,6 +150,7 @@ public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityM
         result = 31 * result + (unitType != null ? unitType.hashCode() : 0);
         result = 31 * result + (recordDate != null ? recordDate.hashCode() : 0);
         result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (trackingId != null ? trackingId.hashCode() : 0);
         return result;
     }
 
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
index a10a2c0..89fc3a2 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
@@ -30,31 +30,36 @@ import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 
 @EntitySqlDaoStringTemplate
 public interface RolledUpUsageSqlDao extends EntitySqlDao<RolledUpUsageModelDao, Entity> {
 
-    @SqlUpdate
-    public void create(@BindBean RolledUpUsageModelDao rolledUpUsage,
-                       @InternalTenantContextBinder final InternalCallContext context);
+    @SqlBatch
+    void create(@BindBean Iterable<RolledUpUsageModelDao> usages,
+                @InternalTenantContextBinder final InternalCallContext context);
 
     @SqlQuery
-    public List<RolledUpUsageModelDao> getUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
-                                                               @Bind("startDate") final Date startDate,
-                                                               @Bind("endDate") final Date endDate,
-                                                               @Bind("unitType") final String unitType,
-                                                               @InternalTenantContextBinder final InternalTenantContext context);
+    Long recordsWithTrackingIdExist(@Bind("subscriptionId") final UUID subscriptionId,
+                                    @Bind("trackingId") final String trackingId,
+                                    @InternalTenantContextBinder final InternalTenantContext context);
 
     @SqlQuery
-    public List<RolledUpUsageModelDao> getAllUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
-                                                                  @Bind("startDate") final Date startDate,
-                                                                  @Bind("endDate") final Date endDate,
-                                                                  @InternalTenantContextBinder final InternalTenantContext context);
+    List<RolledUpUsageModelDao> getUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
+                                                        @Bind("startDate") final Date startDate,
+                                                        @Bind("endDate") final Date endDate,
+                                                        @Bind("unitType") final String unitType,
+                                                        @InternalTenantContextBinder final InternalTenantContext context);
 
     @SqlQuery
-    public List<RolledUpUsageModelDao> getRawUsageForAccount(@Bind("startDate") final Date startDate,
-                                                             @Bind("endDate") final Date endDate,
-                                                             @InternalTenantContextBinder final InternalTenantContext context);
+    List<RolledUpUsageModelDao> getAllUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
+                                                           @Bind("startDate") final Date startDate,
+                                                           @Bind("endDate") final Date endDate,
+                                                           @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    List<RolledUpUsageModelDao> getRawUsageForAccount(@Bind("startDate") final Date startDate,
+                                                      @Bind("endDate") final Date endDate,
+                                                      @InternalTenantContextBinder final InternalTenantContext context);
 }
diff --git a/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg b/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
index 94fcb03..b1d739c 100644
--- a/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
+++ b/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
@@ -8,6 +8,7 @@ tableFields(prefix) ::= <<
 , <prefix>unit_type
 , <prefix>record_date
 , <prefix>amount
+, <prefix>tracking_id
 , <prefix>created_by
 , <prefix>created_date
 >>
@@ -17,10 +18,21 @@ tableValues() ::= <<
 , :unitType
 , :recordDate
 , :amount
+, :trackingId
 , :userName
 , :createdDate
 >>
 
+recordsWithTrackingIdExist() ::= <<
+select
+  1
+from <tableName()>
+where subscription_id = :subscriptionId
+and tracking_id = :trackingId
+<AND_CHECK_TENANT()>
+limit 1
+;
+>>
 
 getUsageForSubscription() ::= <<
 select
diff --git a/usage/src/main/resources/org/killbill/billing/usage/ddl.sql b/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
index 44837a9..12940eb 100644
--- a/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
+++ b/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
@@ -4,10 +4,11 @@ DROP TABLE IF EXISTS rolled_up_usage;
 CREATE TABLE rolled_up_usage (
     record_id serial unique,
     id varchar(36) NOT NULL,
-    subscription_id varchar(36),
-    unit_type varchar(255),
+    subscription_id varchar(36) NOT NULL,
+    unit_type varchar(255) NOT NULL,
     record_date date NOT NULL,
     amount bigint NOT NULL,
+    tracking_id varchar(128) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     account_record_id bigint /*! unsigned */ not null,
@@ -18,3 +19,4 @@ CREATE UNIQUE INDEX rolled_up_usage_id ON rolled_up_usage(id);
 CREATE INDEX rolled_up_usage_subscription_id ON rolled_up_usage(subscription_id ASC);
 CREATE INDEX rolled_up_usage_tenant_account_record_id ON rolled_up_usage(tenant_record_id, account_record_id);
 CREATE INDEX rolled_up_usage_account_record_id ON rolled_up_usage(account_record_id);
+CREATE INDEX rolled_up_usage_tracking_id_subscription_id_tenant_record_id ON rolled_up_usage(tracking_id, subscription_id, tenant_record_id);
diff --git a/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java b/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java
index 5be6e0b..69a6cde 100644
--- a/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java
+++ b/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java
@@ -17,14 +17,18 @@
 
 package org.killbill.billing.usage.dao;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
 import org.killbill.billing.usage.UsageTestSuiteWithEmbeddedDB;
+import org.killbill.billing.util.UUIDs;
+import org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
 
 public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
 
@@ -37,8 +41,12 @@ public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
         final Long amount1 = 10L;
         final Long amount2 = 5L;
 
-        rolledUpUsageDao.record(subscriptionId, unitType, startDate, amount1, internalCallContext);
-        rolledUpUsageDao.record(subscriptionId, unitType, endDate.minusDays(1), amount2, internalCallContext);
+        RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType, startDate, amount1, UUID.randomUUID().toString());
+        RolledUpUsageModelDao usage2 = new RolledUpUsageModelDao(subscriptionId, unitType, endDate.minusDays(1), amount2, UUID.randomUUID().toString());
+        List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+        usages.add(usage1);
+        usages.add(usage2);
+        rolledUpUsageDao.record(usages, internalCallContext);
 
         final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
         assertEquals(result.size(), 2);
@@ -63,10 +71,14 @@ public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
         final Long amount2 = 5L;
         final Long amount3 = 13L;
 
-        rolledUpUsageDao.record(subscriptionId, unitType1, startDate, amount1, internalCallContext);
-        rolledUpUsageDao.record(subscriptionId, unitType1, startDate.plusDays(1), amount2, internalCallContext);
-
-        rolledUpUsageDao.record(subscriptionId, unitType2, endDate.minusDays(1), amount3, internalCallContext);
+        RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate, amount1, UUID.randomUUID().toString());
+        RolledUpUsageModelDao usage2 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate.plusDays(1), amount2, UUID.randomUUID().toString());
+        RolledUpUsageModelDao usage3 = new RolledUpUsageModelDao(subscriptionId, unitType2, endDate.minusDays(1), amount3, UUID.randomUUID().toString());
+        List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+        usages.add(usage1);
+        usages.add(usage2);
+        usages.add(usage3);
+        rolledUpUsageDao.record(usages, internalCallContext);
 
         final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getAllUsageForSubscription(subscriptionId, startDate, endDate, internalCallContext);
         assertEquals(result.size(), 3);
@@ -91,9 +103,71 @@ public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
         final LocalDate startDate = new LocalDate(2013, 1, 1);
         final LocalDate endDate = new LocalDate(2013, 2, 1);
 
-        rolledUpUsageDao.record(subscriptionId, unitType, endDate, 9L, internalCallContext);
+        RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType, endDate, 9L, UUID.randomUUID().toString());
+        List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+        usages.add(usage1);
+        rolledUpUsageDao.record(usages, internalCallContext);
 
         final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
         assertEquals(result.size(), 0);
     }
-}
+
+    @Test(groups = "slow")
+    public void testDuplicateRecords() {
+        final UUID subscriptionId = UUID.randomUUID();
+        final String unitType1 = "foo";
+        final String unitType2 = "bar";
+        final LocalDate startDate = new LocalDate(2013, 1, 1);
+        final LocalDate endDate = new LocalDate(2013, 2, 1);
+        final Long amount1 = 10L;
+        final Long amount2 = 5L;
+        final Long amount3 = 13L;
+
+        RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate, amount1, UUID.randomUUID().toString());
+        RolledUpUsageModelDao usage2 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate.plusDays(1), amount2, UUID.randomUUID().toString());
+        RolledUpUsageModelDao usage3 = new RolledUpUsageModelDao(subscriptionId, unitType2, endDate.minusDays(1), amount3, UUID.randomUUID().toString());
+
+        List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+        usages.add(usage1);
+        usages.add(usage2);
+        usages.add(usage3);
+        rolledUpUsageDao.record(usages, internalCallContext);
+
+        final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getAllUsageForSubscription(subscriptionId, startDate, endDate, internalCallContext);
+        assertEquals(result.size(), 3);
+
+        try {
+            rolledUpUsageDao.record(usages, internalCallContext);
+            fail("duplicate records accepted");
+        } catch (UnableToExecuteStatementException e) {
+            assertEquals(result.size(), 3);
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testRecordsWithTrackingIdExist() {
+        final UUID subscriptionId = UUIDs.randomUUID();
+        final String unitType1 = "foo";
+        final String unitType2 = "bar";
+        final LocalDate startDate = new LocalDate(2013, 1, 1);
+        final LocalDate endDate = new LocalDate(2013, 2, 1);
+        final Long amount1 = 10L;
+        final Long amount2 = 5L;
+        final Long amount3 = 13L;
+
+        String trackingId = UUIDs.randomUUID().toString();
+
+        RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate, amount1, trackingId);
+        RolledUpUsageModelDao usage2 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate.plusDays(1), amount2, trackingId);
+        RolledUpUsageModelDao usage3 = new RolledUpUsageModelDao(subscriptionId, unitType2, endDate.minusDays(1), amount3, UUID.randomUUID().toString());
+
+        List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+        usages.add(usage1);
+        usages.add(usage2);
+        usages.add(usage3);
+        rolledUpUsageDao.record(usages, internalCallContext);
+
+        assertEquals(rolledUpUsageDao.recordsWithTrackingIdExist(subscriptionId, trackingId, internalCallContext),
+                     Boolean.TRUE);
+    }
+}
\ No newline at end of file
diff --git a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java
index 15078d8..e34500f 100644
--- a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java
+++ b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java
@@ -22,6 +22,7 @@ import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
 import org.killbill.billing.account.glue.DefaultAccountModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.CacheModule;
+import org.killbill.billing.util.glue.ConfigModule;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
 
 public class TestUsageModuleWithEmbeddedDB extends TestUsageModule {
@@ -36,6 +37,7 @@ public class TestUsageModuleWithEmbeddedDB extends TestUsageModule {
 
         install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new NonEntityDaoModule(configSource));
         install(new DefaultAccountModule(configSource));
     }
diff --git a/util/src/main/assembly/migrator.xml b/util/src/main/assembly/migrator.xml
new file mode 100644
index 0000000..0e7eefe
--- /dev/null
+++ b/util/src/main/assembly/migrator.xml
@@ -0,0 +1,28 @@
+<assembly
+        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+    <id>flyway</id>
+    <formats>
+        <format>jar</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <dependencySets>
+        <dependencySet>
+            <outputDirectory>/</outputDirectory>
+            <useProjectArtifact>true</useProjectArtifact>
+            <unpack>true</unpack>
+            <scope>test</scope>
+        </dependencySet>
+    </dependencySets>
+    <fileSets>
+        <fileSet>
+            <directory>${project.build.directory}/test-classes</directory>
+            <outputDirectory>/</outputDirectory>
+            <includes>
+                <include>**/*.class</include>
+            </includes>
+            <useDefaultExcludes>true</useDefaultExcludes>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/util/src/main/java/org/killbill/billing/util/broadcast/DefaultBroadcastApi.java b/util/src/main/java/org/killbill/billing/util/broadcast/DefaultBroadcastApi.java
index 7c24739..0a1a922 100644
--- a/util/src/main/java/org/killbill/billing/util/broadcast/DefaultBroadcastApi.java
+++ b/util/src/main/java/org/killbill/billing/util/broadcast/DefaultBroadcastApi.java
@@ -51,7 +51,7 @@ public class DefaultBroadcastApi implements BroadcastApi {
             try {
                 eventBus.post(busEvent);
             } catch (final EventBusException e) {
-                logger.warn("Failed to deliver bus event ", e);
+                logger.warn("Failed to post event {}", event, e);
             }
         } else {
             final BroadcastModelDao modelDao = new BroadcastModelDao(serviceName, type, event, createdDate, createdBy);
diff --git a/util/src/main/java/org/killbill/billing/util/broadcast/DefaultBroadcastService.java b/util/src/main/java/org/killbill/billing/util/broadcast/DefaultBroadcastService.java
index 7ee1a99..d3c48a0 100644
--- a/util/src/main/java/org/killbill/billing/util/broadcast/DefaultBroadcastService.java
+++ b/util/src/main/java/org/killbill/billing/util/broadcast/DefaultBroadcastService.java
@@ -29,15 +29,13 @@ import org.killbill.billing.platform.api.LifecycleHandlerType;
 import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
 import org.killbill.billing.util.broadcast.dao.BroadcastDao;
 import org.killbill.billing.util.broadcast.dao.BroadcastModelDao;
-import org.killbill.billing.util.config.BroadcastConfig;
+import org.killbill.billing.util.config.definition.BroadcastConfig;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.commons.concurrent.Executors;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.eventbus.EventBus;
-
 public class DefaultBroadcastService implements BroadcastService {
 
     private final static int TERMINATION_TIMEOUT_SEC = 5;
@@ -93,7 +91,7 @@ public class DefaultBroadcastService implements BroadcastService {
             broadcastExecutor.shutdown();
             boolean success = broadcastExecutor.awaitTermination(TERMINATION_TIMEOUT_SEC, TimeUnit.SECONDS);
             if (!success) {
-                logger.warn("BroadcastExecutor failed to complete termination within " + TERMINATION_TIMEOUT_SEC + "sec");
+                logger.warn("BroadcastExecutor failed to complete termination within {} sec", TERMINATION_TIMEOUT_SEC);
             }
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
@@ -139,11 +137,11 @@ public class DefaultBroadcastService implements BroadcastService {
                     return;
                 }
 
+                final BroadcastInternalEvent event = new DefaultBroadcastInternalEvent(cur.getServiceName(), cur.getType(), cur.getEvent());
                 try {
-                    final BroadcastInternalEvent event = new DefaultBroadcastInternalEvent(cur.getServiceName(), cur.getType(), cur.getEvent());
                     eventBus.post(event);
                 } catch (final EventBusException e) {
-                    logger.error("Failed to send event BroadcastInternalEvent: ", e);
+                    logger.warn("Failed to post event {}", event, e);
                 } finally {
                     parent.setLatestRecordIdProcessed(cur.getRecordId());
                 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
index 6035ff3..f20c9a6 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
@@ -33,6 +33,7 @@ public @interface Cachable {
     String AUDIT_LOG_VIA_HISTORY_CACHE_NAME = "audit-log-via-history";
     String TENANT_CATALOG_CACHE_NAME = "tenant-catalog";
     String TENANT_OVERDUE_CONFIG_CACHE_NAME = "tenant-overdue-config";
+    String TENANT_CONFIG_CACHE_NAME = "tenant-config";
     String TENANT_KV_CACHE_NAME = "tenant-kv";
     String TENANT_CACHE_NAME = "tenant";
     String OVERRIDDEN_PLAN_CACHE_NAME = "overridden-plan";
@@ -67,6 +68,9 @@ public @interface Cachable {
         /* Tenant overdue config cache */
         TENANT_OVERDUE_CONFIG(TENANT_OVERDUE_CONFIG_CACHE_NAME, false),
 
+        /* Tenant overdue config cache */
+        TENANT_CONFIG(TENANT_CONFIG_CACHE_NAME, false),
+
         /* Tenant config cache */
         TENANT_KV(TENANT_KV_CACHE_NAME, false),
 
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
index f90b1d1..7ce311c 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
@@ -27,7 +27,7 @@ import java.util.LinkedList;
 import javax.inject.Inject;
 import javax.inject.Provider;
 
-import org.killbill.billing.util.config.CacheConfig;
+import org.killbill.billing.util.config.definition.EhCacheConfig;
 import org.killbill.xmlloader.UriAccessor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -45,12 +45,12 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
     private static final Logger logger = LoggerFactory.getLogger(EhCacheCacheManagerProvider.class);
 
     private final MetricRegistry metricRegistry;
-    private final CacheConfig cacheConfig;
+    private final EhCacheConfig cacheConfig;
     private final Collection<BaseCacheLoader> cacheLoaders = new LinkedList<BaseCacheLoader>();
 
     @Inject
     public EhCacheCacheManagerProvider(final MetricRegistry metricRegistry,
-                                       final CacheConfig cacheConfig,
+                                       final EhCacheConfig cacheConfig,
                                        final ImmutableAccountCacheLoader accountCacheLoader,
                                        final AccountBCDCacheLoader accountBCDCacheLoader,
                                        final RecordIdCacheLoader recordIdCacheLoader,
@@ -60,6 +60,7 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
                                        final AuditLogCacheLoader auditLogCacheLoader,
                                        final AuditLogViaHistoryCacheLoader auditLogViaHistoryCacheLoader,
                                        final TenantCatalogCacheLoader tenantCatalogCacheLoader,
+                                       final TenantConfigCacheLoader tenantConfigCacheLoader,
                                        final TenantOverdueConfigCacheLoader tenantOverdueConfigCacheLoader,
                                        final TenantKVCacheLoader tenantKVCacheLoader,
                                        final TenantCacheLoader tenantCacheLoader,
@@ -75,6 +76,7 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
         cacheLoaders.add(auditLogCacheLoader);
         cacheLoaders.add(auditLogViaHistoryCacheLoader);
         cacheLoaders.add(tenantCatalogCacheLoader);
+        cacheLoaders.add(tenantConfigCacheLoader);
         cacheLoaders.add(tenantOverdueConfigCacheLoader);
         cacheLoaders.add(tenantKVCacheLoader);
         cacheLoaders.add(tenantCacheLoader);
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
index 897e75d..b3f9305 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
@@ -72,11 +72,10 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
             return null;
         }
         try {
-            log.info("Loading catalog cache for tenant " + internalTenantContext.getTenantRecordId());
+            log.info("Loading catalog cache for tenantRecordId='{}'", internalTenantContext.getTenantRecordId());
             return callback.loadCatalog(catalogXMLs, tenantRecordId);
         } catch (final CatalogApiException e) {
-            throw new IllegalStateException(String.format("Failed to de-serialize catalog for tenant %s : %s",
-                                                          internalTenantContext.getTenantRecordId(), e.getMessage()), e);
+            throw new IllegalStateException(String.format("Failed to de-serialize catalog for tenantRecordId='%s'", internalTenantContext.getTenantRecordId()), e);
         }
     }
 
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java
new file mode 100644
index 0000000..caced95
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.util.cache;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.tenant.api.TenantInternalApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class TenantConfigCacheLoader extends BaseCacheLoader {
+
+    private final Logger log = LoggerFactory.getLogger(TenantConfigCacheLoader.class);
+
+    private final TenantInternalApi tenantApi;
+
+    @Inject
+    public TenantConfigCacheLoader(final TenantInternalApi tenantApi) {
+        super();
+        this.tenantApi = tenantApi;
+    }
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.TENANT_CONFIG;
+    }
+
+    @Override
+    public Object load(final Object key, final Object argument) {
+        checkCacheLoaderStatus();
+
+        if (!(key instanceof Long)) {
+            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
+        }
+        if (!(argument instanceof CacheLoaderArgument)) {
+            throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
+        }
+
+        final Long tenantRecordId = (Long) key;
+        final InternalTenantContext internalTenantContext = new InternalTenantContext(tenantRecordId);
+        final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
+
+        if (cacheLoaderArgument.getArgs() == null || !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
+            throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
+        }
+
+        final LoaderCallback loader = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
+
+        final String jsonValue = tenantApi.getTenantConfig(internalTenantContext);
+
+        try {
+            return loader.loadConfig(jsonValue);
+        } catch (final IOException e) {
+            throw new IllegalArgumentException("Failed to deserialize per tenant config for tenant recordId = " + tenantRecordId, e);
+        }
+    }
+
+    public interface LoaderCallback {
+        public Object loadConfig(final String inputJson) throws IOException;
+    }
+
+}
diff --git a/util/src/main/java/org/killbill/billing/util/callcontext/CallContextFactory.java b/util/src/main/java/org/killbill/billing/util/callcontext/CallContextFactory.java
index 324a610..3fb5188 100644
--- a/util/src/main/java/org/killbill/billing/util/callcontext/CallContextFactory.java
+++ b/util/src/main/java/org/killbill/billing/util/callcontext/CallContextFactory.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -20,18 +22,10 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
-import org.joda.time.DateTime;
-
 public interface CallContextFactory {
 
     TenantContext createTenantContext(@Nullable UUID tenantId);
 
-    CallContext createCallContext(@Nullable UUID tenantId, String userName, CallOrigin callOrigin, UserType userType, UUID userToken);
-
     CallContext createCallContext(@Nullable UUID tenantId, String userName, CallOrigin callOrigin, UserType userType,
                                   String reasonCode, String comment, UUID userToken);
-
-    CallContext createCallContext(@Nullable UUID tenantId, String userName, CallOrigin callOrigin, UserType userType);
-
-    CallContext toMigrationCallContext(@Nullable CallContext callContext, DateTime createdDate, DateTime updatedDate);
 }
diff --git a/util/src/main/java/org/killbill/billing/util/callcontext/DefaultCallContextFactory.java b/util/src/main/java/org/killbill/billing/util/callcontext/DefaultCallContextFactory.java
index a8f9b22..30ba050 100644
--- a/util/src/main/java/org/killbill/billing/util/callcontext/DefaultCallContextFactory.java
+++ b/util/src/main/java/org/killbill/billing/util/callcontext/DefaultCallContextFactory.java
@@ -20,8 +20,6 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
-import org.joda.time.DateTime;
-
 import org.killbill.billing.callcontext.DefaultCallContext;
 import org.killbill.billing.callcontext.DefaultTenantContext;
 import org.killbill.clock.Clock;
@@ -44,23 +42,7 @@ public class DefaultCallContextFactory implements CallContextFactory {
 
     @Override
     public CallContext createCallContext(@Nullable final UUID tenantId, final String userName, final CallOrigin callOrigin,
-                                         final UserType userType, @Nullable final UUID userToken) {
-        return new DefaultCallContext(tenantId, userName, callOrigin, userType, userToken, clock);
-    }
-
-    @Override
-    public CallContext createCallContext(@Nullable final UUID tenantId, final String userName, final CallOrigin callOrigin,
                                          final UserType userType, final String reasonCode, final String comment, final UUID userToken) {
         return new DefaultCallContext(tenantId, userName, callOrigin, userType, reasonCode, comment, userToken, clock);
     }
-
-    @Override
-    public CallContext createCallContext(@Nullable final UUID tenantId, final String userName, final CallOrigin callOrigin, final UserType userType) {
-        return createCallContext(tenantId, userName, callOrigin, userType, null);
-    }
-
-    @Override
-    public CallContext toMigrationCallContext(final CallContext callContext, final DateTime createdDate, final DateTime updatedDate) {
-        return new MigrationCallContext(callContext, createdDate, updatedDate);
-    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
index 27a6889..f85796b 100644
--- a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
+++ b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
@@ -35,6 +35,7 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.clock.Clock;
+import org.slf4j.MDC;
 
 import com.google.common.base.MoreObjects;
 
@@ -43,6 +44,9 @@ public class InternalCallContextFactory {
 
     public static final long INTERNAL_TENANT_RECORD_ID = 0L;
 
+    public static final String MDC_KB_ACCOUNT_RECORD_ID = "kb.accountRecordId";
+    public static final String MDC_KB_TENANT_RECORD_ID = "kb.tenantRecordId";
+
     private final ImmutableAccountInternalApi accountInternalApi;
     private final Clock clock;
     private final NonEntityDao nonEntityDao;
@@ -129,6 +133,8 @@ public class InternalCallContextFactory {
      * @return internal tenant callcontext
      */
     public InternalTenantContext createInternalTenantContext(final Long tenantRecordId, @Nullable final Long accountRecordId) {
+        populateMDCContext(accountRecordId, tenantRecordId);
+
         if (accountRecordId == null) {
             return new InternalTenantContext(tenantRecordId);
         } else {
@@ -210,6 +216,7 @@ public class InternalCallContextFactory {
     public InternalCallContext createInternalCallContextWithoutAccountRecordId(final CallContext context) {
         // If tenant id is null, this will default to the default tenant record id (multi-tenancy disabled)
         final Long tenantRecordId = getTenantRecordIdSafe(context);
+        populateMDCContext(null, tenantRecordId);
         return new InternalCallContext(tenantRecordId, context, clock.getUTCNow());
     }
 
@@ -217,6 +224,7 @@ public class InternalCallContextFactory {
     public InternalCallContext createInternalCallContext(final Long accountRecordId, final InternalCallContext context) {
         final DateTimeZone fixedOffsetTimeZone = getFixedOffsetTimeZone(accountRecordId, context.getTenantRecordId());
         final DateTime referenceTime = getReferenceTime(accountRecordId, context.getTenantRecordId());
+        populateMDCContext(accountRecordId, context.getTenantRecordId());
         return new InternalCallContext(context, accountRecordId, fixedOffsetTimeZone, referenceTime, clock.getUTCNow());
     }
 
@@ -237,6 +245,8 @@ public class InternalCallContextFactory {
         final DateTimeZone fixedOffsetTimeZone = getFixedOffsetTimeZone(accountRecordId, tenantRecordId);
         final DateTime referenceTime = getReferenceTime(accountRecordId, tenantRecordId);
 
+        populateMDCContext(accountRecordId, nonNulTenantRecordId);
+
         return new InternalCallContext(nonNulTenantRecordId,
                                        accountRecordId,
                                        fixedOffsetTimeZone,
@@ -256,6 +266,8 @@ public class InternalCallContextFactory {
             return null;
         }
 
+        populateMDCContext(accountRecordId, tenantRecordId);
+
         final ImmutableAccountData immutableAccountData = getImmutableAccountData(accountRecordId, tenantRecordId);
         // Will be null while creating the account
         return immutableAccountData == null ? null : immutableAccountData.getFixedOffsetTimeZone();
@@ -273,7 +285,6 @@ public class InternalCallContextFactory {
 
     private ImmutableAccountData getImmutableAccountData(@Nullable final Long accountRecordId, final Long tenantRecordId) {
         final InternalTenantContext tmp = new InternalTenantContext(tenantRecordId, accountRecordId, null, null);
-
         try {
             return accountInternalApi.getImmutableAccountDataByRecordId(accountRecordId, tmp);
         } catch (final AccountApiException e) {
@@ -281,6 +292,13 @@ public class InternalCallContextFactory {
         }
     }
 
+    private void populateMDCContext(@Nullable final Long accountRecordId, final Long tenantRecordId) {
+        if (accountRecordId != null) {
+            MDC.put(MDC_KB_ACCOUNT_RECORD_ID, String.valueOf(accountRecordId));
+        }
+        MDC.put(MDC_KB_TENANT_RECORD_ID, String.valueOf(tenantRecordId));
+    }
+
     //
     // Safe NonEntityDao public wrappers
     //
diff --git a/util/src/main/java/org/killbill/billing/util/config/DefaultConfigKillbillService.java b/util/src/main/java/org/killbill/billing/util/config/DefaultConfigKillbillService.java
new file mode 100644
index 0000000..d0ce0c0
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/config/DefaultConfigKillbillService.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.util.config;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.billing.tenant.api.TenantInternalApi;
+import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
+import org.killbill.billing.tenant.api.TenantKV.TenantKey;
+import org.killbill.billing.util.glue.CacheModule;
+import org.killbill.billing.util.glue.ConfigModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultConfigKillbillService implements ConfigKillbillService {
+
+    private static final Logger logger = LoggerFactory.getLogger(DefaultConfigKillbillService.class);
+
+    public static final String CONFIG_SERVICE_NAME = "config-service";
+
+    private final TenantInternalApi tenantInternalApi;
+    private final CacheInvalidationCallback cacheInvalidationCallback;
+
+    @Inject
+    public DefaultConfigKillbillService(final TenantInternalApi tenantInternalApi, @Named(ConfigModule.CONFIG_INVALIDATION_CALLBACK) final CacheInvalidationCallback cacheInvalidationCallback) {
+        this.tenantInternalApi = tenantInternalApi;
+        this.cacheInvalidationCallback = cacheInvalidationCallback;
+    }
+
+    @Override
+    public String getName() {
+        return CONFIG_SERVICE_NAME;
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+    public synchronized void initialize() throws ServiceException {
+        tenantInternalApi.initializeCacheInvalidationCallback(TenantKey.PER_TENANT_CONFIG, cacheInvalidationCallback);
+    }
+
+}
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java b/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java
new file mode 100644
index 0000000..1338b65
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.util.config.tenant;
+
+import java.io.IOException;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.cache.CacheLoaderArgument;
+import org.killbill.billing.util.cache.TenantConfigCacheLoader.LoaderCallback;
+import org.killbill.billing.util.jackson.ObjectMapper;
+
+import com.google.inject.Inject;
+
+public class CacheConfig {
+
+    private final CacheController cacheController;
+    private final CacheLoaderArgument cacheLoaderArgument;
+
+    private final ObjectMapper objectMapper;
+
+    @Inject
+    public CacheConfig(final CacheControllerDispatcher cacheControllerDispatcher) {
+        this.cacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CONFIG);
+        this.objectMapper = new ObjectMapper();
+        this.cacheLoaderArgument = initializeCacheLoaderArgument();
+
+    }
+
+    public PerTenantConfig getPerTenantConfig(final InternalTenantContext tenantContext) {
+        final PerTenantConfig perTenantConfig = (PerTenantConfig) cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
+        return perTenantConfig;
+    }
+
+    public void clearPerTenantConfig(final InternalTenantContext tenantContext) {
+        cacheController.remove(tenantContext.getTenantRecordId());
+    }
+
+    private CacheLoaderArgument initializeCacheLoaderArgument() {
+        final LoaderCallback loaderCallback = new LoaderCallback() {
+            @Override
+            public Object loadConfig(@Nullable final String inputJson) throws IOException {
+                return inputJson != null ? objectMapper.readValue(inputJson, PerTenantConfig.class) : new PerTenantConfig();
+            }
+        };
+        final Object[] args = new Object[1];
+        args[0] = loaderCallback;
+        final ObjectType irrelevant = null;
+        final InternalTenantContext notUsed = null;
+        return new CacheLoaderArgument(irrelevant, args, notUsed);
+    }
+
+}
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java b/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java
new file mode 100644
index 0000000..e186d0c
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.util.config.tenant;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.skife.config.Config;
+import org.skife.config.Separator;
+import org.skife.config.TimeSpan;
+import org.weakref.jmx.internal.guava.base.Function;
+import org.weakref.jmx.internal.guava.collect.Iterables;
+
+import com.google.common.collect.ImmutableList;
+
+public abstract class MultiTenantConfigBase {
+
+    protected final CacheConfig cacheConfig;
+
+    private final static Function<String, Integer> INT_CONVERTER = new Function<String, Integer>() {
+        @Override
+        public Integer apply(final String input) {
+            return Integer.valueOf(input);
+        }
+    };
+
+    private final static Function<String, TimeSpan> TIME_SPAN_CONVERTER = new Function<String, TimeSpan>() {
+        @Override
+        public TimeSpan apply(final String input) {
+            return new TimeSpan(input);
+        }
+    };
+
+    public MultiTenantConfigBase(final CacheConfig cacheConfig) {
+        this.cacheConfig = cacheConfig;
+    }
+
+    //
+    // The conversion methds are rather limited (but this is all we need).
+    // Ideally we could reuse the bully/Coercer from skife package, but those are kept private.
+    //
+
+    protected List<String> convertToListString(final String value, final String methodName) {
+        final Method method = getConfigStaticMethodWithChecking(methodName);
+        final Iterable<String> tokens = getTokens(method, value);
+        return ImmutableList.copyOf(tokens);
+    }
+
+    protected List<TimeSpan> convertToListTimeSpan(final String value, final String methodName) {
+        final Method method = getConfigStaticMethodWithChecking(methodName);
+        final Iterable<String> tokens = getTokens(method, value);
+        return ImmutableList.copyOf(Iterables.transform(tokens, TIME_SPAN_CONVERTER));
+    }
+
+    protected List<Integer> convertToListInteger(final String value, final String methodName) {
+        final Method method = getConfigStaticMethodWithChecking(methodName);
+        final Iterable<String> tokens = getTokens(method, value);
+        return ImmutableList.copyOf(Iterables.transform(tokens, INT_CONVERTER));
+    }
+
+    protected String getStringTenantConfig(final String methodName, final InternalTenantContext tenantContext) {
+        // That means we want to default to static config value
+        if (tenantContext == null) {
+            return null;
+        }
+        final Method method = getConfigStaticMethodWithChecking(methodName);
+        return getCachedValue(method.getAnnotation(Config.class), tenantContext);
+    }
+
+    private String getCachedValue(final Config annotation, final InternalTenantContext tenantContext) {
+        final PerTenantConfig perTenantConfig = cacheConfig.getPerTenantConfig(tenantContext);
+        for (final String propertyName : annotation.value()) {
+            final String result = perTenantConfig.get(propertyName);
+            if (result != null) {
+                return result;
+            }
+        }
+        return null;
+    }
+
+    private Method getConfigStaticMethodWithChecking(final String methodName) {
+        final Method method = getConfigStaticMethod(methodName);
+        if (!method.isAnnotationPresent(Config.class)) {
+            throw new RuntimeException("Missing @Config annotation to skife config method " + method.getName());
+        }
+        return method;
+    }
+
+    private List<String> getTokens(final Method method, final String value) {
+        final Separator separator = method.getAnnotation(Separator.class);
+        return ImmutableList.copyOf(value.split(separator == null ? Separator.DEFAULT : separator.value()));
+    }
+
+    protected abstract Method getConfigStaticMethod(final String methodName);
+}
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfigInvalidationCallback.java b/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfigInvalidationCallback.java
new file mode 100644
index 0000000..bbc9a97
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfigInvalidationCallback.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.util.config.tenant;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
+import org.killbill.billing.tenant.api.TenantKV.TenantKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PerTenantConfigInvalidationCallback implements CacheInvalidationCallback {
+
+
+    private final Logger log = LoggerFactory.getLogger(PerTenantConfigInvalidationCallback.class);
+
+    private final CacheConfig cacheConfig;
+
+    @Inject
+    public PerTenantConfigInvalidationCallback(final CacheConfig cacheConfig) {
+        this.cacheConfig = cacheConfig;
+    }
+
+    @Override
+    public void invalidateCache(final TenantKey key, final Object cookie, final InternalTenantContext tenantContext) {
+        log.info("Invalidate config cache for tenant {} ", tenantContext.getTenantRecordId());
+        cacheConfig.clearPerTenantConfig(tenantContext);
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/customfield/dao/DefaultCustomFieldDao.java b/util/src/main/java/org/killbill/billing/util/customfield/dao/DefaultCustomFieldDao.java
index 1416fb4..6561b8f 100644
--- a/util/src/main/java/org/killbill/billing/util/customfield/dao/DefaultCustomFieldDao.java
+++ b/util/src/main/java/org/killbill/billing/util/customfield/dao/DefaultCustomFieldDao.java
@@ -146,7 +146,7 @@ public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, Cu
         try {
             bus.postFromTransaction(customFieldEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
         } catch (final PersistentBus.EventBusException e) {
-            log.warn("Failed to post tag event for custom field " + customField.getId().toString(), e);
+            log.warn("Failed to post tag event for customFieldId='{}'", customField.getId().toString(), e);
         }
 
     }
diff --git a/util/src/main/java/org/killbill/billing/util/email/DefaultEmailSender.java b/util/src/main/java/org/killbill/billing/util/email/DefaultEmailSender.java
index 7c5c8f2..7f8bac4 100644
--- a/util/src/main/java/org/killbill/billing/util/email/DefaultEmailSender.java
+++ b/util/src/main/java/org/killbill/billing/util/email/DefaultEmailSender.java
@@ -89,7 +89,7 @@ public class DefaultEmailSender implements EmailSender {
 
             email.setSSL(config.useSSL());
 
-            log.info("Sending email to {}, cc {}, subject {}", new Object[]{to, cc, subject});
+            log.info("Sending email to='{}', cc='{}', subject='{}'", to, cc, subject);
             email.send();
         } catch (EmailException ee) {
             throw new EmailApiException(ee, ErrorCode.EMAIL_SENDING_FAILED);
diff --git a/util/src/main/java/org/killbill/billing/util/email/EmailConfig.java b/util/src/main/java/org/killbill/billing/util/email/EmailConfig.java
index edcfdea..fe3700a 100644
--- a/util/src/main/java/org/killbill/billing/util/email/EmailConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/email/EmailConfig.java
@@ -21,7 +21,7 @@ import org.skife.config.Default;
 import org.skife.config.DefaultNull;
 import org.skife.config.Description;
 
-import org.killbill.billing.util.config.KillbillConfig;
+import org.killbill.billing.util.config.definition.KillbillConfig;
 
 public interface EmailConfig extends KillbillConfig {
 
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationHelper.java b/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationHelper.java
index a417357..d3ffa24 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationHelper.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationHelper.java
@@ -74,7 +74,7 @@ public class DefaultPaginationHelper {
                     maxNbRecords = Math.max(maxNbRecords, pages.getMaxNbRecords());
                 }
             } catch (final BillingExceptionBase e) {
-                log.warn("Error while searching plugin " + pluginName, e);
+                log.warn("Error while searching plugin='{}'", pluginName, e);
                 // Non-fatal, continue to search other plugins
             }
         }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/BroadcastModule.java b/util/src/main/java/org/killbill/billing/util/glue/BroadcastModule.java
index 72fb5be..df81cec 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/BroadcastModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/BroadcastModule.java
@@ -24,7 +24,7 @@ import org.killbill.billing.util.broadcast.DefaultBroadcastApi;
 import org.killbill.billing.util.broadcast.DefaultBroadcastService;
 import org.killbill.billing.util.broadcast.dao.BroadcastDao;
 import org.killbill.billing.util.broadcast.dao.DefaultBroadcastDao;
-import org.killbill.billing.util.config.BroadcastConfig;
+import org.killbill.billing.util.config.definition.BroadcastConfig;
 import org.skife.config.ConfigurationObjectFactory;
 
 public class BroadcastModule extends KillBillModule {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
index f01e061..5a3b660 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
@@ -22,7 +22,7 @@ import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.cache.CacheControllerDispatcherProvider;
 import org.killbill.billing.util.cache.EhCacheCacheManagerProvider;
-import org.killbill.billing.util.config.CacheConfig;
+import org.killbill.billing.util.config.definition.EhCacheConfig;
 import org.skife.config.ConfigurationObjectFactory;
 
 import net.sf.ehcache.CacheManager;
@@ -35,8 +35,8 @@ public class CacheModule extends KillBillModule {
 
     @Override
     protected void configure() {
-        final CacheConfig config = new ConfigurationObjectFactory(skifeConfigSource).build(CacheConfig.class);
-        bind(CacheConfig.class).toInstance(config);
+        final EhCacheConfig config = new ConfigurationObjectFactory(skifeConfigSource).build(EhCacheConfig.class);
+        bind(EhCacheConfig.class).toInstance(config);
 
         // EhCache specifics
         bind(CacheManager.class).toProvider(EhCacheCacheManagerProvider.class).asEagerSingleton();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/ConfigModule.java b/util/src/main/java/org/killbill/billing/util/glue/ConfigModule.java
new file mode 100644
index 0000000..0e73a81
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/ConfigModule.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.util.glue;
+
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
+import org.killbill.billing.util.config.ConfigKillbillService;
+import org.killbill.billing.util.config.DefaultConfigKillbillService;
+import org.killbill.billing.util.config.tenant.CacheConfig;
+import org.killbill.billing.util.config.tenant.PerTenantConfigInvalidationCallback;
+
+import com.google.inject.name.Names;
+
+public class ConfigModule extends KillBillModule {
+
+    public static final String CONFIG_INVALIDATION_CALLBACK = "ConfigInvalidationCallback";
+
+    public ConfigModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
+
+    @Override
+    protected void configure() {
+        bind(CacheConfig.class).asEagerSingleton();
+        bind(CacheInvalidationCallback.class).annotatedWith(Names.named(CONFIG_INVALIDATION_CALLBACK)).to(PerTenantConfigInvalidationCallback.class).asEagerSingleton();
+        bind(ConfigKillbillService.class).to(DefaultConfigKillbillService.class).asEagerSingleton();;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java b/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
index 8e74e76..a31bd06 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
@@ -28,7 +28,7 @@ import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.realm.Realm;
 import org.apache.shiro.realm.text.IniRealm;
 import org.apache.shiro.util.Factory;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/util/src/main/java/org/killbill/billing/util/glue/JDBCSessionDaoProvider.java b/util/src/main/java/org/killbill/billing/util/glue/JDBCSessionDaoProvider.java
index f9c6290..106bc83 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/JDBCSessionDaoProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/JDBCSessionDaoProvider.java
@@ -23,7 +23,7 @@ import org.apache.shiro.session.mgt.DefaultSessionManager;
 import org.apache.shiro.session.mgt.SessionManager;
 import org.skife.jdbi.v2.IDBI;
 
-import org.killbill.billing.util.config.RbacConfig;
+import org.killbill.billing.util.config.definition.RbacConfig;
 import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
 
 public class JDBCSessionDaoProvider implements Provider<JDBCSessionDao> {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index 00551eb..4e48913 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -24,7 +24,7 @@ import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.session.mgt.DefaultSessionManager;
 import org.apache.shiro.session.mgt.SessionManager;
 import org.killbill.billing.platform.api.KillbillConfigSource;
-import org.killbill.billing.util.config.RbacConfig;
+import org.killbill.billing.util.config.definition.RbacConfig;
 import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
 import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
 import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
diff --git a/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java b/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
index 1f0920c..6b82c92 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
@@ -20,7 +20,7 @@ package org.killbill.billing.util.glue;
 
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.security.api.SecurityApi;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.killbill.billing.util.security.api.DefaultSecurityApi;
 import org.killbill.billing.util.security.api.DefaultSecurityService;
 import org.killbill.billing.util.security.api.SecurityService;
diff --git a/util/src/main/java/org/killbill/billing/util/nodes/DefaultKillbillNodesApi.java b/util/src/main/java/org/killbill/billing/util/nodes/DefaultKillbillNodesApi.java
index 13bcb98..8503a8c 100644
--- a/util/src/main/java/org/killbill/billing/util/nodes/DefaultKillbillNodesApi.java
+++ b/util/src/main/java/org/killbill/billing/util/nodes/DefaultKillbillNodesApi.java
@@ -20,6 +20,8 @@ package org.killbill.billing.util.nodes;
 import java.io.IOException;
 import java.util.List;
 
+import javax.annotation.Nullable;
+
 import org.killbill.CreatorName;
 import org.killbill.billing.broadcast.BroadcastApi;
 import org.killbill.billing.osgi.api.PluginInfo;
@@ -34,6 +36,7 @@ import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
@@ -46,38 +49,43 @@ public class DefaultKillbillNodesApi implements KillbillNodesApi {
     private final BroadcastApi broadcastApi;
     private final NodeInfoMapper mapper;
     private final Clock clock;
-    private final PluginsInfoApi pluginInfoApi;
+    private final Function<NodeInfoModelDao, NodeInfo> nodeTransfomer;
 
     @Inject
-    public DefaultKillbillNodesApi(final NodeInfoDao nodeInfoDao, final BroadcastApi broadcastApi, final NodeInfoMapper mapper, final Clock clock, final PluginsInfoApi pluginInfoApi) {
+    public DefaultKillbillNodesApi(final NodeInfoDao nodeInfoDao, final BroadcastApi broadcastApi, final NodeInfoMapper mapper, final Clock clock) {
         this.nodeInfoDao = nodeInfoDao;
         this.broadcastApi = broadcastApi;
-        this.pluginInfoApi = pluginInfoApi;
         this.clock = clock;
         this.mapper = mapper;
-    }
-
-    @Override
-    public Iterable<NodeInfo> getNodesInfo() {
-        final List<NodeInfoModelDao> allNodes = nodeInfoDao.getAll();
-
-        final Iterable<NodeInfoModelJson> allModelNodes = Iterables.transform(allNodes, new Function<NodeInfoModelDao, NodeInfoModelJson>() {
+        this.nodeTransfomer = new Function<NodeInfoModelDao, NodeInfo>() {
             @Override
-            public NodeInfoModelJson apply(final NodeInfoModelDao input) {
+            public NodeInfo apply(final NodeInfoModelDao input) {
                 try {
-                    return mapper.deserializeNodeInfo(input.getNodeInfo());
-                } catch (IOException e) {
+                    final NodeInfoModelJson nodeInfoModelJson = mapper.deserializeNodeInfo(input.getNodeInfo());
+                    return new DefaultNodeInfo(nodeInfoModelJson);
+                } catch (final IOException e) {
                     throw new RuntimeException(e);
                 }
             }
-        });
+        };
+    }
+
+    @Override
+    public Iterable<NodeInfo> getNodesInfo() {
+        final List<NodeInfoModelDao> allNodes = nodeInfoDao.getAll();
+        return Iterables.transform(allNodes, nodeTransfomer);
+    }
 
-        return Iterables.transform(allModelNodes, new Function<NodeInfoModelJson, NodeInfo>() {
+    @Override
+    public NodeInfo getCurrentNodeInfo() {
+        final List<NodeInfoModelDao> allNodes = nodeInfoDao.getAll();
+        final NodeInfoModelDao current = Iterables.find(allNodes, new Predicate<NodeInfoModelDao>() {
             @Override
-            public NodeInfo apply(final NodeInfoModelJson input) {
-                return new DefaultNodeInfo(input);
+            public boolean apply(final NodeInfoModelDao input) {
+                return input.getNodeName().equals(CreatorName.get());
             }
         });
+        return nodeTransfomer.apply(current);
     }
 
     @Override
@@ -94,10 +102,10 @@ public class DefaultKillbillNodesApi implements KillbillNodesApi {
     }
 
     @Override
-    public void notifyPluginChanged(final PluginInfo plugin) {
+    public void notifyPluginChanged(final PluginInfo plugin,final Iterable<PluginInfo> latestPlugins) {
         final String updatedNodeInfoJson;
         try {
-            updatedNodeInfoJson = computeLatestNodeInfo();
+            updatedNodeInfoJson = computeLatestNodeInfo(latestPlugins);
             nodeInfoDao.updateNodeInfo(CreatorName.get(), updatedNodeInfoJson);
         } catch (final IOException e) {
             logger.warn("Failed to update nodeInfo after plugin change", e);
@@ -105,12 +113,11 @@ public class DefaultKillbillNodesApi implements KillbillNodesApi {
     }
 
 
-    private String computeLatestNodeInfo() throws IOException {
+    private String computeLatestNodeInfo(final Iterable<PluginInfo> rawPluginInfo) throws IOException {
 
         final NodeInfoModelDao nodeInfo = nodeInfoDao.getByNodeName(CreatorName.get());
         NodeInfoModelJson nodeInfoJson = mapper.deserializeNodeInfo(nodeInfo.getNodeInfo());
 
-        final Iterable<PluginInfo> rawPluginInfo = pluginInfoApi.getPluginsInfo();
         final List<PluginInfo> pluginInfos = rawPluginInfo.iterator().hasNext() ? ImmutableList.<PluginInfo>copyOf(rawPluginInfo) : ImmutableList.<PluginInfo>of();
 
         final NodeInfoModelJson updatedNodeInfoJson = new NodeInfoModelJson(CreatorName.get(),
diff --git a/util/src/main/java/org/killbill/billing/util/nodes/DefaultKillbillNodesService.java b/util/src/main/java/org/killbill/billing/util/nodes/DefaultKillbillNodesService.java
index a5b2a74..6236bb3 100644
--- a/util/src/main/java/org/killbill/billing/util/nodes/DefaultKillbillNodesService.java
+++ b/util/src/main/java/org/killbill/billing/util/nodes/DefaultKillbillNodesService.java
@@ -66,10 +66,22 @@ public class DefaultKillbillNodesService implements KillbillNodesService {
         return NODES_SERVICE_NAME;
     }
 
-    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.START_SERVICE)
+    @LifecycleHandlerType(LifecycleLevel.BOOT)
+    public void init() {
+        try {
+            // Compute a first version early on before plugins were installed to at least provide info about Kill Bill component versions
+            createBootNodeInfo(true);
+        } catch (JsonProcessingException e) {
+            logger.error("Failed to create bootNodeInfo", e);
+        }
+    }
+
+
+    @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
     public void start() {
         try {
-            createBootNodeInfo();
+            // Re-Compute including the plugins
+            createBootNodeInfo(false);
         } catch (JsonProcessingException e) {
             logger.error("Failed to create bootNodeInfo", e);
         }
@@ -80,10 +92,10 @@ public class DefaultKillbillNodesService implements KillbillNodesService {
         nodeInfoDao.delete(CreatorName.get());
     }
 
-    private void createBootNodeInfo() throws JsonProcessingException {
+    private void createBootNodeInfo(final boolean skipPlugins) throws JsonProcessingException {
 
         final DateTime bootTime = clock.getUTCNow();
-        final Iterable<PluginInfo> rawPluginInfo = pluginInfoApi.getPluginsInfo();
+        final Iterable<PluginInfo> rawPluginInfo = skipPlugins ? ImmutableList.<PluginInfo>of() : pluginInfoApi.getPluginsInfo();
         final List<PluginInfo> pluginInfo = rawPluginInfo.iterator().hasNext() ? ImmutableList.<PluginInfo>copyOf(rawPluginInfo) : ImmutableList.<PluginInfo>of();
         final String kbVersion = org.killbill.billing.util.nodes.KillbillVersions.getKillbillVersion();
         final String kbApiVersion  = org.killbill.billing.util.nodes.KillbillVersions.getApiVersion();
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
index 28b9ca3..b5c4363 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
@@ -28,7 +28,7 @@ import org.apache.shiro.util.ByteSource;
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.security.SecurityApiException;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
 import org.killbill.clock.Clock;
 import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/KillbillCredentialsMatcher.java b/util/src/main/java/org/killbill/billing/util/security/shiro/KillbillCredentialsMatcher.java
index 1830226..c24eb12 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/KillbillCredentialsMatcher.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/KillbillCredentialsMatcher.java
@@ -21,7 +21,7 @@ package org.killbill.billing.util.security.shiro;
 import org.apache.shiro.authc.credential.CredentialsMatcher;
 import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
 import org.apache.shiro.crypto.hash.Sha512Hash;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 
 public class KillbillCredentialsMatcher {
 
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java
index 87c8588..6970497 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java
@@ -24,7 +24,7 @@ import javax.sql.DataSource;
 import org.apache.shiro.realm.jdbc.JdbcRealm;
 import org.apache.shiro.subject.PrincipalCollection;
 import org.killbill.billing.platform.glue.KillBillPlatformModuleBase;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
 
 public class KillBillJdbcRealm extends JdbcRealm {
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java
index 1c7c00f..7779670 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java
@@ -42,7 +42,7 @@ import org.apache.shiro.subject.PrincipalCollection;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
@@ -137,7 +137,7 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
             systemLdapCtx = ldapContextFactory.getSystemLdapContext();
             return findLDAPGroupsForUser(username, systemLdapCtx);
         } catch (AuthenticationException ex) {
-            log.info("LDAP authentication exception: " + ex.getLocalizedMessage());
+            log.info("LDAP authentication exception='{}'", ex.getLocalizedMessage());
             return ImmutableSet.<String>of();
         } finally {
             LdapUtils.closeContext(systemLdapCtx);
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDao.java
index 62543e5..ed2d38c 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDao.java
@@ -142,7 +142,7 @@ public class DefaultTagDao extends EntityDaoBase<TagModelDao, Tag, TagApiExcepti
         try {
             bus.postFromTransaction(tagEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
         } catch (final PersistentBus.EventBusException e) {
-            log.warn("Failed to post tag event for tag " + tag.getId().toString(), e);
+            log.warn("Failed to post tag event for tagId='{}'", tag.getId().toString(), e);
         }
     }
 
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
index 9039976..46128ce 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -184,7 +184,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
                     try {
                         bus.postFromTransaction(tagDefinitionEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
                     } catch (final PersistentBus.EventBusException e) {
-                        log.warn("Failed to post tag definition creation event for tag " + tagDefinition.getId(), e);
+                        log.warn("Failed to post tag definition creation event for tagDefinitionId='{}'", tagDefinition.getId(), e);
                     }
 
                     return tagDefinition;
@@ -263,7 +263,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
         try {
             bus.postFromTransaction(tagDefinitionEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
         } catch (final PersistentBus.EventBusException e) {
-            log.warn("Failed to post tag definition event for tag " + tagDefinition.getId().toString(), e);
+            log.warn("Failed to post tag definition event for tagDefinitionId='{}'", tagDefinition.getId().toString(), e);
         }
     }
 
diff --git a/util/src/main/resources/ehcache.xml b/util/src/main/resources/ehcache.xml
index ced3336..6d9c27f 100644
--- a/util/src/main/resources/ehcache.xml
+++ b/util/src/main/resources/ehcache.xml
@@ -135,8 +135,19 @@
            overflowToDisk="false"
            diskPersistent="false"
            memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
+           statistics="true">
+        <cacheEventListenerFactory
+                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
+                properties=""/>
+    </cache>
+
+    <cache name="tenant-config"
+           maxElementsInMemory="1000"
+           maxElementsOnDisk="0"
+           overflowToDisk="false"
+           diskPersistent="false"
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true">
         <cacheEventListenerFactory
                 class="org.killbill.billing.util.cache.ExpirationListenerFactory"
                 properties=""/>
diff --git a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
index 13022fe..d05e54e 100644
--- a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
@@ -298,7 +298,6 @@ values (
 <accountRecordIdValueWithComma()>
 <tenantRecordIdValueWithComma()>
 )
-;
 >>
 
 /** Audits, History **/
diff --git a/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java b/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java
new file mode 100644
index 0000000..0cf27a2
--- /dev/null
+++ b/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.flywaydb.core;
+
+import java.sql.Connection;
+import java.util.List;
+
+import org.flywaydb.core.api.FlywayException;
+import org.flywaydb.core.api.callback.FlywayCallback;
+import org.flywaydb.core.api.resolver.MigrationResolver;
+import org.flywaydb.core.internal.dbsupport.DbSupport;
+import org.flywaydb.core.internal.dbsupport.Schema;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.dbsupport.Table;
+import org.flywaydb.core.internal.metadatatable.MetaDataTable;
+import org.flywaydb.core.internal.util.PlaceholderReplacer;
+import org.killbill.billing.util.migration.DbMigrateWithDryRun;
+
+public class FlywayWithDryRun extends Flyway {
+
+    private final List<SqlStatement> sqlStatements;
+
+    public FlywayWithDryRun(final List<SqlStatement> sqlStatements) {
+        this.sqlStatements = sqlStatements;
+    }
+
+    // Note: we assume the schemas have already been created and baseline() has already been called
+    public int dryRunMigrate() throws FlywayException {
+        final PlaceholderReplacer placeholderReplacer = new PlaceholderReplacer(getPlaceholders(),
+                                                                                getPlaceholderPrefix(),
+                                                                                getPlaceholderSuffix());
+        return execute(new Command<Integer>() {
+            public Integer execute(final Connection connectionMetaDataTable,
+                                   final Connection connectionUserObjects,
+                                   final MigrationResolver migrationResolver,
+                                   final MetaDataTable metaDataTable,
+                                   final DbSupport dbSupport,
+                                   final Schema[] schemas,
+                                   final FlywayCallback[] flywayCallbacks) {
+                final Table metaDataDBTable = schemas[0].getTable(getTable());
+
+                final DbMigrateWithDryRun dbMigrate = new DbMigrateWithDryRun(sqlStatements,
+                                                                              placeholderReplacer,
+                                                                              getEncoding(),
+                                                                              metaDataDBTable,
+                                                                              connectionMetaDataTable,
+                                                                              connectionUserObjects,
+                                                                              dbSupport,
+                                                                              metaDataTable,
+                                                                              schemas[0],
+                                                                              migrationResolver,
+                                                                              getTarget(),
+                                                                              isIgnoreFutureMigrations(),
+                                                                              false,
+                                                                              isOutOfOrder(),
+                                                                              flywayCallbacks);
+                return dbMigrate.dryRunMigrate();
+            }
+        });
+    }
+}
diff --git a/util/src/test/java/org/killbill/billing/api/TestApiListener.java b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
index cbe643b..61c1252 100644
--- a/util/src/test/java/org/killbill/billing/api/TestApiListener.java
+++ b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
@@ -40,9 +40,9 @@ import org.killbill.billing.events.NullInvoiceInternalEvent;
 import org.killbill.billing.events.PaymentErrorInternalEvent;
 import org.killbill.billing.events.PaymentInfoInternalEvent;
 import org.killbill.billing.events.PaymentPluginErrorInternalEvent;
-import org.killbill.billing.events.RepairSubscriptionInternalEvent;
 import org.killbill.billing.events.TagDefinitionInternalEvent;
 import org.killbill.billing.events.TagInternalEvent;
+import org.killbill.clock.Clock;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.tweak.HandleCallback;
@@ -67,6 +67,7 @@ public class TestApiListener {
 
     private final List<NextEvent> nextExpectedEvent;
     private final IDBI idbi;
+    private final Clock clock;
 
     private boolean isListenerFailed = false;
     private String listenerFailedMsg;
@@ -74,10 +75,11 @@ public class TestApiListener {
     private volatile boolean completed;
 
     @Inject
-    public TestApiListener(final IDBI idbi) {
+    public TestApiListener(final IDBI idbi, final Clock clock) {
         nextExpectedEvent = new Stack<NextEvent>();
         this.completed = false;
         this.idbi = idbi;
+        this.clock = clock;
     }
 
     public void assertListenerStatus() {
@@ -119,7 +121,6 @@ public class TestApiListener {
         PAYMENT,
         PAYMENT_ERROR,
         PAYMENT_PLUGIN_ERROR,
-        REPAIR_BUNDLE,
         TAG,
         TAG_DEFINITION,
         CUSTOM_FIELD,
@@ -133,13 +134,6 @@ public class TestApiListener {
     }
 
     @Subscribe
-    public void handleRepairSubscriptionEvents(final RepairSubscriptionInternalEvent event) {
-        log.info(String.format("Got RepairSubscriptionEvent event %s", event.toString()));
-        assertEqualsNicely(NextEvent.REPAIR_BUNDLE);
-        notifyIfStackEmpty();
-    }
-
-    @Subscribe
     public void handleEntitlementEvents(final BlockingTransitionInternalEvent event) {
         log.info(String.format("Got entitlement event %s", event.toString()));
         assertEqualsNicely(NextEvent.BLOCK);
@@ -305,16 +299,9 @@ public class TestApiListener {
                         await().atMost(timeout, TimeUnit.MILLISECONDS).until(new Callable<Boolean>() {
                             @Override
                             public Boolean call() throws Exception {
-                                final long inProcessing = idbi.withHandle(new HandleCallback<Long>() {
-                                    @Override
-                                    public Long withHandle(final Handle handle) throws Exception {
-                                        return (Long) handle.select("select count(distinct record_id) count from bus_events").get(0).get("count") +
-                                               // We assume all ready notifications have been picked up
-                                               (Long) handle.select("select count(distinct record_id) count from notifications where processing_state = 'IN_PROCESSING'").get(0).get("count");
-                                    }
-                                });
-                                log.debug("Events still in processing: {}", inProcessing);
-                                return inProcessing == 0;
+                                final long pending = idbi.withHandle(new PendingBusOrNotificationCallback(clock));
+                                log.debug("Events still in processing: {}", pending);
+                                return pending == 0;
                             }
                         });
                         return completed;
@@ -322,19 +309,9 @@ public class TestApiListener {
                     final long after = System.currentTimeMillis();
                     waitTimeMs -= (after - before);
                 } catch (final Exception ignore) {
-                    final StringBuilder errorBuilder = new StringBuilder("isCompleted got interrupted. Exception: ").append(ignore)
-                                                                                                                    .append("\nRemaining bus events:\n");
-                    idbi.withHandle(new HandleCallback<Void>() {
-                        @Override
-                        public Void withHandle(final Handle handle) throws Exception {
-                            final List<Map<String, Object>> busEvents = handle.select("select * from bus_events");
-                            for (final Map<String, Object> busEvent : busEvents) {
-                                errorBuilder.append(busEvent).append("\n");
-                            }
-                            return null;
-                        }
-                    });
-                    log.error(errorBuilder.toString());
+                    // Rerun one more time to provide details
+                    final long pending = idbi.withHandle(new PendingBusOrNotificationCallback(clock));
+                    log.error("isCompleted : Received all events but found remaining unprocessed bus events/notifications =  {}", pending);
                     return false;
                 }
             } while (waitTimeMs > 0 && !completed);
@@ -344,10 +321,24 @@ public class TestApiListener {
             final Joiner joiner = Joiner.on(" ");
             log.error("TestApiListener did not complete in " + timeout + " ms, remaining events are " + joiner.join(nextExpectedEvent));
         }
-
         return completed;
     }
 
+    private static class PendingBusOrNotificationCallback implements HandleCallback<Long> {
+
+        private final Clock clock;
+
+        public PendingBusOrNotificationCallback(final Clock clock) {
+            this.clock = clock;
+        }
+        @Override
+        public Long withHandle(final Handle handle) throws Exception {
+            return (Long) handle.select("select count(distinct record_id) count from bus_events").get(0).get("count") +
+                   (Long) handle.select("select count(distinct record_id) count from notifications where effective_date < ?", clock.getUTCNow().toDate()).get(0).get("count") +
+                   (Long) handle.select("select count(distinct record_id) count from notifications where processing_state = 'IN_PROCESSING'").get(0).get("count");
+        }
+    }
+
     private void notifyIfStackEmpty() {
         log.debug("TestApiListener notifyIfStackEmpty ENTER");
         synchronized (this) {
diff --git a/util/src/test/java/org/killbill/billing/DBTestingHelper.java b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
index e6bb516..7023039 100644
--- a/util/src/test/java/org/killbill/billing/DBTestingHelper.java
+++ b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
@@ -21,12 +21,14 @@ package org.killbill.billing;
 import java.io.IOException;
 import java.net.URL;
 import java.util.Enumeration;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.killbill.billing.platform.test.PlatformDBTestingHelper;
 import org.killbill.billing.util.dao.AuditLogModelDaoMapper;
 import org.killbill.billing.util.dao.RecordIdIdMappingsMapper;
 import org.killbill.billing.util.io.IOUtils;
 import org.killbill.billing.util.security.shiro.dao.SessionModelDao;
+import org.killbill.commons.embeddeddb.EmbeddedDB;
 import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
 import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.IDBI;
@@ -37,6 +39,8 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
 
     private static DBTestingHelper dbTestingHelper = null;
 
+    private AtomicBoolean initialized;
+
     public static synchronized DBTestingHelper get() {
         if (dbTestingHelper == null) {
             dbTestingHelper = new DBTestingHelper();
@@ -44,20 +48,26 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
         return dbTestingHelper;
     }
 
-    protected DBTestingHelper() {
+    private DBTestingHelper() {
         super();
+        initialized = new AtomicBoolean(false);
     }
 
     @Override
-    public synchronized IDBI getDBI() throws IOException {
+    public IDBI getDBI() {
         final DBI dbi = (DBI) super.getDBI();
-        dbi.registerMapper(new AuditLogModelDaoMapper());
-        dbi.registerMapper(new RecordIdIdMappingsMapper());
-        dbi.registerMapper(new LowerToCamelBeanMapperFactory(SessionModelDao.class));
+        // Register KB specific mappers
+        if (initialized.compareAndSet(false, true)) {
+            dbi.registerMapper(new AuditLogModelDaoMapper());
+            dbi.registerMapper(new RecordIdIdMappingsMapper());
+            dbi.registerMapper(new LowerToCamelBeanMapperFactory(SessionModelDao.class));
+        }
         return dbi;
     }
 
     protected synchronized void executePostStartupScripts() throws IOException {
+
+        final EmbeddedDB instance = getInstance();
         final String databaseSpecificDDL = "org/killbill/billing/util/" + "ddl-" + instance.getDBEngine().name().toLowerCase() + ".sql";
         installDDLSilently(databaseSpecificDDL);
 
@@ -181,7 +191,7 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
             final String ddl;
             try {
                 ddl = IOUtils.toString(inputStream.openStream());
-                instance.executeScript(ddl);
+                getInstance().executeScript(ddl);
             } catch (final Exception ignored) {
                 // The test doesn't have this module ddl in the classpath - that's fine
             }
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java
index 0a88273..381bb9e 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java
@@ -19,9 +19,7 @@
 package org.killbill.billing.mock.glue;
 
 import org.killbill.billing.glue.OverdueModule;
-import org.killbill.billing.overdue.OverdueInternalApi;
 import org.killbill.billing.overdue.api.OverdueApi;
-import org.killbill.billing.overdue.api.OverdueConfig;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.mockito.Mockito;
@@ -37,11 +35,9 @@ public class MockOverdueModule extends KillBillModule implements OverdueModule {
 
     @Override
     public void installOverdueUserApi() {
-        bind(OverdueInternalApi.class).toInstance(Mockito.mock(OverdueInternalApi.class));
         bind(OverdueApi.class).toInstance(Mockito.mock(OverdueApi.class));
     }
 
-
     @Override
     protected void configure() {
         installOverdueUserApi();
diff --git a/util/src/test/java/org/killbill/billing/mock/MockPlan.java b/util/src/test/java/org/killbill/billing/mock/MockPlan.java
index 004346e..ab6ae03 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockPlan.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockPlan.java
@@ -63,7 +63,7 @@ public class MockPlan implements Plan {
     }
 
     @Override
-    public Date getEffectiveDateForExistingSubscriptons() {
+    public Date getEffectiveDateForExistingSubscriptions() {
         return new Date();
     }
 
diff --git a/util/src/test/java/org/killbill/billing/util/callcontext/TestDefaultCallContext.java b/util/src/test/java/org/killbill/billing/util/callcontext/TestDefaultCallContext.java
index 376f8f5..b5866d2 100644
--- a/util/src/test/java/org/killbill/billing/util/callcontext/TestDefaultCallContext.java
+++ b/util/src/test/java/org/killbill/billing/util/callcontext/TestDefaultCallContext.java
@@ -19,11 +19,10 @@ package org.killbill.billing.util.callcontext;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.callcontext.DefaultCallContext;
 import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestDefaultCallContext extends UtilTestSuiteNoDB {
 
diff --git a/util/src/test/java/org/killbill/billing/util/config/TestCacheConfig.java b/util/src/test/java/org/killbill/billing/util/config/TestCacheConfig.java
new file mode 100644
index 0000000..717c482
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/config/TestCacheConfig.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.util.config;
+
+import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.killbill.billing.util.config.tenant.PerTenantConfig;
+import org.killbill.billing.util.jackson.ObjectMapper;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestCacheConfig extends UtilTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testDeSerialization() throws Exception {
+
+        final ObjectMapper objectMapper = new ObjectMapper();
+
+        final PerTenantConfig input = new PerTenantConfig();
+        input.put("key1", "foo");
+        input.put("key2", "bar");
+        input.put("key3", "34346");
+        input.put("key4", "23.999");
+
+        final String inputString = objectMapper.writeValueAsString(input);
+
+        final PerTenantConfig result = objectMapper.readValue(inputString, PerTenantConfig.class);
+        Assert.assertEquals(result.size(), 4);
+    }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModule.java b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModule.java
index 030cd05..25b262c 100644
--- a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModule.java
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModule.java
@@ -42,6 +42,7 @@ public class TestUtilModule extends KillBillModule {
     protected void configure() {
         //install(new CallContextModule());
         install(new CacheModule(configSource));
+        install(new ConfigModule(configSource));
         install(new MockTenantModule(configSource));
         installHacks();
     }
diff --git a/util/src/test/java/org/killbill/billing/util/migration/CapturingMetaDataTable.java b/util/src/test/java/org/killbill/billing/util/migration/CapturingMetaDataTable.java
new file mode 100644
index 0000000..164a58a
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/migration/CapturingMetaDataTable.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.util.migration;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.flywaydb.core.api.MigrationVersion;
+import org.flywaydb.core.internal.dbsupport.DbSupport;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.dbsupport.Table;
+import org.flywaydb.core.internal.metadatatable.AppliedMigration;
+import org.flywaydb.core.internal.metadatatable.MetaDataTableImpl;
+
+public class CapturingMetaDataTable extends MetaDataTableImpl {
+
+    private final List<SqlStatement> sqlStatements;
+    private final DbSupport dbSupport;
+    private final Table table;
+
+    /**
+     * Creates a new instance of the metadata table support.
+     *
+     * @param sqlStatements The current list of all pending migrations.
+     * @param dbSupport     Database-specific functionality.
+     * @param table         The metadata table used by flyway.
+     */
+    public CapturingMetaDataTable(final List<SqlStatement> sqlStatements, final DbSupport dbSupport, final Table table) {
+        super(dbSupport, table);
+        this.sqlStatements = sqlStatements;
+        this.dbSupport = dbSupport;
+        this.table = table;
+    }
+
+    @Override
+    public void addAppliedMigration(final AppliedMigration appliedMigration) {
+        final MigrationVersion version = appliedMigration.getVersion();
+        final String versionStr = version == null ? null : version.toString();
+        final int calculateInstalledRank;
+        try {
+            calculateInstalledRank = calculateInstalledRank();
+        } catch (final SQLException e) {
+            throw new RuntimeException(e);
+        }
+
+        final String sql = new StringBuilder().append("INSERT INTO ")
+                                              .append(table)
+                                              .append(" (")
+                                              .append(dbSupport.quote("installed_rank")).append(",")
+                                              .append(dbSupport.quote("version")).append(",")
+                                              .append(dbSupport.quote("description")).append(",")
+                                              .append(dbSupport.quote("type")).append(",")
+                                              .append(dbSupport.quote("script")).append(",")
+                                              .append(dbSupport.quote("checksum")).append(",")
+                                              .append(dbSupport.quote("installed_by")).append(",")
+                                              .append(dbSupport.quote("execution_time")).append(",")
+                                              .append(dbSupport.quote("success"))
+                                              .append(")")
+                                              .append(" VALUES (")
+                                              .append(calculateInstalledRank + appliedMigration.getInstalledRank()).append(",")
+                                              .append("'").append(versionStr).append("',")
+                                              .append("'").append(appliedMigration.getDescription()).append("',")
+                                              .append("'").append(appliedMigration.getType().name()).append("',")
+                                              .append("'").append(appliedMigration.getScript()).append("',")
+                                              .append(appliedMigration.getChecksum()).append(",")
+                                              .append(dbSupport.getCurrentUserFunction()).append(",")
+                                              .append(appliedMigration.getExecutionTime()).append(",")
+                                              .append(appliedMigration.isSuccess())
+                                              .append(")")
+                                              .toString();
+
+        sqlStatements.add(new SqlStatement(0, sql, false));
+    }
+
+    /**
+     * Calculates the installed rank for the new migration to be inserted.
+     *
+     * @return The installed rank.
+     */
+    private int calculateInstalledRank() throws SQLException {
+        final int currentMax = dbSupport.getJdbcTemplate().queryForInt("SELECT MAX(" + dbSupport.quote("installed_rank") + ")" + " FROM " + table);
+        return currentMax + 1;
+    }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/migration/CapturingSqlMigrationExecutor.java b/util/src/test/java/org/killbill/billing/util/migration/CapturingSqlMigrationExecutor.java
new file mode 100644
index 0000000..6360b75
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/migration/CapturingSqlMigrationExecutor.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.util.migration;
+
+import java.sql.Connection;
+import java.util.List;
+
+import org.flywaydb.core.api.resolver.MigrationExecutor;
+import org.flywaydb.core.internal.dbsupport.DbSupport;
+import org.flywaydb.core.internal.dbsupport.SqlScript;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.util.PlaceholderReplacer;
+import org.flywaydb.core.internal.util.scanner.Resource;
+
+public class CapturingSqlMigrationExecutor implements MigrationExecutor {
+
+    private final List<SqlStatement> sqlStatements;
+    private final DbSupport dbSupport;
+    private final PlaceholderReplacer placeholderReplacer;
+    private final Resource sqlScriptResource;
+    private final String encoding;
+
+    /**
+     * Creates a new sql script migration based on this sql script.
+     *
+     * @param sqlStatements       The current list of all pending migrations.
+     * @param dbSupport           The database-specific support.
+     * @param sqlScriptResource   The resource containing the sql script.
+     * @param placeholderReplacer The placeholder replacer to apply to sql migration scripts.
+     * @param encoding            The encoding of this Sql migration.
+     */
+    public CapturingSqlMigrationExecutor(final List<SqlStatement> sqlStatements,
+                                         final DbSupport dbSupport,
+                                         final Resource sqlScriptResource,
+                                         final PlaceholderReplacer placeholderReplacer,
+                                         final String encoding) {
+        this.sqlStatements = sqlStatements;
+        this.dbSupport = dbSupport;
+        this.sqlScriptResource = sqlScriptResource;
+        this.encoding = encoding;
+        this.placeholderReplacer = placeholderReplacer;
+    }
+
+    @Override
+    public void execute(final Connection connection) {
+        final SqlScript sqlScript = new SqlScript(dbSupport, sqlScriptResource, placeholderReplacer, encoding);
+        for (final SqlStatement sqlStatement : sqlScript.getSqlStatements()) {
+            sqlStatements.add(sqlStatement);
+        }
+    }
+
+    @Override
+    public boolean executeInTransaction() {
+        return true;
+    }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/migration/DbMigrateWithDryRun.java b/util/src/test/java/org/killbill/billing/util/migration/DbMigrateWithDryRun.java
new file mode 100644
index 0000000..7b81a39
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/migration/DbMigrateWithDryRun.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.util.migration;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.flywaydb.core.api.FlywayException;
+import org.flywaydb.core.api.MigrationInfo;
+import org.flywaydb.core.api.MigrationVersion;
+import org.flywaydb.core.api.callback.FlywayCallback;
+import org.flywaydb.core.api.resolver.MigrationExecutor;
+import org.flywaydb.core.api.resolver.MigrationResolver;
+import org.flywaydb.core.internal.command.DbMigrate;
+import org.flywaydb.core.internal.dbsupport.DbSupport;
+import org.flywaydb.core.internal.dbsupport.DbSupportFactory;
+import org.flywaydb.core.internal.dbsupport.Schema;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.dbsupport.Table;
+import org.flywaydb.core.internal.info.MigrationInfoImpl;
+import org.flywaydb.core.internal.info.MigrationInfoServiceImpl;
+import org.flywaydb.core.internal.metadatatable.AppliedMigration;
+import org.flywaydb.core.internal.metadatatable.MetaDataTable;
+import org.flywaydb.core.internal.util.PlaceholderReplacer;
+import org.flywaydb.core.internal.util.jdbc.TransactionCallback;
+import org.flywaydb.core.internal.util.jdbc.TransactionTemplate;
+import org.flywaydb.core.internal.util.logging.Log;
+import org.flywaydb.core.internal.util.logging.LogFactory;
+import org.flywaydb.core.internal.util.scanner.filesystem.FileSystemResource;
+
+public class DbMigrateWithDryRun extends DbMigrate {
+
+    private static final Log LOG = LogFactory.getLog(DbMigrateWithDryRun.class);
+
+    private final List<SqlStatement> sqlStatements;
+    private final PlaceholderReplacer placeholderReplacer;
+    private final String encoding;
+    private final MigrationVersion target;
+    private final DbSupport dbSupport;
+    private final MetaDataTable metaDataTableForDryRun;
+    private final Schema schema;
+    private final MigrationResolver migrationResolver;
+    private final Connection connectionMetaDataTable;
+    private final Connection connectionUserObjects;
+    private final boolean outOfOrder;
+    private final FlywayCallback[] callbacks;
+    private final DbSupport dbSupportUserObjects;
+
+    /**
+     * Creates a new database migrator.
+     *
+     * @param sqlStatements               The current list of all pending migrations.
+     * @param placeholderReplacer         The placeholder replacer to apply to sql migration scripts.
+     * @param encoding                    The encoding of Sql migrations.
+     * @param metaDataDBTable             The database metadata DB Table.
+     * @param connectionMetaDataTable     The connection to use.
+     * @param connectionUserObjects       The connection to use to perform the actual database migrations.
+     * @param dbSupport                   Database-specific functionality.
+     * @param metaDataTable               The database metadata table.
+     * @param migrationResolver           The migration resolver.
+     * @param target                      The target version of the migration.
+     * @param ignoreFutureMigrations      Flag whether to ignore future migrations or not.
+     * @param ignoreFailedFutureMigration Flag whether to ignore failed future migrations or not.
+     * @param outOfOrder                  Allows migrations to be run "out of order".
+     */
+    public DbMigrateWithDryRun(final List<SqlStatement> sqlStatements,
+                               final PlaceholderReplacer placeholderReplacer,
+                               final String encoding,
+                               final Table metaDataDBTable,
+                               final Connection connectionMetaDataTable,
+                               final Connection connectionUserObjects,
+                               final DbSupport dbSupport,
+                               final MetaDataTable metaDataTable,
+                               final Schema schema,
+                               final MigrationResolver migrationResolver,
+                               final MigrationVersion target,
+                               final boolean ignoreFutureMigrations,
+                               final boolean ignoreFailedFutureMigration,
+                               final boolean outOfOrder,
+                               final FlywayCallback[] callbacks) {
+        super(connectionMetaDataTable, connectionUserObjects, dbSupport, metaDataTable, schema, migrationResolver, target, ignoreFutureMigrations, ignoreFailedFutureMigration, outOfOrder, callbacks);
+        this.sqlStatements = sqlStatements;
+        this.placeholderReplacer = placeholderReplacer;
+        this.encoding = encoding;
+        this.connectionMetaDataTable = connectionMetaDataTable;
+        this.connectionUserObjects = connectionUserObjects;
+        this.dbSupport = dbSupport;
+        this.schema = schema;
+        this.migrationResolver = migrationResolver;
+        this.target = target;
+        this.outOfOrder = outOfOrder;
+        this.callbacks = callbacks;
+
+        this.dbSupportUserObjects = DbSupportFactory.createDbSupport(connectionUserObjects, false);
+
+        // PIERRE: change MetaDataTable to capture the SQL
+        this.metaDataTableForDryRun = new CapturingMetaDataTable(sqlStatements, dbSupport, metaDataDBTable);
+    }
+
+    public int dryRunMigrate() throws FlywayException {
+        try {
+            for (final FlywayCallback callback : callbacks) {
+                new TransactionTemplate(connectionUserObjects).execute(new TransactionCallback<Object>() {
+                    @Override
+                    public Object doInTransaction() throws SQLException {
+                        dbSupportUserObjects.changeCurrentSchemaTo(schema);
+                        callback.beforeMigrate(connectionUserObjects);
+                        return null;
+                    }
+                });
+            }
+
+            // PIERRE: perform a single query to the metadata table
+            final MigrationInfoServiceImpl infoService = new MigrationInfoServiceImpl(migrationResolver, metaDataTableForDryRun, target, outOfOrder, true, true);
+            infoService.refresh();
+
+            final MigrationInfoImpl[] pendingMigrations = infoService.pending();
+            new TransactionTemplate(connectionMetaDataTable, false).execute(new TransactionCallback<Boolean>() {
+                public Boolean doInTransaction() {
+                    int i = 1;
+                    for (final MigrationInfoImpl migrationInfo : pendingMigrations) {
+                        applyMigration(i, migrationInfo);
+                        i++;
+                    }
+
+                    return true;
+                }
+            });
+
+            for (final FlywayCallback callback : callbacks) {
+                new TransactionTemplate(connectionUserObjects).execute(new TransactionCallback<Object>() {
+                    @Override
+                    public Object doInTransaction() throws SQLException {
+                        dbSupportUserObjects.changeCurrentSchemaTo(schema);
+                        callback.afterMigrate(connectionUserObjects);
+                        return null;
+                    }
+                });
+            }
+
+            return pendingMigrations.length;
+        } finally {
+            dbSupportUserObjects.restoreCurrentSchema();
+        }
+    }
+
+    private void applyMigration(final int installedRnk, final MigrationInfoImpl migration) {
+        final MigrationVersion version = migration.getVersion();
+        final String migrationText;
+        if (version != null) {
+            migrationText = "schema " + schema + " to version " + version + " - " + migration.getDescription();
+        } else {
+            migrationText = "schema " + schema + " with repeatable migration " + migration.getDescription();
+        }
+        LOG.info("Migrating " + migrationText);
+
+        // PIERRE: override the executor to capture the SQL
+        final FileSystemResource sqlScriptResource = new FileSystemResource(migration.getResolvedMigration().getPhysicalLocation());
+        final MigrationExecutor migrationExecutor = new CapturingSqlMigrationExecutor(sqlStatements,
+                                                                                      dbSupport,
+                                                                                      sqlScriptResource,
+                                                                                      placeholderReplacer,
+                                                                                      encoding);
+        try {
+            doMigrate(migration, migrationExecutor, migrationText);
+        } catch (final SQLException e) {
+            throw new FlywayException("Unable to apply migration", e);
+        }
+
+        final AppliedMigration appliedMigration = new AppliedMigration(installedRnk,
+                                                                       version,
+                                                                       migration.getDescription(),
+                                                                       migration.getType(),
+                                                                       migration.getScript(),
+                                                                       migration.getResolvedMigration().getChecksum(),
+                                                                       null,
+                                                                       null,
+                                                                       -1,
+                                                                       true);
+        metaDataTableForDryRun.addAppliedMigration(appliedMigration);
+    }
+
+    private void doMigrate(final MigrationInfo migration, final MigrationExecutor migrationExecutor, final String migrationText) throws SQLException {
+        for (final FlywayCallback callback : callbacks) {
+            dbSupportUserObjects.changeCurrentSchemaTo(schema);
+            callback.beforeEachMigrate(connectionUserObjects, migration);
+        }
+
+        dbSupportUserObjects.changeCurrentSchemaTo(schema);
+        migrationExecutor.execute(connectionUserObjects);
+        LOG.debug("Successfully completed migration of " + migrationText);
+
+        for (final FlywayCallback callback : callbacks) {
+            dbSupportUserObjects.changeCurrentSchemaTo(schema);
+            callback.afterEachMigrate(connectionUserObjects, migration);
+        }
+    }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/migration/Migrator.java b/util/src/test/java/org/killbill/billing/util/migration/Migrator.java
new file mode 100644
index 0000000..0b5b6e8
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/migration/Migrator.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.util.migration;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.flywaydb.core.FlywayWithDryRun;
+import org.flywaydb.core.api.FlywayException;
+import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.info.MigrationInfoDumper;
+import org.flywaydb.core.internal.util.ClassUtils;
+import org.flywaydb.core.internal.util.FileCopyUtils;
+import org.flywaydb.core.internal.util.StringUtils;
+import org.flywaydb.core.internal.util.VersionPrinter;
+import org.flywaydb.core.internal.util.logging.Log;
+import org.flywaydb.core.internal.util.logging.LogFactory;
+import org.flywaydb.core.internal.util.logging.console.ConsoleLog.Level;
+import org.flywaydb.core.internal.util.logging.console.ConsoleLogCreator;
+import org.flywaydb.core.internal.util.scanner.classpath.ClassPathResource;
+
+// Copied over from org.flywaydb.commandline.Main (not easily extensible unfortunately) to support dry-run
+public class Migrator {
+
+    /**
+     * The property name for the directory containing a list of jars to load on the classpath.
+     */
+    private static final String PROPERTY_JAR_DIRS = "flyway.jarDirs";
+    private static Log LOG;
+
+    /**
+     * Initializes the logging.
+     *
+     * @param level The minimum level to log at.
+     */
+    private static void initLogging(final Level level) {
+        LogFactory.setLogCreator(new ConsoleLogCreator(level));
+        LOG = LogFactory.getLog(Migrator.class);
+    }
+
+    /**
+     * Main method.
+     *
+     * @param args The command-line arguments.
+     */
+    public static void main(final String[] args) {
+        final Level logLevel = getLogLevel(args);
+        initLogging(logLevel);
+
+        try {
+            if (isPrintVersionAndExit(args)) {
+                printVersion();
+                System.exit(0);
+            }
+
+            final List<String> operations = determineOperations(args);
+            if (operations.isEmpty()) {
+                printUsage();
+                return;
+            }
+
+            final Properties properties = new Properties();
+            initializeDefaults(properties);
+            loadConfiguration(properties, args);
+            overrideConfiguration(properties, args);
+            dumpConfiguration(properties);
+
+            loadJdbcDrivers();
+            loadJavaMigrationsFromJarDirs(properties);
+
+            final List<SqlStatement> sqlStatements = new LinkedList<SqlStatement>();
+            final FlywayWithDryRun flyway = new FlywayWithDryRun(sqlStatements);
+            filterProperties(properties);
+            flyway.configure(properties);
+
+            for (final String operation : operations) {
+                executeOperation(flyway, operation, sqlStatements);
+            }
+        } catch (final Exception e) {
+            if (logLevel == Level.DEBUG) {
+                LOG.error("Unexpected error", e);
+            } else {
+                if (e instanceof FlywayException) {
+                    LOG.error(e.getMessage());
+                } else {
+                    LOG.error(e.toString());
+                }
+            }
+            System.exit(1);
+        }
+    }
+
+    private static boolean isPrintVersionAndExit(final String[] args) {
+        for (final String arg : args) {
+            if ("-v".equals(arg)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Executes this operation on this Flyway instance.
+     *
+     * @param flyway        The Flyway instance.
+     * @param operation     The operation to execute.
+     * @param sqlStatements The current list of all pending migrations.
+     */
+    private static void executeOperation(final FlywayWithDryRun flyway, final String operation, final Iterable<SqlStatement> sqlStatements) {
+        if ("clean".equals(operation)) {
+            flyway.clean();
+        } else if ("baseline".equals(operation)) {
+            flyway.baseline();
+        } else if ("migrate".equals(operation)) {
+            flyway.migrate();
+        } else if ("dryRunMigrate".equals(operation)) {
+            flyway.dryRunMigrate();
+
+            final StringBuilder stringBuilder = new StringBuilder("BEGIN;\n");
+            for (final SqlStatement sqlStatement : sqlStatements) {
+                stringBuilder.append(sqlStatement.getSql())
+                             .append(";\n");
+            }
+            stringBuilder.append("COMMIT;");
+            LOG.info("\n" + stringBuilder.toString());
+        } else if ("validate".equals(operation)) {
+            flyway.validate();
+        } else if ("info".equals(operation)) {
+            LOG.info("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all()));
+        } else if ("repair".equals(operation)) {
+            flyway.repair();
+        } else {
+            LOG.error("Invalid operation: " + operation);
+            printUsage();
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Checks the desired log level.
+     *
+     * @param args The command-line arguments.
+     * @return The desired log level.
+     */
+    private static Level getLogLevel(final String[] args) {
+        for (final String arg : args) {
+            if ("-X".equals(arg)) {
+                return Level.DEBUG;
+            }
+            if ("-q".equals(arg)) {
+                return Level.WARN;
+            }
+        }
+        return Level.INFO;
+    }
+
+    /**
+     * Initializes the properties with the default configuration for the command-line tool.
+     *
+     * @param properties The properties object to initialize.
+     */
+    private static void initializeDefaults(final Properties properties) {
+        properties.put("flyway.locations", "filesystem:" + new File(getInstallationDir(), "sql").getAbsolutePath());
+        properties.put(PROPERTY_JAR_DIRS, new File(getInstallationDir(), "jars").getAbsolutePath());
+    }
+
+    /**
+     * Filters there properties to remove the Flyway Commandline-specific ones.
+     *
+     * @param properties The properties to filter.
+     */
+    private static void filterProperties(final Properties properties) {
+        properties.remove(PROPERTY_JAR_DIRS);
+        properties.remove("flyway.configFile");
+        properties.remove("flyway.configFileEncoding");
+    }
+
+    /**
+     * Prints the version number on the console.
+     *
+     * @throws IOException when the version could not be read.
+     */
+    private static void printVersion() throws IOException {
+        final String version = new ClassPathResource("org/flywaydb/core/internal/version.txt", VersionPrinter.class.getClassLoader()).loadAsString("UTF-8");
+        LOG.info("Flyway " + version + " for Kill Bill");
+
+        LOG.debug("Java " + System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")");
+        LOG.debug(System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch") + "\n");
+    }
+
+    /**
+     * Prints the usage instructions on the console.
+     */
+    private static void printUsage() {
+        LOG.info("Usage");
+        LOG.info("=====");
+        LOG.info("");
+        LOG.info("flyway [options] command");
+        LOG.info("");
+        LOG.info("By default, the configuration will be read from conf/flyway.conf.");
+        LOG.info("Options passed from the command-line override the configuration.");
+        LOG.info("");
+        LOG.info("Commands");
+        LOG.info("--------");
+        LOG.info("migrate        : Migrates the database");
+        LOG.info("dryRunMigrate  : Migrates the database (dry-run)");
+        LOG.info("clean          : Drops all objects in the configured schemas");
+        LOG.info("info           : Prints the information about applied, current and pending migrations");
+        LOG.info("validate       : Validates the applied migrations against the ones on the classpath");
+        LOG.info("baseline       : Baselines an existing database at the baselineVersion");
+        LOG.info("repair         : Repairs the metadata table");
+        LOG.info("");
+        LOG.info("Options (Format: -key=value)");
+        LOG.info("-------");
+        LOG.info("driver                       : Fully qualified classname of the jdbc driver");
+        LOG.info("url                          : Jdbc url to use to connect to the database");
+        LOG.info("user                         : User to use to connect to the database");
+        LOG.info("password                     : Password to use to connect to the database");
+        LOG.info("schemas                      : Comma-separated list of the schemas managed by Flyway");
+        LOG.info("table                        : Name of Flyway's metadata table");
+        LOG.info("locations                    : Classpath locations to scan recursively for migrations");
+        LOG.info("resolvers                    : Comma-separated list of custom MigrationResolvers");
+        LOG.info("skipDefaultResolvers         : Skips default resolvers (jdbc, sql and Spring-jdbc)");
+        LOG.info("sqlMigrationPrefix           : File name prefix for sql migrations");
+        LOG.info("repeatableSqlMigrationPrefix : File name prefix for repeatable sql migrations");
+        LOG.info("sqlMigrationSeparator        : File name separator for sql migrations");
+        LOG.info("sqlMigrationSuffix           : File name suffix for sql migrations");
+        LOG.info("encoding                     : Encoding of sql migrations");
+        LOG.info("placeholderReplacement       : Whether placeholders should be replaced");
+        LOG.info("placeholders                 : Placeholders to replace in sql migrations");
+        LOG.info("placeholderPrefix            : Prefix of every placeholder");
+        LOG.info("placeholderSuffix            : Suffix of every placeholder");
+        LOG.info("target                       : Target version up to which Flyway should use migrations");
+        LOG.info("outOfOrder                   : Allows migrations to be run \"out of order\"");
+        LOG.info("callbacks                    : Comma-separated list of FlywayCallback classes");
+        LOG.info("skipDefaultCallbacks         : Skips default callbacks (sql)");
+        LOG.info("validateOnMigrate            : Validate when running migrate");
+        LOG.info("ignoreFutureMigrations       : Allow future migrations when validating");
+        LOG.info("cleanOnValidationError       : Automatically clean on a validation error");
+        LOG.info("cleanDisabled                : Whether to disable clean");
+        LOG.info("baselineVersion              : Version to tag schema with when executing baseline");
+        LOG.info("baselineDescription          : Description to tag schema with when executing baseline");
+        LOG.info("baselineOnMigrate            : Baseline on migrate against uninitialized non-empty schema");
+        LOG.info("configFile                   : Config file to use (default: conf/flyway.properties)");
+        LOG.info("configFileEncoding           : Encoding of the config file (default: UTF-8)");
+        LOG.info("jarDirs                      : Dirs for Jdbc drivers & Java migrations (default: jars)");
+        LOG.info("");
+        LOG.info("Add -X to print debug output");
+        LOG.info("Add -q to suppress all output, except for errors and warnings");
+        LOG.info("Add -v to print the Flyway version and exit");
+        LOG.info("");
+        LOG.info("Example");
+        LOG.info("-------");
+        LOG.info("flyway -user=myuser -password=s3cr3t -url=jdbc:h2:mem -placeholders.abc=def migrate");
+        LOG.info("");
+        LOG.info("More info at https://flywaydb.org/documentation/commandline");
+    }
+
+    /**
+     * Loads all the driver jars contained in the drivers folder. (For Jdbc drivers)
+     *
+     * @throws IOException When the jars could not be loaded.
+     */
+    private static void loadJdbcDrivers() throws IOException {
+        final File driversDir = new File(getInstallationDir(), "drivers");
+        final File[] files = driversDir.listFiles(new FilenameFilter() {
+            public boolean accept(final File dir, final String name) {
+                return name.endsWith(".jar");
+            }
+        });
+
+        // see javadoc of listFiles(): null if given path is not a real directory
+        if (files == null) {
+            return;
+        }
+
+        for (final File file : files) {
+            addJarOrDirectoryToClasspath(file.getPath());
+        }
+    }
+
+    /**
+     * Loads all the jars contained in the jars folder. (For Java Migrations)
+     * This will also indirectly load custom driver jars.
+     *
+     * @param properties The configured properties.
+     * @throws IOException When the jars could not be loaded.
+     */
+    private static void loadJavaMigrationsFromJarDirs(final Properties properties) throws IOException {
+        String jarDirs = properties.getProperty(PROPERTY_JAR_DIRS);
+        if (!StringUtils.hasLength(jarDirs)) {
+            return;
+        }
+
+        jarDirs = jarDirs.replace(File.pathSeparator, ",");
+        final String[] dirs = StringUtils.tokenizeToStringArray(jarDirs, ",");
+
+        for (final String dirName : dirs) {
+            final File dir = new File(dirName);
+            final File[] files = dir.listFiles(new FilenameFilter() {
+                public boolean accept(final File dir, final String name) {
+                    return name.endsWith(".jar");
+                }
+            });
+
+            // see javadoc of listFiles(): null if given path is not a real directory
+            if (files == null) {
+                continue;
+            }
+
+            for (final File file : files) {
+                addJarOrDirectoryToClasspath(file.getPath());
+            }
+        }
+    }
+
+    /**
+     * Adds a jar or a directory with this name to the classpath.
+     *
+     * @param name The name of the jar or directory to add.
+     * @throws IOException when the jar or directory could not be found.
+     */
+    private static void addJarOrDirectoryToClasspath(final String name) throws IOException {
+        LOG.debug("Adding location to classpath: " + name);
+
+        try {
+            final URL url = new File(name).toURI().toURL();
+            final URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+            final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+            method.setAccessible(true);
+            method.invoke(sysloader, url);
+        } catch (final Exception e) {
+            throw new FlywayException("Unable to load " + name, e);
+        }
+    }
+
+    /**
+     * Loads the configuration from the various possible locations.
+     *
+     * @param properties The properties object to load to configuration into.
+     * @param args       The command-line arguments passed in.
+     */
+    private static void loadConfiguration(final Properties properties, final String[] args) {
+        final String encoding = determineConfigurationFileEncoding(args);
+
+        loadConfigurationFile(properties, getInstallationDir() + "/conf/flyway.conf", encoding, false);
+        loadConfigurationFile(properties, System.getProperty("user.home") + "/flyway.conf", encoding, false);
+        loadConfigurationFile(properties, "flyway.conf", encoding, false);
+
+        final String configFile = determineConfigurationFileArgument(args);
+        if (configFile != null) {
+            loadConfigurationFile(properties, configFile, encoding, true);
+        }
+    }
+
+    /**
+     * Loads the configuration from the configuration file. If a configuration file is specified using the -configfile
+     * argument it will be used, otherwise the default config file (conf/flyway.properties) will be loaded.
+     *
+     * @param properties    The properties object to load to configuration into.
+     * @param file          The configuration file to load.
+     * @param encoding      The encoding of the configuration file.
+     * @param failIfMissing Whether to fail if the file is missing.
+     * @return Whether the file was loaded successfully.
+     * @throws FlywayException when the configuration file could not be loaded.
+     */
+    private static boolean loadConfigurationFile(final Properties properties, final String file, final String encoding, final boolean failIfMissing) throws FlywayException {
+        final File configFile = new File(file);
+        final String errorMessage = "Unable to load config file: " + configFile.getAbsolutePath();
+
+        if (!configFile.isFile() || !configFile.canRead()) {
+            if (!failIfMissing) {
+                LOG.debug(errorMessage);
+                return false;
+            }
+            throw new FlywayException(errorMessage);
+        }
+
+        LOG.debug("Loading config file: " + configFile.getAbsolutePath());
+        try {
+            final String contents = FileCopyUtils.copyToString(new InputStreamReader(new FileInputStream(configFile), encoding));
+            properties.load(new StringReader(contents.replace("\\", "\\\\")));
+            return true;
+        } catch (final IOException e) {
+            throw new FlywayException(errorMessage, e);
+        }
+    }
+
+    /**
+     * Dumps the configuration to the console when debug output is activated.
+     *
+     * @param properties The configured properties.
+     */
+    private static void dumpConfiguration(final Properties properties) {
+        LOG.debug("Using configuration:");
+        for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
+            String value = entry.getValue().toString();
+            value = "flyway.password".equals(entry.getKey()) ? StringUtils.trimOrPad("", value.length(), '*') : value;
+            LOG.debug(entry.getKey() + " -> " + value);
+        }
+    }
+
+    /**
+     * Determines the file to use for loading the configuration.
+     *
+     * @param args The command-line arguments passed in.
+     * @return The path of the configuration file on disk.
+     */
+    private static String determineConfigurationFileArgument(final String[] args) {
+        for (final String arg : args) {
+            if (isPropertyArgument(arg) && "configFile".equals(getArgumentProperty(arg))) {
+                return getArgumentValue(arg);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @return The installation directory of the Flyway Command-line tool.
+     */
+    @SuppressWarnings("ConstantConditions")
+    private static String getInstallationDir() {
+        final String path = ClassUtils.getLocationOnDisk(Migrator.class);
+        return new File(path).getParentFile().getParentFile().getAbsolutePath();
+    }
+
+    /**
+     * Determines the encoding to use for loading the configuration.
+     *
+     * @param args The command-line arguments passed in.
+     * @return The encoding. (default: UTF-8)
+     */
+    private static String determineConfigurationFileEncoding(final String[] args) {
+        for (final String arg : args) {
+            if (isPropertyArgument(arg) && "configFileEncoding".equals(getArgumentProperty(arg))) {
+                return getArgumentValue(arg);
+            }
+        }
+
+        return "UTF-8";
+    }
+
+    /**
+     * Overrides the configuration from the config file with the properties passed in directly from the command-line.
+     *
+     * @param properties The properties to override.
+     * @param args       The command-line arguments that were passed in.
+     */
+    private static void overrideConfiguration(final Properties properties, final String[] args) {
+        for (final String arg : args) {
+            if (isPropertyArgument(arg)) {
+                properties.put("flyway." + getArgumentProperty(arg), getArgumentValue(arg));
+            }
+        }
+    }
+
+    /**
+     * Checks whether this command-line argument tries to set a property.
+     *
+     * @param arg The command-line argument to check.
+     * @return {@code true} if it does, {@code false} if not.
+     */
+    private static boolean isPropertyArgument(final String arg) {
+        return arg.startsWith("-") && arg.contains("=");
+    }
+
+    /**
+     * Retrieves the property this command-line argument tries to assign.
+     *
+     * @param arg The command-line argument to check, typically in the form -key=value.
+     * @return The property.
+     */
+    private static String getArgumentProperty(final String arg) {
+        final int index = arg.indexOf("=");
+
+        return arg.substring(1, index);
+    }
+
+    /**
+     * Retrieves the value this command-line argument tries to assign.
+     *
+     * @param arg The command-line argument to check, typically in the form -key=value.
+     * @return The value or an empty string if no value is assigned.
+     */
+    private static String getArgumentValue(final String arg) {
+        final int index = arg.indexOf("=");
+
+        if ((index < 0) || (index == arg.length())) {
+            return "";
+        }
+
+        return arg.substring(index + 1);
+    }
+
+    /**
+     * Determine the operations Flyway should execute.
+     *
+     * @param args The command-line arguments passed in.
+     * @return The operations. An empty list if none.
+     */
+    private static List<String> determineOperations(final String[] args) {
+        final List<String> operations = new ArrayList<String>();
+
+        for (final String arg : args) {
+            if (!arg.startsWith("-")) {
+                operations.add(arg);
+            }
+        }
+
+        return operations;
+    }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJndiLdapRealm.java b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJndiLdapRealm.java
index 262a5a7..1440ff6 100644
--- a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJndiLdapRealm.java
+++ b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJndiLdapRealm.java
@@ -31,7 +31,7 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import org.killbill.billing.util.UtilTestSuiteNoDB;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 
 import com.google.common.collect.Sets;
 
diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
index 5317d3f..17ce189 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
@@ -26,7 +26,7 @@ import org.killbill.billing.security.api.SecurityApi;
 import org.killbill.billing.util.audit.dao.AuditDao;
 import org.killbill.billing.util.broadcast.dao.BroadcastDao;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.SecurityConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.killbill.billing.util.customfield.api.DefaultCustomFieldUserApi;
 import org.killbill.billing.util.customfield.dao.CustomFieldDao;
 import org.killbill.billing.util.dao.NonEntityDao;