killbill-memoizeit

Merge remote-tracking branch 'killbill/work-for-release-0.19.x'

5/2/2018 8:46:27 PM

Changes

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

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

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

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

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

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

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

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

NEWS 4(+4 -0)

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

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

pom.xml 4(+2 -2)

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

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

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

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

Details

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

diff --git a/account/pom.xml b/account/pom.xml
index 03f02dd..d5faf24 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
diff --git a/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java b/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java
index 447b18d..d892e83 100644
--- a/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java
+++ b/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java
@@ -18,17 +18,11 @@
 
 package org.killbill.billing.account;
 
-import org.killbill.billing.account.api.ImmutableAccountInternalApi;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-
 import org.killbill.billing.GuicyKillbillTestSuiteNoDB;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.account.api.ImmutableAccountInternalApi;
 import org.killbill.billing.account.dao.AccountDao;
 import org.killbill.billing.account.glue.TestAccountModuleNoDB;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.clock.Clock;
 import org.killbill.billing.util.audit.dao.AuditDao;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.customfield.dao.CustomFieldDao;
@@ -36,6 +30,11 @@ import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.tag.api.user.TagEventBuilder;
 import org.killbill.billing.util.tag.dao.TagDao;
 import org.killbill.billing.util.tag.dao.TagDefinitionDao;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.clock.Clock;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 
 import com.google.inject.Guice;
 import com.google.inject.Inject;
@@ -70,12 +69,20 @@ public abstract class AccountTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestAccountModuleNoDB(configSource));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         bus.start();
     }
 
diff --git a/account/src/test/java/org/killbill/billing/account/AccountTestSuiteWithEmbeddedDB.java b/account/src/test/java/org/killbill/billing/account/AccountTestSuiteWithEmbeddedDB.java
index 1e146a5..2f20eda 100644
--- a/account/src/test/java/org/killbill/billing/account/AccountTestSuiteWithEmbeddedDB.java
+++ b/account/src/test/java/org/killbill/billing/account/AccountTestSuiteWithEmbeddedDB.java
@@ -69,12 +69,20 @@ public abstract class AccountTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestAccountModuleWithEmbeddedDB(configSource));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
         controllerDispatcher.clearAll();
         bus.start();

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

diff --git a/api/pom.xml b/api/pom.xml
index c25649f..bb1e42c 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-internal-api</artifactId>
diff --git a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
index 20027f2..a62fc85 100644
--- a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
+++ b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -64,6 +64,8 @@ public interface EventsStream {
 
     boolean isBlockChange(final DateTime effectiveDate);
 
+    boolean isBlockEntitlement(final DateTime effectiveDate);
+
     int getDefaultBillCycleDayLocal();
 
     Collection<BlockingState> getPendingEntitlementCancellationEvents();
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index ac1df1a..0728a12 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -65,6 +65,8 @@ public interface SubscriptionBaseInternalApi {
 
     public Iterable<UUID> getNonAOSubscriptionIdsForKey(String bundleKey, InternalTenantContext context);
 
+    public SubscriptionBaseBundle getActiveBundleForKey(String bundleKey, Catalog catalog, InternalTenantContext context);
+
     public List<SubscriptionBase> getSubscriptionsForBundle(UUID bundleId, DryRunArguments dryRunArguments, InternalTenantContext context)
             throws SubscriptionBaseApiException;
 

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

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index e6c9447..fa183a3 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteNoDB.java b/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteNoDB.java
index 0b30880..9e1af49 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteNoDB.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteNoDB.java
@@ -27,6 +27,10 @@ public abstract class BeatrixTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new BeatrixIntegrationModuleNoDB(configSource));
         injector.injectMembers(this);
     }
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 0fae546..6bdf4e5 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
@@ -24,6 +24,7 @@ import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
 import org.killbill.billing.account.glue.DefaultAccountModule;
 import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.beatrix.glue.BeatrixModule;
+import org.killbill.billing.beatrix.integration.db.TestDBRouterAPI;
 import org.killbill.billing.beatrix.integration.overdue.IntegrationTestOverdueModule;
 import org.killbill.billing.beatrix.util.AccountChecker;
 import org.killbill.billing.beatrix.util.AuditChecker;
@@ -57,6 +58,7 @@ import org.killbill.billing.util.glue.ExportModule;
 import org.killbill.billing.util.glue.GlobalLockerModule;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.billing.util.glue.KillBillShiroModule;
+import org.killbill.billing.util.glue.KillbillApiAopModule;
 import org.killbill.billing.util.glue.NodesModule;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
 import org.killbill.billing.util.glue.RecordIdModule;
@@ -111,6 +113,7 @@ public class BeatrixIntegrationModule extends KillBillModule {
         install(new BroadcastModule(configSource));
         install(new KillBillShiroModuleOnlyIniRealm(configSource));
         install(new BeatrixModule(configSource));
+        install(new KillbillApiAopModule());
 
         bind(AccountChecker.class).asEagerSingleton();
         bind(SubscriptionChecker.class).asEagerSingleton();
@@ -119,6 +122,7 @@ public class BeatrixIntegrationModule extends KillBillModule {
         bind(RefundChecker.class).asEagerSingleton();
         bind(AuditChecker.class).asEagerSingleton();
         bind(TestApiListener.class).asEagerSingleton();
+        bind(TestDBRouterAPI.class).asEagerSingleton();
     }
 
     private final class DefaultInvoiceModuleWithSwitchRepairLogic extends DefaultInvoiceModule {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/db/TestDBRouter.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/db/TestDBRouter.java
new file mode 100644
index 0000000..a8d4727
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/db/TestDBRouter.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.beatrix.integration.db;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.integration.TestIntegrationBase;
+import org.killbill.billing.callcontext.DefaultTenantContext;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.notification.plugin.api.ExtBusEvent;
+import org.killbill.billing.osgi.api.ROTenantContext;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.eventbus.Subscribe;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.awaitility.Awaitility.await;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+public class TestDBRouter extends TestIntegrationBase {
+
+    @Inject
+    private TestDBRouterAPI testDBRouterAPI;
+
+    private PublicListener publicListener;
+    private AtomicInteger externalBusCount;
+
+    @Override
+    @BeforeMethod(groups = "slow")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+
+        this.externalBusCount = new AtomicInteger(0);
+        testDBRouterAPI.reset();
+    }
+
+    @Override
+    protected void registerHandlers() throws EventBusException {
+        super.registerHandlers();
+
+        publicListener = new PublicListener();
+        externalBus.register(publicListener);
+    }
+
+    @AfterMethod(groups = "slow")
+    public void afterMethod() throws Exception {
+        externalBus.unregister(publicListener);
+        super.afterMethod();
+    }
+
+    @Test(groups = "slow")
+    public void testWithBusEvents() throws Exception {
+        final DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
+        clock.setTime(initialDate);
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(2));
+        assertNotNull(account);
+
+        final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        assertNotNull(bpEntitlement);
+
+        await().atMost(10, SECONDS)
+               .until(new Callable<Boolean>() {
+                   @Override
+                   public Boolean call() throws Exception {
+                       // Expecting ACCOUNT_CREATE, ACCOUNT_CHANGE, SUBSCRIPTION_CREATION (2), ENTITLEMENT_CREATE INVOICE_CREATION
+                       return externalBusCount.get() == 6;
+                   }
+               });
+    }
+
+    private void assertNbCalls(final int expectedNbRWCalls, final int expectedNbROCalls) {
+        assertEquals(testDBRouterAPI.getNbRWCalls(), expectedNbRWCalls);
+        assertEquals(testDBRouterAPI.getNbRoCalls(), expectedNbROCalls);
+    }
+
+    public class PublicListener {
+
+        @Subscribe
+        public void handleExternalEvents(final ExtBusEvent event) {
+            testDBRouterAPI.reset();
+
+            final TenantContext tenantContext = new DefaultTenantContext(callContext.getAccountId(), callContext.getTenantId());
+            // Only RO tenant will trigger use of RO DBI (initiated by plugins)
+            final ROTenantContext roTenantContext = new ROTenantContext(tenantContext);
+
+            // RO calls goes to RW DB by default
+            testDBRouterAPI.doROCall(tenantContext);
+            assertNbCalls(1, 0);
+
+            testDBRouterAPI.doROCall(callContext);
+            assertNbCalls(2, 0);
+
+            // Even if the thread is dirty (previous RW calls), the plugin asked for RO DBI
+            testDBRouterAPI.doROCall(roTenantContext);
+            assertNbCalls(2, 1);
+
+            // Make sure subsequent calls go back to the RW DB
+            testDBRouterAPI.doROCall(tenantContext);
+            assertNbCalls(3, 1);
+
+            testDBRouterAPI.doRWCall(callContext);
+            assertNbCalls(4, 1);
+
+            testDBRouterAPI.doROCall(roTenantContext);
+            assertNbCalls(4, 2);
+
+            testDBRouterAPI.doROCall(callContext);
+            assertNbCalls(5, 2);
+
+            testDBRouterAPI.doROCall(tenantContext);
+            assertNbCalls(6, 2);
+
+            testDBRouterAPI.doChainedROCall(tenantContext);
+            assertNbCalls(7, 2);
+
+            testDBRouterAPI.doChainedRWCall(callContext);
+            assertNbCalls(8, 2);
+
+            // Increment only if there are no errors
+            externalBusCount.incrementAndGet();
+        }
+    }
+}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/db/TestDBRouterAPI.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/db/TestDBRouterAPI.java
new file mode 100644
index 0000000..73eb7c1
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/db/TestDBRouterAPI.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.beatrix.integration.db;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.KillbillApi;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.entity.dao.DBRouterUntyped;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+
+public class TestDBRouterAPI implements KillbillApi {
+
+    private final AtomicInteger rwCalls = new AtomicInteger(0);
+    private final AtomicInteger roCalls = new AtomicInteger(0);
+
+    private final DBRouterUntyped dbRouter;
+
+    @Inject
+    public TestDBRouterAPI() {
+        final IDBI dbi = Mockito.mock(IDBI.class);
+        Mockito.when(dbi.open()).thenAnswer(new Answer<Handle>() {
+            @Override
+            public Handle answer(final InvocationOnMock invocation) {
+                rwCalls.incrementAndGet();
+                return null;
+            }
+        });
+        final IDBI roDbi = Mockito.mock(IDBI.class);
+        Mockito.when(roDbi.open()).thenAnswer(new Answer<Handle>() {
+            @Override
+            public Handle answer(final InvocationOnMock invocation) {
+                roCalls.incrementAndGet();
+                return null;
+            }
+        });
+
+        this.dbRouter = new DBRouterUntyped(dbi, roDbi);
+    }
+
+    public void reset() {
+        rwCalls.set(0);
+        roCalls.set(0);
+    }
+
+    public void doRWCall(final CallContext callContext) {
+        dbRouter.getHandle(false);
+    }
+
+    public void doROCall(final TenantContext tenantContext) {
+        dbRouter.getHandle(true);
+    }
+
+    // Nesting dolls
+    public void doChainedROCall(final TenantContext tenantContext) {
+        doROCall(tenantContext);
+    }
+
+    // Nesting dolls
+    public void doChainedRWCall(final CallContext callContext) {
+        doRWCall(callContext);
+    }
+
+    public int getNbRWCalls() {
+        return rwCalls.get();
+    }
+
+    public int getNbRoCalls() {
+        return roCalls.get();
+    }
+}
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 33fe5ab..504dafe 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
@@ -120,6 +120,7 @@ import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.nodes.KillbillNodesApi;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.skife.config.ConfigurationObjectFactory;
 import org.skife.config.TimeSpan;
 import org.slf4j.Logger;
@@ -306,6 +307,10 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
 
     @BeforeClass(groups = "slow")
     public void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final InvoiceConfig defaultInvoiceConfig = new ConfigurationObjectFactory(skifeConfigSource).build(InvoiceConfig.class);
         invoiceConfig = new ConfigurableInvoiceConfig(defaultInvoiceConfig);
         // The default value is 50, i.e. wait 50 x 100ms = 5s to get the lock. This isn't always enough and can lead to random tests failures
@@ -335,12 +340,16 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
 
         // Start services
         lifecycle.fireStartupSequencePriorEventRegistration();
-        busService.getBus().register(busHandler);
+        registerHandlers();
         lifecycle.fireStartupSequencePostEventRegistration();
 
         paymentPlugin.clear();
     }
 
+    protected void registerHandlers() throws EventBusException {
+        busService.getBus().register(busHandler);
+    }
+
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
         lifecycle.fireShutdownSequencePriorEventUnRegistration();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
index 34b5650..6fa5adb 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
@@ -675,39 +675,39 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
                                                                  lastInvoice.getInvoiceDate(), lastInvoice.getTargetDate(), lastInvoice.getCurrency(), false, InvoiceStatus.COMMITTED, false);
 
         final InvoiceItemModelDao recurring1 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.RECURRING, lastInvoice.getId(), lastInvoice.getAccountId(),
-                                                                       bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), "", "shotgun-monthly", "shotgun-monthly-evergreen",
+                                                                       bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), "", "Shotgun", "shotgun-monthly", "shotgun-monthly-evergreen",
                                                                        null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("249.95"), new BigDecimal("249.95"), account.getCurrency(), null);
 
         final InvoiceItemModelDao repair1 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
-                                                                    bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+                                                                    bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null, null,
                                                                     null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("-249.95"), new BigDecimal("-249.95"), account.getCurrency(), recurring1.getId());
 
         final InvoiceItemModelDao recurring2 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.RECURRING, lastInvoice.getId(), lastInvoice.getAccountId(),
-                                                                       bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), "", "shotgun-monthly", "shotgun-monthly-evergreen",
+                                                                       bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), "", "Shotgun", "shotgun-monthly", "shotgun-monthly-evergreen",
                                                                        null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("249.95"), new BigDecimal("249.95"), account.getCurrency(), null);
 
 
         final InvoiceItemModelDao repair21 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
-                                                                     bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+                                                                     bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null, null,
                                                                      null, new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 13), new BigDecimal("-100.95"), new BigDecimal("-100.95"), account.getCurrency(), recurring2.getId());
 
         final InvoiceItemModelDao repair22 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
-                                                                     bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+                                                                     bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null, null,
                                                                      null, new LocalDate(2012, 5, 13), new LocalDate(2012, 5, 22), new BigDecimal("-100"), new BigDecimal("-100"), account.getCurrency(), recurring2.getId());
 
         final InvoiceItemModelDao repair23 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
-                                                                     bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+                                                                     bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null, null,
                                                                      null, new LocalDate(2012, 5, 22), new LocalDate(2012, 6, 1), new BigDecimal("-49"), new BigDecimal("-49"), account.getCurrency(), recurring2.getId());
 
 
 
         final InvoiceItemModelDao recurring3 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.RECURRING, lastInvoice.getId(), lastInvoice.getAccountId(),
-                                                                       bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), "", "shotgun-monthly", "shotgun-monthly-evergreen",
+                                                                       bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), "", "Shotgun", "shotgun-monthly", "shotgun-monthly-evergreen",
                                                                        null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("249.95"), new BigDecimal("249.95"), account.getCurrency(), null);
 
 
         final InvoiceItemModelDao repair3 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
-                                                                    bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+                                                                    bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null, null,
                                                                     null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("-249.95"), new BigDecimal("-249.95"), account.getCurrency(), recurring3.getId());
 
         List<InvoiceItemModelDao> newItems = new ArrayList<InvoiceItemModelDao>();
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 60e3176..f3892bf 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
@@ -312,12 +312,20 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
 
     @BeforeClass(groups = "slow")
     public void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector g = Guice.createInjector(Stage.PRODUCTION, Modules.override(new BeatrixIntegrationModule(configSource)).with(new OverrideModuleForOSGI()));
         g.injectMembers(this);
     }
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         log.debug("RESET TEST FRAMEWORK");
 
         cleanupAllTables();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
index f770663..55bb042 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
@@ -146,6 +146,8 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
                                                                                    null,
                                                                                    null,
                                                                                    null,
+                                                                                   null,
+                                                                                   null,
                                                                                    "My charge",
                                                                                    clock.getUTCToday(),
                                                                                    null,

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

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 2a45bf3..91f99c7 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteNoDB.java b/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteNoDB.java
index 3109de4..1301e8e 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteNoDB.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteNoDB.java
@@ -52,6 +52,10 @@ public abstract class CatalogTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestCatalogModuleNoDB(configSource));
         injector.injectMembers(this);
     }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java b/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java
index a556b15..2767ce8 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java
@@ -47,6 +47,10 @@ public class CatalogTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEm
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestCatalogModuleWithEmbeddedDB(configSource));
         injector.injectMembers(this);
     }

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

diff --git a/currency/pom.xml b/currency/pom.xml
index 29f302d..8a414d3 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-currency</artifactId>
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 82352f1..cc4d0ed 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>
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 475527c..595060f 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
@@ -38,6 +38,9 @@ import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.Catalog;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.CatalogInternalApi;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
@@ -72,12 +75,12 @@ import org.killbill.clock.Clock;
 import org.killbill.notificationq.api.NotificationQueueService;
 
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
-import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCreateEntitlement;
 import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCreateEntitlementsWithAOs;
 import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logPauseResumeEntitlement;
 import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logTransferEntitlement;
@@ -102,6 +105,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
     private final NotificationQueueService notificationQueueService;
     private final EntitlementPluginExecution pluginExecution;
     private final SecurityApi securityApi;
+    private final CatalogInternalApi catalogInternalApi;
 
     @Inject
     public DefaultEntitlementApi(final PersistentBus eventBus, final InternalCallContextFactory internalCallContextFactory,
@@ -110,6 +114,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
                                  final BlockingChecker checker, final NotificationQueueService notificationQueueService,
                                  final EventsStreamBuilder eventsStreamBuilder, final EntitlementUtils entitlementUtils,
                                  final EntitlementPluginExecution pluginExecution,
+                                 final CatalogInternalApi catalogInternalApi,
                                  final SecurityApi securityApi) {
         super(eventBus, null, pluginExecution, internalCallContextFactory, subscriptionInternalApi, accountApi, blockingStateDao, clock, checker, notificationQueueService, eventsStreamBuilder, entitlementUtils, securityApi);
         this.internalCallContextFactory = internalCallContextFactory;
@@ -123,6 +128,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
         this.entitlementUtils = entitlementUtils;
         this.pluginExecution = pluginExecution;
         this.securityApi = securityApi;
+        this.catalogInternalApi = catalogInternalApi;
         this.dateHelper = new EntitlementDateHelper();
     }
 
@@ -137,7 +143,8 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
                                                                                                                                     entitlementEffectiveDate,
                                                                                                                                     billingEffectiveDate,
                                                                                                                                     isMigrated);
-        final List<UUID> createdEntitlements = createBaseEntitlementsWithAddOns(accountId,
+        final List<UUID> createdEntitlements = createBaseEntitlementsWithAddOns(OperationType.CREATE_SUBSCRIPTION,
+                                                                                accountId,
                                                                                 ImmutableList.<BaseEntitlementWithAddOnsSpecifier>of(baseEntitlementWithAddOnsSpecifier),
                                                                                 renameCancelledBundleIfExist,
                                                                                 properties,
@@ -145,182 +152,41 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
         return createdEntitlements.get(0);
     }
 
-    private BaseEntitlementWithAddOnsSpecifier getFirstBaseEntitlementWithAddOnsSpecifier(final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers) throws SubscriptionBaseApiException {
-        if (baseEntitlementWithAddOnsSpecifiers == null) {
-            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
-        }
-
-        final Iterator<BaseEntitlementWithAddOnsSpecifier> iterator = baseEntitlementWithAddOnsSpecifiers.iterator();
-        if (!iterator.hasNext()) {
-            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
-        }
-
-        return iterator.next();
-    }
-
     @Override
     public List<UUID> createBaseEntitlementsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> originalBaseEntitlementWithAddOnsSpecifiers, final boolean renameCancelledBundleIfExist, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
-        logCreateEntitlementsWithAOs(log, originalBaseEntitlementWithAddOnsSpecifiers);
-
-        final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SHOPPING_CART_SUBSCRIPTIONS,
-                                                                               accountId,
-                                                                               null,
-                                                                               originalBaseEntitlementWithAddOnsSpecifiers,
-                                                                               null,
-                                                                               properties,
-                                                                               callContext);
-
-        final WithEntitlementPlugin<List<UUID>> createBaseEntitlementsWithAddOns = new WithEntitlementPlugin<List<UUID>>() {
-            @Override
-            public List<UUID> doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
-                final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
-
-                final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiersAfterPlugins = updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers();
-                final Collection<SubscriptionBaseWithAddOnsSpecifier> subscriptionBaseWithAddOnsSpecifiers = new LinkedList<SubscriptionBaseWithAddOnsSpecifier>();
-                DateTime upTo = null;
-                for (final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier : baseEntitlementWithAddOnsSpecifiersAfterPlugins) {
-                    // Entitlement
-                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(),
-                                                                                                       updatedPluginContext.getCreatedDate(),
-                                                                                                       contextWithValidAccountRecordId);
-                    upTo = upTo == null || upTo.compareTo(entitlementRequestedDate) < 0 ? entitlementRequestedDate : upTo;
-
-                    final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier = new SubscriptionBaseWithAddOnsSpecifier(baseEntitlementWithAddOnsSpecifier.getBundleId(),
-                                                                                                                                            baseEntitlementWithAddOnsSpecifier.getExternalKey(),
-                                                                                                                                            baseEntitlementWithAddOnsSpecifier.getEntitlementSpecifier(),
-                                                                                                                                            baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(),
-                                                                                                                                            baseEntitlementWithAddOnsSpecifier.isMigrated());
-                    subscriptionBaseWithAddOnsSpecifiers.add(subscriptionBaseWithAddOnsSpecifier);
-                }
-
-                try {
-                    // Verify if operation is allowed by looking for is_block_change on Account
-                    // Note that to fully check for block_change we should also look for BlockingState at the BUNDLE/SUBSCRIPTION level in case some of the input contain a BP that already exists.
-                    checkForAccountBlockingChange(accountId, upTo, contextWithValidAccountRecordId);
-
-                    final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(subscriptionBaseWithAddOnsSpecifiers,
-                                                                                                                            renameCancelledBundleIfExist,
-                                                                                                                            contextWithValidAccountRecordId);
-                    final List<UUID> createdSubscriptionIds = new LinkedList<UUID>();
-                    final Map<BlockingState, UUID> blockingStateMap = new HashMap<BlockingState, UUID>();
-                    int i = 0;
-                    for (final Iterator<BaseEntitlementWithAddOnsSpecifier> it = baseEntitlementWithAddOnsSpecifiersAfterPlugins.iterator(); it.hasNext(); i++) {
-                        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = it.next();
-                        for (final SubscriptionBase subscriptionBase : subscriptionsWithAddOns.get(i).getSubscriptionBaseList()) {
-                            final BlockingState blockingState = new DefaultBlockingState(subscriptionBase.getId(),
-                                                                                         BlockingStateType.SUBSCRIPTION,
-                                                                                         DefaultEntitlementApi.ENT_STATE_START,
-                                                                                         EntitlementService.ENTITLEMENT_SERVICE_NAME,
-                                                                                         false,
-                                                                                         false,
-                                                                                         false,
-                                                                                         dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), updatedPluginContext.getCreatedDate(), contextWithValidAccountRecordId));
-                            blockingStateMap.put(blockingState, subscriptionsWithAddOns.get(i).getBundle().getId());
-
-                            createdSubscriptionIds.add(subscriptionBase.getId());
-                        }
-                    }
-                    entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(blockingStateMap, contextWithValidAccountRecordId);
-                    return createdSubscriptionIds;
-                } catch (final SubscriptionBaseApiException e) {
-                    throw new EntitlementApiException(e);
-                }
-            }
-        };
-        return pluginExecution.executeWithPlugin(createBaseEntitlementsWithAddOns, pluginContext);
+        return createBaseEntitlementsWithAddOns(OperationType.CREATE_SHOPPING_CART_SUBSCRIPTIONS,
+                                                accountId,
+                                                originalBaseEntitlementWithAddOnsSpecifiers,
+                                                renameCancelledBundleIfExist,
+                                                properties,
+                                                callContext);
     }
 
     @Override
     public UUID addEntitlement(final UUID bundleId, final PlanPhaseSpecifier planPhaseSpecifier, final List<PlanPhasePriceOverride> overrides, @Nullable final LocalDate entitlementEffectiveDate, @Nullable final LocalDate billingEffectiveDate,
-            final boolean isMigrated, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
-        logCreateEntitlement(log, bundleId, planPhaseSpecifier, overrides, entitlementEffectiveDate, billingEffectiveDate);
-
+                               final boolean isMigrated, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
         final EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(planPhaseSpecifier, overrides);
-        final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
-        entitlementSpecifierList.add(entitlementSpecifier);
-        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
-                bundleId,
-                null,
-                entitlementSpecifierList,
-                entitlementEffectiveDate,
-                billingEffectiveDate,
-                isMigrated);
-        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
-        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
-
-        final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
-                                                                               null,
-                                                                               null,
-                                                                               baseEntitlementWithAddOnsSpecifierList,
-                                                                               null,
-                                                                               properties,
-                                                                               callContext);
-
-        final WithEntitlementPlugin<UUID> addEntitlementWithPlugin = new WithEntitlementPlugin<UUID>() {
-            @Override
-            public UUID doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
-                final InternalCallContext context = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, callContext);
-
-                final List<SubscriptionBase> subscriptionsByBundle;
-                try {
-                    subscriptionsByBundle = subscriptionBaseInternalApi.getSubscriptionsForBundle(bundleId, null, context);
-
-                    if (subscriptionsByBundle == null || subscriptionsByBundle.isEmpty()) {
-                        throw new EntitlementApiException(ErrorCode.SUB_NO_ACTIVE_SUBSCRIPTIONS, bundleId);
-                    }
-                } catch (final SubscriptionBaseApiException e) {
-                    throw new EntitlementApiException(e);
-                }
-                final boolean isStandalone = ProductCategory.STANDALONE.equals(subscriptionsByBundle.get(0).getCategory());
-
-                final EventsStream eventsStreamForBaseSubscription = isStandalone ? null : eventsStreamBuilder.buildForBaseSubscription(bundleId, callContext);
-
-                final DateTime entitlementRequestedDateRaw = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), updatedPluginContext.getCreatedDate(), context);
-                DateTime entitlementRequestedDate = entitlementRequestedDateRaw;
-                if (!isStandalone) {
-                    final DateTime baseEntitlementStartDate = eventsStreamForBaseSubscription.getEntitlementEffectiveStartDateTime();
-                    entitlementRequestedDate = entitlementRequestedDateRaw.isBefore(baseEntitlementStartDate) ? baseEntitlementStartDate : entitlementRequestedDateRaw;
-                    preCheckAddEntitlement(bundleId, entitlementRequestedDate, baseEntitlementWithAddOnsSpecifier, eventsStreamForBaseSubscription, callContext);
-                }
-
-                try {
-                    final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifierAfterPlugins = getFirstBaseEntitlementWithAddOnsSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
-                    final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier = new SubscriptionBaseWithAddOnsSpecifier(baseEntitlementWithAddOnsSpecifierAfterPlugins.getBundleId(),
-                                                                                                                                            baseEntitlementWithAddOnsSpecifierAfterPlugins.getExternalKey(),
-                                                                                                                                            baseEntitlementWithAddOnsSpecifierAfterPlugins.getEntitlementSpecifier(),
-                                                                                                                                            baseEntitlementWithAddOnsSpecifierAfterPlugins.getBillingEffectiveDate(),
-                                                                                                                                            baseEntitlementWithAddOnsSpecifierAfterPlugins.isMigrated());
-
-                    final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(ImmutableList.<SubscriptionBaseWithAddOnsSpecifier>of(subscriptionBaseWithAddOnsSpecifier),
-                                                                                                                                                   false,
-                                                                                                                                                   context);
-                    final SubscriptionBaseWithAddOns subscriptionBaseWithAddOns = subscriptionsWithAddOns.get(0);
-                    final UUID subscriptionId = subscriptionBaseWithAddOns.getSubscriptionBaseList().get(0).getId();
-
-                    final BlockingState newBlockingState = new DefaultBlockingState(subscriptionId, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
-                    entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(newBlockingState), subscriptionBaseWithAddOns.getBundle().getId(), context);
-
-                    return subscriptionId;
-                } catch (final SubscriptionBaseApiException e) {
-                    throw new EntitlementApiException(e);
-                }
-            }
-        };
-        return pluginExecution.executeWithPlugin(addEntitlementWithPlugin, pluginContext);
-    }
-
-    private void preCheckAddEntitlement(final UUID bundleId, final DateTime entitlementRequestedDate, final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier, final EventsStream eventsStreamForBaseSubscription, final CallContext callContext) throws EntitlementApiException {
-        if (eventsStreamForBaseSubscription.isEntitlementCancelled() ||
-            (eventsStreamForBaseSubscription.isEntitlementPending() &&
-             (baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate() == null ||
-              baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate().compareTo(eventsStreamForBaseSubscription.getEntitlementEffectiveStartDate()) < 0))) {
-            throw new EntitlementApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(bundleId,
+                                                                                                                                    null,
+                                                                                                                                    ImmutableList.<EntitlementSpecifier>of(entitlementSpecifier),
+                                                                                                                                    entitlementEffectiveDate,
+                                                                                                                                    billingEffectiveDate,
+                                                                                                                                    isMigrated);
+        final InternalCallContext context = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, callContext);
+        final UUID accountId;
+        try {
+            accountId = subscriptionBaseInternalApi.getAccountIdFromBundleId(bundleId, context);
+        } catch (final SubscriptionBaseApiException e) {
+            throw new EntitlementApiException(e);
         }
 
-        // Check the base entitlement state is not blocked
-        if (eventsStreamForBaseSubscription.isBlockChange(entitlementRequestedDate)) {
-            throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
-        }
+        final List<UUID> createdEntitlements = createBaseEntitlementsWithAddOns(OperationType.CREATE_SUBSCRIPTION,
+                                                                                accountId,
+                                                                                ImmutableList.<BaseEntitlementWithAddOnsSpecifier>of(baseEntitlementWithAddOnsSpecifier),
+                                                                                false,
+                                                                                properties,
+                                                                                callContext);
+        return createdEntitlements.get(0);
     }
 
     @Override
@@ -506,8 +372,185 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
         return pluginExecution.executeWithPlugin(transferWithPlugin, pluginContext);
     }
 
-    private void checkForAccountBlockingChange(final UUID accountId, @Nullable final DateTime upTo, final InternalCallContext context) throws EntitlementApiException {
+    private List<UUID> createBaseEntitlementsWithAddOns(final OperationType operationType,
+                                                        final UUID accountId,
+                                                        final Iterable<BaseEntitlementWithAddOnsSpecifier> originalBaseEntitlementWithAddOnsSpecifiers,
+                                                        final boolean renameCancelledBundleIfExist,
+                                                        final Iterable<PluginProperty> properties,
+                                                        final CallContext callContext) throws EntitlementApiException {
+        logCreateEntitlementsWithAOs(log, originalBaseEntitlementWithAddOnsSpecifiers);
+
+        final EntitlementContext pluginContext = new DefaultEntitlementContext(operationType,
+                                                                               accountId,
+                                                                               null,
+                                                                               originalBaseEntitlementWithAddOnsSpecifiers,
+                                                                               null,
+                                                                               properties,
+                                                                               callContext);
 
+        final WithEntitlementPlugin<List<UUID>> createBaseEntitlementsWithAddOns = new WithEntitlementPlugin<List<UUID>>() {
+            @Override
+            public List<UUID> doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+                final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
+
+                final Catalog catalog;
+                try {
+                    catalog = catalogInternalApi.getFullCatalog(true, true, contextWithValidAccountRecordId);
+                } catch (final CatalogApiException e) {
+                    throw new EntitlementApiException(e);
+                }
+
+                final Map<UUID, Optional<EventsStream>> eventsStreamForBaseSubscriptionPerBundle = new HashMap<UUID, Optional<EventsStream>>();
+                final Map<String, Optional<UUID>> bundleKeyToIdMapping = new HashMap<String, Optional<UUID>>();
+                final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiersAfterPlugins = updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers();
+                final Collection<SubscriptionBaseWithAddOnsSpecifier> subscriptionBaseWithAddOnsSpecifiers = new LinkedList<SubscriptionBaseWithAddOnsSpecifier>();
+                DateTime upTo = null;
+                for (final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier : baseEntitlementWithAddOnsSpecifiersAfterPlugins) {
+                    // Entitlement
+                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(),
+                                                                                                       updatedPluginContext.getCreatedDate(),
+                                                                                                       contextWithValidAccountRecordId);
+                    upTo = upTo == null || upTo.compareTo(entitlementRequestedDate) < 0 ? entitlementRequestedDate : upTo;
+
+                    final UUID bundleId = populateCaches(baseEntitlementWithAddOnsSpecifier,
+                                                         eventsStreamForBaseSubscriptionPerBundle,
+                                                         bundleKeyToIdMapping,
+                                                         catalog,
+                                                         callContext,
+                                                         contextWithValidAccountRecordId);
+                    if (bundleId != null) {
+                        final Optional<EventsStream> eventsStreamForBaseSubscription = eventsStreamForBaseSubscriptionPerBundle.get(bundleId);
+                        if (eventsStreamForBaseSubscription.isPresent()) {
+                            // Verify if the operation is valid for that bundle
+                            preCheckAddEntitlement(bundleId, entitlementRequestedDate, baseEntitlementWithAddOnsSpecifier, eventsStreamForBaseSubscription.get());
+                        }
+                    }
+
+                    final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier = new SubscriptionBaseWithAddOnsSpecifier(baseEntitlementWithAddOnsSpecifier.getBundleId(),
+                                                                                                                                            baseEntitlementWithAddOnsSpecifier.getExternalKey(),
+                                                                                                                                            baseEntitlementWithAddOnsSpecifier.getEntitlementSpecifier(),
+                                                                                                                                            baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(),
+                                                                                                                                            baseEntitlementWithAddOnsSpecifier.isMigrated());
+                    subscriptionBaseWithAddOnsSpecifiers.add(subscriptionBaseWithAddOnsSpecifier);
+                }
+
+                // Verify if operation is allowed by looking for is_block_change on Account
+                // Note that to fully check for block_change we should also look for BlockingState at the BUNDLE/SUBSCRIPTION level in case some of the input contain a BP that already exists.
+                checkForAccountBlockingChange(accountId, upTo, contextWithValidAccountRecordId);
+
+                final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns;
+                try {
+                    subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(subscriptionBaseWithAddOnsSpecifiers,
+                                                                                                            renameCancelledBundleIfExist,
+                                                                                                            contextWithValidAccountRecordId);
+                } catch (final SubscriptionBaseApiException e) {
+                    throw new EntitlementApiException(e);
+                }
+
+                return createEntitlementEvents(baseEntitlementWithAddOnsSpecifiersAfterPlugins, subscriptionsWithAddOns, updatedPluginContext, contextWithValidAccountRecordId);
+            }
+        };
+        return pluginExecution.executeWithPlugin(createBaseEntitlementsWithAddOns, pluginContext);
+    }
+
+    private BaseEntitlementWithAddOnsSpecifier getFirstBaseEntitlementWithAddOnsSpecifier(final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers) throws SubscriptionBaseApiException {
+        if (baseEntitlementWithAddOnsSpecifiers == null) {
+            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+        }
+
+        final Iterator<BaseEntitlementWithAddOnsSpecifier> iterator = baseEntitlementWithAddOnsSpecifiers.iterator();
+        if (!iterator.hasNext()) {
+            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+        }
+
+        return iterator.next();
+    }
+
+    private UUID populateCaches(final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier,
+                                final Map<UUID, Optional<EventsStream>> eventsStreamForBaseSubscriptionPerBundle,
+                                final Map<String, Optional<UUID>> bundleKeyToIdMapping,
+                                final Catalog catalog,
+                                final TenantContext callContext,
+                                final InternalCallContext contextWithValidAccountRecordId) throws EntitlementApiException {
+        // In the addEntitlement codepath, bundleId is always set. But, technically, an existing bundle could be specified by externalKey in
+        // the createBaseEntitlementsWithAddOns codepath. In that case, we should also check if that bundle is blocked.
+        UUID bundleId = baseEntitlementWithAddOnsSpecifier.getBundleId();
+        if (bundleId == null && baseEntitlementWithAddOnsSpecifier.getExternalKey() != null) {
+            populateBundleKeyToIdMappingCache(baseEntitlementWithAddOnsSpecifier, bundleKeyToIdMapping, catalog, contextWithValidAccountRecordId);
+
+            final Optional<UUID> bundleIdForKey = bundleKeyToIdMapping.get(baseEntitlementWithAddOnsSpecifier.getExternalKey());
+            if (bundleIdForKey.isPresent()) {
+                bundleId = bundleIdForKey.get();
+            }
+        }
+
+        if (bundleId == null) {
+            return null;
+        }
+
+        populateEventsStreamForBaseSubscriptionPerBundleCache(bundleId, eventsStreamForBaseSubscriptionPerBundle, callContext, contextWithValidAccountRecordId);
+
+        return bundleId;
+    }
+
+    private void populateBundleKeyToIdMappingCache(final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier, final Map<String, Optional<UUID>> bundleKeyToIdMapping, final Catalog catalog, final InternalCallContext contextWithValidAccountRecordId) throws EntitlementApiException {
+        if (bundleKeyToIdMapping.get(baseEntitlementWithAddOnsSpecifier.getExternalKey()) == null) {
+            final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getActiveBundleForKey(baseEntitlementWithAddOnsSpecifier.getExternalKey(), catalog, contextWithValidAccountRecordId);
+            if (bundle != null) {
+                bundleKeyToIdMapping.put(baseEntitlementWithAddOnsSpecifier.getExternalKey(), Optional.<UUID>of(bundle.getId()));
+            } else {
+                bundleKeyToIdMapping.put(baseEntitlementWithAddOnsSpecifier.getExternalKey(), Optional.<UUID>absent());
+            }
+        }
+    }
+
+    private void populateEventsStreamForBaseSubscriptionPerBundleCache(final UUID bundleId, final Map<UUID, Optional<EventsStream>> eventsStreamForBaseSubscriptionPerBundle, final TenantContext callContext, final InternalCallContext contextWithValidAccountRecordId) throws EntitlementApiException {
+        if (eventsStreamForBaseSubscriptionPerBundle.get(bundleId) == null) {
+            final List<SubscriptionBase> subscriptionsByBundle;
+            try {
+                subscriptionsByBundle = subscriptionBaseInternalApi.getSubscriptionsForBundle(bundleId, null, contextWithValidAccountRecordId);
+
+                if (subscriptionsByBundle == null || subscriptionsByBundle.isEmpty()) {
+                    throw new EntitlementApiException(ErrorCode.SUB_NO_ACTIVE_SUBSCRIPTIONS, bundleId);
+                }
+            } catch (final SubscriptionBaseApiException e) {
+                throw new EntitlementApiException(e);
+            }
+
+            final boolean isStandalone = Iterables.any(subscriptionsByBundle,
+                                                       new Predicate<SubscriptionBase>() {
+                                                           @Override
+                                                           public boolean apply(final SubscriptionBase input) {
+                                                               return ProductCategory.STANDALONE.equals(input.getCategory());
+                                                           }
+                                                       });
+
+            if (!isStandalone) {
+                final EventsStream eventsStreamForBaseSubscription = eventsStreamBuilder.buildForBaseSubscription(bundleId, callContext);
+                eventsStreamForBaseSubscriptionPerBundle.put(bundleId, Optional.<EventsStream>of(eventsStreamForBaseSubscription));
+            } else {
+                eventsStreamForBaseSubscriptionPerBundle.put(bundleId, Optional.<EventsStream>absent());
+            }
+        }
+    }
+
+    private void preCheckAddEntitlement(final UUID bundleId, final DateTime entitlementRequestedDate, final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier, final EventsStream eventsStreamForBaseSubscription) throws EntitlementApiException {
+        if (eventsStreamForBaseSubscription.isEntitlementCancelled() ||
+            (eventsStreamForBaseSubscription.isEntitlementPending() &&
+             (baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate() == null ||
+              baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate().compareTo(eventsStreamForBaseSubscription.getEntitlementEffectiveStartDate()) < 0))) {
+            throw new EntitlementApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
+        }
+
+        // Check the base entitlement state is not blocked
+        if (eventsStreamForBaseSubscription.isBlockChange(entitlementRequestedDate)) {
+            throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
+        } else if (eventsStreamForBaseSubscription.isBlockEntitlement(entitlementRequestedDate)) {
+            throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_ENTITLEMENT, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
+        }
+    }
+
+    private void checkForAccountBlockingChange(final UUID accountId, @Nullable final DateTime upTo, final InternalCallContext context) throws EntitlementApiException {
         try {
             final BlockingAggregator blockingAggregator = checker.getBlockedStatus(accountId, BlockingStateType.ACCOUNT, upTo, context);
             if (blockingAggregator.isBlockChange()) {
@@ -517,4 +560,31 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
             throw new EntitlementApiException(e);
         }
     }
+
+    private List<UUID> createEntitlementEvents(final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiersAfterPlugins,
+                                               final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns,
+                                               final CallContext updatedPluginContext,
+                                               final InternalCallContext contextWithValidAccountRecordId) throws EntitlementApiException {
+        final List<UUID> createdSubscriptionIds = new LinkedList<UUID>();
+        final Map<BlockingState, UUID> blockingStateMap = new HashMap<BlockingState, UUID>();
+        int i = 0;
+        for (final Iterator<BaseEntitlementWithAddOnsSpecifier> it = baseEntitlementWithAddOnsSpecifiersAfterPlugins.iterator(); it.hasNext(); i++) {
+            final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = it.next();
+            for (final SubscriptionBase subscriptionBase : subscriptionsWithAddOns.get(i).getSubscriptionBaseList()) {
+                final BlockingState blockingState = new DefaultBlockingState(subscriptionBase.getId(),
+                                                                             BlockingStateType.SUBSCRIPTION,
+                                                                             DefaultEntitlementApi.ENT_STATE_START,
+                                                                             EntitlementService.ENTITLEMENT_SERVICE_NAME,
+                                                                             false,
+                                                                             false,
+                                                                             false,
+                                                                             dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), updatedPluginContext.getCreatedDate(), contextWithValidAccountRecordId));
+                blockingStateMap.put(blockingState, subscriptionsWithAddOns.get(i).getBundle().getId());
+
+                createdSubscriptionIds.add(subscriptionBase.getId());
+            }
+        }
+        entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(blockingStateMap, contextWithValidAccountRecordId);
+        return createdSubscriptionIds;
+    }
 }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java
index dbf7876..0fc92b0 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java
@@ -42,4 +42,16 @@ public class DefaultEntitlementSpecifier implements EntitlementSpecifier {
         return overrides;
     }
 
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("DefaultEntitlementSpecifier{");
+        sb.append("planName=").append(planPhaseSpecifier.getPlanName());
+        sb.append(", productName=").append(planPhaseSpecifier.getProductName());
+        sb.append(", billingPeriod=").append(planPhaseSpecifier.getBillingPeriod());
+        sb.append(", phaseType=").append(planPhaseSpecifier.getPhaseType());
+        sb.append(", priceListName=").append(planPhaseSpecifier.getPriceListName());
+        sb.append(", overrides=").append(overrides);
+        sb.append('}');
+        return sb.toString();
+    }
 }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
index 344a645..abc01c9 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -25,12 +25,13 @@ import java.util.UUID;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.util.audit.ChangeType;
 import org.killbill.billing.util.entity.dao.Audited;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
 import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
 import org.skife.jdbi.v2.sqlobject.Bind;
-import org.killbill.commons.jdbi.binder.SmartBindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 
@@ -45,10 +46,15 @@ public interface BlockingStateSqlDao extends EntitySqlDao<BlockingStateModelDao,
 
     @SqlQuery
     public abstract List<BlockingStateModelDao> getBlockingState(@Bind("blockableId") UUID blockableId,
+                                                                 @Bind("type") BlockingStateType blockingStateType,
                                                                  @Bind("effectiveDate") Date effectiveDate,
                                                                  @SmartBindBean final InternalTenantContext context);
 
     @SqlQuery
+    public abstract List<BlockingStateModelDao> getBlockingAllUpToForAccount(@Bind("effectiveDate") Date effectiveDate,
+                                                                             @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
     public abstract List<BlockingStateModelDao> getBlockingHistoryForService(@Bind("blockableId") UUID blockableId,
                                                                              @Bind("service") String serviceName,
                                                                              @SmartBindBean final InternalTenantContext context);
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 68660ff..0e7aada 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
@@ -157,9 +157,20 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
 
     private List<BlockingState> getBlockingState(final BlockingStateSqlDao sqlDao, final UUID blockableId, final BlockingStateType blockingStateType, final DateTime upToDate, final InternalTenantContext context) {
         final Date upTo = upToDate.toDate();
-        final List<BlockingStateModelDao> models = sqlDao.getBlockingState(blockableId, upTo, context);
-        final Collection<BlockingStateModelDao> modelsFiltered = filterBlockingStates(models, blockingStateType);
-        return new ArrayList<BlockingState>(Collections2.transform(modelsFiltered,
+        final List<BlockingStateModelDao> models = sqlDao.getBlockingState(blockableId, blockingStateType, upTo, context);
+        return new ArrayList<BlockingState>(Collections2.transform(models,
+                                                                   new Function<BlockingStateModelDao, BlockingState>() {
+                                                                       @Override
+                                                                       public BlockingState apply(@Nullable final BlockingStateModelDao src) {
+                                                                           return BlockingStateModelDao.toBlockingState(src);
+                                                                       }
+                                                                   }));
+    }
+
+    private List<BlockingState> getBlockingAllUpToForAccountRecordId(final BlockingStateSqlDao sqlDao, final DateTime upToDate, final InternalTenantContext context) {
+        final Date upTo = upToDate.toDate();
+        final List<BlockingStateModelDao> models = sqlDao.getBlockingAllUpToForAccount(upTo, context);
+        return new ArrayList<BlockingState>(Collections2.transform(models,
                                                                    new Function<BlockingStateModelDao, BlockingState>() {
                                                                        @Override
                                                                        public BlockingState apply(@Nullable final BlockingStateModelDao src) {
@@ -265,13 +276,15 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
         final List<BlockingState> subscriptionBlockingStates;
         if (type == BlockingStateType.SUBSCRIPTION) {
             final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, objectIdCacheController, handle);
-            accountBlockingStates = getBlockingState(sqlDao, accountId, BlockingStateType.ACCOUNT, upToDate, context);
-            bundleBlockingStates = getBlockingState(sqlDao, bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
-            subscriptionBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.SUBSCRIPTION, upToDate, context);
+            final List<BlockingState> allBlockingStatesForAccount = getBlockingAllUpToForAccountRecordId(sqlDao, upToDate, context);
+            accountBlockingStates = filterBlockingStates(allBlockingStatesForAccount, accountId, BlockingStateType.ACCOUNT);
+            bundleBlockingStates = filterBlockingStates(allBlockingStatesForAccount, bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE);
+            subscriptionBlockingStates = filterBlockingStates(allBlockingStatesForAccount, blockableId, BlockingStateType.SUBSCRIPTION);
         } else if (type == BlockingStateType.SUBSCRIPTION_BUNDLE) {
             final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, objectIdCacheController, handle);
-            accountBlockingStates = getBlockingState(sqlDao, accountId, BlockingStateType.ACCOUNT, upToDate, context);
-            bundleBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
+            final List<BlockingState> allBlockingStatesForAccount = getBlockingAllUpToForAccountRecordId(sqlDao, upToDate, context);
+            accountBlockingStates = filterBlockingStates(allBlockingStatesForAccount, accountId, BlockingStateType.ACCOUNT);
+            bundleBlockingStates = filterBlockingStates(allBlockingStatesForAccount, blockableId, BlockingStateType.SUBSCRIPTION_BUNDLE);
             subscriptionBlockingStates = ImmutableList.<BlockingState>of();
         } else { // BlockingStateType.ACCOUNT {
             accountBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.ACCOUNT, upToDate, context);
@@ -373,13 +386,13 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
         });
     }
 
-    private Collection<BlockingStateModelDao> filterBlockingStates(final Collection<BlockingStateModelDao> models, final BlockingStateType blockingStateType) {
-        return Collections2.<BlockingStateModelDao>filter(models,
-                                                          new Predicate<BlockingStateModelDao>() {
-                                                              @Override
-                                                              public boolean apply(final BlockingStateModelDao input) {
-                                                                  return input.getType().equals(blockingStateType);
-                                                              }
-                                                          });
+    private List<BlockingState> filterBlockingStates(final Collection<BlockingState> models, final UUID objectId, final BlockingStateType blockingStateType) {
+        return ImmutableList.<BlockingState>copyOf(Collections2.<BlockingState>filter(models,
+                                                                                      new Predicate<BlockingState>() {
+                                                                                          @Override
+                                                                                          public boolean apply(final BlockingState input) {
+                                                                                              return input.getBlockedId().equals(objectId) && input.getType().equals(blockingStateType);
+                                                                                          }
+                                                                                      }));
     }
 }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index 5ff2339..9c5a89b 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -226,6 +226,13 @@ public class DefaultEventsStream implements EventsStream {
     }
 
     @Override
+    public boolean isBlockEntitlement(final DateTime effectiveDate) {
+        Preconditions.checkState(effectiveDate != null);
+        final BlockingAggregator aggregator = getBlockingAggregator(effectiveDate);
+        return aggregator.isBlockEntitlement();
+    }
+
+    @Override
     public int getDefaultBillCycleDayLocal() {
         return defaultBillCycleDayLocal;
     }
diff --git a/entitlement/src/main/resources/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.sql.stg b/entitlement/src/main/resources/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.sql.stg
index 9d78819..1e6d9dd 100644
--- a/entitlement/src/main/resources/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.sql.stg
+++ b/entitlement/src/main/resources/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.sql.stg
@@ -59,23 +59,48 @@ limit 1
 >>
 
 getBlockingState() ::= <<
- select
- <allTableFields("t.")>
- from
- <tableName()> t
- join (
-   select max(record_id) record_id
-         , service
-         from blocking_states
-         where blockable_id = :blockableId
-         and effective_date \<= :effectiveDate
-         and is_active
-         <AND_CHECK_TENANT("")>
-         group by service
- ) tmp
- on t.record_id = tmp.record_id
- <defaultOrderBy("t.")>
-  ;
+select
+<allTableFields("t.")>
+from
+<tableName()> t
+join (
+  select max(record_id) record_id
+        , service
+        from blocking_states
+        where blockable_id = :blockableId
+        and type = :type
+        and effective_date \<= :effectiveDate
+        <andCheckSoftDeletionWithComma("")>
+        and <accountRecordIdField("")> = :accountRecordId
+        <AND_CHECK_TENANT("")>
+        group by service
+) tmp
+on t.record_id = tmp.record_id
+where t.type = :type
+and <accountRecordIdField("t.")> = :accountRecordId
+<defaultOrderBy("t.")>
+;
+ >>
+
+getBlockingAllUpToForAccount() ::= <<
+select
+<allTableFields("t.")>
+from
+<tableName()> t
+join (
+  select max(record_id) record_id
+        , service
+        from blocking_states
+        where effective_date \<= :effectiveDate
+        <andCheckSoftDeletionWithComma("")>
+        and <accountRecordIdField("")> = :accountRecordId
+        <AND_CHECK_TENANT("")>
+        group by service
+) tmp
+on t.record_id = tmp.record_id
+where <accountRecordIdField("t.")> = :accountRecordId
+<defaultOrderBy("t.")>
+;
  >>
 
 getBlockingHistoryForService() ::= <<
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java
index df95234..cd116fc 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java
@@ -69,17 +69,29 @@ public abstract class EntitlementTestSuiteNoDB extends GuicyKillbillTestSuiteNoD
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestEntitlementModuleNoDB(configSource));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         bus.start();
     }
 
     @AfterMethod(groups = "fast")
     public void afterMethod() {
+        if (hasFailed()) {
+            return;
+        }
+
         bus.stop();
     }
 }
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
index 27c7b67..b8f8c94 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
@@ -130,12 +130,20 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(Stage.PRODUCTION, new TestEntitlementModuleWithEmbeddedDB(configSource));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
         startTestFamework(testListener, clock, busService, subscriptionBaseService, entitlementService);
         this.catalog = initCatalog(catalogService);
@@ -171,6 +179,10 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
 
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         securityApi.logout();
 
         stopTestFramework(testListener, busService, subscriptionBaseService, entitlementService);

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

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 111e16c..3160299 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>
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 fc0dbb9..5550652 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
@@ -329,8 +329,10 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
                                                                                      accountId,
                                                                                      charge.getBundleId(),
                                                                                      charge.getSubscriptionId(),
+                                                                                     charge.getProductName(),
                                                                                      charge.getPlanName(),
                                                                                      charge.getPhaseName(),
+                                                                                     charge.getPrettyProductName(),
                                                                                      charge.getPrettyPlanName(),
                                                                                      charge.getPrettyPhaseName(),
                                                                                      charge.getDescription(),
@@ -527,6 +529,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
                                                input.getBundleId(),
                                                input.getSubscriptionId(),
                                                input.getDescription(),
+                                               input.getProductName(),
                                                input.getPlanName(),
                                                input.getPhaseName(),
                                                input.getUsageName(),
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 e8acdde..5c26d11 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
@@ -916,7 +916,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 // First, adjust the same invoice with the CBA amount to "delete"
                 final InvoiceItemModelDao cbaAdjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.CBA_ADJ, invoice.getId(), invoice.getAccountId(),
-                                                                               null, null, null, null, null, null, context.getCreatedDate().toLocalDate(),
+                                                                               null, null, null, null, null, null, null, context.getCreatedDate().toLocalDate(),
                                                                                null, cbaItem.getAmount().negate(), null, cbaItem.getCurrency(), cbaItem.getId());
                 createInvoiceItemFromTransaction(invoiceItemSqlDao, cbaAdjItem, context);
 
@@ -972,7 +972,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                         // Add the adjustment on that invoice
                         final InvoiceItemModelDao nextCBAAdjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.CBA_ADJ, invoiceFollowing.getId(),
-                                                                                           invoice.getAccountId(), null, null, null, null, null, null,
+                                                                                           invoice.getAccountId(), null, null, null, null, null, null, null,
                                                                                            context.getCreatedDate().toLocalDate(), null,
                                                                                            positiveCBAAdjItemAmount, null, cbaItem.getCurrency(), cbaItem.getId());
                         createInvoiceItemFromTransaction(invoiceItemSqlDao, nextCBAAdjItem, context);
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 0fd4823..b89497e 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
@@ -219,7 +219,7 @@ public class InvoiceDaoHelper {
         // Finally, create the adjustment
         // Note! The amount is negated here!
         return new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.ITEM_ADJ, invoiceItemToBeAdjusted.getInvoiceId(), invoiceItemToBeAdjusted.getAccountId(),
-                                       null, null, null, null, null, null, effectiveDate, effectiveDate, amountToAdjust.negate(), null, currencyForAdjustment, invoiceItemToBeAdjusted.getId());
+                                       null, null, null, null, null, null, null, effectiveDate, effectiveDate, amountToAdjust.negate(), null, currencyForAdjustment, invoiceItemToBeAdjusted.getId());
     }
 
     public void populateChildren(final InvoiceModelDao invoice, final List<Tag> invoicesTags, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
index 8104474..c036a76 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
@@ -38,6 +38,7 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
     private UUID bundleId;
     private UUID subscriptionId;
     private String description;
+    private String productName;
     private String planName;
     private String phaseName;
     private String usageName;
@@ -53,9 +54,12 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
     public InvoiceItemModelDao() { /* For the DAO mapper */ }
 
     public InvoiceItemModelDao(final UUID id, final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId, final UUID accountId,
-                               final UUID childAccountId, final UUID bundleId, final UUID subscriptionId, final String description, final String planName,
-                               final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
-                               final BigDecimal rate, final Currency currency, final UUID linkedItemId, final Integer quantity, final String itemDetails) {
+                               final UUID childAccountId, final UUID bundleId, final UUID subscriptionId, final String description, final String productName,
+                               final String planName, final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate,
+                               final BigDecimal amount, final BigDecimal rate, final Currency currency, final UUID linkedItemId, final Integer quantity,
+                               final String itemDetails) {
+
+
         super(id, createdDate, createdDate);
         this.type = type;
         this.invoiceId = invoiceId;
@@ -64,6 +68,7 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
         this.bundleId = bundleId;
         this.subscriptionId = subscriptionId;
         this.description = description;
+        this.productName = productName;
         this.planName = planName;
         this.phaseName = phaseName;
         this.usageName = usageName;
@@ -78,44 +83,36 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
     }
 
     public InvoiceItemModelDao(final UUID id, final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId, final UUID accountId,
-                               final UUID childAccountId, final UUID bundleId, final UUID subscriptionId, final String description, final String planName,
+                               final UUID childAccountId, final UUID bundleId, final UUID subscriptionId, final String description, final String productName, final String planName,
                                final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
                                final BigDecimal rate, final Currency currency, final UUID linkedItemId) {
-        this(id, createdDate, type, invoiceId, accountId, childAccountId, bundleId, subscriptionId, description, planName, phaseName, usageName,
+        this(id, createdDate, type, invoiceId, accountId, childAccountId, bundleId, subscriptionId, description, productName, planName, phaseName, usageName,
              startDate, endDate, amount, rate, currency, linkedItemId, null, null);
 
     }
 
     public InvoiceItemModelDao(final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId, final UUID accountId,
-                               final UUID bundleId, final UUID subscriptionId, final String description, final String planName,
+                               final UUID bundleId, final UUID subscriptionId, final String description, final String productName, final String planName,
                                final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
                                final BigDecimal rate, final Currency currency, final UUID linkedItemId, final Integer quantity, final String itemDetails) {
-        this(UUIDs.randomUUID(), createdDate, type, invoiceId, accountId, null, bundleId, subscriptionId, description, planName, phaseName, usageName,
+        this(UUIDs.randomUUID(), createdDate, type, invoiceId, accountId, null, bundleId, subscriptionId, description, productName, planName, phaseName, usageName,
              startDate, endDate, amount, rate, currency, linkedItemId, quantity, itemDetails);
     }
 
     public InvoiceItemModelDao(final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId, final UUID accountId,
-                               final UUID bundleId, final UUID subscriptionId, final String description, final String planName,
+                               final UUID bundleId, final UUID subscriptionId, final String description, final String productName, final String planName,
                                final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
                                final BigDecimal rate, final Currency currency, final UUID linkedItemId) {
-        this(UUIDs.randomUUID(), createdDate, type, invoiceId, accountId, null, bundleId, subscriptionId, description, planName, phaseName, usageName,
+        this(UUIDs.randomUUID(), createdDate, type, invoiceId, accountId, null, bundleId, subscriptionId, description, productName, planName, phaseName, usageName,
              startDate, endDate, amount, rate, currency, linkedItemId, null, null);
     }
 
     public InvoiceItemModelDao(final InvoiceItem invoiceItem) {
         this(invoiceItem.getId(), invoiceItem.getCreatedDate(), invoiceItem.getInvoiceItemType(), invoiceItem.getInvoiceId(), invoiceItem.getAccountId(), invoiceItem.getChildAccountId(), invoiceItem.getBundleId(),
-             invoiceItem.getSubscriptionId(), invoiceItem.getDescription(), invoiceItem.getPlanName(), invoiceItem.getPhaseName(), invoiceItem.getUsageName(), invoiceItem.getStartDate(), invoiceItem.getEndDate(),
+             invoiceItem.getSubscriptionId(), invoiceItem.getDescription(), invoiceItem.getProductName(), invoiceItem.getPlanName(), invoiceItem.getPhaseName(), invoiceItem.getUsageName(), invoiceItem.getStartDate(), invoiceItem.getEndDate(),
              invoiceItem.getAmount(), invoiceItem.getRate(), invoiceItem.getCurrency(), invoiceItem.getLinkedItemId(), invoiceItem.getQuantity(), invoiceItem.getItemDetails());
     }
 
-    /*
-    public InvoiceItemModelDao(final InvoiceItem invoiceItem, final UUID invoiceId) {
-        this(invoiceItem.getId(), invoiceItem.getCreatedDate(), invoiceItem.getInvoiceItemType(), invoiceId, invoiceItem.getAccountId(), invoiceItem.getBundleId(),
-             invoiceItem.getSubscriptionId(), invoiceItem.getDescription(), invoiceItem.getPlanName(), invoiceItem.getPhaseName(), invoiceItem.getUsageName(),
-             invoiceItem.getStartDate(), invoiceItem.getEndDate(),
-             invoiceItem.getAmount(), invoiceItem.getRate(), invoiceItem.getCurrency(), invoiceItem.getLinkedItemId());
-    }
-*/
     public InvoiceItemType getType() {
         return type;
     }
@@ -172,6 +169,14 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
         this.description = description;
     }
 
+    public String getProductName() {
+        return productName;
+    }
+
+    public void setProductName(final String productName) {
+        this.productName = productName;
+    }
+
     public String getPlanName() {
         return planName;
     }
@@ -270,6 +275,7 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
         sb.append(", bundleId=").append(bundleId);
         sb.append(", subscriptionId=").append(subscriptionId);
         sb.append(", description='").append(description).append('\'');
+        sb.append(", productName='").append(productName).append('\'');
         sb.append(", planName='").append(planName).append('\'');
         sb.append(", phaseName='").append(phaseName).append('\'');
         sb.append(", usageName='").append(usageName).append('\'');
@@ -332,6 +338,9 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
         if (planName != null ? !planName.equals(that.planName) : that.planName != null) {
             return false;
         }
+        if (productName != null ? !productName.equals(that.productName) : that.productName != null) {
+            return false;
+        }
         if (usageName != null ? !usageName.equals(that.usageName) : that.usageName != null) {
             return false;
         }
@@ -367,6 +376,7 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
         result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
         result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
         result = 31 * result + (description != null ? description.hashCode() : 0);
+        result = 31 * result + (productName != null ? productName.hashCode() : 0);
         result = 31 * result + (planName != null ? planName.hashCode() : 0);
         result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
         result = 31 * result + (usageName != null ? usageName.hashCode() : 0);
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 0fd86a0..afc6032 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
@@ -237,6 +237,7 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator 
                                                                                                 accountId,
                                                                                                 thisEvent.getSubscription().getBundleId(),
                                                                                                 thisEvent.getSubscription().getId(),
+                                                                                                thisEvent.getPlan().getProduct().getName(),
                                                                                                 thisEvent.getPlan().getName(),
                                                                                                 thisEvent.getPlanPhase().getName(),
                                                                                                 itemDatum.getStartDate(),
@@ -398,7 +399,7 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator 
             if (fixedPrice != null) {
                 final FixedPriceInvoiceItem fixedPriceInvoiceItem = new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(),
                                                                                               thisEvent.getSubscription().getId(),
-                                                                                              thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
+                                                                                              thisEvent.getPlan().getProduct().getName(), thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
                                                                                               roundedStartDate, fixedPrice, currency);
 
                 // For debugging purposes
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 c46b094..78aca9c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
@@ -229,9 +229,11 @@ public class InvoicePluginDispatcher {
                                           immutableField("bundleId", existingItem, existingItem != null ? existingItem.getBundleId() : null, additionalInvoiceItem.getBundleId(), invoicePlugin),
                                           immutableField("subscriptionId", existingItem, existingItem != null ? existingItem.getSubscriptionId() : null, additionalInvoiceItem.getSubscriptionId(), invoicePlugin),
                                           mutableField("description", existingItem != null ? existingItem.getDescription() : null, additionalInvoiceItem.getDescription(), invoicePlugin),
+                                          immutableField("productName", existingItem, existingItem != null ? existingItem.getProductName() : null, additionalInvoiceItem.getProductName(), invoicePlugin),
                                           immutableField("planName", existingItem, existingItem != null ? existingItem.getPlanName() : null, additionalInvoiceItem.getPlanName(), invoicePlugin),
                                           immutableField("phaseName", existingItem, existingItem != null ? existingItem.getPhaseName() : null, additionalInvoiceItem.getPhaseName(), invoicePlugin),
                                           immutableField("usageName", existingItem, existingItem != null ? existingItem.getUsageName() : null, additionalInvoiceItem.getUsageName(), invoicePlugin),
+                                          mutableField("prettyProductName", existingItem != null ? existingItem.getPrettyProductName() : null, additionalInvoiceItem.getPrettyProductName(), invoicePlugin),
                                           mutableField("prettyPlanName", existingItem != null ? existingItem.getPrettyPlanName() : null, additionalInvoiceItem.getPrettyPlanName(), invoicePlugin),
                                           mutableField("prettyPhaseName", existingItem != null ? existingItem.getPrettyPhaseName() : null, additionalInvoiceItem.getPrettyPhaseName(), invoicePlugin),
                                           mutableField("prettyUsageName", existingItem != null ? existingItem.getPrettyUsageName() : null, additionalInvoiceItem.getPrettyUsageName(), invoicePlugin),
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
index 78ff132..ec6312f 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -43,13 +43,13 @@ public class ExternalChargeInvoiceItem extends InvoiceItemCatalogBase {
 
     public ExternalChargeInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
                                      @Nullable final String description, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, @Nullable final String itemDetails) {
-        super(id, createdDate, invoiceId, accountId, bundleId, null, description, null, null, null, startDate, endDate, amount, null, currency, null, null, itemDetails, InvoiceItemType.EXTERNAL_CHARGE);
+        super(id, createdDate, invoiceId, accountId, bundleId, null, description, null, null, null, null, startDate, endDate, amount, null, currency, null, null, itemDetails, InvoiceItemType.EXTERNAL_CHARGE);
     }
 
     public ExternalChargeInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
-                                     final String planName, final String phaseName, final String prettyPlanName, final String prettyPhaseName, @Nullable final String description, final LocalDate startDate, final LocalDate endDate,
+                                     final String productName, final String planName, final String phaseName, final String prettyProductName, final String prettyPlanName, final String prettyPhaseName, @Nullable final String description, final LocalDate startDate, final LocalDate endDate,
                                      final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId, @Nullable String itemDetails) {
-        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, null, prettyPlanName, prettyPhaseName, null, startDate, endDate, amount, rate, currency, linkedItemId, null, itemDetails, InvoiceItemType.EXTERNAL_CHARGE);
+        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, productName, planName, phaseName, null, prettyProductName, prettyPlanName, prettyPhaseName, null, startDate, endDate, amount, rate, currency, linkedItemId, null, itemDetails, InvoiceItemType.EXTERNAL_CHARGE);
     }
 
     @Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/FixedPriceInvoiceItem.java
index 9f9d90e..f301a99 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -32,21 +32,23 @@ import org.killbill.billing.util.UUIDs;
 public class FixedPriceInvoiceItem extends InvoiceItemCatalogBase {
 
     public FixedPriceInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId,
-                                 final String planName, final String phaseName,
+                                 final String productName, final String planName, final String phaseName,
                                  final LocalDate date, final BigDecimal amount, final Currency currency) {
-        this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, null, date, amount, currency);
+        this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, null, date, amount, currency);
     }
 
     public FixedPriceInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId,
-                                 final UUID subscriptionId, final String planName, final String phaseName,
+                                 final UUID subscriptionId, final String productName, final String planName, final String phaseName,
                                  @Nullable final String description, final LocalDate date, final BigDecimal amount, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, null, date, null, amount, null, currency, null, InvoiceItemType.FIXED);
+        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, productName, planName, phaseName, null, date, null, amount, null, currency, null, InvoiceItemType.FIXED);
     }
 
     public FixedPriceInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId,
-                                 final UUID subscriptionId, final String planName, final String phaseName, final String prettyPlanName, final String prettyPhaseName,
+                                 final UUID subscriptionId, final String productName, final String planName, final String phaseName,
+                                 final String prettyProductName, final String prettyPlanName, final String prettyPhaseName,
                                  @Nullable final String description, final LocalDate date, final BigDecimal amount, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, null, prettyPlanName, prettyPhaseName, null, date, null, amount, null, currency, null, InvoiceItemType.FIXED);
+        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, productName, planName, phaseName, null, prettyProductName, prettyPlanName, prettyPhaseName,
+              null, date, null, amount, null, currency, null, InvoiceItemType.FIXED);
     }
 
     @Override
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 ed31c6a..68b111a 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
@@ -155,6 +155,16 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
     }
 
     @Override
+    public String getProductName() {
+        return null;
+    }
+
+    @Override
+    public String getPrettyProductName() {
+        return null;
+    }
+
+    @Override
     public String getPlanName() {
         return null;
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemCatalogBase.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemCatalogBase.java
index 4afb0e8..ce7bcee 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemCatalogBase.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemCatalogBase.java
@@ -30,49 +30,64 @@ import org.killbill.billing.invoice.api.InvoiceItemType;
 
 public class InvoiceItemCatalogBase extends InvoiceItemBase implements InvoiceItem {
 
+    protected final String productName;
     protected final String planName;
     protected final String phaseName;
     protected final String usageName;
 
+    protected final String prettyProductName;
     protected final String prettyPlanName;
     protected final String prettyPhaseName;
     protected final String prettyUsageName;
 
     public InvoiceItemCatalogBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
-                                  @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+                                  @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String productName, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
                                   final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId, final InvoiceItemType invoiceItemType) {
-        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, null, null, null, startDate, endDate, amount, rate, currency, linkedItemId, null, null, invoiceItemType);
+        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, productName, planName, phaseName, usageName, null, null, null, null, startDate, endDate, amount, rate, currency, linkedItemId, null, null, invoiceItemType);
     }
 
     public InvoiceItemCatalogBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
-                                  @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+                                  @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String productName, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
                                   final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId,
                                   @Nullable final Integer quantity, @Nullable final String itemDetails, final InvoiceItemType invoiceItemType) {
-        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, null, null, null, startDate, endDate, amount, rate, currency, linkedItemId, quantity, itemDetails, invoiceItemType);
+        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, productName, planName, phaseName, usageName, null, null, null, null, startDate, endDate, amount, rate, currency, linkedItemId, quantity, itemDetails, invoiceItemType);
     }
 
     public InvoiceItemCatalogBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
-                                  @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
-                                  @Nullable final String prettyPlanName, @Nullable final String prettyPhaseName, @Nullable final String prettyUsageName,
+                                  @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String productName, @Nullable final String planName, @Nullable final String phaseName,
+                                  @Nullable final String usageName, @Nullable final String prettyProductName, @Nullable final String prettyPlanName, @Nullable final String prettyPhaseName, @Nullable final String prettyUsageName,
                                   final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId, final InvoiceItemType invoiceItemType) {
-        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, prettyPlanName, prettyPhaseName, prettyUsageName, startDate, endDate, amount, rate, currency, linkedItemId, null, null, invoiceItemType);
+        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, productName, planName, phaseName, usageName, prettyProductName, prettyPlanName, prettyPhaseName, prettyUsageName,
+             startDate, endDate, amount, rate, currency, linkedItemId, null, null, invoiceItemType);
     }
 
     public InvoiceItemCatalogBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
-                                  @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
-                                  @Nullable final String prettyPlanName, @Nullable final String prettyPhaseName, @Nullable final String prettyUsageName,
+                                  @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String productName, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+                                  @Nullable final String prettyProductName, @Nullable final String prettyPlanName, @Nullable final String prettyPhaseName, @Nullable final String prettyUsageName,
                                   final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId,
                                   @Nullable final Integer quantity, @Nullable final String itemDetails, final InvoiceItemType invoiceItemType) {
         super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, startDate, endDate, amount, rate, currency, linkedItemId, quantity, itemDetails, invoiceItemType);
+        this.productName = productName;
         this.planName = planName;
         this.phaseName = phaseName;
         this.usageName = usageName;
+        this.prettyProductName = prettyProductName;
         this.prettyPlanName = prettyPlanName;
         this.prettyPhaseName = prettyPhaseName;
         this.prettyUsageName = prettyUsageName;
     }
 
     @Override
+    public String getProductName() {
+        return productName;
+    }
+
+    @Override
+    public String getPrettyProductName() {
+        return prettyProductName;
+    }
+
+    @Override
     public String getPlanName() {
         return planName;
     }
@@ -132,7 +147,8 @@ public class InvoiceItemCatalogBase extends InvoiceItemBase implements InvoiceIt
         // Note: we don't use all fields here, as the output would be overwhelming
         // (we output all invoice items as they are generated).
         final StringBuilder sb = new StringBuilder().append(invoiceItemType).append("{");
-        sb.append("planName='").append(planName).append('\'');
+        sb.append("productName='").append(productName).append('\'');
+        sb.append(", planName='").append(planName).append('\'');
         sb.append(", phaseName='").append(phaseName).append('\'');
         sb.append(", usageName='").append(usageName).append('\'');
         sb.append(", startDate=").append(startDate);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
index 00f3d2f..038ade8 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
@@ -37,6 +37,8 @@ import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Preconditions;
+
 public class InvoiceItemFactory {
 
     private static final Logger log = LoggerFactory.getLogger(InvoiceItemFactory.class);
@@ -59,6 +61,7 @@ public class InvoiceItemFactory {
         final UUID childAccountId = invoiceItemModelDao.getChildAccountId();
         final UUID bundleId = invoiceItemModelDao.getBundleId();
         final UUID subscriptionId = invoiceItemModelDao.getSubscriptionId();
+        final String productName = invoiceItemModelDao.getProductName();
         final String planName = invoiceItemModelDao.getPlanName();
         final String phaseName = invoiceItemModelDao.getPhaseName();
         final String usageName = invoiceItemModelDao.getUsageName();
@@ -74,21 +77,22 @@ public class InvoiceItemFactory {
 
         final InvoiceItemType type = invoiceItemModelDao.getType();
 
-        final String [] prettyNames = computePrettyName(type, createdDate, planName, phaseName, usageName, catalog);
-        String prettyPlanName = prettyNames[0];
-        String prettyPlanPhaseName = prettyNames[1];
-        String prettyUsageName = prettyNames[2];
+        final String [] prettyNames = computePrettyName(type, createdDate, productName, planName, phaseName, usageName, catalog);
+        String prettyProductName = prettyNames[0];
+        String prettyPlanName = prettyNames[1];
+        String prettyPlanPhaseName = prettyNames[2];
+        String prettyUsageName = prettyNames[3];
 
         final InvoiceItem item;
         switch (type) {
             case EXTERNAL_CHARGE:
-                item = new ExternalChargeInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, prettyPlanName, prettyPlanPhaseName, description, startDate, endDate, amount, rate, currency, linkedItemId, itemDetails);
+                item = new ExternalChargeInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, prettyProductName, prettyPlanName, prettyPlanPhaseName, description, startDate, endDate, amount, rate, currency, linkedItemId, itemDetails);
                 break;
             case FIXED:
-                item = new FixedPriceInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, prettyPlanName, prettyPlanPhaseName, description, startDate, amount, currency);
+                item = new FixedPriceInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, prettyProductName, prettyPlanName, prettyPlanPhaseName, description, startDate, amount, currency);
                 break;
             case RECURRING:
-                item = new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, prettyPlanName, prettyPlanPhaseName, description, startDate, endDate, amount, rate, currency);
+                item = new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, prettyProductName, prettyPlanName, prettyPlanPhaseName, description, startDate, endDate, amount, rate, currency);
                 break;
             case CBA_ADJ:
                 item = new CreditBalanceAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, linkedItemId, description, amount, currency);
@@ -103,10 +107,10 @@ public class InvoiceItemFactory {
                 item = new ItemAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, description, amount, currency, linkedItemId, itemDetails);
                 break;
             case USAGE:
-                item = new UsageInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, prettyPlanName, prettyPlanPhaseName, prettyUsageName, startDate, endDate, description, amount, rate, currency, quantity, itemDetails);
+                item = new UsageInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, prettyProductName, prettyPlanName, prettyPlanPhaseName, prettyUsageName, startDate, endDate, description, amount, rate, currency, quantity, itemDetails);
                 break;
             case TAX:
-                item = new TaxInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, prettyPlanName, prettyPlanPhaseName, prettyUsageName, startDate, description, amount, currency, linkedItemId);
+                item = new TaxInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, prettyProductName, prettyPlanName, prettyPlanPhaseName, prettyUsageName, startDate, description, amount, currency, linkedItemId);
                 break;
             case PARENT_SUMMARY:
                 item = new ParentInvoiceItem(id, createdDate, invoiceId, accountId, childAccountId, amount, currency, description);
@@ -120,13 +124,14 @@ public class InvoiceItemFactory {
     //
     // Returns an array of string for 'pretty' names [prettyPlanName, prettyPlanPhaseName, prettyUsageName]
     //
-    private static String [] computePrettyName(final InvoiceItemType type, final DateTime createdDate, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName, @Nullable final Catalog catalog) {
+    private static String [] computePrettyName(final InvoiceItemType type, final DateTime createdDate, @Nullable final String productName, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName, @Nullable final Catalog catalog) {
 
-        final String [] result = new String[3];
+        final String [] result = new String[4];
 
         final boolean computePrettyName = catalog != null &&
                                           (type == InvoiceItemType.FIXED || type == InvoiceItemType.RECURRING || type == InvoiceItemType.TAX || type == InvoiceItemType.USAGE);
 
+        String prettyProductName = null;
         String prettyPlanName = null;
         String prettyPlanPhaseName = null;
         String prettyUsageName = null;
@@ -140,6 +145,11 @@ public class InvoiceItemFactory {
                     if (plan != null) {
                         prettyPlanName = plan.getPrettyName();
 
+                        if (productName != null) {
+                            Preconditions.checkState(plan.getProduct().getName().equals(productName));
+                            prettyProductName = plan.getProduct().getPrettyName();
+                        }
+
                         if (phaseName != null) {
                             final PlanPhase planPhase = plan.findPhase(phaseName);
                             if (planPhase != null) {
@@ -165,9 +175,10 @@ public class InvoiceItemFactory {
         } catch (final CatalogApiException e) {
             log.warn("Failed to compute invoice pretty names:", e.getMessage());
         } finally {
-            result[0] = prettyPlanName;
-            result[1] = prettyPlanPhaseName;
-            result[2] = prettyUsageName;
+            result[0] = prettyProductName;
+            result[1] = prettyPlanName;
+            result[2] = prettyPlanPhaseName;
+            result[3] = prettyUsageName;
         }
         return result;
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItem.java
index 678ca98..e5e668c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItem.java
@@ -34,21 +34,21 @@ import com.google.common.base.MoreObjects;
 public class RecurringInvoiceItem extends InvoiceItemCatalogBase {
 
     public RecurringInvoiceItem(final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
-                                final String planName, final String phaseName, final LocalDate startDate, final LocalDate endDate,
+                                final String productName, final String planName, final String phaseName, final LocalDate startDate, final LocalDate endDate,
                                 final BigDecimal amount, final BigDecimal rate, final Currency currency) {
-        this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, rate, currency);
+        this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount, rate, currency);
     }
 
     public RecurringInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
-                                final String planName, final String phaseName, final LocalDate startDate, final LocalDate endDate,
+                                final String productName, final String planName, final String phaseName, final LocalDate startDate, final LocalDate endDate,
                                 final BigDecimal amount, final BigDecimal rate, final Currency currency) {
-        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, null, null, null, startDate, endDate, amount, rate, currency);
+        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, null, null, null, null, startDate, endDate, amount, rate, currency);
     }
 
     public RecurringInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
-                                final String planName, final String phaseName, final String prettyPlanName, final String prettyPhaseName, @Nullable final String description, final LocalDate startDate, final LocalDate endDate,
+                                final String productName, final String planName, final String phaseName, final String prettyProductName, final String prettyPlanName, final String prettyPhaseName, @Nullable final String description, final LocalDate startDate, final LocalDate endDate,
                                 final BigDecimal amount, final BigDecimal rate, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, null, prettyPlanName, prettyPhaseName, null, startDate, endDate, amount, rate, currency, null, InvoiceItemType.RECURRING);
+        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, productName, planName, phaseName, null, prettyProductName, prettyPlanName, prettyPhaseName, null, startDate, endDate, amount, rate, currency, null, InvoiceItemType.RECURRING);
     }
 
     @Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/TaxInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/TaxInvoiceItem.java
index 4acad06..efc0aea 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/TaxInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/TaxInvoiceItem.java
@@ -37,14 +37,14 @@ public class TaxInvoiceItem extends InvoiceItemCatalogBase {
 
     public TaxInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
                           @Nullable final String description, final LocalDate date, final BigDecimal amount, final Currency currency) {
-        this(id, null, invoiceId, accountId, bundleId, null, null, null, null, null, null, null, date, description, amount, currency, null);
+        this(id, null, invoiceId, accountId, bundleId, null, null, null, null, null, null, null, null, null, date, description, amount, currency, null);
     }
 
     public TaxInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
-                          @Nullable final UUID subscriptionId, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
-                          @Nullable final String prettyPlanName, @Nullable final String prettyPhaseName, @Nullable final String prettyUsageName,
+                          @Nullable final UUID subscriptionId, @Nullable final String productName, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+                          @Nullable final String prettyProductName, @Nullable final String prettyPlanName, @Nullable final String prettyPhaseName, @Nullable final String prettyUsageName,
                           final LocalDate date, @Nullable final String description, final BigDecimal amount, final Currency currency, @Nullable final UUID linkedItemId) {
-        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, prettyPlanName, prettyPhaseName, prettyUsageName, date, null, amount, null, currency, linkedItemId, InvoiceItemType.TAX);
+        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, productName, planName, phaseName, usageName, prettyProductName, prettyPlanName, prettyPhaseName, prettyUsageName, date, null, amount, null, currency, linkedItemId, InvoiceItemType.TAX);
     }
 
     @Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
index 6857259..7639ccf 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
@@ -33,23 +33,23 @@ import com.google.common.base.MoreObjects;
 public class UsageInvoiceItem extends InvoiceItemCatalogBase {
 
     public UsageInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId,
-                            final String planName, final String phaseName, final String usageName,
+                            final String productName, final String planName, final String phaseName, final String usageName,
                             final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
-        this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, null, null, null, startDate, endDate, null, amount, null, currency, null, null);
+        this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, null, null, null, null, startDate, endDate, null, amount, null, currency, null, null);
     }
 
     public UsageInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId,
-                            final String planName, final String phaseName, final String usageName,
+                            final String productName, final String planName, final String phaseName, final String usageName,
                             final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final Integer quantity, @Nullable final String itemDetails) {
-        this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, null, null, null, startDate, endDate, null, amount, rate, currency, quantity, itemDetails);
+        this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, null, null, null, null, startDate, endDate, null, amount, rate, currency, quantity, itemDetails);
     }
 
     public UsageInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId,
-                            final UUID subscriptionId, final String planName, final String phaseName, final String usageName,
-                            final String prettyPlanName, final String prettyPhaseName, final String prettyUsageName,
+                            final UUID subscriptionId, final String productName, final String planName, final String phaseName, final String usageName,
+                            final String prettyProductName, final String prettyPlanName, final String prettyPhaseName, final String prettyUsageName,
                             final LocalDate startDate, final LocalDate endDate, @Nullable final String description, final BigDecimal amount, final BigDecimal rate,
                             final Currency currency, @Nullable final Integer quantity, @Nullable final String itemDetails) {
-        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, prettyPlanName, prettyPhaseName, prettyUsageName, startDate, endDate, amount, rate, currency, null, quantity, itemDetails, InvoiceItemType.USAGE);
+        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, productName, planName, phaseName, usageName, prettyProductName, prettyPlanName, prettyPhaseName, prettyUsageName, startDate, endDate, amount, rate, currency, null, quantity, itemDetails, InvoiceItemType.USAGE);
     }
 
     @Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index c411381..ab557cd 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -138,6 +138,16 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
     }
 
     @Override
+    public String getProductName() {
+        return Strings.nullToEmpty(translator.getTranslation(item.getProductName()));
+    }
+
+    @Override
+    public String getPrettyProductName() {
+        return Strings.nullToEmpty(translator.getTranslation(item.getPrettyProductName()));
+    }
+
+    @Override
     public String getPlanName() {
         return Strings.nullToEmpty(translator.getTranslation(item.getPlanName()));
     }
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 4f778b6..f18242d 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
@@ -48,6 +48,7 @@ public class Item {
     private final UUID subscriptionId;
     private final UUID targetInvoiceId;
     private final UUID invoiceId;
+    private final String productName;
     private final String planName;
     private final String phaseName;
     private final LocalDate startDate;
@@ -75,6 +76,7 @@ public class Item {
         this.subscriptionId = item.subscriptionId;
         this.targetInvoiceId = item.targetInvoiceId;
         this.invoiceId = item.invoiceId;
+        this.productName = item.productName;
         this.planName = item.planName;
         this.phaseName = item.phaseName;
         this.startDate = item.startDate;
@@ -102,6 +104,7 @@ public class Item {
         this.subscriptionId = item.getSubscriptionId();
         this.targetInvoiceId = targetInvoiceId;
         this.invoiceId = item.getInvoiceId();
+        this.productName = item.getProductName();
         this.planName = item.getPlanName();
         this.phaseName = item.getPhaseName();
         this.startDate = startDate;
@@ -131,7 +134,7 @@ public class Item {
                                                                      .multiply(amount) : amount;
 
         if (action == ItemAction.ADD) {
-            return new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, newStartDate, newEndDate, positiveAmount, rate, currency);
+            return new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, productName, 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 maxAvailableAmountForRepair = getNetAmount();
@@ -214,6 +217,7 @@ public class Item {
         sb.append(", subscriptionId=").append(subscriptionId);
         sb.append(", targetInvoiceId=").append(targetInvoiceId);
         sb.append(", invoiceId=").append(invoiceId);
+        sb.append(", productName='").append(productName).append('\'');
         sb.append(", planName='").append(planName).append('\'');
         sb.append(", phaseName='").append(phaseName).append('\'');
         sb.append(", startDate=").append(startDate);
@@ -283,6 +287,9 @@ public class Item {
         if (planName != null ? !planName.equals(item.planName) : item.planName != null) {
             return false;
         }
+        if (productName != null ? !productName.equals(item.productName) : item.productName != null) {
+            return false;
+        }
         if (rate != null ? rate.compareTo(item.rate) != 0 : item.rate != null) {
             return false;
         }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
index 474c5b3..90b9501 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
@@ -69,7 +69,7 @@ public class ContiguousIntervalCapacityUsageInArrear extends ContiguousIntervalU
 
         if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
             final String itemDetails = areAllBilledItemsWithDetails ? toJson(toBeBilledUsageDetails) : null;
-            final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+            final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getProductName(), getPlanName(),
                                                           getPhaseName(), usage.getName(), startDate, endDate, amountToBill, null, getCurrency(), null, itemDetails);
             result.add(item);
         } else if (amountToBill.compareTo(BigDecimal.ZERO) < 0) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
index 7e85038..d6148b5 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
@@ -82,13 +82,13 @@ public class ContiguousIntervalConsumableUsageInArrear extends ContiguousInterva
 
                 for (UsageConsumableInArrearTierUnitAggregate toBeBilledUsageDetail : ((UsageConsumableInArrearAggregate) toBeBilledUsageDetails).getTierDetails()) {
                     final String itemDetails = toJson(toBeBilledUsageDetail);
-                    final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+                    final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getProductName(), getPlanName(),
                                                                   getPhaseName(), usage.getName(), startDate, endDate, toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(), toBeBilledUsageDetail.getQuantity(), itemDetails);
                     result.add(item);
                 }
             } else {
                 final String itemDetails = toJson(toBeBilledUsageDetails);
-                final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+                final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getProductName(), getPlanName(),
                                                               getPhaseName(), usage.getName(), startDate, endDate, amountToBill, null, getCurrency(), null, itemDetails);
                 result.add(item);
             }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
index dd21aed..e4c0e08 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
@@ -176,7 +176,7 @@ public abstract class ContiguousIntervalUsageInArrear {
         LocalDate prevDate = null;
         for (final LocalDate curDate : transitionTimes) {
             if (prevDate != null) {
-                final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+                final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getProductName(), getPlanName(),
                                                               getPhaseName(), usage.getName(), prevDate, curDate, BigDecimal.ZERO, getCurrency());
                 result.add(item);
             }
@@ -422,6 +422,10 @@ public abstract class ContiguousIntervalUsageInArrear {
         return billingEvents.get(0).getPlan().getName();
     }
 
+    public String getProductName() {
+        return billingEvents.get(0).getPlan().getProduct().getName();
+    }
+
     public String getPhaseName() {
         return billingEvents.get(0).getPlanPhase().getName();
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitAggregate.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitAggregate.java
index 0a423d8..57ab28e 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitAggregate.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitAggregate.java
@@ -19,11 +19,12 @@ package org.killbill.billing.invoice.usage.details;
 
 import java.math.BigDecimal;
 
-import org.killbill.billing.invoice.usage.details.UsageInArrearTierUnitDetail;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class UsageConsumableInArrearTierUnitAggregate extends UsageInArrearTierUnitDetail {
 
     private final int tierBlockSize;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java
index 59aebc7..53d1918 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java
@@ -20,8 +20,10 @@ package org.killbill.billing.invoice.usage.details;
 import java.math.BigDecimal;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class UsageInArrearTierUnitDetail {
 
     protected final int tier;
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index eccd1c5..72c9c9e 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -10,6 +10,7 @@ tableFields(prefix) ::= <<
 , <prefix>bundle_id
 , <prefix>subscription_id
 , <prefix>description
+, <prefix>product_name
 , <prefix>plan_name
 , <prefix>phase_name
 , <prefix>usage_name
@@ -33,6 +34,7 @@ tableValues() ::= <<
 , :bundleId
 , :subscriptionId
 , :description
+, :productName
 , :planName
 , :phaseName
 , :usageName
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
index f2bce36..128c664 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
@@ -11,6 +11,7 @@ CREATE TABLE invoice_items (
     bundle_id varchar(36),
     subscription_id varchar(36),
     description varchar(255),
+    product_name varchar(255),
     plan_name varchar(255),
     phase_name varchar(255),
     usage_name varchar(255),
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20180501155616__invoice_item_product_name.sql b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20180501155616__invoice_item_product_name.sql
new file mode 100644
index 0000000..ac0c6d3
--- /dev/null
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20180501155616__invoice_item_product_name.sql
@@ -0,0 +1 @@
+alter table invoice_items add column product_name varchar(255) after description;
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index a369fce..283421f 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -67,7 +67,7 @@ public class TestDefaultInvoiceMigrationApi extends InvoiceTestSuiteWithEmbedded
 
     private UUID createAndCheckMigrationInvoice(final UUID accountId) throws InvoiceApiException {
 
-        final InvoiceItem recurringItem = new RecurringInvoiceItem(null, accountId, null, null, "planName", "phaseName", new LocalDate(2016, 2, 1), new LocalDate(2016, 3, 1), MIGRATION_INVOICE_AMOUNT, MIGRATION_INVOICE_AMOUNT, MIGRATION_INVOICE_CURRENCY);
+        final InvoiceItem recurringItem = new RecurringInvoiceItem(null, accountId, null, null, "productName", "planName", "phaseName", new LocalDate(2016, 2, 1), new LocalDate(2016, 3, 1), MIGRATION_INVOICE_AMOUNT, MIGRATION_INVOICE_AMOUNT, MIGRATION_INVOICE_CURRENCY);
         final UUID migrationInvoiceId = invoiceUserApi.createMigrationInvoice(accountId, date_migrated, ImmutableList.of(recurringItem), callContext);
         Assert.assertNotNull(migrationInvoiceId);
         //Double check it was created and values are correct
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java
index 4ca8023..89b7d85 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java
@@ -135,7 +135,7 @@ public class TestInvoiceFlagBehaviors extends InvoiceTestSuiteWithEmbeddedDB {
         // Add credit on the account
         invoiceUserApi.insertCredit(accountId, BigDecimal.TEN, null, accountCurrency, true, null, null, callContext);
 
-        final UUID invoiceId = invoiceUserApi.createMigrationInvoice(accountId, null, ImmutableList.<InvoiceItem>of(new FixedPriceInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, "foo", "bar", null, null, BigDecimal.ONE, accountCurrency)), callContext);
+        final UUID invoiceId = invoiceUserApi.createMigrationInvoice(accountId, null, ImmutableList.<InvoiceItem>of(new FixedPriceInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, "foo", "bar", null, null, BigDecimal.ONE, accountCurrency)), callContext);
 
         final Invoice invoice1 = invoiceUserApi.getInvoice(invoiceId, callContext);
         assertEquals(invoice1.getBalance().compareTo(BigDecimal.ZERO), 0);
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 d4cdc26..c14fc93 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
@@ -44,6 +44,7 @@ import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.entity.EntityPersistenceException;
 import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
 import org.killbill.billing.invoice.MockBillingEventSet;
@@ -139,7 +140,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final UUID bundleId = UUID.randomUUID();
         final LocalDate startDate = new LocalDate(2010, 1, 1);
         final LocalDate endDate = new LocalDate(2010, 4, 1);
-        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "test plan", "test phase", startDate, endDate,
+        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "test product", "test plan", "test phase", startDate, endDate,
                                                                  new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
 
         invoice.addInvoiceItem(invoiceItem);
@@ -244,19 +245,19 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         LocalDate startDate = new LocalDate(2011, 3, 1);
         LocalDate endDate = startDate.plusMonths(1);
 
-        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId1, "test plan", "test A", startDate, endDate,
+        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId1, "test product", "test plan", "test A", startDate, endDate,
                                                                     rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
-        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId2, "test plan", "test B", startDate, endDate,
+        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId2, "test product", "test plan", "test B", startDate, endDate,
                                                                     rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
 
-        final RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId3, "test plan", "test C", startDate, endDate,
+        final RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId3, "test product", "test plan", "test C", startDate, endDate,
                                                                     rate3, rate3, Currency.USD);
         invoiceUtil.createInvoiceItem(item3, context);
 
-        final RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId4, "test plan", "test D", startDate, endDate,
+        final RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId4, "test product", "test plan", "test D", startDate, endDate,
                                                                     rate4, rate4, Currency.USD);
         invoiceUtil.createInvoiceItem(item4, context);
 
@@ -269,15 +270,15 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         startDate = endDate;
         endDate = startDate.plusMonths(1);
 
-        final RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId1, "test plan", "test phase A", startDate, endDate,
+        final RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId1, "test product", "test plan", "test phase A", startDate, endDate,
                                                                     rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item5, context);
 
-        final RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId2, "test plan", "test phase B", startDate, endDate,
+        final RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId2, "test product", "test plan", "test phase B", startDate, endDate,
                                                                     rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item6, context);
 
-        final RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId3, "test plan", "test phase C", startDate, endDate,
+        final RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId3, "test product", "test plan", "test phase C", startDate, endDate,
                                                                     rate3, rate3, Currency.USD);
         invoiceUtil.createInvoiceItem(item7, context);
 
@@ -320,19 +321,19 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         LocalDate startDate = new LocalDate(2011, 3, 1);
         LocalDate endDate = startDate.plusMonths(1);
 
-        final FixedPriceInvoiceItem item1 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId1, "test plan", "test A", startDate,
+        final FixedPriceInvoiceItem item1 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId1, "test product", "test plan", "test A", startDate,
                                                                       rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
-        final FixedPriceInvoiceItem item2 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId2, "test plan", "test B", startDate,
+        final FixedPriceInvoiceItem item2 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId2, "test product", "test plan", "test B", startDate,
                                                                       rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
 
-        final FixedPriceInvoiceItem item3 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId3, "test plan", "test C", startDate,
+        final FixedPriceInvoiceItem item3 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId3, "test product", "test plan", "test C", startDate,
                                                                       rate3, Currency.USD);
         invoiceUtil.createInvoiceItem(item3, context);
 
-        final FixedPriceInvoiceItem item4 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId4, "test plan", "test D", startDate,
+        final FixedPriceInvoiceItem item4 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId4, "test product", "test plan", "test D", startDate,
                                                                       rate4, Currency.USD);
         invoiceUtil.createInvoiceItem(item4, context);
 
@@ -344,15 +345,15 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         startDate = endDate;
 
-        final FixedPriceInvoiceItem item5 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId1, "test plan", "test phase A", startDate,
+        final FixedPriceInvoiceItem item5 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId1, "test product", "test plan", "test phase A", startDate,
                                                                       rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item5, context);
 
-        final FixedPriceInvoiceItem item6 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId2, "test plan", "test phase B", startDate,
+        final FixedPriceInvoiceItem item6 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId2, "test product", "test plan", "test phase B", startDate,
                                                                       rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item6, context);
 
-        final FixedPriceInvoiceItem item7 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId3, "test plan", "test phase C", startDate,
+        final FixedPriceInvoiceItem item7 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId3, "test product", "test plan", "test phase C", startDate,
                                                                       rate3, Currency.USD);
         invoiceUtil.createInvoiceItem(item7, context);
 
@@ -395,35 +396,35 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         LocalDate startDate = new LocalDate(2011, 3, 1);
         LocalDate endDate = startDate.plusMonths(1);
 
-        final RecurringInvoiceItem recurringItem1 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId1, "test plan", "test A", startDate, endDate,
+        final RecurringInvoiceItem recurringItem1 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId1, "test product", "test plan", "test A", startDate, endDate,
                                                                              rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(recurringItem1, context);
 
-        final RecurringInvoiceItem recurringItem2 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId2, "test plan", "test B", startDate, endDate,
+        final RecurringInvoiceItem recurringItem2 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId2, "test product", "test plan", "test B", startDate, endDate,
                                                                              rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(recurringItem2, context);
 
-        final RecurringInvoiceItem recurringItem3 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId3, "test plan", "test C", startDate, endDate,
+        final RecurringInvoiceItem recurringItem3 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId3, "test product", "test plan", "test C", startDate, endDate,
                                                                              rate3, rate3, Currency.USD);
         invoiceUtil.createInvoiceItem(recurringItem3, context);
 
-        final RecurringInvoiceItem recurringItem4 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId4, "test plan", "test D", startDate, endDate,
+        final RecurringInvoiceItem recurringItem4 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId4, "test product", "test plan", "test D", startDate, endDate,
                                                                              rate4, rate4, Currency.USD);
         invoiceUtil.createInvoiceItem(recurringItem4, context);
 
-        final FixedPriceInvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId1, "test plan", "test A", startDate,
+        final FixedPriceInvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId1, "test product", "test plan", "test A", startDate,
                                                                            rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(fixedItem1, context);
 
-        final FixedPriceInvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId2, "test plan", "test B", startDate,
+        final FixedPriceInvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId2, "test product", "test plan", "test B", startDate,
                                                                            rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(fixedItem2, context);
 
-        final FixedPriceInvoiceItem fixedItem3 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId3, "test plan", "test C", startDate,
+        final FixedPriceInvoiceItem fixedItem3 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId3, "test product", "test plan", "test C", startDate,
                                                                            rate3, Currency.USD);
         invoiceUtil.createInvoiceItem(fixedItem3, context);
 
-        final FixedPriceInvoiceItem fixedItem4 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId4, "test plan", "test D", startDate,
+        final FixedPriceInvoiceItem fixedItem4 = new FixedPriceInvoiceItem(invoiceId1, accountId, bundleId, subscriptionId4, "test product", "test plan", "test D", startDate,
                                                                            rate4, Currency.USD);
         invoiceUtil.createInvoiceItem(fixedItem4, context);
 
@@ -436,26 +437,26 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         startDate = endDate;
         endDate = startDate.plusMonths(1);
 
-        final RecurringInvoiceItem recurringItem5 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId1, "test plan", "test phase A", startDate, endDate,
+        final RecurringInvoiceItem recurringItem5 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId1, "test product", "test plan", "test phase A", startDate, endDate,
                                                                              rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(recurringItem5, context);
 
-        final RecurringInvoiceItem recurringItem6 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId2, "test plan", "test phase B", startDate, endDate,
+        final RecurringInvoiceItem recurringItem6 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId2, "test product", "test plan", "test phase B", startDate, endDate,
                                                                              rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(recurringItem6, context);
 
-        final RecurringInvoiceItem recurringItem7 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId3, "test plan", "test phase C", startDate, endDate,
+        final RecurringInvoiceItem recurringItem7 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId3, "test product", "test plan", "test phase C", startDate, endDate,
                                                                              rate3, rate3, Currency.USD);
         invoiceUtil.createInvoiceItem(recurringItem7, context);
-        final FixedPriceInvoiceItem fixedItem5 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId1, "test plan", "test phase A", startDate,
+        final FixedPriceInvoiceItem fixedItem5 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId1, "test product", "test plan", "test phase A", startDate,
                                                                            rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(fixedItem5, context);
 
-        final FixedPriceInvoiceItem fixedItem6 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId2, "test plan", "test phase B", startDate,
+        final FixedPriceInvoiceItem fixedItem6 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId2, "test product", "test plan", "test phase B", startDate,
                                                                            rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(fixedItem6, context);
 
-        final FixedPriceInvoiceItem fixedItem7 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId3, "test plan", "test phase C", startDate,
+        final FixedPriceInvoiceItem fixedItem7 = new FixedPriceInvoiceItem(invoiceId2, accountId, bundleId, subscriptionId3, "test product", "test plan", "test phase C", startDate,
                                                                            rate3, Currency.USD);
         invoiceUtil.createInvoiceItem(fixedItem7, context);
 
@@ -516,11 +517,11 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal rate1 = new BigDecimal("17.0");
         final BigDecimal rate2 = new BigDecimal("42.0");
 
-        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate,
+        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase A", startDate,
                                                                     endDate, rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
-        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate,
                                                                     endDate, rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
 
@@ -545,7 +546,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         final BigDecimal rate1 = new BigDecimal("17.0");
 
-        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate,
+        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase A", startDate,
                                                                     endDate, rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
@@ -570,11 +571,11 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal rate1 = new BigDecimal("17.0");
         final BigDecimal rate2 = new BigDecimal("42.0");
 
-        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
+        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase A", startDate, endDate,
                                                                     rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
-        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
+        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate, endDate,
                                                                     rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
 
@@ -617,7 +618,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal refund1 = new BigDecimal("7.00");
 
         // Recurring item
-        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate,
                                                                     endDate, rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
         BigDecimal balance = invoiceDao.getAccountBalance(accountId, context);
@@ -666,7 +667,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal amount = new BigDecimal("20.0");
 
         // Recurring item
-        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate,
                                                                     endDate, amount, amount, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
         BigDecimal accountBalance = invoiceDao.getAccountBalance(accountId, context);
@@ -756,7 +757,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal rate2 = new BigDecimal("10.0");
 
         // Fixed Item
-        final FixedPriceInvoiceItem item1 = new FixedPriceInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate,
+        final FixedPriceInvoiceItem item1 = new FixedPriceInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase A", startDate,
                                                                       amount1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
@@ -764,7 +765,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         assertEquals(balance.compareTo(new BigDecimal("5.00")), 0);
 
         // Recurring item
-        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate,
                                                                     endDate, rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
         balance = invoiceDao.getAccountBalance(accountId, context);
@@ -780,7 +781,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         // Repair previous item with rate 2
         final RepairAdjInvoiceItem item2Repair = new RepairAdjInvoiceItem(invoice1.getId(), accountId, startDate, endDate, rate1.negate(), Currency.USD, item2.getId());
-        final RecurringInvoiceItem item2Replace = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+        final RecurringInvoiceItem item2Replace = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate,
                                                                            endDate, rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item2Repair, context);
         invoiceUtil.createInvoiceItem(item2Replace, context);
@@ -899,7 +900,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal rate2 = new BigDecimal("10.0");
 
         // Fixed Item
-        final FixedPriceInvoiceItem item1 = new FixedPriceInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate,
+        final FixedPriceInvoiceItem item1 = new FixedPriceInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase A", startDate,
                                                                       amount1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
@@ -907,7 +908,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         assertEquals(balance.compareTo(new BigDecimal("5.00")), 0);
 
         // Recurring item
-        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate,
                                                                     endDate, rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
         balance = invoiceDao.getAccountBalance(accountId, context);
@@ -922,7 +923,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         // Repair previous item with rate 2
         final RepairAdjInvoiceItem item2Repair = new RepairAdjInvoiceItem(invoice1.getId(), accountId, startDate, endDate, rate1.negate(), Currency.USD, item2.getId());
-        final RecurringInvoiceItem item2Replace = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+        final RecurringInvoiceItem item2Replace = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate,
                                                                            endDate, rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item2Repair, context);
         invoiceUtil.createInvoiceItem(item2Replace, context);
@@ -954,7 +955,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final Invoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1.plusMonths(1), Currency.USD);
         invoiceUtil.createInvoice(invoice2, context);
 
-        final RecurringInvoiceItem nextItem = new RecurringInvoiceItem(invoice2.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test bla", startDate.plusMonths(1),
+        final RecurringInvoiceItem nextItem = new RecurringInvoiceItem(invoice2.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test bla", startDate.plusMonths(1),
                                                                        endDate.plusMonths(1), rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(nextItem, context);
         balance = invoiceDao.getAccountBalance(accountId, context);
@@ -1046,7 +1047,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal amount1 = new BigDecimal("5.0");
 
         // Fixed Item
-        final FixedPriceInvoiceItem item1 = new FixedPriceInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate,
+        final FixedPriceInvoiceItem item1 = new FixedPriceInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase A", startDate,
                                                                       amount1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
@@ -1090,11 +1091,11 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal rate1 = new BigDecimal("17.0");
         final BigDecimal rate2 = new BigDecimal("42.0");
 
-        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
+        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase A", startDate, endDate,
                                                                     rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
-        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
+        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate, endDate,
                                                                     rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
 
@@ -1118,7 +1119,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         final BigDecimal rate3 = new BigDecimal("21.0");
 
-        final RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2,
+        final RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase C", startDate2, endDate2,
                                                                     rate3, rate3, Currency.USD);
         invoiceUtil.createInvoiceItem(item3, context);
 
@@ -1145,11 +1146,11 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BigDecimal rate1 = new BigDecimal("17.0");
         final BigDecimal rate2 = new BigDecimal("42.0");
 
-        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
+        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase A", startDate, endDate,
                                                                     rate1, rate1, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
-        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
+        final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test product", "test plan", "test phase B", startDate, endDate,
                                                                     rate2, rate2, Currency.USD);
         invoiceUtil.createInvoiceItem(item2, context);
 
@@ -1409,7 +1410,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final Invoice invoice = new DefaultInvoice(accountId, targetDate, targetDate, Currency.USD);
         final UUID invoiceId = invoice.getId();
 
-        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "test-plan", "test-phase-rec",
+        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "test product", "test-plan", "test-phase-rec",
                                                                  recuringStartDate, recuringEndDate, new BigDecimal("239.00"), new BigDecimal("239.00"), Currency.USD);
 
         invoice.addInvoiceItem(invoiceItem);
@@ -1439,8 +1440,12 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final BillingEventSet events = new MockBillingEventSet();
         final SubscriptionBase subscription = getZombieSubscription(subscriptionId);
 
+        final Product product = Mockito.mock(Product.class);
+        Mockito.when(product.getName()).thenReturn("product");
+
         final Plan plan = Mockito.mock(Plan.class);
         Mockito.when(plan.getName()).thenReturn("plan");
+        Mockito.when(plan.getProduct()).thenReturn(product);
         Mockito.when(plan.getRecurringBillingMode()).thenReturn(BillingMode.IN_ADVANCE);
 
         final PlanPhase phase1 = Mockito.mock(PlanPhase.class);
@@ -1469,8 +1474,13 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         final SubscriptionBase subscription = getZombieSubscription();
 
+        final Product product = Mockito.mock(Product.class);
+        Mockito.when(product.getName()).thenReturn("product");
+
         final Plan plan = Mockito.mock(Plan.class);
         Mockito.when(plan.getName()).thenReturn("plan");
+        Mockito.when(plan.getProduct()).thenReturn(product);
+
         Mockito.when(plan.getRecurringBillingMode()).thenReturn(BillingMode.IN_ADVANCE);
 
         final PlanPhase phase1 = Mockito.mock(PlanPhase.class);
@@ -1518,7 +1528,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // * $-10 repair
         // * $10 generated CBA due to the repair (assume previous payment)
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
-        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
                                                                  UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
         final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
                                                                                    fixedItem1.getStartDate(), fixedItem1.getEndDate(),
@@ -1555,7 +1565,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // * $-10 repair
         // * $10 generated CBA due to the repair (assume previous payment)
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
-        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
                                                                  UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
         final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
                                                                                    fixedItem1.getStartDate(), fixedItem1.getEndDate(),
@@ -1581,7 +1591,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // * $5 item
         // * $-5 CBA used
         final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
-        final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+        final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null,  null, UUID.randomUUID().toString(),
                                                                  UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
         final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
                                                                                                          fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
@@ -1615,7 +1625,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // * $-10 repair
         // * $10 generated CBA due to the repair (assume previous payment)
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
-        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null,  null, UUID.randomUUID().toString(),
                                                                  UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
         final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
                                                                                    fixedItem1.getStartDate(), fixedItem1.getEndDate(),
@@ -1641,7 +1651,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // * $5 item
         // * $-5 CBA used
         final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
-        final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+        final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null,  null, UUID.randomUUID().toString(),
                                                                  UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
         final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
                                                                                                          fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
@@ -1655,7 +1665,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // * $5 item
         // * $-5 CBA used
         final DefaultInvoice invoice3 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
-        final InvoiceItem fixedItem3 = new FixedPriceInvoiceItem(invoice3.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+        final InvoiceItem fixedItem3 = new FixedPriceInvoiceItem(invoice3.getId(), invoice1.getAccountId(), null, null,  null, UUID.randomUUID().toString(),
                                                                  UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
         final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem3 = new CreditBalanceAdjInvoiceItem(fixedItem3.getInvoiceId(), fixedItem3.getAccountId(),
                                                                                                          fixedItem3.getStartDate(), fixedItem3.getAmount().negate(),
@@ -1725,7 +1735,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         final UUID bundleId = UUID.randomUUID();
         final UUID subscriptionId = UUID.randomUUID();
-        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice.getId(), accountId,     bundleId, subscriptionId, "test plan", "test ZOO", clock.getUTCNow().plusMonths(-1).toLocalDate(), clock.getUTCNow().toLocalDate(),
+        final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice.getId(), accountId,     bundleId, subscriptionId, "test product", "test plan", "test ZOO", clock.getUTCNow().plusMonths(-1).toLocalDate(), clock.getUTCNow().toLocalDate(),
                                                                     BigDecimal.TEN, BigDecimal.TEN, Currency.USD);
         invoiceUtil.createInvoiceItem(item1, context);
 
@@ -1823,7 +1833,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final UUID bundleId = UUID.randomUUID();
         final LocalDate startDate = new LocalDate(2010, 1, 1);
         final LocalDate endDate = new LocalDate(2010, 4, 1);
-        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, childAccountId, bundleId, subscriptionId, "test plan", "test phase", startDate, endDate,
+        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, childAccountId, bundleId, subscriptionId, "test product", "test plan", "test phase", startDate, endDate,
                                                                  new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
         final InvoiceItem invoiceAdj = new ItemAdjInvoiceItem(invoiceItem, startDate, new BigDecimal("-5.00"), Currency.USD);
 
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 14db61d..fa52a80 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
@@ -66,7 +66,7 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
         final LocalDate endDate = new LocalDate(2011, 11, 1);
         final BigDecimal rate = new BigDecimal("20.00");
 
-        final RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "test plan", "test phase", startDate, endDate,
+        final RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "test product", "test plan", "test phase", startDate, endDate,
                                                                    rate, rate, Currency.USD);
         invoiceUtil.createInvoiceItem(item, context);
 
@@ -96,7 +96,7 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
             final UUID invoiceId = UUID.randomUUID();
 
             final RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId,
-                                                                       "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1),
+                                                                       "test product", "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1),
                                                                        rate, rate, Currency.USD);
             invoiceUtil.createInvoiceItem(item, context);
         }
@@ -118,7 +118,7 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
             final BigDecimal amount = rate.multiply(new BigDecimal(i + 1));
 
             final RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId,
-                                                                       "test plan", "test phase", startDate, startDate.plusMonths(1),
+                                                                       "test product", "test plan", "test phase", startDate, startDate.plusMonths(1),
                                                                        amount, amount, Currency.USD);
             invoiceUtil.createInvoiceItem(item, context);
         }
@@ -143,7 +143,7 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
         final UUID subscriptionId = UUID.randomUUID();
 
         final RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId,
-                                                                   "test plan", "test phase", startDate, startDate.plusMonths(1),
+                                                                   "test product", "test plan", "test phase", startDate, startDate.plusMonths(1),
                                                                    rate, rate, Currency.USD);
         invoiceUtil.createInvoiceItem(item, context);
 
@@ -171,7 +171,7 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
         final LocalDate startDate = new LocalDate(2012, 4, 1);
 
         final InvoiceItem fixedPriceInvoiceItem = new FixedPriceInvoiceItem(invoiceId, accountId, UUID.randomUUID(),
-                                                                            UUID.randomUUID(), "test plan", "test phase", startDate, TEN, Currency.USD);
+                                                                            UUID.randomUUID(), "test product", "test plan", "test phase", startDate, TEN, Currency.USD);
         invoiceUtil.createInvoiceItem(fixedPriceInvoiceItem, context);
 
         final InvoiceItemModelDao savedItem = invoiceUtil.getInvoiceItemById(fixedPriceInvoiceItem.getId(), context);
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 45f8c46..9418c7d 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
@@ -917,7 +917,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         final LocalDate startDate = new LocalDate(2013, 06, 15);
         final LocalDate endDate = new LocalDate(2013, 07, 15);
         final InvoiceItem recurringInvoiceItem = new RecurringInvoiceItem(existingInvoice.getId(), accountId, subscription.getBundleId(),
-                                                                          subscription.getId(), plan.getName(), phase.getName(),
+                                                                          subscription.getId(), null, plan.getName(), phase.getName(),
                                                                           startDate, endDate, recurringPrice.getPrice(currency),
                                                                           recurringPrice.getPrice(currency), Currency.USD);
         existingInvoice.addInvoiceItem(recurringInvoiceItem);
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 0e283c4..87d8535 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
@@ -115,7 +115,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
         final PlanPhase phase = new MockPlanPhase(null, fixedPrice, BillingPeriod.NO_BILLING_PERIOD, PhaseType.TRIAL);
 
         final UUID invoiceId = UUID.randomUUID();
-        final InvoiceItem prevInvoiceItem = new FixedPriceInvoiceItem(invoiceId, account.getId(), UUID.randomUUID(), UUID.randomUUID(), plan.getName(),
+        final InvoiceItem prevInvoiceItem = new FixedPriceInvoiceItem(invoiceId, account.getId(), UUID.randomUUID(), UUID.randomUUID(), null, plan.getName(),
                                                                       phase.getName(), invoiceItemDate, fixedPriceAmount, Currency.USD);
 
         final BillingEvent event = invoiceUtil.createMockBillingEvent(account, subscription, new DateTime("2016-01-08"),
@@ -138,7 +138,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
         final PlanPhase phase = new MockPlanPhase(null, fixedPrice, BillingPeriod.NO_BILLING_PERIOD, PhaseType.TRIAL);
 
         final UUID invoiceId = UUID.randomUUID();
-        final InvoiceItem prevInvoiceItem = new FixedPriceInvoiceItem(invoiceId, account.getId(), subscription.getBundleId(), subscription.getId(), plan.getName(),
+        final InvoiceItem prevInvoiceItem = new FixedPriceInvoiceItem(invoiceId, account.getId(), subscription.getBundleId(), subscription.getId(), null, plan.getName(),
                                                                       phase.getName(), invoiceItemDate, fixedPriceAmount, Currency.USD);
 
         final BillingEvent event = invoiceUtil.createMockBillingEvent(account, subscription, new DateTime("2016-02-01"),
@@ -161,7 +161,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
         final PlanPhase phase = new MockPlanPhase(null, fixedPrice, BillingPeriod.NO_BILLING_PERIOD, PhaseType.TRIAL);
 
         final UUID invoiceId = UUID.randomUUID();
-        final InvoiceItem prevInvoiceItem = new FixedPriceInvoiceItem(invoiceId, account.getId(), subscription.getBundleId(), subscription.getId(), plan.getName(),
+        final InvoiceItem prevInvoiceItem = new FixedPriceInvoiceItem(invoiceId, account.getId(), subscription.getBundleId(), subscription.getId(), null, plan.getName(),
                                                                       phase.getName(), invoiceItemDate, fixedPriceAmount, Currency.USD);
 
         final BillingEvent event = invoiceUtil.createMockBillingEvent(account, subscription, new DateTime("2016-01-08"),
@@ -326,6 +326,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                             account.getId(),
                                                             subscription.getBundleId(),
                                                             subscription.getId(),
+                                                            null,
                                                             event.getPlan().getName(),
                                                             event.getPlanPhase().getName(),
                                                             startDate.plusMonths(i),
@@ -354,6 +355,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                             account.getId(),
                                                             subscription.getBundleId(),
                                                             subscription.getId(),
+                                                            null,
                                                             event.getPlan().getName(),
                                                             event.getPlanPhase().getName(),
                                                             startDate.plusMonths(i),
@@ -414,6 +416,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                              account.getId(),
                                                              subscription.getBundleId(),
                                                              subscription.getId(),
+                                                             null,
                                                              event.getPlan().getName(),
                                                              event.getPlanPhase().getName(),
                                                              "Buggy fixed item",
@@ -471,6 +474,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                             account.getId(),
                                                             subscription.getBundleId(),
                                                             subscription.getId(),
+                                                            null,
                                                             event.getPlan().getName(),
                                                             event.getPlanPhase().getName(),
                                                             startDate,
@@ -532,6 +536,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         event.getPlan().getName(),
                                                         event.getPlanPhase().getName(),
                                                         startDate,
@@ -594,6 +599,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         event.getPlan().getName(),
                                                         event.getPlanPhase().getName(),
                                                         startDate,
@@ -608,6 +614,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         event.getPlan().getName(),
                                                         event.getPlanPhase().getName(),
                                                         startDate,
@@ -675,6 +682,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         event.getPlan().getName(),
                                                         event.getPlanPhase().getName(),
                                                         startDate,
@@ -744,6 +752,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         event.getPlan().getName(),
                                                         event.getPlanPhase().getName(),
                                                         startDate,
@@ -823,6 +832,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         event.getPlan().getName(),
                                                         event.getPlanPhase().getName(),
                                                         startDate,
@@ -947,6 +957,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         "my-plan",
                                                         "my-plan-monthly",
                                                         startDate,
@@ -1035,6 +1046,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         plan.getName(),
                                                         planPhase.getName(),
                                                         startDate,
@@ -1119,6 +1131,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         plan.getName(),
                                                         planPhase.getName(),
                                                         startDate,
@@ -1203,6 +1216,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         plan.getName(),
                                                         planPhase.getName(),
                                                         startDate,
@@ -1256,6 +1270,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                                             account.getId(),
                                                                             subscription.getBundleId(),
                                                                             subscription.getId(),
+                                                                            null,
                                                                             "planName",
                                                                             "phaseName",
                                                                             "description",
@@ -1280,6 +1295,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                          account.getId(),
                                                          subscription.getBundleId(),
                                                          subscription.getId(),
+                                                         null,
                                                          "planName",
                                                          "phaseName",
                                                          "description",
@@ -1310,6 +1326,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                                           account.getId(),
                                                                           subscription.getBundleId(),
                                                                           subscription.getId(),
+                                                                          null,
                                                                           "planName",
                                                                           "phaseName",
                                                                           startDate,
@@ -1335,6 +1352,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                         account.getId(),
                                                         subscription.getBundleId(),
                                                         subscription.getId(),
+                                                        null,
                                                         "planName",
                                                         "phaseName",
                                                         startDate,
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceWithMetadata.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceWithMetadata.java
index afb5c54..ac52c6d 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceWithMetadata.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceWithMetadata.java
@@ -101,6 +101,7 @@ public class TestInvoiceWithMetadata extends InvoiceTestSuiteNoDB {
                                                                   account.getId(),
                                                                   subscription.getBundleId(),
                                                                   subscription.getId(),
+                                                                  null,
                                                                   event.getPlan().getName(),
                                                                   event.getPlanPhase().getName(),
                                                                   invoiceDate,
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java
index dff38c1..e097fac 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java
@@ -112,17 +112,29 @@ public abstract class InvoiceTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestInvoiceModuleNoDB(configSource));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() {
+        if (hasFailed()) {
+            return;
+        }
+
         bus.start();
     }
 
     @AfterMethod(groups = "fast")
     public void afterMethod() {
+        if (hasFailed()) {
+            return;
+        }
+
         bus.stop();
     }
 }
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 f0789e8..523e480 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
@@ -117,6 +117,10 @@ public abstract class InvoiceTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestInvoiceModuleWithEmbeddedDb(configSource));
         injector.injectMembers(this);
     }
@@ -124,6 +128,10 @@ public abstract class InvoiceTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @Override
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
         controllerDispatcher.clearAll();
         bus.start();
@@ -142,6 +150,10 @@ public abstract class InvoiceTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
 
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         bus.stop();
         stopInvoiceService(invoiceService);
     }
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 92bce8a..7d0c111 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
@@ -115,7 +115,7 @@ public class InvoiceTestUtils {
 
     public static InvoiceItem createInvoiceItem(final Clock clock, final UUID invoiceId, final UUID accountId, final BigDecimal amount, final Currency currency) {
         return new FixedPriceInvoiceItem(invoiceId, accountId, UUID.randomUUID(), UUID.randomUUID(),
-                                         "charge back test", "charge back phase", clock.getUTCToday(), amount, currency);
+                                         null, "charge back test", "charge back phase", clock.getUTCToday(), amount, currency);
     }
 
     public static InvoicePayment createAndPersistPayment(final InvoiceInternalApi invoicePaymentApi,
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 d31887a..c24a19f 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
@@ -70,7 +70,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
         // * $-10 CBA
         // * $10 CBA
         final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                           new LocalDate(), BigDecimal.TEN, Currency.USD);
         final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem = new CreditBalanceAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
                                                                                                         fixedItem.getStartDate(), fixedItem.getAmount(),
@@ -107,7 +107,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
         // * $-1 credit adjustment
         // * $1 generated CBA due to the credit adjustment
         final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                           new LocalDate(), BigDecimal.TEN, Currency.USD);
         final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
                                                                                    fixedItem.getStartDate(), fixedItem.getEndDate(),
@@ -154,7 +154,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testFormattedAmountFranceAndEUR() throws Exception {
         final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                              new LocalDate(), new BigDecimal("1499.95"), Currency.EUR);
         final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR);
         invoiceEUR.addInvoiceItem(fixedItemEUR);
@@ -184,7 +184,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testFormattedAmountFranceAndOMR() throws Exception {
         final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                           new LocalDate(), new BigDecimal("1499.958"), Currency.OMR);
         final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.OMR);
         invoice.addInvoiceItem(fixedItem);
@@ -214,7 +214,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testFormattedAmountFranceAndJPY() throws Exception {
         final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                           new LocalDate(), new BigDecimal("1500.00"), Currency.JPY);
         final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.JPY);
         invoice.addInvoiceItem(fixedItem);
@@ -244,7 +244,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testFormattedAmountUSAndBTC() throws Exception {
         final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                           new LocalDate(), new BigDecimal("1105.28843439"), Currency.BTC);
         final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.BTC);
         invoice.addInvoiceItem(fixedItem);
@@ -274,7 +274,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testFormattedAmountUSAndEUR() throws Exception {
         final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                           new LocalDate(), new BigDecimal("2635.14"), Currency.EUR);
         final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR);
         invoice.addInvoiceItem(fixedItem);
@@ -304,7 +304,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testFormattedAmountUSAndBRL() throws Exception {
         final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                           new LocalDate(), new BigDecimal("2635.14"), Currency.BRL);
         final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.BRL);
         invoice.addInvoiceItem(fixedItem);
@@ -334,7 +334,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testFormattedAmountUSAndGBP() throws Exception {
         final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                           new LocalDate(), new BigDecimal("1499.95"), Currency.GBP);
         final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.GBP);
         invoice.addInvoiceItem(fixedItem);
@@ -402,7 +402,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     public void testForDisplay() throws Exception {
 
         final FixedPriceInvoiceItem fixedItemBRL = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                              new LocalDate(), new BigDecimal("1499.95"), Currency.BRL);
 
         final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), UUID.randomUUID(), new Integer(234), new LocalDate(), new LocalDate(), Currency.BRL,  false, InvoiceStatus.COMMITTED);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
index fbac29d..090b4c6 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
@@ -55,7 +55,7 @@ public class TestDefaultInvoiceItemFormatter extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testBasicUSD() throws Exception {
         final FixedPriceInvoiceItem fixedItemUSD = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                              new LocalDate(), new BigDecimal("-1114.751625346"), Currency.USD);
         checkOutput(fixedItemUSD, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
                     "<td class=\"amount\">($1,114.75)</td>", LocaleUtils.toLocale("en_US"));
@@ -64,19 +64,19 @@ public class TestDefaultInvoiceItemFormatter extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testFormattedAmount() throws Exception {
         final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                              new LocalDate(), new BigDecimal("1499.95"), Currency.EUR);
         checkOutput(fixedItemEUR, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
                     "<td class=\"amount\">1 499,95 €</td>", Locale.FRANCE);
 
         final FixedPriceInvoiceItem fixedItemUSD = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                              new LocalDate(), new BigDecimal("-1114.751625346"), Currency.USD);
         checkOutput(fixedItemUSD, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}", "<td class=\"amount\">($1,114.75)</td>");
 
         // Check locale/currency mismatch (locale is set at the account level)
         final FixedPriceInvoiceItem fixedItemGBP = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                              new LocalDate(), new BigDecimal("8.07"), Currency.GBP);
         checkOutput(fixedItemGBP, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
                     "<td class=\"amount\">8,07 GBP</td>", Locale.FRANCE);
@@ -86,7 +86,7 @@ public class TestDefaultInvoiceItemFormatter extends InvoiceTestSuiteNoDB {
     public void testNullEndDate() throws Exception {
         final LocalDate startDate = new LocalDate(2012, 12, 1);
         final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                           startDate, BigDecimal.TEN, Currency.USD);
         checkOutput(fixedItem,
                     "{{#invoiceItem}}<td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td>{{/invoiceItem}}",
@@ -98,7 +98,7 @@ public class TestDefaultInvoiceItemFormatter extends InvoiceTestSuiteNoDB {
         final LocalDate startDate = new LocalDate(2012, 12, 1);
         final LocalDate endDate = new LocalDate(2012, 12, 31);
         final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
-                                                                            UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                            UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                                             startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, Currency.USD);
         checkOutput(recurringItem,
                     "{{#invoiceItem}}<td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td>{{/invoiceItem}}",
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 c4db83f..682022e 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
@@ -163,6 +163,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
                                                                                  subscription.getBundleId(),
                                                                                  subscription.getId(),
                                                                                  "Bad data",
+                                                                                 null,
                                                                                  plan.getName(),
                                                                                  planPhase.getName(),
                                                                                  null,
@@ -179,6 +180,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
                                                                                  subscription.getBundleId(),
                                                                                  subscription.getId(),
                                                                                  "Bad data",
+                                                                                 null,
                                                                                  plan.getName(),
                                                                                  planPhase.getName(),
                                                                                  null,
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 49f3ce2..4582e6d 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
@@ -59,6 +59,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
     private final UUID accountId = UUID.randomUUID();
     private final UUID subscriptionId = UUID.randomUUID();
     private final UUID bundleId = UUID.randomUUID();
+    private final String productName = "my-product";
     private final String planName = "my-plan";
     private final String phaseName = "my-phase";
     private final Currency currency = Currency.USD;
@@ -83,18 +84,18 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate = BigDecimal.TEN;
         final BigDecimal amount = rate;
 
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, rate, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount, rate, currency);
 
-        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startRepairDate1, endRepairDate1, amount, rate, currency);
+        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startRepairDate1, endRepairDate1, amount, rate, currency);
         final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, startRepairDate1, endRepairDate1, amount.negate(), currency, initial.getId());
 
-        final InvoiceItem newItem11 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startRepairDate11, endRepairDate12, amount, rate, currency);
+        final InvoiceItem newItem11 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startRepairDate11, endRepairDate12, amount, rate, currency);
         final InvoiceItem repair12 = new RepairAdjInvoiceItem(invoiceId, accountId, startRepairDate11, endRepairDate12, amount.negate(), currency, newItem1.getId());
 
-        final InvoiceItem newItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startRepairDate2, endRepairDate2, amount, rate, currency);
+        final InvoiceItem newItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startRepairDate2, endRepairDate2, amount, rate, currency);
         final InvoiceItem repair2 = new RepairAdjInvoiceItem(invoiceId, accountId, startRepairDate2, endRepairDate2, amount.negate(), currency, initial.getId());
 
-        final InvoiceItem newItem21 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startRepairDate21, endRepairDate22, amount, rate, currency);
+        final InvoiceItem newItem21 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startRepairDate21, endRepairDate22, amount, rate, currency);
         final InvoiceItem repair22 = new RepairAdjInvoiceItem(invoiceId, accountId, startRepairDate21, endRepairDate22, amount.negate(), currency, newItem2.getId());
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
@@ -121,8 +122,8 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate = new BigDecimal("40.00");
 
         // We assume we have the right items for the period [2016, 9, 8; 2016, 10, 8] but split in pieces
-        final InvoiceItem recurring1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, new LocalDate(2016, 9, 8), new LocalDate(2016, 9, 9), new BigDecimal("2.0"), rate, currency);
-        final InvoiceItem recurring2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, new LocalDate(2016, 9, 9), new LocalDate(2016, 10, 8), new BigDecimal("38.0"), rate, currency);
+        final InvoiceItem recurring1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, new LocalDate(2016, 9, 8), new LocalDate(2016, 9, 9), new BigDecimal("2.0"), rate, currency);
+        final InvoiceItem recurring2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, new LocalDate(2016, 9, 9), new LocalDate(2016, 10, 8), new BigDecimal("38.0"), rate, currency);
 
         final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
         existingItems.add(recurring1);
@@ -136,7 +137,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         tree.flatten(true);
 
         // We  generate the correct item for the period [2016, 9, 8; 2016, 10, 8]
-        final InvoiceItem proposedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, new LocalDate(2016, 9, 8), new LocalDate(2016, 10, 8), rate, rate, currency);
+        final InvoiceItem proposedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, new LocalDate(2016, 9, 8), new LocalDate(2016, 10, 8), rate, rate, currency);
         tree.mergeProposedItem(proposedItem);
         tree.buildForMerge();
 
@@ -161,7 +162,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         tree.flatten(true);
 
         // Regenerate proposedItem so it has a different id and Item#isSameKind correctly detect we are proposing the same kind
-        final InvoiceItem proposedItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, new LocalDate(2016, 9, 8), new LocalDate(2016, 10, 8), rate, rate, currency);
+        final InvoiceItem proposedItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, new LocalDate(2016, 9, 8), new LocalDate(2016, 10, 8), rate, rate, currency);
 
         tree.mergeProposedItem(proposedItem2);
         tree.buildForMerge();
@@ -184,14 +185,14 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate2 = new BigDecimal("14.85");
         final BigDecimal amount2 = rate2;
 
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
-        final InvoiceItem newItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", repairDate, endDate, amount2, rate2, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem newItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", "someelse", repairDate, endDate, amount2, rate2, currency);
         final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, repairDate, endDate, amount1.negate(), currency, initial.getId());
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
-        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, repairDate, new BigDecimal("8.52"), rate1, currency);
+        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, repairDate, new BigDecimal("8.52"), rate1, currency);
         expectedResult.add(expected1);
-        final InvoiceItem expected2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", repairDate, endDate, amount2, rate2, currency);
+        final InvoiceItem expected2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", "someelse", repairDate, endDate, amount2, rate2, currency);
         expectedResult.add(expected2);
 
         // First test with items in order
@@ -223,7 +224,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
 
         final BigDecimal rate = new BigDecimal("12.00");
 
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, rate, rate, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, rate, rate, currency);
         final InvoiceItem tooEarlyRepair = new RepairAdjInvoiceItem(invoiceId, accountId, startDate.minusDays(1), endDate, rate.negate(), currency, initial.getId());
         final InvoiceItem tooLateRepair = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, endDate.plusDays(1), rate.negate(), currency, initial.getId());
 
@@ -283,19 +284,19 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate3 = new BigDecimal("19.23");
         final BigDecimal amount3 = rate3;
 
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
-        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, repairDate1, endDate, amount2, rate2, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, repairDate1, endDate, amount2, rate2, currency);
         final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, repairDate1, endDate, amount1.negate(), currency, initial.getId());
 
-        final InvoiceItem newItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, repairDate2, endDate, amount3, rate3, currency);
+        final InvoiceItem newItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, repairDate2, endDate, amount3, rate3, currency);
         final InvoiceItem repair2 = new RepairAdjInvoiceItem(invoiceId, accountId, repairDate2, endDate, amount2.negate(), currency, newItem1.getId());
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
-        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, repairDate1, new BigDecimal("8.52"), rate1, currency);
+        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, repairDate1, new BigDecimal("8.52"), rate1, currency);
         expectedResult.add(expected1);
-        final InvoiceItem expected2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, repairDate1, repairDate2, new BigDecimal("4.95"), rate2, currency);
+        final InvoiceItem expected2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, repairDate1, repairDate2, new BigDecimal("4.95"), rate2, currency);
         expectedResult.add(expected2);
-        final InvoiceItem expected3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, repairDate2, endDate, amount3, rate3, currency);
+        final InvoiceItem expected3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, repairDate2, endDate, amount3, rate3, currency);
         expectedResult.add(expected3);
 
         // First test with items in order
@@ -342,16 +343,16 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate1 = new BigDecimal("12.00");
         final BigDecimal amount1 = rate1;
 
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
         final InvoiceItem block1 = new RepairAdjInvoiceItem(invoiceId, accountId, blockStart1, unblockStart1, amount1.negate(), currency, initial.getId());
         final InvoiceItem block2 = new RepairAdjInvoiceItem(invoiceId, accountId, blockStart2, unblockStart2, amount1.negate(), currency, initial.getId());
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
-        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, blockStart1, new BigDecimal("2.71"), rate1, currency);
+        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, blockStart1, new BigDecimal("2.71"), rate1, currency);
         expectedResult.add(expected1);
-        final InvoiceItem expected2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockStart1, blockStart2, new BigDecimal("2.71"), rate1, currency);
+        final InvoiceItem expected2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, unblockStart1, blockStart2, new BigDecimal("2.71"), rate1, currency);
         expectedResult.add(expected2);
-        final InvoiceItem expected3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockStart2, endDate, new BigDecimal("3.48"), rate1, currency);
+        final InvoiceItem expected3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, unblockStart2, endDate, new BigDecimal("3.48"), rate1, currency);
         expectedResult.add(expected3);
 
         // First test with items in order
@@ -375,14 +376,14 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate1 = new BigDecimal("12.00");
         final BigDecimal amount1 = rate1;
 
-        final InvoiceItem first = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate1, startDate2, amount1, rate1, currency);
-        final InvoiceItem second = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate2, endDate, amount1, rate1, currency);
+        final InvoiceItem first = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate1, startDate2, amount1, rate1, currency);
+        final InvoiceItem second = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate2, endDate, amount1, rate1, currency);
         final InvoiceItem block1 = new RepairAdjInvoiceItem(invoiceId, accountId, blockDate, startDate2, amount1.negate(), currency, first.getId());
         final InvoiceItem block2 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate2, unblockDate, amount1.negate(), currency, first.getId());
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
-        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate1, blockDate, new BigDecimal("9.29"), rate1, currency);
-        final InvoiceItem expected2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate, endDate, new BigDecimal("9.43"), rate1, currency);
+        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate1, blockDate, new BigDecimal("9.29"), rate1, currency);
+        final InvoiceItem expected2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, unblockDate, endDate, new BigDecimal("9.43"), rate1, currency);
         expectedResult.add(expected1);
         expectedResult.add(expected2);
 
@@ -409,10 +410,10 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate2 = new BigDecimal("10.00");
         final BigDecimal amount2 = rate2;
 
-        final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
         final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, endDate, amount1.negate(), currency, annual.getId());
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", startDate, firstMonthlyEndDate, amount2, rate2, currency);
-        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", firstMonthlyEndDate, secondMonthlyEndDate, amount2, rate2, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse","someelse", "someelse", startDate, firstMonthlyEndDate, amount2, rate2, currency);
+        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", "someelse", firstMonthlyEndDate, secondMonthlyEndDate, amount2, rate2, currency);
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
         expectedResult.add(monthly1);
@@ -458,15 +459,15 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal yearlyRate = new BigDecimal("100.00");
         final BigDecimal yearlyAmount = yearlyRate;
 
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, endMonthly2, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, endMonthly1, endMonthly2, monthlyAmount, monthlyRate, currency);
         final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, switchToAnnualDate, endMonthly2, monthlyAmount.negate(), currency, monthly2.getId());
-        final InvoiceItem leadingAnnualProration = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, switchToAnnualDate, endMonthly2, yearlyAmount, yearlyRate, currency);
-        final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly2, endDate, yearlyAmount, yearlyRate, currency);
+        final InvoiceItem leadingAnnualProration = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, switchToAnnualDate, endMonthly2, yearlyAmount, yearlyRate, currency);
+        final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, endMonthly2, endDate, yearlyAmount, yearlyRate, currency);
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
         expectedResult.add(monthly1);
-        final InvoiceItem monthly2Prorated = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, switchToAnnualDate, new BigDecimal("9.43"), monthlyRate, currency);
+        final InvoiceItem monthly2Prorated = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, endMonthly1, switchToAnnualDate, new BigDecimal("9.43"), monthlyRate, currency);
         expectedResult.add(monthly2Prorated);
         expectedResult.add(leadingAnnualProration);
         expectedResult.add(annual);
@@ -497,14 +498,14 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal yearlyRate = new BigDecimal("100.00");
         final BigDecimal yearlyAmount = yearlyRate;
 
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, endMonthly2, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, endMonthly1, endMonthly2, monthlyAmount, monthlyRate, currency);
         final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, switchToAnnualDate, endMonthly2, monthlyAmount.negate(), currency, monthly2.getId());
-        final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, switchToAnnualDate, endDate, yearlyAmount, yearlyRate, currency);
+        final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, switchToAnnualDate, endDate, yearlyAmount, yearlyRate, currency);
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
         expectedResult.add(monthly1);
-        final InvoiceItem monthly2Prorated = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, switchToAnnualDate, new BigDecimal("9.43"), monthlyRate, currency);
+        final InvoiceItem monthly2Prorated = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, endMonthly1, switchToAnnualDate, new BigDecimal("9.43"), monthlyRate, currency);
         expectedResult.add(monthly2Prorated);
         expectedResult.add(annual);
 
@@ -527,8 +528,8 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate = BigDecimal.TEN;
         final BigDecimal amount = rate;
 
-        final InvoiceItem recurring1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate1, endDate, amount, rate, currency);
-        final InvoiceItem recurring2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate2, endDate, amount, rate, currency);
+        final InvoiceItem recurring1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate1, endDate, amount, rate, currency);
+        final InvoiceItem recurring2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate2, endDate, amount, rate, currency);
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
         tree.addItem(recurring1);
@@ -549,8 +550,8 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate = BigDecimal.TEN;
         final BigDecimal amount = rate;
 
-        final InvoiceItem recurring1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate1, endDate, amount, rate, currency);
-        final InvoiceItem recurring2 = new RecurringInvoiceItem(UUID.randomUUID(), accountId, bundleId, subscriptionId, planName, phaseName, startDate1, endDate, amount, rate, currency);
+        final InvoiceItem recurring1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate1, endDate, amount, rate, currency);
+        final InvoiceItem recurring2 = new RecurringInvoiceItem(UUID.randomUUID(), accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate1, endDate, amount, rate, currency);
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
         tree.addItem(recurring1);
@@ -581,11 +582,11 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate3 = new BigDecimal("19.23");
         final BigDecimal amount3 = rate3;
 
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
-        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, repairDate1, endDate, amount2, rate2, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, repairDate1, endDate, amount2, rate2, currency);
         final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, repairDate1, endDate, amount1.negate(), currency, initial.getId());
 
-        final InvoiceItem newItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, repairDate2, endDate, amount3, rate3, currency);
+        final InvoiceItem newItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, repairDate2, endDate, amount3, rate3, currency);
         // This repair should point to newItem1 instead
         final InvoiceItem repair2 = new RepairAdjInvoiceItem(invoiceId, accountId, repairDate2, endDate, amount2.negate(), currency, initial.getId());
 
@@ -619,8 +620,8 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal rate2 = new BigDecimal("14.85");
         final BigDecimal amount2 = rate2;
 
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
-        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, repairDate1, endDate, amount2, rate2, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, repairDate1, endDate, amount2, rate2, currency);
         final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, repairDate2, endDate, amount1.negate(), currency, initial.getId());
 
         // Out-of-order insertion to show ordering doesn't matter
@@ -653,15 +654,15 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal amount2 = rate2;
 
         // Start with a ANNUAL plan (high rate, rate1)
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
 
         // Change to MONTHLY plan 10 days later (low rate, rate2)
         final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, changeDate, endDate, amount1.negate(), currency, initial.getId());
-        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", changeDate, monthlyAlignmentDate, amount2, rate2, currency);
+        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", "someelse", changeDate, monthlyAlignmentDate, amount2, rate2, currency);
 
         // On the same day, now revert back to ANNUAL
         final InvoiceItem repair2 = new RepairAdjInvoiceItem(invoiceId, accountId, changeDate, monthlyAlignmentDate, amount2.negate(), currency, newItem1.getId());
-        final InvoiceItem newItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, changeDate, endDate, amount1, rate1, currency);
+        final InvoiceItem newItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, changeDate, endDate, amount1, rate1, currency);
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
         tree.addItem(initial);
@@ -671,7 +672,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         tree.addItem(repair2);
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
-        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, changeDate, new BigDecimal("65.75"), rate1, currency);
+        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, changeDate, new BigDecimal("65.75"), rate1, currency);
         expectedResult.add(expected1);
         expectedResult.add(newItem2);
 
@@ -696,10 +697,10 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyRate = new BigDecimal("12.00");
         final BigDecimal monthlyAmount = monthlyRate;
 
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate1, endDate1, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate2, endDate2, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate1, endDate1, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate2, endDate2, monthlyAmount, monthlyRate, currency);
         final InvoiceItem repairMonthly2 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate2, endDate2, new BigDecimal("-12.00"), currency, monthly2.getId());
-        final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate3, endDate3, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate3, endDate3, monthlyAmount, monthlyRate, currency);
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
         expectedResult.add(monthly1);
@@ -735,13 +736,13 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyRateFinal = new BigDecimal("15.00");
         final BigDecimal monthlyAmountFinal = monthlyRateFinal;
 
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate1, endDate1, monthlyRateInit, monthlyAmountInit, currency);
-        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate2, endDate2, monthlyRateInit, monthlyAmountInit, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate1, endDate1, monthlyRateInit, monthlyAmountInit, currency);
+        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate2, endDate2, monthlyRateInit, monthlyAmountInit, currency);
         final InvoiceItem repairMonthly2 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate2, endDate2, new BigDecimal("-12.00"), currency, monthly2.getId());
 
-        final InvoiceItem monthly2New = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate2, endDate2, monthlyRateFinal, monthlyAmountFinal, currency);
+        final InvoiceItem monthly2New = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate2, endDate2, monthlyRateFinal, monthlyAmountFinal, currency);
 
-        final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate3, endDate3, monthlyRateFinal, monthlyAmountFinal, currency);
+        final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate3, endDate3, monthlyRateFinal, monthlyAmountFinal, currency);
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
         expectedResult.add(monthly1);
@@ -774,19 +775,19 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyAmount = monthlyRate;
 
 
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         final InvoiceItem repair11 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, intermediate1, new BigDecimal("3.00"), currency, monthly1.getId());
         final InvoiceItem repair12 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate1, intermediate2, new BigDecimal("3.00"), currency, monthly1.getId());
         final InvoiceItem repair13 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate2, intermediate3, new BigDecimal("3.00"), currency, monthly1.getId());
         final InvoiceItem repair14 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate3, endDate, new BigDecimal("3.00"), currency, monthly1.getId());
 
-        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         final InvoiceItem repair21 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, intermediate1, new BigDecimal("3.00"), currency, monthly2.getId());
         final InvoiceItem repair22 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate1, intermediate2, new BigDecimal("3.00"), currency, monthly2.getId());
         final InvoiceItem repair23 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate2, intermediate3, new BigDecimal("3.00"), currency, monthly2.getId());
         final InvoiceItem repair24 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate3, endDate, new BigDecimal("3.00"), currency, monthly2.getId());
 
-        final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
 
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
@@ -826,16 +827,16 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyAmount = monthlyRate;
 
 
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         final InvoiceItem repair11 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, endDate, new BigDecimal("-12.00"), currency, monthly1.getId());
 
-        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         final InvoiceItem repair21 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, intermediate1, new BigDecimal("3.00"), currency, monthly2.getId());
         final InvoiceItem repair22 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate1, intermediate2, new BigDecimal("3.00"), currency, monthly2.getId());
         final InvoiceItem repair23 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate2, intermediate3, new BigDecimal("3.00"), currency, monthly2.getId());
         final InvoiceItem repair24 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate3, endDate, new BigDecimal("3.00"), currency, monthly2.getId());
 
-        final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
 
 
         final List<InvoiceItem> expectedResult = Lists.newLinkedList();
@@ -870,7 +871,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         tree.mergeProposedItem(proposed1);
         tree.buildForMerge();
 
@@ -889,11 +890,11 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyAmount = monthlyRate;
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         tree.addItem(monthly1);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
 
         tree.mergeProposedItem(proposed1);
         tree.buildForMerge();
@@ -915,11 +916,11 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyAmount2 = monthlyRate2;
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
         tree.addItem(monthly1);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount2, monthlyRate2, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount2, monthlyRate2, currency);
 
         tree.mergeProposedItem(proposed1);
         tree.buildForMerge();
@@ -942,11 +943,11 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyAmount1 = monthlyRate1;
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
         tree.addItem(monthly1);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, blockDate, endDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, blockDate, endDate, monthlyAmount1, monthlyRate1, currency);
 
         tree.mergeProposedItem(proposed1);
         tree.buildForMerge();
@@ -968,11 +969,11 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyAmount1 = monthlyRate1;
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
         tree.addItem(monthly1);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, cancelDate, monthlyAmount1, monthlyRate1, currency);
         tree.mergeProposedItem(proposed1);
         tree.buildForMerge();
 
@@ -994,12 +995,12 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyAmount1 = monthlyRate1;
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
         tree.addItem(monthly1);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, blockDate, monthlyAmount1, monthlyRate1, currency);
-        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate, endDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, blockDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, unblockDate, endDate, monthlyAmount1, monthlyRate1, currency);
 
         tree.mergeProposedItem(proposed1);
         tree.mergeProposedItem(proposed2);
@@ -1025,13 +1026,13 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyAmount = monthlyRate;
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         tree.addItem(monthly);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, blockDate1, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate1, blockDate2, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem proposed3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate2, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, blockDate1, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, unblockDate1, blockDate2, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, unblockDate2, endDate, monthlyAmount, monthlyRate, currency);
 
         tree.mergeProposedItem(proposed1);
         tree.mergeProposedItem(proposed2);
@@ -1047,13 +1048,13 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
 
         // Dot it again but with proposed items out of order
         final SubscriptionItemTree treeAgain = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthlyAgain = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthlyAgain = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         treeAgain.addItem(monthlyAgain);
         treeAgain.flatten(true);
 
-        final InvoiceItem proposed2Again = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate1, blockDate2, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem proposed1Again = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, blockDate1, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem proposed3Again = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate2, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed2Again = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, unblockDate1, blockDate2, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed1Again = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, blockDate1, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed3Again = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, unblockDate2, endDate, monthlyAmount, monthlyRate, currency);
 
         treeAgain.mergeProposedItem(proposed1Again);
         treeAgain.mergeProposedItem(proposed2Again);
@@ -1077,12 +1078,12 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyAmount2 = monthlyRate1;
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
         tree.addItem(monthly1);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, upgradeDate, monthlyAmount1, monthlyRate1, currency);
-        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "foo", "foo", upgradeDate, endDate, monthlyAmount2, monthlyRate2, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, upgradeDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "foo", "foo", "foo", upgradeDate, endDate, monthlyAmount2, monthlyRate2, currency);
         tree.mergeProposedItem(proposed1);
         tree.mergeProposedItem(proposed2);
         tree.buildForMerge();
@@ -1112,8 +1113,8 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal proratedAmount3 = new BigDecimal("23.19");
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
-        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "foo", "foo", change1, endDate, proratedAmount2, rate2, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "foo", "foo", "foo", change1, endDate, proratedAmount2, rate2, currency);
         final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, change1, endDate, new BigDecimal("-483.86"), currency, initial.getId());
 
         tree.addItem(initial);
@@ -1121,9 +1122,9 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         tree.addItem(repair1);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, change1, amount1, rate1, currency);
-        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "foo", "foo", change1, change2, proratedAmount3, rate2, currency);
-        final InvoiceItem proposed3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "bar", "bar", change2, endDate, proratedAmount3, rate3, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, change1, amount1, rate1, currency);
+        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "foo", "foo", "foo", change1, change2, proratedAmount3, rate2, currency);
+        final InvoiceItem proposed3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "bar", "bar", "bar", change2, endDate, proratedAmount3, rate3, currency);
         tree.mergeProposedItem(proposed1);
         tree.mergeProposedItem(proposed2);
         tree.mergeProposedItem(proposed3);
@@ -1147,13 +1148,13 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal fixedAmount = new BigDecimal("5.00");
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem fixed = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, fixedAmount, currency);
+        final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem fixed = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, fixedAmount, currency);
         tree.addItem(monthly);
         tree.addItem(fixed);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         tree.mergeProposedItem(proposed1);
         tree.mergeProposedItem(fixed);
         tree.buildForMerge();
@@ -1173,12 +1174,12 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal fixedAmount = new BigDecimal("5.00");
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
         tree.addItem(monthly);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem fixed = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, fixedAmount, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem fixed = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, fixedAmount, currency);
         tree.mergeProposedItem(proposed1);
         tree.mergeProposedItem(fixed);
         tree.buildForMerge();
@@ -1201,13 +1202,13 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal amount1 = rate1;
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
         final InvoiceItem itemAdj = new ItemAdjInvoiceItem(initial, itemAdjDate, new BigDecimal("-2.00"), currency);
         tree.addItem(initial);
         tree.addItem(itemAdj);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
         tree.mergeProposedItem(proposed1);
         tree.buildForMerge();
 
@@ -1231,13 +1232,13 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal amount1 = rate1;
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, amount1, rate1, currency);
         final InvoiceItem itemAdj = new ItemAdjInvoiceItem(initial, itemAdjDate, new BigDecimal("-10.00"), currency);
         tree.addItem(initial);
         tree.addItem(itemAdj);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
         tree.mergeProposedItem(proposed1);
         tree.buildForMerge();
 
@@ -1263,8 +1264,8 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal yearlyRate = new BigDecimal("100.00");
         final BigDecimal yearlyAmount = yearlyRate;
 
-        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
-        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, endMonthly2, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, endMonthly1, endMonthly2, monthlyAmount, monthlyRate, currency);
 
         // First test with items in order
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
@@ -1272,10 +1273,10 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         tree.addItem(monthly2);
         tree.flatten(true);
 
-        final InvoiceItem proposed = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, switchToAnnualDate, endDate, yearlyAmount, yearlyRate, currency);
-        final InvoiceItem proposedMonthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, switchToAnnualDate, endDate, yearlyAmount, yearlyRate, currency);
+        final InvoiceItem proposedMonthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
         tree.mergeProposedItem(proposedMonthly1);
-        final InvoiceItem proRatedmonthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, switchToAnnualDate, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proRatedmonthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, endMonthly1, switchToAnnualDate, monthlyAmount, monthlyRate, currency);
         tree.mergeProposedItem(proRatedmonthly2);
         tree.mergeProposedItem(proposed);
         tree.buildForMerge();
@@ -1292,11 +1293,11 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
     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);
+        final InvoiceItem yearly1 = new RecurringInvoiceItem(id1, new DateTime(), invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, new LocalDate("2014-01-01"), new LocalDate("2015-01-01"), BigDecimal.TEN, BigDecimal.TEN, currency);
         tree.addItem(yearly1);
 
         final UUID id2 = UUID.fromString("48db1317-9a6e-4666-bcc5-fc7d3d0defc8");
-        final InvoiceItem newItem = new RecurringInvoiceItem(id2, new DateTime(), invoiceId, accountId, bundleId, subscriptionId, "other-plan", "other-plan", new LocalDate("2014-08-01"), new LocalDate("2015-01-01"), BigDecimal.ONE, BigDecimal.ONE, currency);
+        final InvoiceItem newItem = new RecurringInvoiceItem(id2, new DateTime(), invoiceId, accountId, bundleId, subscriptionId, "other-product","other-plan", "other-plan", new LocalDate("2014-08-01"), new LocalDate("2015-01-01"), BigDecimal.ONE, BigDecimal.ONE, currency);
         tree.addItem(newItem);
 
         final UUID id3 = UUID.fromString("02ec57f5-2723-478b-86ba-ebeaedacb9db");
@@ -1323,7 +1324,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
 
-        final InvoiceItem existing1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem existing1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
         tree.addItem(existing1);
         // Fully item adjust the recurring item
         final InvoiceItem existingItemAdj1 = new ItemAdjInvoiceItem(existing1, startDate, monthlyRate1.negate(), currency);
@@ -1332,7 +1333,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
 
         //printTree(tree);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, cancelDate, monthlyAmount1, monthlyRate1, currency);
         tree.mergeProposedItem(proposed1);
         tree.buildForMerge();
 
@@ -1354,14 +1355,14 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
 
-        final InvoiceItem existing1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem existing1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
         tree.addItem(existing1);
         // Partially item adjust the recurring item
         final InvoiceItem existingItemAdj1 = new ItemAdjInvoiceItem(existing1, startDate, monthlyRate1.negate().add(BigDecimal.ONE), currency);
         tree.addItem(existingItemAdj1);
         tree.flatten(true);
 
-        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, monthlyAmount1, monthlyRate1, currency);
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, cancelDate, monthlyAmount1, monthlyRate1, currency);
         tree.mergeProposedItem(proposed1);
         tree.buildForMerge();
 
@@ -1385,6 +1386,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
                                                                       accountId,
                                                                       bundleId,
                                                                       subscriptionId,
+                                                                      productName,
                                                                       planName,
                                                                       phaseName,
                                                                       wrongStartDate,
@@ -1404,6 +1406,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
                                                                         accountId,
                                                                         bundleId,
                                                                         subscriptionId,
+                                                                        productName,
                                                                         planName,
                                                                         phaseName,
                                                                         correctStartDate,
@@ -1456,7 +1459,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
 
         final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
         final List<InvoiceItem> proposedItems = new ArrayList<InvoiceItem>();
-        final InvoiceItem wrongInitialItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, wrongStartDate, endDate, amount1, rate1, currency);
+        final InvoiceItem wrongInitialItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, wrongStartDate, endDate, amount1, rate1, currency);
         proposedItems.add(wrongInitialItem);
 
         int previousExistingSize = existingItems.size();
@@ -1484,7 +1487,7 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
             previousExistingSize = existingItems.size();
 
             proposedItems.clear();
-            final InvoiceItem correctInitialItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, correctStartDate, endDate, amount1, rate1, currency);
+            final InvoiceItem correctInitialItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, correctStartDate, endDate, amount1, rate1, currency);
             proposedItems.add(correctInitialItem);
             iteration++;
         } while (iteration < 10);
@@ -1502,13 +1505,13 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         final BigDecimal monthlyRate2 = new BigDecimal("24.00");
 
         final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
-        final InvoiceItem freeMonthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.ZERO, BigDecimal.ZERO, currency);
+        final InvoiceItem freeMonthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, BigDecimal.ZERO, BigDecimal.ZERO, currency);
         tree.addItem(freeMonthly);
-        final InvoiceItem payingMonthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyRate1, monthlyRate1, currency);
+        final InvoiceItem payingMonthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyRate1, monthlyRate1, currency);
         tree.addItem(payingMonthly1);
         tree.flatten(true);
 
-        final InvoiceItem proposedPayingMonthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyRate2, monthlyRate2, currency);
+        final InvoiceItem proposedPayingMonthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, endDate, monthlyRate2, monthlyRate2, currency);
         tree.mergeProposedItem(proposedPayingMonthly2);
         tree.buildForMerge();
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
index 043d29b..43897cc 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
@@ -92,21 +92,21 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
                                                                                                                  );
 
         final List<InvoiceItem> existingUsage = Lists.newArrayList();
-        final UsageInvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
+        final UsageInvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
         existingUsage.add(ii1);
-        final UsageInvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
+        final UsageInvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
         existingUsage.add(ii2);
 
         // Will be ignored as is starts one day earlier.
-        final UsageInvoiceItem ii3 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate.minusDays(1), endDate, BigDecimal.TEN, currency);
+        final UsageInvoiceItem ii3 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), startDate.minusDays(1), endDate, BigDecimal.TEN, currency);
         existingUsage.add(ii3);
 
         // Will be ignored as it is for a different udsage section
-        final UsageInvoiceItem ii4 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, "other", startDate, endDate, BigDecimal.TEN, currency);
+        final UsageInvoiceItem ii4 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, "other", startDate, endDate, BigDecimal.TEN, currency);
         existingUsage.add(ii4);
 
         // Will be ignored because non usage item
-        final FixedPriceInvoiceItem ii5 = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, BigDecimal.TEN, currency);
+        final FixedPriceInvoiceItem ii5 = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, BigDecimal.TEN, currency);
         existingUsage.add(ii5);
 
         final Iterable<InvoiceItem> billedItems = intervalCapacityInArrear.getBilledItems(startDate, endDate, existingUsage);
@@ -216,10 +216,10 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
         final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, true, event1, event2);
 
         final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
-        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, firstBCDDate, BigDecimal.ONE, currency);
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), startDate, firstBCDDate, BigDecimal.ONE, currency);
         invoiceItems.add(ii1);
 
-        final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
+        final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
         invoiceItems.add(ii2);
 
         final UsageInArrearItemsAndNextNotificationDate usageResult = intervalCapacityInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
@@ -362,7 +362,7 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
         final String existingUsageJson = objectMapper.writeValueAsString(new UsageCapacityInArrearAggregate(existingUsage, BigDecimal.TEN));
 
         final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
-        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), BigDecimal.TEN, null, currency, null, existingUsageJson);
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), BigDecimal.TEN, null, currency, null, existingUsageJson);
         existingItems.add(ii1);
 
         List<InvoiceItem> result = produceInvoiceItems(rawUsages, UsageDetailMode.AGGREGATE, existingItems);
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 bc7f22c..5e13615 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
@@ -113,7 +113,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         final String existingUsageJson = objectMapper.writeValueAsString(usageConsumableInArrearDetail);
 
         final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
-        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("570.00"), null, currency, null, existingUsageJson);
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("570.00"), null, currency, null, existingUsageJson);
         existingItems.add(ii1);
 
         final List<UsageConsumableInArrearTierUnitAggregate> aggregateDetails = intervalConsumableInArrear.getBilledDetailsForUnitType(existingItems, "FOO");
@@ -145,21 +145,21 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
                                                                                                                      );
 
         final List<InvoiceItem> existingUsage = Lists.newArrayList();
-        final UsageInvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
+        final UsageInvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
         existingUsage.add(ii1);
-        final UsageInvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
+        final UsageInvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
         existingUsage.add(ii2);
 
         // Will be ignored as is starts one day earlier.
-        final UsageInvoiceItem ii3 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate.minusDays(1), endDate, BigDecimal.TEN, currency);
+        final UsageInvoiceItem ii3 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), startDate.minusDays(1), endDate, BigDecimal.TEN, currency);
         existingUsage.add(ii3);
 
         // Will be ignored as it is for a different udsage section
-        final UsageInvoiceItem ii4 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, "other", startDate, endDate, BigDecimal.TEN, currency);
+        final UsageInvoiceItem ii4 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, "other", startDate, endDate, BigDecimal.TEN, currency);
         existingUsage.add(ii4);
 
         // Will be ignored because non usage item
-        final FixedPriceInvoiceItem ii5 = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, BigDecimal.TEN, currency);
+        final FixedPriceInvoiceItem ii5 = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, startDate, BigDecimal.TEN, currency);
         existingUsage.add(ii5);
 
         final BigDecimal result = intervalConsumableInArrear.computeBilledUsage(intervalConsumableInArrear.getBilledItems(startDate, endDate, existingUsage));
@@ -381,10 +381,10 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, event1, event2);
 
         final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
-        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, firstBCDDate, BigDecimal.ONE, currency);
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), startDate, firstBCDDate, BigDecimal.ONE, currency);
         invoiceItems.add(ii1);
 
-        final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
+        final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
         invoiceItems.add(ii2);
 
         final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
@@ -880,7 +880,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 80L /* already built */ + 120L)); // tier 2
 
         final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
-        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("570.00"), null, currency, null, existingUsageJson);
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("570.00"), null, currency, null, existingUsageJson);
         existingItems.add(ii1);
 
         List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, existingItems);
@@ -945,9 +945,9 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
         // Same as previous example bu instead of creating JSON we create one item per type/tier
         final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
-        final InvoiceItem i1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("10.00") /* amount */, new BigDecimal("1.00") /* rate = tierPrice*/, currency, 10 /* # units*/, usageInArrearDetail1);
-        final InvoiceItem i2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("400.00"), new BigDecimal("10.00"), currency, 40, usageInArrearDetail2);
-        final InvoiceItem i3 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("160.00"), new BigDecimal("2.00"), currency, 80, usageInArrearDetail3);
+        final InvoiceItem i1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("10.00") /* amount */, new BigDecimal("1.00") /* rate = tierPrice*/, currency, 10 /* # units*/, usageInArrearDetail1);
+        final InvoiceItem i2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("400.00"), new BigDecimal("10.00"), currency, 40, usageInArrearDetail2);
+        final InvoiceItem i3 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("160.00"), new BigDecimal("2.00"), currency, 80, usageInArrearDetail3);
         existingItems.addAll(ImmutableList.<InvoiceItem>of(i1, i2, i3));
 
         List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, existingItems);
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 41201b9..5939e2f 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
@@ -127,6 +127,6 @@ public class TestRawUsageOptimizer extends TestUsageInArrearBase {
     }
 
     private InvoiceItem createUsageItem(final LocalDate startDate) {
-        return new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, startDate, startDate.plusMonths(1), BigDecimal.TEN, Currency.USD);
+        return new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, startDate, startDate.plusMonths(1), BigDecimal.TEN, Currency.USD);
     }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
index 42c1709..93ca59f 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
@@ -36,6 +36,7 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.TierBlockPolicy;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.catalog.api.UsageType;
@@ -55,6 +56,7 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
     protected UUID bundleId;
     protected UUID subscriptionId;
     protected UUID invoiceId;
+    protected String productName;
     protected String planName;
     protected String phaseName;
     protected Currency currency;
@@ -71,6 +73,7 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
         bundleId = UUID.randomUUID();
         subscriptionId = UUID.randomUUID();
         invoiceId = UUID.randomUUID();
+        productName = "productName";
         planName = "planName";
         phaseName = "phaseName";
         currency = Currency.BTC;
@@ -174,8 +177,12 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
         Mockito.when(subscription.getBundleId()).thenReturn(bundleId);
         Mockito.when(result.getSubscription()).thenReturn(subscription);
 
+        final Product product = Mockito.mock(Product.class);
+        Mockito.when(product.getName()).thenReturn(productName);
+
         final Plan plan = Mockito.mock(Plan.class);
         Mockito.when(plan.getName()).thenReturn(planName);
+        Mockito.when(plan.getProduct()).thenReturn(product);
         Mockito.when(result.getPlan()).thenReturn(plan);
 
         final PlanPhase phase = Mockito.mock(PlanPhase.class);

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

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index cbde768..712326e 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-jaxrs</artifactId>
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 f19cc88..6e33670 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
@@ -49,9 +49,11 @@ public class InvoiceItemJson extends JsonBase {
     private final UUID childAccountId;
     private final UUID bundleId;
     private final UUID subscriptionId;
+    private final String productName;
     private final String planName;
     private final String phaseName;
     private final String usageName;
+    private final String prettyProductName;
     private final String prettyPlanName;
     private final String prettyPhaseName;
     private final String prettyUsageName;
@@ -74,9 +76,11 @@ public class InvoiceItemJson extends JsonBase {
                            @JsonProperty("childAccountId") final UUID childAccountId,
                            @JsonProperty("bundleId") final UUID bundleId,
                            @JsonProperty("subscriptionId") final UUID subscriptionId,
+                           @JsonProperty("productName") final String productName,
                            @JsonProperty("planName") final String planName,
                            @JsonProperty("phaseName") final String phaseName,
                            @JsonProperty("usageName") final String usageName,
+                           @JsonProperty("prettyProductName") final String prettyProductName,
                            @JsonProperty("prettyPlanName") final String prettyPlanName,
                            @JsonProperty("prettyPhaseName") final String prettyPhaseName,
                            @JsonProperty("prettyUsageName") final String prettyUsageName,
@@ -99,9 +103,11 @@ public class InvoiceItemJson extends JsonBase {
         this.childAccountId = childAccountId;
         this.bundleId = bundleId;
         this.subscriptionId = subscriptionId;
+        this.productName = productName;
         this.planName = planName;
         this.phaseName = phaseName;
         this.usageName = usageName;
+        this.prettyProductName = prettyProductName;
         this.prettyPlanName = prettyPlanName;
         this.prettyPhaseName = prettyPhaseName;
         this.prettyUsageName = prettyUsageName;
@@ -120,8 +126,8 @@ public class InvoiceItemJson extends JsonBase {
     public InvoiceItemJson(final InvoiceItem item, final List<InvoiceItem> childItems, @Nullable final List<AuditLog> auditLogs) {
         this(item.getId(), item.getInvoiceId(), item.getLinkedItemId(),
              item.getAccountId(), item.getChildAccountId(), item.getBundleId(), item.getSubscriptionId(),
-             item.getPlanName(), item.getPhaseName(), item.getUsageName(),
-             item.getPrettyPlanName(), item.getPrettyPhaseName(), item.getPrettyUsageName(),
+             item.getProductName(), item.getPlanName(), item.getPhaseName(), item.getUsageName(),
+             item.getPrettyProductName(), item.getPrettyPlanName(), item.getPrettyPhaseName(), item.getPrettyUsageName(),
              item.getInvoiceItemType().toString(),
              item.getDescription(), item.getStartDate(), item.getEndDate(),
              item.getAmount(), item.getRate(), item.getCurrency().name(),
@@ -198,6 +204,16 @@ public class InvoiceItemJson extends JsonBase {
             }
 
             @Override
+            public String getProductName() {
+                return null;
+            }
+
+            @Override
+            public String getPrettyProductName() {
+                return null;
+            }
+
+            @Override
             public String getPlanName() {
                 return planName;
             }
@@ -297,6 +313,10 @@ public class InvoiceItemJson extends JsonBase {
         return subscriptionId;
     }
 
+    public String getProductName() {
+        return productName;
+    }
+
     public String getPlanName() {
         return planName;
     }
@@ -309,6 +329,10 @@ public class InvoiceItemJson extends JsonBase {
         return usageName;
     }
 
+    public String getPrettyProductName() {
+        return prettyProductName;
+    }
+
     public String getPrettyPlanName() {
         return prettyPlanName;
     }
@@ -366,6 +390,7 @@ public class InvoiceItemJson extends JsonBase {
         sb.append(", childAccountId='").append(childAccountId).append('\'');
         sb.append(", bundleId='").append(bundleId).append('\'');
         sb.append(", subscriptionId='").append(subscriptionId).append('\'');
+        sb.append(", productName='").append(productName).append('\'');
         sb.append(", planName='").append(planName).append('\'');
         sb.append(", phaseName='").append(phaseName).append('\'');
         sb.append(", usageName='").append(usageName).append('\'');
@@ -433,6 +458,9 @@ public class InvoiceItemJson extends JsonBase {
         if (planName != null ? !planName.equals(that.planName) : that.planName != null) {
             return false;
         }
+        if (productName != null ? !productName.equals(that.productName) : that.productName != null) {
+            return false;
+        }
         if (!((startDate == null && that.startDate == null) ||
               (startDate != null && that.startDate != null && startDate.compareTo(that.startDate) == 0))) {
             return false;
@@ -465,6 +493,7 @@ public class InvoiceItemJson extends JsonBase {
         result = 31 * result + (childAccountId != null ? childAccountId.hashCode() : 0);
         result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
         result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (productName != null ? productName.hashCode() : 0);
         result = 31 * result + (planName != null ? planName.hashCode() : 0);
         result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
         result = 31 * result + (usageName != null ? usageName.hashCode() : 0);
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 d590177..f5abcdf 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
@@ -117,7 +117,7 @@ public class CreditResource extends JaxRsResourceBase {
         verifyNonNullOrEmpty(json.getAccountId(), "CreditJson accountId needs to be set",
                              json.getCreditAmount(), "CreditJson creditAmount needs to be set");
 
-        final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
+        final CallContext callContext = context.createCallContextWithAccountId(json.getAccountId(), createdBy, reason, comment, request);
 
         final Account account = accountUserApi.getAccountById(json.getAccountId(), callContext);
         final LocalDate effectiveDate = new LocalDate(callContext.getCreatedDate(), account.getTimeZone());
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 4726dc4..7ca3659 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
@@ -563,9 +563,11 @@ public class InvoiceResource extends JaxRsResourceBase {
                                                    input.getChildAccountId(),
                                                    input.getBundleId(),
                                                    input.getSubscriptionId(),
+                                                   input.getProductName(),
                                                    input.getPlanName(),
                                                    input.getPhaseName(),
                                                    input.getUsageName(),
+                                                   input.getPrettyProductName(),
                                                    input.getPrettyPlanName(),
                                                    input.getPrettyPhaseName(),
                                                    input.getPrettyUsageName(),
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 1e51050..000d28d 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
@@ -59,9 +59,12 @@ import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.clock.Clock;
 import org.killbill.commons.metrics.TimedResource;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Ordering;
 import com.google.inject.Singleton;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -124,8 +127,11 @@ public class UsageResource extends JaxRsResourceBase {
         final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
         // Verify subscription exists..
         final Entitlement entitlement = entitlementApi.getEntitlementForId(json.getSubscriptionId(), callContext);
-        if (entitlement.getState() != EntitlementState.ACTIVE) {
-            return Response.status(Status.BAD_REQUEST).build();
+        if (entitlement.getEffectiveEndDate() != null) {
+            final LocalDate highestRecordDate = getHighestRecordDate(json.getUnitUsageRecords());
+            if (entitlement.getEffectiveEndDate().compareTo(highestRecordDate) < 0) {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         }
 
         final SubscriptionUsageRecord record = json.toSubscriptionUsageRecord();
@@ -133,6 +139,28 @@ public class UsageResource extends JaxRsResourceBase {
         return Response.status(Status.CREATED).build();
     }
 
+    @VisibleForTesting
+    LocalDate getHighestRecordDate(final List<UnitUsageRecordJson> records) {
+        final Iterable<Iterable<LocalDate>> recordedDates = Iterables.transform(records, new Function<UnitUsageRecordJson, Iterable<LocalDate>>() {
+
+            @Override
+            public Iterable<LocalDate> apply(final UnitUsageRecordJson input) {
+                final Iterable<LocalDate> result = Iterables.transform(input.getUsageRecords(), new Function<UsageRecordJson, LocalDate>() {
+                    @Override
+                    public LocalDate apply(final UsageRecordJson input) {
+                        return input.getRecordDate();
+                    }
+                });
+                return result;
+            }
+        });
+        final Iterable<LocalDate> sortedRecordedDates = Ordering.<LocalDate>natural()
+                .reverse()
+                .sortedCopy(Iterables.concat(recordedDates));
+
+        return Iterables.getFirst(sortedRecordedDates, null);
+    }
+
     @TimedResource
     @GET
     @Path("/{subscriptionId:" + UUID_PATTERN + "}/{unitType}")
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/JaxrsTestSuiteNoDB.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/JaxrsTestSuiteNoDB.java
index ee72582..4d4eb02 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/JaxrsTestSuiteNoDB.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/JaxrsTestSuiteNoDB.java
@@ -34,6 +34,10 @@ public abstract class JaxrsTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestJaxrsModuleNoDB(configSource));
         injector.injectMembers(this);
     }
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 c852acd..997eee9 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
@@ -42,6 +42,7 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuiteNoDB {
         final UUID childAccountId = UUID.randomUUID();
         final UUID bundleId = UUID.randomUUID();
         final UUID subscriptionId = UUID.randomUUID();
+        final String productName = UUID.randomUUID().toString();
         final String planName = UUID.randomUUID().toString();
         final String phaseName = UUID.randomUUID().toString();
         final String usageName = UUID.randomUUID().toString();
@@ -53,8 +54,8 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuiteNoDB {
         final Currency currency = Currency.MXN;
         final List<AuditLogJson> auditLogs = createAuditLogsJson(clock.getUTCNow());
         final InvoiceItemJson invoiceItemJson = new InvoiceItemJson(invoiceItemId, invoiceId, linkedInvoiceItemId, accountId, childAccountId,
-                                                                    bundleId, subscriptionId, planName, phaseName, usageName,
-                                                                    null, null, null,
+                                                                    bundleId, subscriptionId, productName, planName, phaseName, usageName,
+                                                                    null, null, null, null,
                                                                     type, description,
                                                                     startDate, endDate, amount, null, currency.name(), null, null, null, auditLogs);
         Assert.assertEquals(invoiceItemJson.getInvoiceItemId(), invoiceItemId);
@@ -64,6 +65,7 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuiteNoDB {
         Assert.assertEquals(invoiceItemJson.getChildAccountId(), childAccountId);
         Assert.assertEquals(invoiceItemJson.getBundleId(), bundleId);
         Assert.assertEquals(invoiceItemJson.getSubscriptionId(), subscriptionId);
+        Assert.assertEquals(invoiceItemJson.getProductName(), productName);
         Assert.assertEquals(invoiceItemJson.getPlanName(), planName);
         Assert.assertEquals(invoiceItemJson.getPhaseName(), phaseName);
         Assert.assertEquals(invoiceItemJson.getUsageName(), usageName);
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 85195c0..979f012 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
@@ -19,7 +19,10 @@ package org.killbill.billing.jaxrs.resources;
 
 import java.util.List;
 
+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.payment.api.PluginProperty;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -67,4 +70,50 @@ public class TestJaxRsResourceBase extends JaxrsTestSuiteNoDB {
             super(null, null, null, null, null, null, null, null, null);
         }
     }
+
+
+    @Test(groups = "fast")
+    public void testGetHighestRecordDate() throws Exception {
+        final UsageResourceTest usageResource = new UsageResourceTest();
+
+        final List<UsageRecordJson> fooRecords = ImmutableList.<UsageRecordJson>builder()
+                .add(new UsageRecordJson(new LocalDate(2018, 03, 04), 28L))
+                .add(new UsageRecordJson(new LocalDate(2018, 03, 05), 2L))
+                .add(new UsageRecordJson(new LocalDate(2018, 03, 01), 1L))
+                .add(new UsageRecordJson(new LocalDate(2018, 04, 06), 24L))
+                .build();
+        final UnitUsageRecordJson unitRecordFoo = new UnitUsageRecordJson("foo", fooRecords);
+
+        final List<UsageRecordJson> barRecords = ImmutableList.<UsageRecordJson>builder()
+                .add(new UsageRecordJson(new LocalDate(2018, 02, 04), 28L))
+                .add(new UsageRecordJson(new LocalDate(2018, 03, 06), 2L))
+                .add(new UsageRecordJson(new LocalDate(2018, 04, 18), 1L)) // Highest date point
+                .add(new UsageRecordJson(new LocalDate(2018, 04, 13), 24L))
+                .build();
+        final UnitUsageRecordJson unitRecordBar = new UnitUsageRecordJson("bar", barRecords);
+
+        final List<UsageRecordJson> zooRecords = ImmutableList.<UsageRecordJson>builder()
+                .add(new UsageRecordJson(new LocalDate(2018, 02, 04), 28L))
+                .add(new UsageRecordJson(new LocalDate(2018, 03, 06), 2L))
+                .add(new UsageRecordJson(new LocalDate(2018, 04, 17), 1L))
+                .add(new UsageRecordJson(new LocalDate(2018, 04, 12), 24L))
+                .build();
+        final UnitUsageRecordJson unitRecordZoo = new UnitUsageRecordJson("zoo", zooRecords);
+
+        final List<UnitUsageRecordJson> input = ImmutableList.<UnitUsageRecordJson>builder()
+                .add(unitRecordFoo)
+                .add(unitRecordBar)
+                .add(unitRecordZoo)
+                .build();
+        final LocalDate result = usageResource.getHighestRecordDate(input);
+
+        Assert.assertTrue(result.compareTo(new LocalDate(2018, 04, 18)) == 0);
+    }
+
+
+    private static class UsageResourceTest extends UsageResource {
+        public UsageResourceTest() {
+            super(null, null, null, null, null, null, null, null, null, null);
+        }
+    }
 }

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

diff --git a/junction/pom.xml b/junction/pom.xml
index 6c36aed..51c6488 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-junction</artifactId>
diff --git a/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteNoDB.java b/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteNoDB.java
index c471a7d..eaa44c9 100644
--- a/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteNoDB.java
+++ b/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteNoDB.java
@@ -60,17 +60,29 @@ public abstract class JunctionTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestJunctionModuleNoDB(configSource));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         bus.start();
     }
 
     @AfterMethod(groups = "fast")
     public void afterMethod() {
+        if (hasFailed()) {
+            return;
+        }
+
         bus.stop();
     }
 }
diff --git a/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java b/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
index 859d024..499fd9a 100644
--- a/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
+++ b/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
@@ -99,12 +99,20 @@ public abstract class JunctionTestSuiteWithEmbeddedDB extends GuicyKillbillTestS
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(Stage.PRODUCTION, new TestJunctionModuleWithEmbeddedDB(configSource));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
         startTestFamework();
         this.catalog = initCatalog(catalogService);
@@ -112,6 +120,10 @@ public abstract class JunctionTestSuiteWithEmbeddedDB extends GuicyKillbillTestS
 
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         stopTestFramework();
     }
 

NEWS 4(+4 -0)

diff --git a/NEWS b/NEWS
index 28f753d..abe6ebb 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,7 @@
+0.19.13
+    Entitlement/subscription refactoring and incremental perf improvements
+    New implementation pass to allow a mix of RO and RW calls
+
 0.19.12
     New API to retrieve history info from any objects
     Entitlement/subscription refactoring and incremental perf improvements

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

diff --git a/overdue/pom.xml b/overdue/pom.xml
index 9659f11..a8c8fc0 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-overdue</artifactId>
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 d7d6462..5e2f224 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java
@@ -108,12 +108,20 @@ public abstract class OverdueTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestOverdueModuleNoDB(configSource));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         bus.start();
         service.initialize();
         service.start();
@@ -121,6 +129,10 @@ public abstract class OverdueTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @AfterMethod(groups = "fast")
     public void afterMethod() {
+        if (hasFailed()) {
+            return;
+        }
+
         service.stop();
         bus.stop();
     }
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 b74d629..da37b6c 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
@@ -102,12 +102,20 @@ public abstract class OverdueTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestOverdueModuleWithEmbeddedDB(configSource));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
         cacheControllerDispatcher.clearAll();
         bus.start();
@@ -119,6 +127,10 @@ public abstract class OverdueTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
 
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         service.stop();
         bus.unregister(listener);
         bus.stop();

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

diff --git a/payment/pom.xml b/payment/pom.xml
index 27f27cf..aa485cc 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>
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 651d2d1..367db03 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
@@ -327,6 +327,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
                                                                  callContext,
                                                                  internalCallContext);
 
+            log.debug("retryPaymentTransaction result: payment='{}'", payment);
             paymentTransaction = Iterables.<PaymentTransaction>find(Lists.<PaymentTransaction>reverse(payment.getTransactions()),
                                                                     new Predicate<PaymentTransaction>() {
                                                                         @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
index 7c503ea..91fea81 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
@@ -35,6 +35,8 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.util.config.definition.PaymentConfig;
 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;
@@ -44,6 +46,8 @@ import com.google.common.collect.ImmutableList;
 //
 public class CompletionControlOperation extends OperationControlCallback {
 
+    private static final Logger logger = LoggerFactory.getLogger(CompletionControlOperation.class);
+
     private static final Joiner JOINER = Joiner.on(", ");
 
     public CompletionControlOperation(final GlobalLocker locker,
@@ -84,6 +88,7 @@ public class CompletionControlOperation extends OperationControlCallback {
                                                                                                             paymentStateContext.getCallContext());
                 try {
                     final Payment result = doCallSpecificOperationCallback();
+                    logger.debug("doOperationCallback payment='{}', transaction='{}'", result, transaction);
                     ((PaymentStateControlContext) paymentStateContext).setResult(result);
 
                     final boolean success = transaction.getTransactionStatus() == TransactionStatus.SUCCESS || transaction.getTransactionStatus() == TransactionStatus.PENDING;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlCompleted.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlCompleted.java
index 4f67980..7c50297 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlCompleted.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlCompleted.java
@@ -34,6 +34,8 @@ import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dao.PluginPropertySerializer;
 import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
 import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
@@ -41,6 +43,8 @@ import com.google.common.collect.Iterables;
 
 public class DefaultControlCompleted implements EnteringStateCallback {
 
+    private static final Logger logger = LoggerFactory.getLogger(DefaultControlCompleted.class);
+
     private final PaymentStateControlContext paymentStateContext;
     private final RetryServiceScheduler retryServiceScheduler;
     private final State retriedState;
@@ -61,6 +65,7 @@ public class DefaultControlCompleted implements EnteringStateCallback {
                                    paymentStateContext.getCurrentTransaction().getId() :
                                    null;
 
+        logger.debug("enteringState attemptId='{}', transactionId='{}', state='{}'", attempt.getId(), transactionId, state.getName());
         // At this stage we can update the paymentAttempt state AND serialize the plugin properties. Control plugins will have had the opportunity to erase sensitive data if required.
         retryablePaymentAutomatonRunner.getPaymentDao().updatePaymentAttemptWithProperties(attempt.getId(),
                                                                                            paymentStateContext.getPaymentMethodId(),
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 cd77f0c..f6e42fa 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
@@ -123,27 +123,6 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
     @Override
     protected abstract PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException;
 
-    protected Iterable<PaymentTransactionModelDao> getOnLeavingStateExistingTransactionsForType(final TransactionType transactionType) {
-        if (paymentStateContext.getOnLeavingStateExistingTransactions() == null || paymentStateContext.getOnLeavingStateExistingTransactions().isEmpty()) {
-            return ImmutableList.of();
-        }
-        return Iterables.filter(paymentStateContext.getOnLeavingStateExistingTransactions(), new Predicate<PaymentTransactionModelDao>() {
-            @Override
-            public boolean apply(final PaymentTransactionModelDao input) {
-                return input.getTransactionStatus() == TransactionStatus.SUCCESS && input.getTransactionType() == transactionType;
-            }
-        });
-    }
-
-    protected BigDecimal getSumAmount(final Iterable<PaymentTransactionModelDao> transactions) {
-        BigDecimal result = BigDecimal.ZERO;
-        final Iterator<PaymentTransactionModelDao> iterator = transactions.iterator();
-        while (iterator.hasNext()) {
-            result = result.add(iterator.next().getAmount());
-        }
-        return result;
-    }
-
     private OperationResult doOperationCallbackWithDispatchAndAccountLock(String pluginName) throws OperationException {
         return dispatchWithAccountLockAndTimeout(pluginName, new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
             @Override
@@ -181,6 +160,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
                     throw new IllegalStateException("Payment plugin returned a null result");
                 }
 
+                logger.debug("Plugin returned paymentTransactionInfoPlugin='{}'", paymentInfoPlugin);
                 paymentStateContext.setPaymentTransactionInfoPlugin(paymentInfoPlugin);
                 return PaymentTransactionInfoPluginConverter.toOperationResult(paymentStateContext.getPaymentTransactionInfoPlugin());
             } else {
@@ -194,6 +174,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
                                                                                                         buildPaymentPluginStatusFromOperationResult(paymentStateContext.getOverridePluginOperationResult()),
                                                                                                         null,
                                                                                                         null);
+                logger.debug("Plugin bypassed, paymentTransactionInfoPlugin='{}'", paymentInfoPlugin);
                 paymentStateContext.setPaymentTransactionInfoPlugin(paymentInfoPlugin);
                 return paymentStateContext.getOverridePluginOperationResult();
             }
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java
index 3e7d30a..c2d81d2 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java
@@ -49,12 +49,20 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeClass();
         mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME);
     }
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
 
         mockPaymentProviderPlugin.clear();
@@ -104,6 +112,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
                                                                                  ImmutableList.<PluginProperty>of(),
                                                                                  paymentOptions,
                                                                                  callContext);
+        Assert.assertEquals(payment.getTransactions().size(), 1);
 
         final PaymentModelDao paymentModelDao = paymentDao.getPayment(payment.getId(), internalCallContext);
         final PaymentTransactionModelDao paymentTransactionModelDao = paymentDao.getPaymentTransaction(payment.getTransactions().get(0).getId(), internalCallContext);
@@ -117,6 +126,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
         adminPaymentApi.fixPaymentTransactionState(payment, payment.getTransactions().get(0), TransactionStatus.PAYMENT_FAILURE, null, "AUTH_ERRORED", ImmutableList.<PluginProperty>of(), callContext);
 
+        Assert.assertEquals(paymentApi.getPayment(payment.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext).getTransactions().size(), 1);
         final PaymentModelDao refreshedPaymentModelDao = paymentDao.getPayment(payment.getId(), internalCallContext);
         final PaymentTransactionModelDao refreshedPaymentTransactionModelDao = paymentDao.getPaymentTransaction(payment.getTransactions().get(0).getId(), internalCallContext);
         Assert.assertEquals(refreshedPaymentModelDao.getStateName(), "AUTH_ERRORED");
@@ -127,6 +137,10 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getGatewayErrorCode(), "");
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getGatewayErrorMsg(), "");
 
+        // Advance the clock to make sure the effective date of the new transaction is after the first one
+        clock.addDays(1);
+        assertListenerStatus();
+
         // Verify subsequent payment retries work
         retryService.retryPaymentTransaction(refreshedPaymentTransactionModelDao.getAttemptId(), ImmutableList.<String>of(MockPaymentControlProviderPlugin.PLUGIN_NAME), internalCallContext);
 
diff --git a/payment/src/test/java/org/killbill/billing/payment/MockRecurringInvoiceItem.java b/payment/src/test/java/org/killbill/billing/payment/MockRecurringInvoiceItem.java
index 1627a6d..626753e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/MockRecurringInvoiceItem.java
+++ b/payment/src/test/java/org/killbill/billing/payment/MockRecurringInvoiceItem.java
@@ -104,6 +104,16 @@ public class MockRecurringInvoiceItem extends EntityBase implements InvoiceItem 
     }
 
     @Override
+    public String getProductName() {
+        return null;
+    }
+
+    @Override
+    public String getPrettyProductName() {
+        return null;
+    }
+
+    @Override
     public String getPlanName() {
         return planName;
     }
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 a41e524..c429872 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
@@ -115,12 +115,20 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestPaymentModuleNoDB(configSource, getClock()));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         stateMachineConfigCache.clearPaymentStateMachineConfig(PLUGIN_NAME, internalCallContext);
         stateMachineConfigCache.loadDefaultPaymentStateMachineConfig(PaymentModule.DEFAULT_STATE_MACHINE_PAYMENT_XML);
 
@@ -132,6 +140,10 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @AfterMethod(groups = "fast")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         paymentExecutors.stop();
         eventBus.stop();
     }
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
index cf7454d..61dca40 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
@@ -119,12 +119,20 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestPaymentModuleWithEmbeddedDB(configSource, getClock()));
         injector.injectMembers(this);
     }
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
 
         stateMachineConfigCache.clearPaymentStateMachineConfig(PLUGIN_NAME, internalCallContext);
@@ -141,6 +149,10 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
 
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         janitor.stop();
         eventBus.stop();
         paymentExecutors.stop();
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 b86f476..32bba1c 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
@@ -48,6 +48,8 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.DefaultPagination;
 import org.killbill.billing.util.entity.Pagination;
+import org.killbill.billing.util.entity.dao.DBRouterUntyped;
+import org.killbill.billing.util.entity.dao.DBRouterUntyped.THREAD_STATE;
 import org.killbill.clock.Clock;
 
 import com.google.common.base.Preconditions;
@@ -87,6 +89,8 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
 
     private final Clock clock;
 
+    private THREAD_STATE lastThreadState = null;
+
     private class InternalPaymentInfo {
 
         private BigDecimal authAmount;
@@ -272,48 +276,60 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
         }
     }
 
+    public THREAD_STATE getLastThreadState() {
+        return lastThreadState;
+    }
+
     @Override
     public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentPluginApiException {
+        updateLastThreadState();
         return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency, properties);
     }
 
     @Override
     public PaymentTransactionInfoPlugin capturePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentPluginApiException {
+        updateLastThreadState();
         return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.CAPTURE, amount, currency, properties);
     }
 
     @Override
     public PaymentTransactionInfoPlugin purchasePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        updateLastThreadState();
         return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, currency, properties);
     }
 
     @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 {
+        updateLastThreadState();
         return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.VOID, null, null, properties);
     }
 
     @Override
     public PaymentTransactionInfoPlugin creditPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentPluginApiException {
+        updateLastThreadState();
         return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.CREDIT, amount, currency, properties);
     }
 
     @Override
     public List<PaymentTransactionInfoPlugin> getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentPluginApiException {
+        updateLastThreadState();
         final List<PaymentTransactionInfoPlugin> result = paymentTransactions.get(kbPaymentId.toString());
         return result != null ? result : ImmutableList.<PaymentTransactionInfoPlugin>of();
     }
 
     @Override
     public Pagination<PaymentTransactionInfoPlugin> searchPayments(final String searchKey, final Long offset, final Long limit, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentPluginApiException {
+        updateLastThreadState();
         throw new IllegalStateException("Not implemented");
     }
 
     @Override
     public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        updateLastThreadState();
         // externalPaymentMethodId is set to a random value
         final PaymentMethodPlugin realWithID = new TestPaymentMethodPlugin(kbPaymentMethodId, paymentMethodProps, UUID.randomUUID().toString());
         paymentMethods.put(kbPaymentMethodId.toString(), realWithID);
@@ -324,26 +340,31 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
 
     @Override
     public void deletePaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        updateLastThreadState();
         paymentMethods.remove(kbPaymentMethodId.toString());
         paymentMethodsInfo.remove(kbPaymentMethodId.toString());
     }
 
     @Override
     public PaymentMethodPlugin getPaymentMethodDetail(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentPluginApiException {
+        updateLastThreadState();
         return paymentMethods.get(kbPaymentMethodId.toString());
     }
 
     @Override
     public void setDefaultPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        updateLastThreadState();
     }
 
     @Override
     public List<PaymentMethodInfoPlugin> getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final Iterable<PluginProperty> properties, final CallContext context) {
+        updateLastThreadState();
         return ImmutableList.<PaymentMethodInfoPlugin>copyOf(paymentMethodsInfo.values());
     }
 
     @Override
     public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentPluginApiException {
+        updateLastThreadState();
         final ImmutableList<PaymentMethodPlugin> results = ImmutableList.<PaymentMethodPlugin>copyOf(Iterables.<PaymentMethodPlugin>filter(paymentMethods.values(), new Predicate<PaymentMethodPlugin>() {
             @Override
             public boolean apply(final PaymentMethodPlugin input) {
@@ -362,6 +383,7 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
 
     @Override
     public void resetPaymentMethods(final UUID kbAccountId, final List<PaymentMethodInfoPlugin> input, final Iterable<PluginProperty> properties, final CallContext callContext) {
+        updateLastThreadState();
         paymentMethodsInfo.clear();
         if (input != null) {
             for (final PaymentMethodInfoPlugin cur : input) {
@@ -372,16 +394,19 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
 
     @Override
     public HostedPaymentPageFormDescriptor buildFormDescriptor(final UUID kbAccountId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext) {
+        updateLastThreadState();
         return new DefaultNoOpHostedPaymentPageFormDescriptor(kbAccountId);
     }
 
     @Override
     public GatewayNotification processNotification(final String notification, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
+        updateLastThreadState();
         return new DefaultNoOpGatewayNotification();
     }
 
     @Override
     public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal refundAmount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        updateLastThreadState();
 
         final InternalPaymentInfo info = payments.get(kbPaymentId.toString());
         if (info == null) {
@@ -479,4 +504,8 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
 
         return result;
     }
+
+    private void updateLastThreadState() {
+        lastThreadState = DBRouterUntyped.getCurrentState();
+    }
 }
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 ecaecf1..1f2e03a 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -56,7 +56,10 @@ import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.entity.dao.DBRouterUntyped;
+import org.killbill.billing.util.entity.dao.DBRouterUntyped.THREAD_STATE;
 import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
 import org.killbill.notificationq.api.NotificationEvent;
 import org.killbill.notificationq.api.NotificationEventWithMetadata;
 import org.killbill.notificationq.api.NotificationQueueService;
@@ -75,8 +78,8 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
-import static org.awaitility.Awaitility.await;
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.awaitility.Awaitility.await;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.fail;
 
@@ -472,6 +475,45 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
         });
     }
 
+    @Test(groups = "slow")
+    public void testDBRouterThreadState() throws Throwable {
+        final Payment payment = (Payment) DBRouterUntyped.withRODBIAllowed(true,
+                                                                           new WithProfilingCallback<Object, Throwable>() {
+                                                                               @Override
+                                                                               public Payment execute() throws Throwable {
+                                                                                   // Shouldn't happen in practice, but it's just to verify the behavior
+                                                                                   assertEquals(DBRouterUntyped.getCurrentState(), THREAD_STATE.RO_ALLOWED);
+
+                                                                                   final BigDecimal requestedAmount = BigDecimal.TEN;
+                                                                                   testListener.pushExpectedEvent(NextEvent.PAYMENT);
+                                                                                   final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), null, UUID.randomUUID().toString(),
+                                                                                                                                          UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+                                                                                   testListener.assertListenerStatus();
+
+                                                                                   // Thread switch, RW by default
+                                                                                   assertEquals(mockPaymentProviderPlugin.getLastThreadState(), THREAD_STATE.RW_ONLY);
+                                                                                   // Switched to RW, because of RW DAO call
+                                                                                   assertEquals(DBRouterUntyped.getCurrentState(), THREAD_STATE.RW_ONLY);
+                                                                                   return payment;
+                                                                               }
+                                                                           });
+
+        DBRouterUntyped.withRODBIAllowed(true,
+                                         new WithProfilingCallback<Object, Throwable>() {
+                                             @Override
+                                             public Object execute() throws Throwable {
+                                                 assertEquals(DBRouterUntyped.getCurrentState(), THREAD_STATE.RO_ALLOWED);
+
+                                                 final Payment retrievedPayment2 = paymentApi.getPayment(payment.getId(), true, false, ImmutableList.<PluginProperty>of(), callContext);
+                                                 Assert.assertEquals(retrievedPayment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+
+                                                 // No thread switch, RO as well
+                                                 assertEquals(mockPaymentProviderPlugin.getLastThreadState(), THREAD_STATE.RO_ALLOWED);
+                                                 assertEquals(DBRouterUntyped.getCurrentState(), THREAD_STATE.RO_ALLOWED);
+                                                 return null;
+                                             }
+                                         });
+    }
 
     private List<PluginProperty> createPropertiesForInvoice(final Invoice invoice) {
         final List<PluginProperty> result = new ArrayList<PluginProperty>();

pom.xml 4(+2 -2)

diff --git a/pom.xml b/pom.xml
index 26a2b46..0737bf3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,10 +21,10 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.141.61</version>
+        <version>0.141.62</version>
     </parent>
     <artifactId>killbill</artifactId>
-    <version>0.19.13-SNAPSHOT</version>
+    <version>0.19.14-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>
diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index c4759f9..f786445 100644
--- a/profiles/killbill/pom.xml
+++ b/profiles/killbill/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-profiles</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-profiles-killbill</artifactId>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/JaxRSAopModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/JaxRSAopModule.java
new file mode 100644
index 0000000..30becf9
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/JaxRSAopModule.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.modules;
+
+import java.lang.reflect.Method;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.killbill.billing.jaxrs.resources.JaxrsResource;
+import org.killbill.billing.util.entity.dao.DBRouterUntyped;
+import org.killbill.billing.util.glue.KillbillApiAopModule;
+import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.matcher.Matchers;
+
+public class JaxRSAopModule extends AbstractModule {
+
+    private static final Logger logger = LoggerFactory.getLogger(KillbillApiAopModule.class);
+
+    private static final Matcher<Method> API_RESOURCE_METHOD_MATCHER = new Matcher<Method>() {
+        @Override
+        public boolean matches(final Method method) {
+            return !method.isSynthetic() &&
+                   (
+                           method.getAnnotation(DELETE.class) != null ||
+                           method.getAnnotation(GET.class) != null ||
+                           method.getAnnotation(HEAD.class) != null ||
+                           method.getAnnotation(OPTIONS.class) != null ||
+                           method.getAnnotation(POST.class) != null ||
+                           method.getAnnotation(PUT.class) != null
+                   );
+        }
+
+        @Override
+        public Matcher<Method> and(final Matcher<? super Method> other) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Matcher<Method> or(final Matcher<? super Method> other) {
+            throw new UnsupportedOperationException();
+        }
+    };
+
+    @Override
+    protected void configure() {
+        bindInterceptor(Matchers.subclassesOf(JaxrsResource.class),
+                        API_RESOURCE_METHOD_MATCHER,
+                        new JaxRsMethodInterceptor());
+    }
+
+    public static class JaxRsMethodInterceptor implements MethodInterceptor {
+
+        @Override
+        public Object invoke(final MethodInvocation invocation) throws Throwable {
+            return DBRouterUntyped.withRODBIAllowed(isRODBIAllowed(invocation),
+                                                    new WithProfilingCallback<Object, Throwable>() {
+                                                        @Override
+                                                        public Object execute() throws Throwable {
+                                                            logger.debug("Entering JAX-RS call {}, arguments: {}", invocation.getMethod(), invocation.getArguments());
+                                                            final Object proceed = invocation.proceed();
+                                                            logger.debug("Exiting  JXA-RS call {}, returning: {}", invocation.getMethod(), proceed);
+                                                            return proceed;
+                                                        }
+                                                    });
+        }
+
+        private boolean isRODBIAllowed(final MethodInvocation invocation) {
+            return invocation.getMethod().getAnnotation(GET.class) != null;
+        }
+    }
+}
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 6ffbc2a..5d43a8f 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
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -179,6 +179,7 @@ public class KillbillServerModule extends KillbillPlatformModule {
         install(new GlobalLockerModule(configSource));
         install(new KillBillShiroAopModule());
         install(new KillbillApiAopModule());
+        install(new JaxRSAopModule());
         install(new KillBillShiroWebModule(servletContext, skifeConfigSource));
         install(new NonEntityDaoModule(configSource));
         install(new PaymentModule(configSource));
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/resources/TestDBRouterResource.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/resources/TestDBRouterResource.java
new file mode 100644
index 0000000..254d526
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/resources/TestDBRouterResource.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.resources;
+
+import java.util.UUID;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+import org.joda.time.DateTimeZone;
+import org.killbill.billing.GuicyKillbillTestSuite;
+import org.killbill.billing.beatrix.integration.db.TestDBRouterAPI;
+import org.killbill.billing.callcontext.MutableCallContext;
+import org.killbill.billing.callcontext.MutableInternalCallContext;
+import org.killbill.billing.util.callcontext.CallOrigin;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.UserType;
+
+@Path("/testDbResource")
+public class TestDBRouterResource implements JaxrsResource {
+
+    private final MutableInternalCallContext internalCallContext = new MutableInternalCallContext(InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID,
+                                                                                                  1687L,
+                                                                                                  DateTimeZone.UTC,
+                                                                                                  GuicyKillbillTestSuite.getClock().getUTCNow(),
+                                                                                                  UUID.randomUUID(),
+                                                                                                  UUID.randomUUID().toString(),
+                                                                                                  CallOrigin.TEST,
+                                                                                                  UserType.TEST,
+                                                                                                  "Testing",
+                                                                                                  "This is a test",
+                                                                                                  GuicyKillbillTestSuite.getClock().getUTCNow(),
+                                                                                                  GuicyKillbillTestSuite.getClock().getUTCNow());
+
+    private final MutableCallContext callContext = new MutableCallContext(internalCallContext);
+
+    private final TestDBRouterAPI testDBRouterAPI;
+
+    @Inject
+    public TestDBRouterResource(final TestDBRouterAPI testDBRouterAPI) {
+        this.testDBRouterAPI = testDBRouterAPI;
+    }
+
+    @POST
+    public Response doChainedRWROCalls() {
+        testDBRouterAPI.reset();
+        testDBRouterAPI.doRWCall(callContext);
+        testDBRouterAPI.doROCall(callContext);
+        return Response.ok().build();
+    }
+
+    @GET
+    public Response doChainedROROCalls() {
+        testDBRouterAPI.reset();
+        testDBRouterAPI.doROCall(callContext);
+        testDBRouterAPI.doROCall(callContext);
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("/chained")
+    public Response doChainedRORWROCalls() {
+        testDBRouterAPI.reset();
+        testDBRouterAPI.doROCall(callContext);
+        // Naughty: @GET method doing a RW call... Verify the underlying code will detect it and mark the thread as dirty
+        testDBRouterAPI.doRWCall(callContext);
+        testDBRouterAPI.doROCall(callContext);
+        return Response.ok().build();
+    }
+}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestDBRouterResources.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestDBRouterResources.java
new file mode 100644
index 0000000..aeabe3d
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestDBRouterResources.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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 javax.inject.Inject;
+
+import org.killbill.billing.beatrix.integration.db.TestDBRouterAPI;
+import org.killbill.billing.jaxrs.resources.TestDBRouterResource;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestDBRouterResources extends TestJaxrsBase {
+
+    @Inject
+    private TestDBRouterAPI testDBRouterAPI;
+
+    @Inject
+    private TestDBRouterResource testDBRouterResource;
+
+    @Test(groups = "slow")
+    public void testJaxRSAoPRouting() throws Exception {
+        testDBRouterResource.doChainedROROCalls();
+        assertNbCalls(0, 2);
+
+        testDBRouterResource.doChainedRWROCalls();
+        assertNbCalls(2, 0);
+
+        testDBRouterResource.doChainedRORWROCalls();
+        assertNbCalls(2, 1);
+    }
+
+    private void assertNbCalls(final int expectedNbRWCalls, final int expectedNbROCalls) {
+        assertEquals(testDBRouterAPI.getNbRWCalls(), expectedNbRWCalls);
+        assertEquals(testDBRouterAPI.getNbRoCalls(), expectedNbROCalls);
+    }
+}
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 a476775..ee74ce5 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
@@ -82,6 +82,14 @@ public class TestInvoice extends TestJaxrsBase {
         }
 
         final Invoice invoiceJson = invoices.get(0);
+        assertEquals(invoiceJson.getItems().size(), 1);
+        final InvoiceItem invoiceItem = invoiceJson.getItems().get(0);
+        assertEquals(invoiceItem.getProductName(), "Shotgun");
+        assertEquals(invoiceItem.getPrettyProductName(), "Shotgun");
+        assertEquals(invoiceItem.getPlanName(), "shotgun-monthly");
+        assertEquals(invoiceItem.getPrettyPlanName(), "Shotgun Monthly");
+        assertEquals(invoiceItem.getPhaseName(), "shotgun-monthly-trial");
+        assertEquals(invoiceItem.getPrettyPhaseName(), "shotgun-monthly-trial");
 
         // Check get with & without items
         assertTrue(killBillClient.getInvoice(invoiceJson.getInvoiceId(), Boolean.FALSE, Boolean.FALSE, requestOptions).getItems().isEmpty());
@@ -89,6 +97,8 @@ public class TestInvoice extends TestJaxrsBase {
         assertEquals(killBillClient.getInvoice(invoiceJson.getInvoiceId(), Boolean.TRUE, Boolean.FALSE, requestOptions).getItems().size(), invoiceJson.getItems().size());
         assertEquals(killBillClient.getInvoice(invoiceJson.getInvoiceNumber(), Boolean.TRUE, Boolean.FALSE, requestOptions).getItems().size(), invoiceJson.getItems().size());
 
+
+
         // Check we can retrieve an individual invoice
         final Invoice firstInvoice = killBillClient.getInvoice(invoiceJson.getInvoiceId(), requestOptions);
         assertEquals(firstInvoice, invoiceJson);
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 83e3d47..91d4f61 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
@@ -38,12 +38,14 @@ import org.eclipse.jetty.servlet.FilterHolder;
 import org.joda.time.LocalDate;
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
 import org.killbill.billing.api.TestApiListener;
+import org.killbill.billing.beatrix.integration.db.TestDBRouterAPI;
 import org.killbill.billing.client.KillBillClient;
 import org.killbill.billing.client.KillBillHttpClient;
 import org.killbill.billing.client.model.Payment;
 import org.killbill.billing.client.model.PaymentTransaction;
 import org.killbill.billing.client.model.Tenant;
 import org.killbill.billing.invoice.glue.DefaultInvoiceModule;
+import org.killbill.billing.jaxrs.resources.TestDBRouterResource;
 import org.killbill.billing.jetty.HttpServer;
 import org.killbill.billing.jetty.HttpServerConfig;
 import org.killbill.billing.lifecycle.glue.BusModule;
@@ -75,6 +77,7 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.inject.Binder;
 import com.google.inject.Module;
 import com.google.inject.util.Modules;
 
@@ -146,7 +149,14 @@ public class TestJaxrsBase extends KillbillClient {
         protected Module getModule(final ServletContext servletContext) {
             return Modules.override(new KillbillServerModule(servletContext, serverConfig, configSource)).with(new GuicyKillbillTestWithEmbeddedDBModule(configSource),
                                                                                                                new InvoiceModuleWithMockSender(configSource),
-                                                                                                               new PaymentMockModule(configSource));
+                                                                                                               new PaymentMockModule(configSource),
+                                                                                                               new Module() {
+                                                                                                                   @Override
+                                                                                                                   public void configure(final Binder binder) {
+                                                                                                                       binder.bind(TestDBRouterAPI.class).asEagerSingleton();
+                                                                                                                       binder.bind(TestDBRouterResource.class).asEagerSingleton();
+                                                                                                                   }
+                                                                                                               });
         }
 
     }
@@ -240,6 +250,10 @@ public class TestJaxrsBase extends KillbillClient {
 
     @BeforeClass(groups = "slow")
     public void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         // TODO PIERRE Unclear why both are needed in beforeClass and beforeSuite
         if (config == null) {
             config = new ConfigurationObjectFactory(System.getProperties()).build(HttpServerConfig.class);
@@ -252,6 +266,10 @@ public class TestJaxrsBase extends KillbillClient {
 
     @BeforeSuite(groups = "slow")
     public void beforeSuite() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeSuite();
 
         if (config == null) {
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/server/log/ServerTestSuiteNoDB.java b/profiles/killbill/src/test/java/org/killbill/billing/server/log/ServerTestSuiteNoDB.java
index f0e5e86..9d12db6 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/server/log/ServerTestSuiteNoDB.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/server/log/ServerTestSuiteNoDB.java
@@ -28,6 +28,10 @@ public abstract class ServerTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestServerModuleNoDB(configSource));
         injector.injectMembers(this);
     }
diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml
index 048b66c..b4ecd51 100644
--- a/profiles/killpay/pom.xml
+++ b/profiles/killpay/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>killbill-profiles</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-profiles-killpay</artifactId>
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 b4b8f6d..38f2e8e 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
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -91,6 +91,7 @@ public class KillpayServerModule extends KillbillServerModule {
         install(new GlobalLockerModule(configSource));
         install(new KillBillShiroAopModule());
         install(new KillbillApiAopModule());
+        install(new JaxRSAopModule());
         install(new KillBillShiroWebModule(servletContext, skifeConfigSource));
         install(new NonEntityDaoModule(configSource));
         install(new PaymentModule(configSource));

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

diff --git a/profiles/pom.xml b/profiles/pom.xml
index b8b4fb4..d4a6789 100644
--- a/profiles/pom.xml
+++ b/profiles/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-profiles</artifactId>
diff --git a/subscription/pom.xml b/subscription/pom.xml
index 1e4001f..9260907 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-subscription</artifactId>
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 10a2b1b..d5c4bb6 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
@@ -148,7 +148,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
     }
 
     private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final SubscriptionBaseBundle bundle,
-                                                                             @Nullable final EntitlementSpecifier baseOrFirstStandalonePlanSpecifier,
+                                                                             final boolean hasBaseOrStandalonePlanSpecifier,
                                                                              final Iterable<EntitlementSpecifier> entitlements,
                                                                              final boolean isMigrated,
                                                                              final InternalCallContext context,
@@ -188,7 +188,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
             }
 
             final DateTime bundleStartDate;
-            if (baseOrFirstStandalonePlanSpecifier != null) {
+            if (hasBaseOrStandalonePlanSpecifier) {
                 bundleStartDate = effectiveDate;
             } else {
                 final SubscriptionBase baseSubscription = dao.getBaseSubscription(bundle.getId(), catalog, context);
@@ -215,13 +215,13 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         return subscriptions;
     }
 
-    private EntitlementSpecifier sanityAndReorderBPOrStandaloneSpecFirst(final Catalog catalog,
-                                                                         final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier,
-                                                                         final DateTime effectiveDate,
-                                                                         final Collection<EntitlementSpecifier> outputEntitlementSpecifier) throws SubscriptionBaseApiException {
+    private boolean sanityAndReorderBPOrStandaloneSpecFirst(final Catalog catalog,
+                                                            final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier,
+                                                            final DateTime effectiveDate,
+                                                            final Collection<EntitlementSpecifier> outputEntitlementSpecifier) throws SubscriptionBaseApiException {
         EntitlementSpecifier basePlanSpecifier = null;
         final Collection<EntitlementSpecifier> addOnSpecifiers = new ArrayList<EntitlementSpecifier>();
-        final List<EntitlementSpecifier> standaloneSpecifiers = new ArrayList<EntitlementSpecifier>();
+        final Collection<EntitlementSpecifier> standaloneSpecifiers = new ArrayList<EntitlementSpecifier>();
         try {
             for (final EntitlementSpecifier cur : subscriptionBaseWithAddOnsSpecifier.getEntitlementSpecifiers()) {
                 final boolean isBase = isBaseSpecifier(catalog, effectiveDate, cur);
@@ -252,10 +252,10 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         }
 
         if (standaloneSpecifiers.isEmpty()) {
-            return basePlanSpecifier;
+            return basePlanSpecifier != null;
         } else {
             outputEntitlementSpecifier.addAll(standaloneSpecifiers);
-            return standaloneSpecifiers.get(0);
+            return true;
         }
     }
 
@@ -283,7 +283,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
 
                 final Collection<EntitlementSpecifier> reorderedSpecifiers = new ArrayList<EntitlementSpecifier>();
                 // Note: billingRequestedDateRaw might not be accurate here (add-on with a too early date passed)?
-                final EntitlementSpecifier baseOrFirstStandalonePlanSpecifier = sanityAndReorderBPOrStandaloneSpecFirst(catalog, subscriptionBaseWithAddOnsSpecifier, billingRequestedDateRaw, reorderedSpecifiers);
+                final boolean hasBaseOrStandalonePlanSpecifier = sanityAndReorderBPOrStandaloneSpecFirst(catalog, subscriptionBaseWithAddOnsSpecifier, billingRequestedDateRaw, reorderedSpecifiers);
 
                 DateTime billingRequestedDate = billingRequestedDateRaw;
                 SubscriptionBaseBundle bundle = null;
@@ -294,9 +294,8 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                         throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
                     }
                 } else if (subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey() != null &&
-                           baseOrFirstStandalonePlanSpecifier == null) { // Skip the expensive checks if we are about to create the bundle (validation will be done in SubscriptionDao#createSubscriptionBundle)
-                    final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(), context);
-                    final SubscriptionBaseBundle tmp = getActiveBundleForKeyNotException(existingBundles, dao, clock, catalog, context);
+                           !hasBaseOrStandalonePlanSpecifier) { // Skip the expensive checks if we are about to create the bundle (validation will be done in SubscriptionDao#createSubscriptionBundle)
+                    final SubscriptionBaseBundle tmp = getActiveBundleForKey(subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(), catalog, context);
                     if (tmp == null) {
                         throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey());
                     } else if (!tmp.getAccountId().equals(accountId)) {
@@ -315,12 +314,12 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                     }
                 }
 
-                if (bundle == null && baseOrFirstStandalonePlanSpecifier != null) {
+                if (bundle == null && hasBaseOrStandalonePlanSpecifier) {
                     bundle = createBundleForAccount(accountId,
                                                     subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(),
                                                     renameCancelledBundleIfExist,
                                                     context);
-                } else if (bundle != null && baseSubscription != null && baseOrFirstStandalonePlanSpecifier != null && isBaseSpecifier(catalog, billingRequestedDateRaw, baseOrFirstStandalonePlanSpecifier)) {
+                } else if (bundle != null && baseSubscription != null && hasBaseOrStandalonePlanSpecifier) {
                     throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundle.getExternalKey());
                 } else if (bundle == null) {
                     log.warn("Invalid specifier: {}", subscriptionBaseWithAddOnsSpecifier);
@@ -330,7 +329,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                 final SubscriptionAndAddOnsSpecifier subscriptionAndAddOnsSpecifier = new SubscriptionAndAddOnsSpecifier(bundle,
                                                                                                                          billingRequestedDate,
                                                                                                                          verifyAndBuildSubscriptionSpecifiers(bundle,
-                                                                                                                                                              baseOrFirstStandalonePlanSpecifier,
+                                                                                                                                                              hasBaseOrStandalonePlanSpecifier,
                                                                                                                                                               reorderedSpecifiers,
                                                                                                                                                               subscriptionBaseWithAddOnsSpecifier.isMigrated(),
                                                                                                                                                               context,
@@ -474,7 +473,9 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         return dao.getNonAOSubscriptionIdsForKey(bundleKey, context);
     }
 
-    public static SubscriptionBaseBundle getActiveBundleForKeyNotException(final Iterable<SubscriptionBaseBundle> existingBundles, final SubscriptionDao dao, final Clock clock, final Catalog catalog, final InternalTenantContext context) {
+    @Override
+    public SubscriptionBaseBundle getActiveBundleForKey(final String bundleKey, final Catalog catalog, final InternalTenantContext context) {
+        final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(bundleKey, context);
         for (final SubscriptionBaseBundle cur : existingBundles) {
             final List<SubscriptionBase> subscriptions;
             try {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
index 2d73917..a075ae9 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -35,6 +35,7 @@ import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.subscription.api.SubscriptionApiBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseApiService;
+import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.svcs.DefaultSubscriptionInternalApi;
 import org.killbill.billing.subscription.api.timeline.BundleBaseTimeline;
 import org.killbill.billing.subscription.api.timeline.SubscriptionBaseRepairException;
@@ -66,14 +67,16 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
 
     private final CatalogInternalApi catalogInternalApi;
     private final SubscriptionBaseTimelineApi timelineApi;
+    private final SubscriptionBaseInternalApi subscriptionBaseInternalApi;
     private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
     public DefaultSubscriptionBaseTransferApi(final Clock clock, final SubscriptionDao dao, final SubscriptionBaseTimelineApi timelineApi, final CatalogInternalApi catalogInternalApi,
-                                              final SubscriptionBaseApiService apiService, final InternalCallContextFactory internalCallContextFactory) {
+                                              final SubscriptionBaseInternalApi subscriptionBaseInternalApi, final SubscriptionBaseApiService apiService, final InternalCallContextFactory internalCallContextFactory) {
         super(dao, apiService, clock);
         this.catalogInternalApi = catalogInternalApi;
         this.timelineApi = timelineApi;
+        this.subscriptionBaseInternalApi = subscriptionBaseInternalApi;
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
@@ -192,13 +195,11 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
                 throw new SubscriptionBaseTransferApiException(ErrorCode.SUB_TRANSFER_INVALID_EFF_DATE, effectiveTransferDate);
             }
 
-            final SubscriptionBaseBundle bundleForAccountAndKey = dao.getSubscriptionBundlesForAccountAndKey(sourceAccountId, bundleKey, fromInternalCallContext);
-            final SubscriptionBaseBundle bundle = DefaultSubscriptionInternalApi.getActiveBundleForKeyNotException(ImmutableList.of(bundleForAccountAndKey), dao, clock, catalog, fromInternalCallContext);
+            final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getActiveBundleForKey(bundleKey, catalog, fromInternalCallContext);
             if (bundle == null) {
                 throw new SubscriptionBaseTransferApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleKey);
             }
 
-
             // Get the bundle timeline for the old account
             final BundleBaseTimeline bundleBaseTimeline = timelineApi.getBundleTimeline(bundle, context);
 
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
index 95ce7e2..9c3818b 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
@@ -73,7 +73,7 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
         final CatalogInternalApi catalogInternalApiWithMockCatalogService = new DefaultCatalogInternalApi(catalogService, internalCallContextFactory);
         final SubscriptionBaseApiService apiService = Mockito.mock(SubscriptionBaseApiService.class);
         final SubscriptionBaseTimelineApi timelineApi = Mockito.mock(SubscriptionBaseTimelineApi.class);
-        transferApi = new DefaultSubscriptionBaseTransferApi(clock, dao, timelineApi, catalogInternalApiWithMockCatalogService, apiService, internalCallContextFactory);
+        transferApi = new DefaultSubscriptionBaseTransferApi(clock, dao, timelineApi, catalogInternalApiWithMockCatalogService, subscriptionInternalApi, apiService, internalCallContextFactory);
         // Overrride catalog with our Mock CatalogService
         this.catalog = catalogInternalApiWithMockCatalogService.getFullCatalog(true, true, internalCallContext);
     }
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
index 253bf4a..5933ced 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
@@ -40,7 +40,8 @@ import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
 import org.killbill.billing.subscription.events.user.ApiEventBuilder;
 import org.killbill.billing.subscription.events.user.ApiEventCreate;
 import org.killbill.billing.util.UUIDs;
-import org.killbill.billing.util.glue.KillbillApiAopModule;
+import org.killbill.billing.util.entity.dao.DBRouterUntyped;
+import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
 import org.mockito.Mockito;
 import org.skife.jdbi.v2.IDBI;
 import org.testng.Assert;
@@ -175,15 +176,10 @@ public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
         assertEquals(result5.get(0).getExternalKey(), bundle2.getExternalKey());
         assertEquals(result5.get(1).getExternalKey(), bundle2.getExternalKey());
         assertEquals(result5.get(2).getExternalKey(), bundle2.getExternalKey());
-
-
     }
 
     @Test(groups = "slow")
-    public void testDirtyFlag() throws SubscriptionBaseApiException {
-        // @BeforeMethod created the account
-        KillbillApiAopModule.resetDirtyDBFlag();
-
+    public void testDirtyFlag() throws Throwable {
         final IDBI dbiSpy = Mockito.spy(dbi);
         final IDBI roDbiSpy = Mockito.spy(roDbi);
         final SubscriptionDao subscriptionDao = new DefaultSubscriptionDao(dbiSpy,
@@ -198,34 +194,49 @@ public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
         Mockito.verify(dbiSpy, Mockito.times(0)).open();
         Mockito.verify(roDbiSpy, Mockito.times(0)).open();
 
-        Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 0);
-        Mockito.verify(dbiSpy, Mockito.times(0)).open();
-        Mockito.verify(roDbiSpy, Mockito.times(1)).open();
-
-        Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 0);
-        Mockito.verify(dbiSpy, Mockito.times(0)).open();
-        Mockito.verify(roDbiSpy, Mockito.times(2)).open();
-
-        final String externalKey = UUID.randomUUID().toString();
-        final DateTime startDate = clock.getUTCNow();
-        final DateTime createdDate = startDate.plusSeconds(10);
-        final DefaultSubscriptionBaseBundle bundleDef = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
-        final SubscriptionBaseBundle bundle = subscriptionDao.createSubscriptionBundle(bundleDef, catalog, false, internalCallContext);
-        Mockito.verify(dbiSpy, Mockito.times(1)).open();
-        Mockito.verify(roDbiSpy, Mockito.times(2)).open();
-
-        Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 1);
-        Mockito.verify(dbiSpy, Mockito.times(2)).open();
-        Mockito.verify(roDbiSpy, Mockito.times(2)).open();
-
-        Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 1);
-        Mockito.verify(dbiSpy, Mockito.times(3)).open();
-        Mockito.verify(roDbiSpy, Mockito.times(2)).open();
-
-        KillbillApiAopModule.resetDirtyDBFlag();
-
-        Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 1);
-        Mockito.verify(dbiSpy, Mockito.times(3)).open();
-        Mockito.verify(roDbiSpy, Mockito.times(3)).open();
+        // @BeforeMethod created the account
+        DBRouterUntyped.withRODBIAllowed(true,
+                                         new WithProfilingCallback<Object, Throwable>() {
+                                      @Override
+                                      public Object execute() throws Throwable {
+                                          Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 0);
+                                          Mockito.verify(dbiSpy, Mockito.times(0)).open();
+                                          Mockito.verify(roDbiSpy, Mockito.times(1)).open();
+
+                                          Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 0);
+                                          Mockito.verify(dbiSpy, Mockito.times(0)).open();
+                                          Mockito.verify(roDbiSpy, Mockito.times(2)).open();
+
+                                          final String externalKey = UUID.randomUUID().toString();
+                                          final DateTime startDate = clock.getUTCNow();
+                                          final DateTime createdDate = startDate.plusSeconds(10);
+                                          final DefaultSubscriptionBaseBundle bundleDef = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
+                                          final SubscriptionBaseBundle bundle = subscriptionDao.createSubscriptionBundle(bundleDef, catalog, false, internalCallContext);
+                                          Mockito.verify(dbiSpy, Mockito.times(1)).open();
+                                          Mockito.verify(roDbiSpy, Mockito.times(2)).open();
+
+                                          Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 1);
+                                          Mockito.verify(dbiSpy, Mockito.times(2)).open();
+                                          Mockito.verify(roDbiSpy, Mockito.times(2)).open();
+
+                                          Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 1);
+                                          Mockito.verify(dbiSpy, Mockito.times(3)).open();
+                                          Mockito.verify(roDbiSpy, Mockito.times(2)).open();
+
+                                          return null;
+                                      }
+                                  });
+
+        DBRouterUntyped.withRODBIAllowed(true,
+                                         new WithProfilingCallback<Object, Throwable>() {
+                                      @Override
+                                      public Object execute() {
+                                          Assert.assertEquals(subscriptionDao.getSubscriptionBundleForAccount(accountId, internalCallContext).size(), 1);
+                                          Mockito.verify(dbiSpy, Mockito.times(3)).open();
+                                          Mockito.verify(roDbiSpy, Mockito.times(3)).open();
+
+                                          return null;
+                                      }
+                                  });
     }
 }
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 255a4d1..3535366 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
@@ -126,6 +126,10 @@ public class SubscriptionTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     public void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector g = Guice.createInjector(Stage.PRODUCTION, new TestDefaultSubscriptionModuleNoDB(configSource));
         g.injectMembers(this);
 
@@ -135,6 +139,10 @@ public class SubscriptionTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         // CLEANUP ALL DB TABLES OR IN MEMORY STRUCTURES
         ((MockSubscriptionDaoMemory) dao).reset();
 
@@ -167,6 +175,10 @@ public class SubscriptionTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @AfterMethod(groups = "fast")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         subscriptionTestInitializer.stopTestFramework(testListener, busService, subscriptionBaseService);
     }
 
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 4f443c0..b22a8c2 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
@@ -105,6 +105,10 @@ public class SubscriptionTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteW
 
     @BeforeClass(groups = "slow")
     public void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector g = Guice.createInjector(Stage.PRODUCTION, new TestDefaultSubscriptionModuleWithEmbeddedDB(configSource));
         g.injectMembers(this);
     }
@@ -112,6 +116,10 @@ public class SubscriptionTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteW
     @Override
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
         subscriptionTestInitializer.startTestFramework(testListener, clock, busService, subscriptionBaseService);
 
@@ -124,6 +132,10 @@ public class SubscriptionTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteW
 
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         subscriptionTestInitializer.stopTestFramework(testListener, busService, subscriptionBaseService);
     }
 

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

diff --git a/tenant/pom.xml b/tenant/pom.xml
index 710dd0e..173271b 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-tenant</artifactId>
diff --git a/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteNoDB.java b/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteNoDB.java
index dac6c5d..5d15d41 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteNoDB.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteNoDB.java
@@ -28,6 +28,10 @@ public class TenantTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestTenantModuleNoDB(configSource));
         injector.injectMembers(this);
     }
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 45486dc..1c5b407 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java
@@ -55,6 +55,10 @@ public class TenantTestSuiteWithEmbeddedDb extends GuicyKillbillTestSuiteWithEmb
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestTenantModuleWithEmbeddedDB(configSource));
         injector.injectMembers(this);
     }

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

diff --git a/usage/pom.xml b/usage/pom.xml
index 056478a..8166a7e 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-usage</artifactId>
diff --git a/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteNoDB.java b/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteNoDB.java
index 2b58cea..54b2182 100644
--- a/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteNoDB.java
+++ b/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteNoDB.java
@@ -30,15 +30,11 @@ public class UsageTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestUsageModuleNoDB(configSource));
         injector.injectMembers(this);
     }
-
-    @BeforeMethod(groups = "fast")
-    public void beforeMethod() {
-    }
-
-    @AfterMethod(groups = "fast")
-    public void afterMethod() {
-    }
 }
diff --git a/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteWithEmbeddedDB.java b/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteWithEmbeddedDB.java
index 81030d7..32c5aea 100644
--- a/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteWithEmbeddedDB.java
+++ b/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteWithEmbeddedDB.java
@@ -35,6 +35,10 @@ public class UsageTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEmbe
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector injector = Guice.createInjector(new TestUsageModuleWithEmbeddedDB(configSource));
         injector.injectMembers(this);
     }

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

diff --git a/util/pom.xml b/util/pom.xml
index 09fe09a..24ba132 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.19.13-SNAPSHOT</version>
+        <version>0.19.14-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>
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 3cb5167..d541e9e 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
@@ -85,7 +85,8 @@ public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, Cu
     }
 
     @Override
-    public List<CustomFieldModelDao> getCustomFieldsForAccountType(final ObjectType objectType, final InternalTenantContext context) {
+    public List<CustomFieldModelDao>
+    getCustomFieldsForAccountType(final ObjectType objectType, final InternalTenantContext context) {
         final List<CustomFieldModelDao> allFields = getCustomFieldsForAccount(context);
 
         return ImmutableList.<CustomFieldModelDao>copyOf(Collections2.filter(allFields, new Predicate<CustomFieldModelDao>() {
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/DBRouterUntyped.java b/util/src/main/java/org/killbill/billing/util/entity/dao/DBRouterUntyped.java
index f369b84..abd2b4b 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/DBRouterUntyped.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/DBRouterUntyped.java
@@ -17,17 +17,29 @@
 
 package org.killbill.billing.util.entity.dao;
 
-import org.killbill.billing.util.glue.KillbillApiAopModule;
+import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.TransactionCallback;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.VisibleForTesting;
+
+import static org.killbill.billing.util.entity.dao.DBRouterUntyped.THREAD_STATE.RO_ALLOWED;
+import static org.killbill.billing.util.entity.dao.DBRouterUntyped.THREAD_STATE.RW_ONLY;
+
 public class DBRouterUntyped {
 
     private static final Logger logger = LoggerFactory.getLogger(DBRouterUntyped.class);
 
+    private static final ThreadLocal<THREAD_STATE> CURRENT_THREAD_STATE = new ThreadLocal<THREAD_STATE>() {
+        @Override
+        public THREAD_STATE initialValue() {
+            return RW_ONLY;
+        }
+    };
+
     protected final IDBI dbi;
     protected final IDBI roDbi;
 
@@ -36,6 +48,49 @@ public class DBRouterUntyped {
         this.roDbi = roDbi;
     }
 
+    public static Object withRODBIAllowed(final boolean allowRODBI,
+                                          final WithProfilingCallback<Object, Throwable> callback) throws Throwable {
+        final THREAD_STATE currentState = getCurrentState();
+        CURRENT_THREAD_STATE.set(allowRODBI ? RO_ALLOWED : RW_ONLY);
+
+        try {
+            return callback.execute();
+        } finally {
+            CURRENT_THREAD_STATE.set(currentState);
+        }
+    }
+
+    @VisibleForTesting
+    public static THREAD_STATE getCurrentState() {
+        return CURRENT_THREAD_STATE.get();
+    }
+
+    boolean shouldUseRODBI(final boolean requestedRO) {
+        if (requestedRO) {
+            if (isRODBIAllowed()) {
+                logger.debug("Using RO DBI");
+                return true;
+            } else {
+                // Redirect to the rw instance, to work-around any replication delay
+                logger.debug("RO DBI requested, but thread state is {}, using RW DBI", getCurrentState());
+                return false;
+            }
+        } else {
+            // Disable RO DBI for future calls in this thread
+            disallowRODBI();
+            logger.debug("Using RW DBI");
+            return false;
+        }
+    }
+
+    private boolean isRODBIAllowed() {
+        return getCurrentState() == RO_ALLOWED;
+    }
+
+    private void disallowRODBI() {
+        CURRENT_THREAD_STATE.set(RW_ONLY);
+    }
+
     public Handle getHandle(final boolean requestedRO) {
         if (shouldUseRODBI(requestedRO)) {
             return roDbi.open();
@@ -60,20 +115,10 @@ public class DBRouterUntyped {
         }
     }
 
-    boolean shouldUseRODBI(final boolean requestedRO) {
-        if (!requestedRO) {
-            KillbillApiAopModule.setDirtyDBFlag();
-            logger.debug("Dirty flag set, using RW DBI");
-            return false;
-        } else {
-            if (KillbillApiAopModule.getDirtyDBFlag()) {
-                // Redirect to the rw instance, to work-around any replication delay
-                logger.debug("RO DBI handle requested, but dirty flag set, using RW DBI");
-                return false;
-            } else {
-                logger.debug("Using RO DBI");
-                return true;
-            }
-        }
+    public enum THREAD_STATE {
+        // Advisory that RO DBI can be used
+        RO_ALLOWED,
+        // Dirty flag, calls must go to RW DBI
+        RW_ONLY
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillbillApiAopModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillbillApiAopModule.java
index 80abe6d..92cc22e 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillbillApiAopModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillbillApiAopModule.java
@@ -22,11 +22,9 @@ import java.lang.reflect.Method;
 import org.aopalliance.intercept.MethodInterceptor;
 import org.aopalliance.intercept.MethodInvocation;
 import org.killbill.billing.KillbillApi;
-import org.killbill.billing.callcontext.DefaultTenantContext;
-import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.osgi.api.ROTenantContext;
 import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.entity.dao.DBRouterUntyped;
 import org.killbill.commons.profiling.Profiling;
 import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
 import org.killbill.commons.profiling.ProfilingFeature.ProfilingFeatureType;
@@ -40,16 +38,26 @@ import com.google.inject.matcher.Matchers;
 public class KillbillApiAopModule extends AbstractModule {
 
     private static final Logger logger = LoggerFactory.getLogger(KillbillApiAopModule.class);
-    private static final ThreadLocal<Boolean> perThreadDirtyDBFlag = new ThreadLocal<Boolean>();
 
-    static {
-        // Set an initial value
-        resetDirtyDBFlag();
-    }
+    private static final Matcher<Method> SYNTHETIC_METHOD_MATCHER = new Matcher<Method>() {
+        @Override
+        public boolean matches(final Method method) {
+            return method.isSynthetic();
+        }
+
+        @Override
+        public Matcher<Method> and(final Matcher<? super Method> other) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Matcher<Method> or(final Matcher<? super Method> other) {
+            throw new UnsupportedOperationException();
+        }
+    };
 
     @Override
     protected void configure() {
-
         bindInterceptor(Matchers.subclassesOf(KillbillApi.class),
                         Matchers.not(SYNTHETIC_METHOD_MATCHER),
                         new ProfilingMethodInterceptor());
@@ -57,34 +65,41 @@ public class KillbillApiAopModule extends AbstractModule {
 
     public static class ProfilingMethodInterceptor implements MethodInterceptor {
 
-        private final Profiling prof = new Profiling<Object, Throwable>();
+        private final Profiling<Object, Throwable> prof = new Profiling<Object, Throwable>();
 
         @Override
         public Object invoke(final MethodInvocation invocation) throws Throwable {
-            return prof.executeWithProfiling(ProfilingFeatureType.API, invocation.getMethod().getName(), new WithProfilingCallback() {
+            final WithProfilingCallback<Object, Throwable> callback = new WithProfilingCallback<Object, Throwable>() {
                 @Override
                 public Object execute() throws Throwable {
-                    final boolean useRODBIfAvailable = shouldUseRODBIfAvailable(invocation);
-                    if (!useRODBIfAvailable) {
-                        setDirtyDBFlag();
-                    }
-
-                    try {
-                        logger.debug("Entering API call {}, arguments: {}", invocation.getMethod(), invocation.getArguments());
-                        final Object proceed = invocation.proceed();
-                        logger.debug("Exiting  API call {}, returning: {}", invocation.getMethod(), proceed);
-                        return proceed;
-                    } finally {
-                        resetDirtyDBFlag();
-                    }
+                    logger.debug("Entering API call {}, arguments: {}", invocation.getMethod(), invocation.getArguments());
+                    final Object proceed = invocation.proceed();
+                    logger.debug("Exiting  API call {}, returning: {}", invocation.getMethod(), proceed);
+                    return proceed;
                 }
-            });
+            };
+
+            if (forcedRODBI(invocation)) {
+                return prof.executeWithProfiling(ProfilingFeatureType.API,
+                                                 invocation.getMethod().getName(),
+                                                 new WithProfilingCallback<Object, Throwable>() {
+                                                     @Override
+                                                     public Object execute() throws Throwable {
+                                                         return DBRouterUntyped.withRODBIAllowed(true, callback);
+                                                     }
+                                                 });
+            } else {
+                return prof.executeWithProfiling(ProfilingFeatureType.API,
+                                                 invocation.getMethod().getName(),
+                                                 callback);
+            }
         }
 
-        private boolean shouldUseRODBIfAvailable(final MethodInvocation invocation) {
-            // Verify if the flag is already set for re-entrant calls
-            if (getDirtyDBFlag()) {
-                return false;
+        private boolean forcedRODBI(final MethodInvocation invocation) {
+            // Snowflakes from server filters
+            final boolean safeROOperations = "getTenantByApiKey".equals(invocation.getMethod().getName()) || "login".equals(invocation.getMethod().getName());
+            if (safeROOperations) {
+                return true;
             }
 
             final Object[] arguments = invocation.getArguments();
@@ -92,21 +107,11 @@ public class KillbillApiAopModule extends AbstractModule {
                 return false;
             }
 
-            // Snowflakes from server filters
-            final boolean safeROOperations = "getTenantByApiKey".equals(invocation.getMethod().getName()) || "login".equals(invocation.getMethod().getName());
-            if (safeROOperations) {
-                return true;
-            }
-
             for (int i = arguments.length - 1; i >= 0; i--) {
                 final Object argument = arguments[i];
-                // DefaultTenantContext belongs to killbill-internal-api and shouldn't be used by plugins
-                final boolean fromJAXRS = argument instanceof DefaultTenantContext && !(argument instanceof CallContext);
-                // Kill Bill internal re-entrant calls
-                final boolean fromInternalAPIs = argument instanceof InternalTenantContext && !(argument instanceof InternalCallContext);
                 // RO DB explicitly requested by a plugin
                 final boolean pluginRequestROInstance = argument instanceof ROTenantContext && !(argument instanceof CallContext);
-                if (fromJAXRS || fromInternalAPIs || pluginRequestROInstance) {
+                if (pluginRequestROInstance) {
                     return true;
                 }
             }
@@ -114,33 +119,4 @@ public class KillbillApiAopModule extends AbstractModule {
             return false;
         }
     }
-
-    public static void setDirtyDBFlag() {
-        perThreadDirtyDBFlag.set(true);
-    }
-
-    public static void resetDirtyDBFlag() {
-        perThreadDirtyDBFlag.set(false);
-    }
-
-    public static Boolean getDirtyDBFlag() {
-        return perThreadDirtyDBFlag.get() == Boolean.TRUE;
-    }
-
-    private static final Matcher<Method> SYNTHETIC_METHOD_MATCHER = new Matcher<Method>() {
-        @Override
-        public boolean matches(final Method method) {
-            return method.isSynthetic();
-        }
-
-        @Override
-        public Matcher<Method> and(final Matcher<? super Method> other) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public Matcher<Method> or(final Matcher<? super Method> other) {
-            throw new UnsupportedOperationException();
-        }
-    };
 }
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java
index 9d48932..135f7aa 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java
@@ -258,6 +258,6 @@ public class GuicyKillbillTestSuite implements IHookable {
     }
 
     public boolean hasFailed() {
-        return hasFailed;
+        return hasFailed || AbortAfterFirstFailureListener.hasFailures();
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
index 37e3f80..c4ca078 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
@@ -23,7 +23,6 @@ import javax.inject.Named;
 import javax.sql.DataSource;
 
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
-import org.killbill.billing.util.glue.KillbillApiAopModule;
 import org.killbill.commons.embeddeddb.EmbeddedDB;
 import org.skife.jdbi.v2.IDBI;
 import org.slf4j.Logger;
@@ -62,9 +61,12 @@ public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         cleanupAllTables();
         controlCacheDispatcher.clearAll();
-        KillbillApiAopModule.resetDirtyDBFlag();
     }
 
     protected void cleanupAllTables() {
diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java
index 920e5cb..cda04b3 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java
@@ -75,17 +75,29 @@ public class UtilTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     public void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector g = Guice.createInjector(Stage.PRODUCTION, new TestUtilModuleNoDB(configSource));
         g.injectMembers(this);
     }
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         eventBus.start();
     }
 
     @AfterMethod(groups = "fast")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         eventBus.stop();
         // Reset the security manager
         ThreadContext.unbindSecurityManager();
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 f734d68..3868612 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
@@ -98,6 +98,10 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
 
     @BeforeClass(groups = "slow")
     public void beforeClass() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         final Injector g = Guice.createInjector(Stage.PRODUCTION, new TestUtilModuleWithEmbeddedDB(configSource));
         g.injectMembers(this);
 
@@ -114,6 +118,10 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
     @Override
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
 
         eventsListener.reset();
@@ -124,6 +132,10 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
 
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         eventBus.unregister(eventsListener);
         eventBus.stop();
     }