killbill-uncached

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

5/23/2018 2:59:30 PM

Changes

catalog/src/test/java/org/killbill/billing/catalog/TestLimits.java 107(+0 -107)

pom.xml 2(+1 -1)

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
index e3d7799..2c9aa27 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
@@ -74,6 +74,10 @@ public class TestPublicBus extends TestIntegrationBase {
     @Override
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         /*
         We copy the initialization instead of invoking the super method so we can add the registration
         of the publicBus event;
@@ -107,6 +111,10 @@ public class TestPublicBus extends TestIntegrationBase {
 
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         externalBus.unregister(publicListener);
         super.afterMethod();
     }
@@ -180,7 +188,7 @@ public class TestPublicBus extends TestIntegrationBase {
                 event.getEventType() == ExtBusEventType.SUBSCRIPTION_UNCANCEL ||
                 event.getEventType() == ExtBusEventType.SUBSCRIPTION_BCD_CHANGE) {
                 try {
-                    final SubscriptionMetadata obj = (SubscriptionMetadata) mapper.readValue(event.getMetaData(), SubscriptionMetadata.class);
+                    final SubscriptionMetadata obj = mapper.readValue(event.getMetaData(), SubscriptionMetadata.class);
                     Assert.assertNotNull(obj.getBundleExternalKey());
                     Assert.assertNotNull(obj.getActionType());
                 } catch (final JsonParseException e) {
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 c6a2b73..db8c664 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
@@ -119,6 +119,10 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
 
     @BeforeMethod(groups = "slow")
     public void setUp() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         testInvoicePluginApi.additionalInvoiceItem = null;
         testInvoicePluginApi.shouldAddTaxItem = true;
         testInvoicePluginApi.isAborted = false;
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
index f9c1c0d..ed0e394 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
@@ -79,14 +79,14 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
     private DefaultUnit[] units;
 
     @XmlElementWrapper(name = "products", required = true)
-    @XmlElement(type=DefaultProduct.class, name = "product", required = false)
+    @XmlElement(type = DefaultProduct.class, name = "product", required = false)
     private CatalogEntityCollection<Product> products;
 
     @XmlElement(name = "rules", required = true)
     private DefaultPlanRules planRules;
 
     @XmlElementWrapper(name = "plans", required = true)
-    @XmlElement(type=DefaultPlan.class, name = "plan", required = false)
+    @XmlElement(type = DefaultPlan.class, name = "plan", required = false)
     private CatalogEntityCollection<Plan> plans;
 
     @XmlElement(name = "priceLists", required = true)
@@ -103,35 +103,35 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         this.effectiveDate = effectiveDate;
     }
 
-    /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.ICatalog#getCalalogName()
-      */
     @Override
     public String getCatalogName() {
         return catalogName;
     }
 
+    public StandaloneCatalog setCatalogName(final String catalogName) {
+        this.catalogName = catalogName;
+        return this;
+    }
+
     @Override
     public Date getEffectiveDate() {
         return effectiveDate;
     }
 
-    /* (non-Javadoc)
-     * @see org.killbill.billing.catalog.ICatalog#getProducts()
-     */
+    public StandaloneCatalog setEffectiveDate(final Date effectiveDate) {
+        this.effectiveDate = effectiveDate;
+        return this;
+    }
+
     @Override
     public Collection<Product> getCurrentProducts() {
         return products.getEntries();
     }
 
-
-    public CatalogEntityCollection<Product>  getCatalogEntityCollectionProduct() {
+    public CatalogEntityCollection<Product> getCatalogEntityCollectionProduct() {
         return products;
     }
 
-    /* (non-Javadoc)
-     * @see org.killbill.billing.catalog.ICatalog#getProducts()
-     */
     @Override
     public DefaultUnit[] getCurrentUnits() {
         return units;
@@ -142,21 +142,18 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return supportedCurrencies;
     }
 
-
-
     @Override
     public Collection<Plan> getCurrentPlans() {
         return plans.getEntries();
     }
 
-
     public CatalogEntityCollection<Plan> getCatalogEntityCollectionPlan() {
         return plans;
     }
 
     public boolean isTemplateCatalog() {
-        return (products == null || products.size() == 0) &&
-               (plans == null || plans.size() == 0) &&
+        return (products == null || products.isEmpty()) &&
+               (plans == null || plans.isEmpty()) &&
                (supportedCurrencies == null || supportedCurrencies.length == 0);
     }
 
@@ -168,6 +165,11 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return planRules;
     }
 
+    public StandaloneCatalog setPlanRules(final DefaultPlanRules planRules) {
+        this.planRules = planRules;
+        return this;
+    }
+
     public DefaultPriceList findCurrentPriceList(final String priceListName) throws CatalogApiException {
         return priceLists.findPriceListFrom(priceListName);
     }
@@ -176,6 +178,11 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return this.priceLists;
     }
 
+    public StandaloneCatalog setPriceLists(final DefaultPriceListSet priceLists) {
+        this.priceLists = priceLists;
+        return this;
+    }
+
     @Override
     public Plan createOrFindCurrentPlan(final PlanSpecifier spec, final PlanPhasePriceOverridesWithCallContext unused) throws CatalogApiException {
         final Plan result;
@@ -194,7 +201,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         }
         if (result == null) {
             throw new CatalogApiException(ErrorCode.CAT_PLAN_NOT_FOUND,
-                                          spec.getPlanName() !=  null ? spec.getPlanName() : "undefined",
+                                          spec.getPlanName() != null ? spec.getPlanName() : "undefined",
                                           spec.getProductName() != null ? spec.getProductName() : "undefined",
                                           spec.getBillingPeriod() != null ? spec.getBillingPeriod() : "undefined",
                                           spec.getPriceListName() != null ? spec.getPriceListName() : "undefined");
@@ -202,6 +209,12 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return result;
     }
 
+    //////////////////////////////////////////////////////////////////////////////
+    //
+    // RULES
+    //
+    //////////////////////////////////////////////////////////////////////////////
+
     @Override
     public DefaultPlan findCurrentPlan(final String name) throws CatalogApiException {
         if (name == null || plans == null) {
@@ -245,16 +258,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return priceLists.findPriceListFrom(name);
     }
 
-    //////////////////////////////////////////////////////////////////////////////
-    //
-    // RULES
-    //
-    //////////////////////////////////////////////////////////////////////////////
-    @Override
-    public BillingActionPolicy planChangePolicy(final PlanPhaseSpecifier from, final PlanSpecifier to) throws CatalogApiException {
-        return planRules.getPlanChangePolicy(from, to, this);
-    }
-
     @Override
     public PlanAlignmentChange planChangeAlignment(final PlanPhaseSpecifier from, final PlanSpecifier to) throws CatalogApiException {
         return planRules.getPlanChangeAlignment(from, to, this);
@@ -275,6 +278,12 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return planRules.getBillingAlignment(planPhase, this);
     }
 
+    //////////////////////////////////////////////////////////////////////////////
+    //
+    // UNIT LIMIT
+    //
+    //////////////////////////////////////////////////////////////////////////////
+
     @Override
     public PlanChangeResult planChange(final PlanPhaseSpecifier from, final PlanSpecifier to)
             throws CatalogApiException {
@@ -283,8 +292,8 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
 
     @Override
     public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
-        validateCollection(catalog, errors, (DefaultProduct[])  products.toArray(new DefaultProduct[products.size()]));
-        validateCollection(catalog, errors, (DefaultPlan[])  plans.toArray(new DefaultPlan[plans.size()]));
+        validateCollection(catalog, errors, (DefaultProduct[]) products.toArray(new DefaultProduct[0]));
+        validateCollection(catalog, errors, (DefaultPlan[]) plans.toArray(new DefaultPlan[0]));
         priceLists.validate(catalog, errors);
         planRules.validate(catalog, errors);
         return errors;
@@ -303,7 +312,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
             cur.initialize(catalog, sourceURI);
         }
         for (final Product p : products.getEntries()) {
-            ((DefaultProduct)p).initialize(catalog, sourceURI);
+            ((DefaultProduct) p).initialize(catalog, sourceURI);
         }
         for (final Plan p : plans.getEntries()) {
             ((DefaultPlan) p).initialize(catalog, sourceURI);
@@ -314,16 +323,9 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return recurringBillingMode;
     }
 
-    //////////////////////////////////////////////////////////////////////////////
-    //
-    // UNIT LIMIT
-    //
-    //////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public boolean compliesWithLimits(final String phaseName, final String unit, final double value) throws CatalogApiException {
-        PlanPhase phase = findCurrentPhase(phaseName);
-        return phase.compliesWithLimits(unit, value);
+    public StandaloneCatalog setRecurringBillingMode(final BillingMode recurringBillingMode) {
+        this.recurringBillingMode = recurringBillingMode;
+        return this;
     }
 
     public StandaloneCatalog setProducts(final Iterable<Product> products) {
@@ -341,60 +343,24 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return this;
     }
 
-    public StandaloneCatalog setCatalogName(final String catalogName) {
-        this.catalogName = catalogName;
-        return this;
-    }
-
-    public StandaloneCatalog setEffectiveDate(final Date effectiveDate) {
-        this.effectiveDate = effectiveDate;
-        return this;
-    }
-
-    public StandaloneCatalog setRecurringBillingMode(final BillingMode recurringBillingMode) {
-        this.recurringBillingMode = recurringBillingMode;
-        return this;
-    }
-
-    public StandaloneCatalog setPlanRules(final DefaultPlanRules planRules) {
-        this.planRules = planRules;
-        return this;
-    }
-
-    public StandaloneCatalog setPriceLists(final DefaultPriceListSet priceLists) {
-        this.priceLists = priceLists;
-        return this;
-    }
-
     public StandaloneCatalog setUnits(final DefaultUnit[] units) {
         this.units = units;
         return this;
     }
 
     @Override
-    public boolean canCreatePlan(final PlanSpecifier specifier) throws CatalogApiException {
-        final Product product = findCurrentProduct(specifier.getProductName());
-        final Plan plan = createOrFindCurrentPlan(specifier, null);
-        final DefaultPriceList priceList = findCurrentPriceList(specifier.getPriceListName());
-
-        return (product != null) &&
-               (plan != null) &&
-               (priceList != null);
-    }
-
-    @Override
     public List<Listing> getAvailableAddOnListings(final String baseProductName, @Nullable final String priceListName) {
         final List<Listing> availAddons = new ArrayList<Listing>();
 
         try {
-            Product product = findCurrentProduct(baseProductName);
+            final Product product = findCurrentProduct(baseProductName);
             if (product != null) {
-                for (Product availAddon : product.getAvailable()) {
-                    for (BillingPeriod billingPeriod : BillingPeriod.values()) {
-                        for (PriceList priceList : getPriceLists().getAllPriceLists()) {
+                for (final Product availAddon : product.getAvailable()) {
+                    for (final BillingPeriod billingPeriod : BillingPeriod.values()) {
+                        for (final PriceList priceList : getPriceLists().getAllPriceLists()) {
                             if (priceListName == null || priceListName.equals(priceList.getName())) {
-                                Collection<Plan> addonInList = priceList.findPlans(availAddon, billingPeriod);
-                                for (Plan cur : addonInList) {
+                                final Collection<Plan> addonInList = priceList.findPlans(availAddon, billingPeriod);
+                                for (final Plan cur : addonInList) {
                                     availAddons.add(new DefaultListing(cur, priceList));
                                 }
                             }
@@ -402,7 +368,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
                     }
                 }
             }
-        } catch (CatalogApiException e) {
+        } catch (final CatalogApiException e) {
             // No such product - just return an empty list
         }
         return availAddons;
@@ -412,10 +378,10 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
     public List<Listing> getAvailableBasePlanListings() {
         final List<Listing> availBasePlans = new ArrayList<Listing>();
 
-        for (Plan plan : getCurrentPlans()) {
+        for (final Plan plan : getCurrentPlans()) {
             if (plan.getProduct().getCategory().equals(ProductCategory.BASE)) {
-                for (PriceList priceList : getPriceLists().getAllPriceLists()) {
-                    for (Plan priceListPlan : priceList.getPlans()) {
+                for (final PriceList priceList : getPriceLists().getAllPriceLists()) {
+                    for (final Plan priceListPlan : priceList.getPlans()) {
                         if (priceListPlan.getName().equals(plan.getName()) &&
                             priceListPlan.getProduct().getName().equals(plan.getProduct().getName())) {
                             availBasePlans.add(new DefaultListing(priceListPlan, priceList));
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index f7d8036..6e171ad 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.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
@@ -44,7 +44,6 @@ import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingAlignment;
-import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
@@ -123,38 +122,12 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
         // This is not strictly correct from an api point of view, but there is no real good use case
         // where the system would ask for the catalog for a date prior any catalog was uploaded and
         // yet time manipulation could end of inn that state -- see https://github.com/killbill/killbill/issues/760
-        if (versions.size() > 0) {
+        if (!versions.isEmpty()) {
             return 0;
         }
         throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, date.toString());
     }
 
-    private class PlanRequestWrapper {
-
-        private final PlanSpecifier spec;
-        private final PlanPhasePriceOverridesWithCallContext overrides;
-
-        public PlanRequestWrapper(final String planName) {
-            this.spec = new PlanSpecifier(planName);
-            this.overrides = null;
-        }
-
-        public PlanRequestWrapper(final PlanSpecifier spec,
-                                  final PlanPhasePriceOverridesWithCallContext overrides) {
-            this.spec = spec;
-            this.overrides = overrides;
-        }
-
-
-        public Plan findPlan(final StandaloneCatalog catalog) throws CatalogApiException {
-            return catalog.createOrFindCurrentPlan(spec, overrides);
-        }
-
-        public PlanSpecifier getSpec() {
-            return spec;
-        }
-    }
-
     private CatalogPlanEntry findCatalogPlanEntry(final PlanRequestWrapper wrapper,
                                                   final DateTime requestedDate,
                                                   final DateTime subscriptionStartDate) throws CatalogApiException {
@@ -180,7 +153,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
                 }
             }
 
-
             final boolean oldestCatalog = (i == 0);
             final DateTime catalogEffectiveDate = CatalogDateHelper.toUTCDateTime(c.getEffectiveDate());
             final boolean catalogOlderThanSubscriptionStartDate = !subscriptionStartDate.isBefore(catalogEffectiveDate);
@@ -212,26 +184,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
                                       spec.getPriceListName() != null ? spec.getPriceListName() : "undefined");
     }
 
-    private static class CatalogPlanEntry {
-
-        private final StaticCatalog staticCatalog;
-        private final Plan plan;
-
-        public CatalogPlanEntry(final StaticCatalog staticCatalog, final Plan plan) {
-            this.staticCatalog = staticCatalog;
-            this.plan = plan;
-        }
-
-        public StaticCatalog getStaticCatalog() {
-            return staticCatalog;
-        }
-
-        public Plan getPlan() {
-            return plan;
-        }
-    }
-
-
     public Clock getClock() {
         return clock;
     }
@@ -243,13 +195,13 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
     //
     // Public methods not exposed in interface
     //
-    public void addAll(final List<StandaloneCatalog> inputVersions) throws CatalogApiException {
+    public void addAll(final Iterable<StandaloneCatalog> inputVersions) {
         for (final StandaloneCatalog cur : inputVersions) {
             add(cur);
         }
     }
 
-    public void add(final StandaloneCatalog e) throws CatalogApiException {
+    public void add(final StandaloneCatalog e) {
         if (catalogName == null && e.getCatalogName() != null) {
             catalogName = e.getCatalogName();
         }
@@ -270,9 +222,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
         return versions.size();
     }
 
-    //
-    // Simple getters
-    //
     @Override
     public String getCatalogName() {
         return catalogName;
@@ -303,9 +252,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
         return versionForDate(requestedDate).getPriceLists();
     }
 
-    //
-    // Find a plan
-    //
     @Override
     public Plan findPlan(final String name,
                          final DateTime requestedDate)
@@ -336,21 +282,15 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
                                  final DateTime requestedDate,
                                  final DateTime subscriptionStartDate)
             throws CatalogApiException {
-        final CatalogPlanEntry entry =  findCatalogPlanEntry(new PlanRequestWrapper(spec, overrides), requestedDate, subscriptionStartDate);
+        final CatalogPlanEntry entry = findCatalogPlanEntry(new PlanRequestWrapper(spec, overrides), requestedDate, subscriptionStartDate);
         return entry.getPlan();
     }
 
-    //
-    // Find a product
-    //
     @Override
     public Product findProduct(final String name, final DateTime requestedDate) throws CatalogApiException {
         return versionForDate(requestedDate).findCurrentProduct(name);
     }
 
-    //
-    // Find a phase
-    //
     @Override
     public PlanPhase findPhase(final String phaseName,
                                final DateTime requestedDate,
@@ -361,9 +301,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
         return plan.findPhase(phaseName);
     }
 
-    //
-    // Find a price list associated to a given subscription
-    //
     @Override
     public PriceList findPriceListForPlan(final String planName,
                                           final DateTime requestedDate,
@@ -373,58 +310,55 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
         return entry.getStaticCatalog().findCurrentPricelist(entry.getPlan().getPriceListName());
     }
 
-
-    public PriceList findPriceList(final String name, final DateTime requestedDate)
-            throws CatalogApiException {
-        return versionForDate(requestedDate).findCurrentPriceList(name);
-    }
-
-
-    //
-    // Rules
-    //
     @Override
-    public BillingActionPolicy planChangePolicy(final PlanPhaseSpecifier from,
-                                                final PlanSpecifier to, final DateTime requestedDate) throws CatalogApiException {
-        return versionForDate(requestedDate).planChangePolicy(from, to);
-    }
-
-    @Override
-    public BillingActionPolicy planCancelPolicy(final PlanPhaseSpecifier planPhase, final DateTime requestedDate) throws CatalogApiException {
-        return versionForDate(requestedDate).planCancelPolicy(planPhase);
+    public BillingActionPolicy planCancelPolicy(final PlanPhaseSpecifier planPhase,
+                                                final DateTime requestedDate,
+                                                final DateTime subscriptionStartDate) throws CatalogApiException {
+        final StaticCatalog staticCatalog = getStaticCatalog(planPhase, requestedDate, subscriptionStartDate);
+        return staticCatalog.planCancelPolicy(planPhase);
     }
 
     @Override
     public PlanAlignmentChange planChangeAlignment(final PlanPhaseSpecifier from,
-                                                   final PlanSpecifier to, final DateTime requestedDate) throws CatalogApiException {
-        return versionForDate(requestedDate).planChangeAlignment(from, to);
+                                                   final PlanSpecifier to,
+                                                   final DateTime requestedDate,
+                                                   final DateTime subscriptionStartDate) throws CatalogApiException {
+        final StaticCatalog staticCatalog = getStaticCatalog(from, requestedDate, subscriptionStartDate);
+        return staticCatalog.planChangeAlignment(from, to);
     }
 
     @Override
-    public PlanAlignmentCreate planCreateAlignment(final PlanSpecifier specifier, final DateTime requestedDate) throws CatalogApiException {
-        return versionForDate(requestedDate).planCreateAlignment(specifier);
+    public PlanAlignmentCreate planCreateAlignment(final PlanSpecifier specifier,
+                                                   final DateTime requestedDate,
+                                                   final DateTime subscriptionStartDate) throws CatalogApiException {
+        final StaticCatalog staticCatalog = getStaticCatalog(specifier, requestedDate, subscriptionStartDate);
+        return staticCatalog.planCreateAlignment(specifier);
     }
 
     @Override
-    public BillingAlignment billingAlignment(final PlanPhaseSpecifier planPhase, final DateTime requestedDate) throws CatalogApiException {
-        return versionForDate(requestedDate).billingAlignment(planPhase);
+    public BillingAlignment billingAlignment(final PlanPhaseSpecifier planPhase,
+                                             final DateTime requestedDate,
+                                             final DateTime subscriptionStartDate) throws CatalogApiException {
+        final StaticCatalog staticCatalog = getStaticCatalog(planPhase, requestedDate, subscriptionStartDate);
+        return staticCatalog.billingAlignment(planPhase);
     }
 
     @Override
-    public PlanChangeResult planChange(final PlanPhaseSpecifier from, final PlanSpecifier to, final DateTime requestedDate)
+    public PlanChangeResult planChange(final PlanPhaseSpecifier from,
+                                       final PlanSpecifier to,
+                                       final DateTime requestedDate,
+                                       final DateTime subscriptionStartDate)
             throws CatalogApiException {
-        return versionForDate(requestedDate).planChange(from, to);
+        final StaticCatalog staticCatalog = getStaticCatalog(from, requestedDate, subscriptionStartDate);
+        return staticCatalog.planChange(from, to);
     }
 
-    @Override
-    public boolean canCreatePlan(final PlanSpecifier specifier, final DateTime requestedDate)
-            throws CatalogApiException {
-        return versionForDate(requestedDate).canCreatePlan(specifier);
+    // Note that the PlanSpecifier billing period must refer here to the recurring phase one when a plan name isn't specified
+    private StaticCatalog getStaticCatalog(final PlanSpecifier spec, final DateTime requestedDate, final DateTime subscriptionStartDate) throws CatalogApiException {
+        final CatalogPlanEntry entry = findCatalogPlanEntry(new PlanRequestWrapper(spec), requestedDate, subscriptionStartDate);
+        return entry.getStaticCatalog();
     }
 
-    //
-    // VerifiableConfig API
-    //
     @Override
     public void initialize(final VersionedCatalog catalog, final URI sourceURI) {
         //
@@ -438,13 +372,12 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
 
     @Override
     public ValidationErrors validate(final VersionedCatalog catalog, final ValidationErrors errors) {
-
         final Set<Date> effectiveDates = new TreeSet<Date>();
 
         for (final StandaloneCatalog c : versions) {
             if (effectiveDates.contains(c.getEffectiveDate())) {
                 errors.add(new ValidationError(String.format("Catalog effective date '%s' already exists for a previous version", c.getEffectiveDate()),
-                        c.getCatalogURI(), VersionedCatalog.class, ""));
+                                               c.getCatalogURI(), VersionedCatalog.class, ""));
             } else {
                 effectiveDates.add(c.getEffectiveDate());
             }
@@ -517,12 +450,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
     }
 
     @Override
-    public BillingActionPolicy planChangePolicy(final PlanPhaseSpecifier from,
-                                                final PlanSpecifier to) throws CatalogApiException {
-        return versionForDate(clock.getUTCNow()).planChangePolicy(from, to);
-    }
-
-    @Override
     public PlanChangeResult planChange(final PlanPhaseSpecifier from, final PlanSpecifier to)
             throws CatalogApiException {
         return versionForDate(clock.getUTCNow()).planChange(from, to);
@@ -553,12 +480,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
     }
 
     @Override
-    public boolean canCreatePlan(final PlanSpecifier specifier)
-            throws CatalogApiException {
-        return versionForDate(clock.getUTCNow()).canCreatePlan(specifier);
-    }
-
-    @Override
     public List<Listing> getAvailableAddOnListings(final String baseProductName, @Nullable final String priceListName) throws CatalogApiException {
         return versionForDate(clock.getUTCNow()).getAvailableAddOnListings(baseProductName, priceListName);
     }
@@ -569,11 +490,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
     }
 
     @Override
-    public boolean compliesWithLimits(final String phaseName, final String unit, final double value) throws CatalogApiException {
-        return versionForDate(clock.getUTCNow()).compliesWithLimits(phaseName, unit, value);
-    }
-
-    @Override
     public void readExternal(final ObjectInput in) throws IOException {
         MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
     }
@@ -582,4 +498,51 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
     public void writeExternal(final ObjectOutput oo) throws IOException {
         MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
     }
+
+    private static class CatalogPlanEntry {
+
+        private final StaticCatalog staticCatalog;
+        private final Plan plan;
+
+        public CatalogPlanEntry(final StaticCatalog staticCatalog, final Plan plan) {
+            this.staticCatalog = staticCatalog;
+            this.plan = plan;
+        }
+
+        public StaticCatalog getStaticCatalog() {
+            return staticCatalog;
+        }
+
+        public Plan getPlan() {
+            return plan;
+        }
+    }
+
+    private class PlanRequestWrapper {
+
+        private final PlanSpecifier spec;
+        private final PlanPhasePriceOverridesWithCallContext overrides;
+
+        public PlanRequestWrapper(final String planName) {
+            this(new PlanSpecifier(planName));
+        }
+
+        public PlanRequestWrapper(final PlanSpecifier spec) {
+            this(spec, null);
+        }
+
+        public PlanRequestWrapper(final PlanSpecifier spec,
+                                  final PlanPhasePriceOverridesWithCallContext overrides) {
+            this.spec = spec;
+            this.overrides = overrides;
+        }
+
+        public Plan findPlan(final StandaloneCatalog catalog) throws CatalogApiException {
+            return catalog.createOrFindCurrentPlan(spec, overrides);
+        }
+
+        public PlanSpecifier getSpec() {
+            return spec;
+        }
+    }
 }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
index 1744d0d..5b3f1b2 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,18 +19,14 @@
 package org.killbill.billing.catalog;
 
 import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.Date;
 import java.util.Iterator;
-import java.util.LinkedList;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingAlignment;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
-import org.killbill.billing.catalog.api.CatalogEntity;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanAlignmentChange;
@@ -42,18 +40,10 @@ import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.Unit;
-import org.killbill.billing.catalog.rules.DefaultCaseCancelPolicy;
-import org.killbill.billing.catalog.rules.DefaultCaseChangePlanAlignment;
-import org.killbill.billing.catalog.rules.DefaultCaseChangePlanPolicy;
-import org.killbill.billing.catalog.rules.DefaultCaseCreateAlignment;
 import org.killbill.billing.catalog.rules.DefaultPlanRules;
 
-import com.google.common.collect.ImmutableList;
-
 public class MockCatalog extends StandaloneCatalog implements Catalog {
 
-    private static final String[] PRODUCT_NAMES = new String[]{"TestProduct1", "TestProduct2", "TestProduct3"};
-    private boolean canCreatePlan;
     private PlanChangeResult planChange;
     private BillingAlignment billingAlignment;
     private PlanAlignmentCreate planCreateAlignment;
@@ -70,15 +60,6 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
         setPlanRules(new DefaultPlanRules());
     }
 
-    public void setRules(
-            final DefaultCaseChangePlanPolicy[] caseChangePlanPolicy,
-            final DefaultCaseChangePlanAlignment[] caseChangePlanAlignment,
-            final DefaultCaseCancelPolicy[] caseCancelPolicy,
-            final DefaultCaseCreateAlignment[] caseCreateAlignment
-                        ) {
-
-    }
-
     public void populatePriceLists() {
         final Collection<Plan> plans = getCurrentPlans();
 
@@ -88,7 +69,7 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
         final Plan initialPlan = it.next();
         while (it.hasNext()) {
             final Plan plan = it.next();
-            priceList[i - 1] = new DefaultPriceList(new DefaultPlan[] { (DefaultPlan) plan}, plan.getName() + "-pl");
+            priceList[i - 1] = new DefaultPriceList(new DefaultPlan[]{(DefaultPlan) plan}, plan.getName() + "-pl");
             i++;
         }
 
@@ -96,37 +77,33 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
         setPriceLists(set);
     }
 
-    public String[] getProductNames() {
-        return PRODUCT_NAMES;
-    }
-
     @Override
-    public Date getStandaloneCatalogEffectiveDate(final DateTime dateTime) throws CatalogApiException {
+    public Date getStandaloneCatalogEffectiveDate(final DateTime dateTime) {
         return getEffectiveDate();
     }
 
     @Override
-    public Currency[] getSupportedCurrencies(final DateTime requestedDate) throws CatalogApiException {
+    public Currency[] getSupportedCurrencies(final DateTime requestedDate) {
         return getCurrentSupportedCurrencies();
     }
 
     @Override
-    public Unit[] getUnits(final DateTime requestedDate) throws CatalogApiException {
+    public Unit[] getUnits(final DateTime requestedDate) {
         return getCurrentUnits();
     }
 
     @Override
-    public Collection<Product> getProducts(final DateTime requestedDate) throws CatalogApiException {
+    public Collection<Product> getProducts(final DateTime requestedDate) {
         return getCurrentProducts();
     }
 
     @Override
-    public Collection<Plan> getPlans(final DateTime requestedDate) throws CatalogApiException {
+    public Collection<Plan> getPlans(final DateTime requestedDate) {
         return getCurrentPlans();
     }
 
     @Override
-    public PriceListSet getPriceLists(final DateTime dateTime) throws CatalogApiException {
+    public PriceListSet getPriceLists(final DateTime dateTime) {
         return getPriceLists();
     }
 
@@ -136,7 +113,7 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
     }
 
     @Override
-    public Plan createOrFindPlan(final PlanSpecifier spec, PlanPhasePriceOverridesWithCallContext overrides, final DateTime requestedDate)
+    public Plan createOrFindPlan(final PlanSpecifier spec, final PlanPhasePriceOverridesWithCallContext overrides, final DateTime requestedDate)
             throws CatalogApiException {
         return createOrFindCurrentPlan(spec, overrides);
     }
@@ -148,8 +125,8 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
     }
 
     @Override
-    public Plan createOrFindPlan(final PlanSpecifier spec, PlanPhasePriceOverridesWithCallContext overrides, final DateTime requestedDate,
-                         final DateTime subscriptionStartDate) throws CatalogApiException {
+    public Plan createOrFindPlan(final PlanSpecifier spec, final PlanPhasePriceOverridesWithCallContext overrides, final DateTime requestedDate,
+                                 final DateTime subscriptionStartDate) throws CatalogApiException {
         return createOrFindCurrentPlan(spec, overrides);
     }
 
@@ -165,62 +142,38 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
     }
 
     @Override
-    public PriceList findPriceList(final String name, final DateTime requestedDate) throws CatalogApiException {
-        return findCurrentPricelist(name);
-    }
-
-    @Override
     public PriceList findPriceListForPlan(final String name, final DateTime requestedDate, final DateTime subscriptionStartDate) throws CatalogApiException {
-        return findCurrentPricelist(name);
-    }
-
-    @Override
-    public BillingActionPolicy planChangePolicy(final PlanPhaseSpecifier from, final PlanSpecifier to, final DateTime requestedDate)
-            throws CatalogApiException {
-        return planChangePolicy(from, to);
+        return findCurrentPricelist(findCurrentPlan(name).getPriceListName());
     }
 
     @Override
-    public PlanChangeResult planChange(final PlanPhaseSpecifier from, final PlanSpecifier to, final DateTime requestedDate)
-            throws CatalogApiException {
+    public PlanChangeResult planChange(final PlanPhaseSpecifier from, final PlanSpecifier to, final DateTime requestedDate, final DateTime subscriptionStartDate) {
         return planChange(from, to);
     }
 
     @Override
-    public BillingActionPolicy planCancelPolicy(final PlanPhaseSpecifier planPhase, final DateTime requestedDate)
+    public BillingActionPolicy planCancelPolicy(final PlanPhaseSpecifier planPhase, final DateTime requestedDate, final DateTime subscriptionStartDate)
             throws CatalogApiException {
         return planCancelPolicy(planPhase);
     }
 
     @Override
-    public PlanAlignmentCreate planCreateAlignment(final PlanSpecifier specifier, final DateTime requestedDate)
-            throws CatalogApiException {
+    public PlanAlignmentCreate planCreateAlignment(final PlanSpecifier specifier, final DateTime requestedDate, final DateTime subscriptionStartDate) {
         return planCreateAlignment(specifier);
     }
 
     @Override
-    public BillingAlignment billingAlignment(final PlanPhaseSpecifier planPhase, final DateTime requestedDate)
-            throws CatalogApiException {
+    public BillingAlignment billingAlignment(final PlanPhaseSpecifier planPhase, final DateTime requestedDate, final DateTime subscriptionStartDate) {
         return billingAlignment(planPhase);
     }
 
     @Override
-    public PlanAlignmentChange planChangeAlignment(final PlanPhaseSpecifier from, final PlanSpecifier to, final DateTime requestedDate)
+    public PlanAlignmentChange planChangeAlignment(final PlanPhaseSpecifier from, final PlanSpecifier to, final DateTime requestedDate, final DateTime subscriptionStartDate)
             throws CatalogApiException {
         return planChangeAlignment(from, to);
     }
 
     @Override
-    public boolean canCreatePlan(final PlanSpecifier specifier, final DateTime requestedDate) throws CatalogApiException {
-        return canCreatePlan(specifier);
-    }
-
-    @Override
-    public BillingActionPolicy planChangePolicy(final PlanPhaseSpecifier from, final PlanSpecifier to) throws CatalogApiException {
-        return super.planChangePolicy(from, to);
-    }
-
-    @Override
     public PlanAlignmentChange planChangeAlignment(final PlanPhaseSpecifier from, final PlanSpecifier to)
             throws CatalogApiException {
         return super.planChangeAlignment(from, to);
@@ -232,46 +185,24 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
     }
 
     @Override
-    public PlanAlignmentCreate planCreateAlignment(final PlanSpecifier specifier) throws CatalogApiException {
+    public PlanAlignmentCreate planCreateAlignment(final PlanSpecifier specifier) {
         return planCreateAlignment;
     }
 
     @Override
-    public BillingAlignment billingAlignment(final PlanPhaseSpecifier planPhase) throws CatalogApiException {
+    public BillingAlignment billingAlignment(final PlanPhaseSpecifier planPhase) {
         return billingAlignment;
     }
 
     @Override
-    public PlanChangeResult planChange(final PlanPhaseSpecifier from, final PlanSpecifier to) throws CatalogApiException {
+    public PlanChangeResult planChange(final PlanPhaseSpecifier from, final PlanSpecifier to) {
         return planChange;
     }
 
-    @Override
-    public boolean canCreatePlan(final PlanSpecifier specifier) throws CatalogApiException {
-        return canCreatePlan;
-    }
-
-
-    public DefaultProduct getCurrentProduct(int idx) {
+    public DefaultProduct getCurrentProduct(final int idx) {
         return (DefaultProduct) getCurrentProducts().toArray()[idx];
     }
 
-    private <T extends CatalogEntity> void convertCurrentEntries(final Collection<T> unordered, final T [] result) {
-        // Tests are not so well written and make assumption on how such entries are ordered
-        final LinkedList<T> list = new LinkedList<T>(unordered);
-        Collections.sort(list, new Comparator<T>() {
-            @Override
-            public int compare(final T o1, final T o2) {
-                return o1.getName().compareTo(o2.getName());
-            }
-        });
-        list.toArray(result);
-    }
-
-    public void setCanCreatePlan(final boolean canCreatePlan) {
-        this.canCreatePlan = canCreatePlan;
-    }
-
     public void setPlanChange(final PlanChangeResult planChange) {
         this.planChange = planChange;
     }
@@ -283,5 +214,4 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
     public void setPlanCreateAlignment(final PlanAlignmentCreate planCreateAlignment) {
         this.planCreateAlignment = planCreateAlignment;
     }
-
 }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockPlan.java b/catalog/src/test/java/org/killbill/billing/catalog/MockPlan.java
index 9fde075..e4ed959 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockPlan.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockPlan.java
@@ -96,6 +96,7 @@ public class MockPlan extends DefaultPlan {
         setInitialPhases(planPhases);
         setPlansAllowedInBundle(plansAllowedInBundle);
         setRecurringBillingMode(BillingMode.IN_ADVANCE);
+        setPriceListName(DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
 
         finalPhase.setPlan(this);
         for (final DefaultPlanPhase pp : planPhases) {
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
index 637ad39..6b1991b 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
@@ -36,14 +36,11 @@ import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.CatalogInternalApi;
 import org.killbill.billing.catalog.api.PhaseType;
-import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.AccountEventsStreams;
 import org.killbill.billing.entitlement.EventsStream;
@@ -423,36 +420,17 @@ public class EventsStreamBuilder {
     }
 
     private PlanPhaseSpecifier createPlanPhaseSpecifier(final SubscriptionBase subscription) {
-
-        final String lastActiveProductName;
-        final BillingPeriod billingPeriod;
-        final ProductCategory productCategory;
-        final String priceListName;
+        final String planName;
         final PhaseType phaseType;
-
         if (subscription.getState() == EntitlementState.PENDING) {
             final SubscriptionBaseTransition transition = subscription.getPendingTransition();
-            final Product pendingProduct = transition.getNextPlan().getProduct();
-            lastActiveProductName = pendingProduct.getName();
-            productCategory = pendingProduct.getCategory();
-            final PlanPhase pendingPlanPhase = transition.getNextPhase();
-            billingPeriod = pendingPlanPhase.getRecurring() != null ? pendingPlanPhase.getRecurring().getBillingPeriod() : BillingPeriod.NO_BILLING_PERIOD;
-            priceListName = transition.getNextPriceList().getName();
+            planName = transition.getNextPlan().getName();
             phaseType = transition.getNextPhase().getPhaseType();
         } else {
-            final Product lastActiveProduct = subscription.getLastActiveProduct();
-            lastActiveProductName = lastActiveProduct.getName();
-            productCategory = lastActiveProduct.getCategory();
-            final PlanPhase lastActivePlanPhase = subscription.getLastActivePhase();
-            billingPeriod = lastActivePlanPhase.getRecurring() != null ? lastActivePlanPhase.getRecurring().getBillingPeriod() : BillingPeriod.NO_BILLING_PERIOD;
-            priceListName = subscription.getLastActivePlan().getPriceListName();
+            planName = subscription.getLastActivePlan().getName();
             phaseType = subscription.getLastActivePhase().getPhaseType();
         }
-        return new PlanPhaseSpecifier(lastActiveProductName,
-                                      billingPeriod,
-                                      priceListName,
-                                      phaseType);
-
+        return new PlanPhaseSpecifier(planName, phaseType);
     }
 
     private Catalog getCatalog(final InternalTenantContext internalTenantContext) throws EntitlementApiException {
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java
index e8ac66b..193f506 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java
@@ -47,6 +47,10 @@ public class TestBlockingChecker extends EntitlementTestSuiteNoDB {
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
         final UUID accountId = UUID.randomUUID();
         account = Mockito.mock(Account.class);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
index 1fae808..7c5af2e 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
@@ -48,6 +48,9 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
 
     @BeforeMethod(groups = "slow")
     public void setUp() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
         account = createAccount(getAccountData(7));
     }
 
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java b/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
index c2b31fd..43974f9 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
@@ -65,6 +65,9 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
 
     @BeforeMethod(groups = "slow")
     public void setUp() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
         clock.setDay(initialDate);
         final Account account = createAccount(getAccountData(7));
 
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 80494ac..3485682 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
@@ -207,7 +207,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
         return getInvoiceInternal(invoiceId, context);
     }
 
-    private DefaultInvoice getInvoiceInternal(final UUID invoiceId, final TenantContext context) throws InvoiceApiException  {
+    private DefaultInvoice getInvoiceInternal(final UUID invoiceId, final TenantContext context) throws InvoiceApiException {
         final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(invoiceId, ObjectType.INVOICE, context);
         return new DefaultInvoice(dao.getById(invoiceId, internalTenantContext), getCatalogSafelyForPrettyNames(internalTenantContext));
     }
@@ -281,7 +281,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     @Override
     public List<InvoiceItem> insertExternalCharges(final UUID accountId, final LocalDate effectiveDate, final Iterable<InvoiceItem> charges, final boolean autoCommit, final CallContext context) throws InvoiceApiException {
         for (final InvoiceItem charge : charges) {
-            if (charge.getAmount() == null || charge.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            if (charge.getAmount() == null || charge.getAmount().compareTo(BigDecimal.ZERO) < 0) {
                 throw new InvoiceApiException(ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID, charge.getAmount());
             }
         }
@@ -646,7 +646,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     private void canInvoiceBeVoided(final Invoice invoice) throws InvoiceApiException {
         final List<InvoicePayment> invoicePayments = invoice.getPayments();
         final BigDecimal amountPaid = InvoiceCalculatorUtils.computeInvoiceAmountPaid(invoice.getCurrency(), invoicePayments)
-                .add(InvoiceCalculatorUtils.computeInvoiceAmountRefunded(invoice.getCurrency(), invoicePayments));
+                                                            .add(InvoiceCalculatorUtils.computeInvoiceAmountRefunded(invoice.getCurrency(), invoicePayments));
 
         if (amountPaid.compareTo(BigDecimal.ZERO) != 0) {
             throw new InvoiceApiException(ErrorCode.CAN_NOT_VOID_INVOICE_THAT_IS_PAID, invoice.getId().toString());
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 7870eba..2a37784 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
@@ -565,7 +565,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                     } else {
                         return new InvoiceItemJson(null,
                                                    input.getInvoiceId(),
-                                                   null,
+                                                   input.getLinkedInvoiceItemId(),
                                                    input.getAccountId(),
                                                    input.getChildAccountId(),
                                                    input.getBundleId(),
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index 154f9e0..73dc2cb 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -235,7 +235,7 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
 
     private int calculateBcdForTransition(final Catalog catalog, final Map<UUID, Integer> bcdCache, final SubscriptionBase baseSubscription, final SubscriptionBase subscription, final int accountBillCycleDayLocal, final EffectiveSubscriptionInternalEvent transition, final InternalTenantContext internalTenantContext)
             throws CatalogApiException, AccountApiException, SubscriptionBaseApiException {
-        final BillingAlignment alignment = catalog.billingAlignment(getPlanPhaseSpecifierFromTransition(catalog, transition), subscription.getStartDate());
+        final BillingAlignment alignment = catalog.billingAlignment(getPlanPhaseSpecifierFromTransition(catalog, transition), transition.getEffectiveTransitionTime(), subscription.getStartDate());
         return BillCycleDayCalculator.calculateBcdForAlignment(bcdCache, subscription, baseSubscription, alignment, internalTenantContext, accountBillCycleDayLocal);
     }
 
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
index 186af3c..4f7e151 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 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
@@ -286,7 +286,7 @@ public class TestBillingApi extends JunctionTestSuiteNoDB {
     private DateTime createSubscriptionCreationEvent(final Plan nextPlan, final PlanPhase nextPhase) throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
         final DateTime then = now.minusDays(1);
-        final PriceList nextPriceList = catalog.findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+        final PriceList nextPriceList = catalog.findPriceListForPlan(nextPlan.getName(), now, now);
 
         final EffectiveSubscriptionInternalEvent t = new MockEffectiveSubscriptionEvent(
                 eventId, subId, bunId, bunKey, then, now, null, null, null, null, null, EntitlementState.ACTIVE,
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
index 9436623..fa0af58 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -77,6 +77,10 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
 
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
+
         super.beforeMethod();
         account = Mockito.mock(Account.class);
         subscription1 = Mockito.mock(SubscriptionBase.class);

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 1340f45..abc03d6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.141.67</version>
+        <version>0.141.68</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.19.16-SNAPSHOT</version>
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
index 278ea3c..35bd5e1 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
@@ -208,7 +208,7 @@ public class PlanAligner extends BaseAligner {
         final PlanSpecifier planSpecifier = new PlanSpecifier(plan.getName());
 
         final DateTime planStartDate;
-        final PlanAlignmentCreate alignment = catalog.planCreateAlignment(planSpecifier, catalogEffectiveDate);
+        final PlanAlignmentCreate alignment = catalog.planCreateAlignment(planSpecifier, catalogEffectiveDate, subscriptionStartDate);
         switch (alignment) {
             case START_OF_SUBSCRIPTION:
                 planStartDate = subscriptionStartDate;
@@ -271,7 +271,7 @@ public class PlanAligner extends BaseAligner {
         final PlanSpecifier toPlanSpecifier = new PlanSpecifier(nextPlan.getName());
         final PhaseType initialPhase;
         final DateTime planStartDate;
-        final PlanAlignmentChange alignment = catalog.planChangeAlignment(fromPlanPhaseSpecifier, toPlanSpecifier, catalogEffectiveDate);
+        final PlanAlignmentChange alignment = catalog.planChangeAlignment(fromPlanPhaseSpecifier, toPlanSpecifier, catalogEffectiveDate, subscriptionStartDate);
         switch (alignment) {
             case START_OF_SUBSCRIPTION:
                 planStartDate = subscriptionStartDate;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java
index 82cc967..79774ef 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java
@@ -17,20 +17,47 @@
 package org.killbill.billing.subscription.api;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.UUID;
 
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
+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.Plan;
+import org.killbill.billing.catalog.api.PlanChangeResult;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
+import org.killbill.billing.invoice.api.DryRunArguments;
+import org.killbill.billing.subscription.api.svcs.DefaultPlanPhasePriceOverridesWithCallContext;
+import org.killbill.billing.subscription.api.svcs.DefaultSubscriptionInternalApi;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
 import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
+import org.killbill.billing.subscription.engine.addon.AddonUtils;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
+import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.clock.Clock;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 
 public class SubscriptionApiBase {
 
@@ -44,11 +71,215 @@ public class SubscriptionApiBase {
         this.apiService = apiService;
         this.clock = clock;
     }
-    protected List<SubscriptionBase> createSubscriptionsForApiUse(final List<SubscriptionBase> internalSubscriptions) {
-        return new ArrayList<SubscriptionBase>(Collections2.transform(internalSubscriptions, new Function<SubscriptionBase, SubscriptionBase>() {
+
+    protected SubscriptionBaseBundle getActiveBundleForKey(final String bundleKey, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+        final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(bundleKey, context);
+        for (final SubscriptionBaseBundle cur : existingBundles) {
+            final List<DefaultSubscriptionBase> subscriptions = dao.getSubscriptions(cur.getId(), ImmutableList.<SubscriptionBaseEvent>of(), catalog, context);
+            for (final SubscriptionBase s : subscriptions) {
+                if (s.getCategory() == ProductCategory.ADD_ON) {
+                    continue;
+                }
+                if (s.getEndDate() == null || s.getEndDate().compareTo(clock.getUTCNow()) > 0) {
+                    return cur;
+                }
+            }
+        }
+        return null;
+    }
+
+    protected SubscriptionBase getBaseSubscription(final UUID bundleId,
+                                                   final Catalog catalog,
+                                                   final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
+        final SubscriptionBase result = dao.getBaseSubscription(bundleId, catalog, context);
+        if (result == null) {
+            throw new SubscriptionBaseApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
+        }
+        return createSubscriptionForApiUse(result);
+    }
+
+    protected List<DefaultSubscriptionBase> getSubscriptionsForBundle(final UUID bundleId,
+                                                                      @Nullable final DryRunArguments dryRunArguments,
+                                                                      final Catalog catalog,
+                                                                      final AddonUtils addonUtils,
+                                                                      final TenantContext tenantContext,
+                                                                      final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
+
+        final List<SubscriptionBaseEvent> outputDryRunEvents = new ArrayList<SubscriptionBaseEvent>();
+        final List<DefaultSubscriptionBase> outputSubscriptions = new ArrayList<DefaultSubscriptionBase>();
+
+        populateDryRunEvents(bundleId, dryRunArguments, outputDryRunEvents, outputSubscriptions, catalog, addonUtils, tenantContext, context);
+        final List<DefaultSubscriptionBase> result = dao.getSubscriptions(bundleId, outputDryRunEvents, catalog, context);
+        if (result != null && !result.isEmpty()) {
+            outputSubscriptions.addAll(result);
+        }
+        Collections.sort(outputSubscriptions, DefaultSubscriptionInternalApi.SUBSCRIPTIONS_COMPARATOR);
+
+        return createSubscriptionsForApiUse(outputSubscriptions);
+    }
+
+    private void populateDryRunEvents(@Nullable final UUID bundleId,
+                                      @Nullable final DryRunArguments dryRunArguments,
+                                      final Collection<SubscriptionBaseEvent> outputDryRunEvents,
+                                      final Collection<DefaultSubscriptionBase> outputSubscriptions,
+                                      final Catalog catalog,
+                                      final AddonUtils addonUtils,
+                                      final TenantContext tenantContext,
+                                      final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
+        if (dryRunArguments == null || dryRunArguments.getAction() == null) {
+            return;
+        }
+
+        final DateTime utcNow = clock.getUTCNow();
+        List<SubscriptionBaseEvent> dryRunEvents = null;
+        final PlanPhaseSpecifier inputSpec = dryRunArguments.getPlanPhaseSpecifier();
+        final boolean isInputSpecNullOrEmpty = inputSpec == null ||
+                                               (inputSpec.getPlanName() == null && inputSpec.getProductName() == null && inputSpec.getBillingPeriod() == null);
+
+        // Create an overridesWithContext with a null context to indicate this is dryRun and no price overridden plan should be created.
+        final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(dryRunArguments.getPlanPhasePriceOverrides(), null);
+        final Plan plan = isInputSpecNullOrEmpty ?
+                          null :
+                          catalog.createOrFindPlan(inputSpec, overridesWithContext, utcNow);
+
+        switch (dryRunArguments.getAction()) {
+            case START_BILLING:
+                final SubscriptionBase baseSubscription = dao.getBaseSubscription(bundleId, catalog, context);
+                final DateTime startEffectiveDate = dryRunArguments.getEffectiveDate() != null ? context.toUTCDateTime(dryRunArguments.getEffectiveDate()) : utcNow;
+                final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, startEffectiveDate, addonUtils, context);
+                final UUID subscriptionId = UUIDs.randomUUID();
+                dryRunEvents = apiService.getEventsOnCreation(subscriptionId, startEffectiveDate, bundleStartDate, plan, inputSpec.getPhaseType(), plan.getPriceListName(),
+                                                              startEffectiveDate, catalog, context);
+                final SubscriptionBuilder builder = new SubscriptionBuilder()
+                        .setId(subscriptionId)
+                        .setBundleId(bundleId)
+                        .setBundleExternalKey(null)
+                        .setCategory(plan.getProduct().getCategory())
+                        .setBundleStartDate(bundleStartDate)
+                        .setAlignStartDate(startEffectiveDate);
+                final DefaultSubscriptionBase newSubscription = new DefaultSubscriptionBase(builder, apiService, clock);
+                newSubscription.rebuildTransitions(dryRunEvents, catalog);
+                outputSubscriptions.add(newSubscription);
+                break;
+
+            case CHANGE:
+                final DefaultSubscriptionBase subscriptionForChange = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), catalog, context);
+
+                DateTime changeEffectiveDate = getDryRunEffectiveDate(dryRunArguments.getEffectiveDate(), subscriptionForChange, context);
+                if (changeEffectiveDate == null) {
+                    BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
+                    if (policy == null) {
+                        final PlanChangeResult planChangeResult = apiService.getPlanChangeResult(subscriptionForChange, inputSpec, utcNow, tenantContext);
+                        policy = planChangeResult.getPolicy();
+                    }
+                    // We pass null for billingAlignment, accountTimezone, account BCD because this is not available which means that dryRun with START_OF_TERM BillingPolicy will fail
+                    changeEffectiveDate = subscriptionForChange.getPlanChangeEffectiveDate(policy, null, -1, context);
+                }
+                dryRunEvents = apiService.getEventsOnChangePlan(subscriptionForChange, plan, plan.getPriceListName(), changeEffectiveDate, true, catalog, context);
+                break;
+
+            case STOP_BILLING:
+                final DefaultSubscriptionBase subscriptionForCancellation = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), catalog, context);
+
+                DateTime cancelEffectiveDate = getDryRunEffectiveDate(dryRunArguments.getEffectiveDate(), subscriptionForCancellation, context);
+                if (dryRunArguments.getEffectiveDate() == null) {
+                    BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
+                    if (policy == null) {
+                        final Plan currentPlan = subscriptionForCancellation.getCurrentPlan();
+                        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(currentPlan.getName(),
+                                                                               subscriptionForCancellation.getCurrentPhase().getPhaseType());
+                        policy = catalog.planCancelPolicy(spec, clock.getUTCNow(), subscriptionForCancellation.getStartDate());
+                    }
+                    // We pass null for billingAlignment, accountTimezone, account BCD because this is not available which means that dryRun with START_OF_TERM BillingPolicy will fail
+                    cancelEffectiveDate = subscriptionForCancellation.getPlanChangeEffectiveDate(policy, null, -1, context);
+                }
+                dryRunEvents = apiService.getEventsOnCancelPlan(subscriptionForCancellation, cancelEffectiveDate, true, catalog, context);
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unexpected dryRunArguments action " + dryRunArguments.getAction());
+        }
+
+        if (dryRunEvents != null && !dryRunEvents.isEmpty()) {
+            outputDryRunEvents.addAll(dryRunEvents);
+        }
+    }
+
+    private DateTime getDryRunEffectiveDate(@Nullable final LocalDate inputDate, final SubscriptionBase subscription, final InternalTenantContext context) {
+        if (inputDate == null) {
+            return null;
+        }
+
+        // We first use context account reference time to get a candidate)
+        final DateTime tmp = context.toUTCDateTime(inputDate);
+        // If we realize that the candidate is on the same LocalDate boundary as the subscription startDate but a bit prior we correct it to avoid weird things down the line
+        if (inputDate.compareTo(context.toLocalDate(subscription.getStartDate())) == 0 && tmp.compareTo(subscription.getStartDate()) < 0) {
+            return subscription.getStartDate();
+        } else {
+            return tmp;
+        }
+    }
+
+    protected DateTime getBundleStartDateWithSanity(final UUID bundleId,
+                                                    @Nullable final SubscriptionBase baseSubscription,
+                                                    final Plan plan,
+                                                    final DateTime effectiveDate,
+                                                    final AddonUtils addonUtils,
+                                                    final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
+        switch (plan.getProduct().getCategory()) {
+            case BASE:
+                if (baseSubscription != null &&
+                    (baseSubscription.getState() == EntitlementState.ACTIVE || baseSubscription.getState() == EntitlementState.PENDING)) {
+                    throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
+                }
+                return effectiveDate;
+
+            case ADD_ON:
+                if (baseSubscription == null) {
+                    throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, bundleId);
+                }
+                if (effectiveDate.isBefore(baseSubscription.getStartDate())) {
+                    throw new SubscriptionBaseApiException(ErrorCode.SUB_INVALID_REQUESTED_DATE, effectiveDate.toString(), baseSubscription.getStartDate().toString());
+                }
+                addonUtils.checkAddonCreationRights(baseSubscription, plan, effectiveDate, context);
+                return baseSubscription.getStartDate();
+
+            case STANDALONE:
+                if (baseSubscription != null) {
+                    throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
+                }
+                // Not really but we don't care, there is no alignment for STANDALONE subscriptions
+                return effectiveDate;
+
+            default:
+                throw new SubscriptionBaseError(String.format("Can't create subscription of type %s",
+                                                              plan.getProduct().getCategory().toString()));
+        }
+    }
+
+    protected SubscriptionBaseBundle createBundleForAccount(final UUID accountId,
+                                                            final String bundleKey,
+                                                            final boolean renameCancelledBundleIfExist,
+                                                            final Catalog catalog,
+                                                            final CacheController<UUID, UUID> accountIdCacheController,
+                                                            final InternalCallContext context) throws SubscriptionBaseApiException {
+        final DateTime now = context.getCreatedDate();
+        final DefaultSubscriptionBaseBundle bundle = new DefaultSubscriptionBaseBundle(bundleKey, accountId, now, now, now, now);
+        if (null != bundleKey && bundleKey.length() > 255) {
+            throw new SubscriptionBaseApiException(ErrorCode.EXTERNAL_KEY_LIMIT_EXCEEDED);
+        }
+
+        final SubscriptionBaseBundle subscriptionBundle = dao.createSubscriptionBundle(bundle, catalog, renameCancelledBundleIfExist, context);
+        accountIdCacheController.putIfAbsent(bundle.getId(), accountId);
+
+        return subscriptionBundle;
+    }
+
+    protected List<DefaultSubscriptionBase> createSubscriptionsForApiUse(final Collection<DefaultSubscriptionBase> internalSubscriptions) {
+        return new ArrayList<DefaultSubscriptionBase>(Collections2.transform(internalSubscriptions, new Function<SubscriptionBase, DefaultSubscriptionBase>() {
             @Override
-            public SubscriptionBase apply(final SubscriptionBase subscription) {
-                return createSubscriptionForApiUse((DefaultSubscriptionBase) subscription);
+            public DefaultSubscriptionBase apply(final SubscriptionBase subscription) {
+                return createSubscriptionForApiUse(subscription);
             }
         }));
     }
@@ -57,9 +288,12 @@ public class SubscriptionApiBase {
         return new DefaultSubscriptionBase((DefaultSubscriptionBase) internalSubscription, apiService, clock);
     }
 
-    protected DefaultSubscriptionBase createSubscriptionForApiUse(SubscriptionBuilder builder, List<SubscriptionBaseEvent> events, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+    protected DefaultSubscriptionBase createSubscriptionForApiUse(final SubscriptionBuilder builder,
+                                                                  final List<SubscriptionBaseEvent> events,
+                                                                  final Catalog catalog,
+                                                                  final InternalTenantContext context) throws CatalogApiException {
         final DefaultSubscriptionBase subscription = new DefaultSubscriptionBase(builder, apiService, clock);
-        if (events.size() > 0) {
+        if (!events.isEmpty()) {
             subscription.rebuildTransitions(events, catalog);
         }
         return subscription;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionBaseCreateApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionBaseCreateApi.java
new file mode 100644
index 0000000..61c5429
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionBaseCreateApi.java
@@ -0,0 +1,348 @@
+/*
+ * 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.subscription.api.svcs;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Catalog;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.EntitlementSpecifier;
+import org.killbill.billing.subscription.api.SubscriptionApiBase;
+import org.killbill.billing.subscription.api.SubscriptionBase;
+import org.killbill.billing.subscription.api.SubscriptionBaseApiService;
+import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
+import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOnsSpecifier;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.killbill.billing.subscription.api.user.SubscriptionAndAddOnsSpecifier;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
+import org.killbill.billing.subscription.api.user.SubscriptionSpecifier;
+import org.killbill.billing.subscription.engine.addon.AddonUtils;
+import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
+import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
+import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultSubscriptionBaseCreateApi extends SubscriptionApiBase {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultSubscriptionBaseCreateApi.class);
+
+    DefaultSubscriptionBaseCreateApi(final SubscriptionDao dao, final SubscriptionBaseApiService apiService, final Clock clock) {
+        super(dao, apiService, clock);
+    }
+
+    List<SubscriptionBaseWithAddOns> createBaseSubscriptionsWithAddOns(final Iterable<SubscriptionBaseWithAddOnsSpecifier> baseAndAddOnEntitlementsSpecifiers,
+                                                                       final boolean renameCancelledBundleIfExist,
+                                                                       final Catalog catalog,
+                                                                       final AddonUtils addonUtils,
+                                                                       final CacheController<UUID, UUID> accountIdCacheController,
+                                                                       final CacheController<UUID, UUID> bundleIdCacheController,
+                                                                       final CallContext callContext,
+                                                                       final InternalCallContext context) throws SubscriptionBaseApiException, CatalogApiException {
+        // Prepare the subscription specifiers from the entitlement specifiers
+        final Collection<SubscriptionAndAddOnsSpecifier> baseAndAddOnSubscriptionsSpecifiers = new ArrayList<SubscriptionAndAddOnsSpecifier>();
+        for (final SubscriptionBaseWithAddOnsSpecifier baseAndAddOnEntitlementsSpecifier : baseAndAddOnEntitlementsSpecifiers) {
+            prepareSubscriptionAndAddOnsSpecifier(baseAndAddOnSubscriptionsSpecifiers,
+                                                  baseAndAddOnEntitlementsSpecifier,
+                                                  renameCancelledBundleIfExist,
+                                                  catalog,
+                                                  addonUtils,
+                                                  accountIdCacheController,
+                                                  callContext,
+                                                  context);
+        }
+
+        // Create the subscriptions
+        final List<SubscriptionBaseWithAddOns> subscriptionBaseWithAddOns = apiService.createPlansWithAddOns(callContext.getAccountId(),
+                                                                                                             baseAndAddOnSubscriptionsSpecifiers,
+                                                                                                             catalog,
+                                                                                                             callContext);
+
+        // Populate the caches
+        for (final SubscriptionBaseWithAddOns subscriptionBaseWithAO : subscriptionBaseWithAddOns) {
+            for (final SubscriptionBase subscriptionBase : subscriptionBaseWithAO.getSubscriptionBaseList()) {
+                bundleIdCacheController.putIfAbsent(subscriptionBase.getId(), subscriptionBaseWithAO.getBundle().getId());
+            }
+        }
+
+        return subscriptionBaseWithAddOns;
+    }
+
+    private void prepareSubscriptionAndAddOnsSpecifier(final Collection<SubscriptionAndAddOnsSpecifier> subscriptionAndAddOns,
+                                                       final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier,
+                                                       final boolean renameCancelledBundleIfExist,
+                                                       final Catalog catalog,
+                                                       final AddonUtils addonUtils,
+                                                       final CacheController<UUID, UUID> accountIdCacheController,
+                                                       final CallContext callContext,
+                                                       final InternalCallContext context) throws SubscriptionBaseApiException, CatalogApiException {
+        SubscriptionBaseBundle bundle = getBundleWithSanity(subscriptionBaseWithAddOnsSpecifier, catalog, callContext, context);
+
+        final DateTime billingRequestedDateRaw = (subscriptionBaseWithAddOnsSpecifier.getBillingEffectiveDate() != null) ?
+                                                 context.toUTCDateTime(subscriptionBaseWithAddOnsSpecifier.getBillingEffectiveDate()) : context.getCreatedDate();
+
+        final SubscriptionBase baseSubscription;
+        final DateTime billingRequestedDate;
+        if (bundle != null) {
+            baseSubscription = dao.getBaseSubscription(bundle.getId(), catalog, context);
+            billingRequestedDate = computeActualBillingRequestedDate(bundle, billingRequestedDateRaw, baseSubscription, catalog, context);
+        } else {
+            baseSubscription = null;
+            billingRequestedDate = billingRequestedDateRaw;
+        }
+
+        final List<EntitlementSpecifier> reorderedSpecifiers = new ArrayList<EntitlementSpecifier>();
+        final List<Plan> createdOrRetrievedPlans = new ArrayList<Plan>();
+        final boolean hasBaseOrStandalonePlanSpecifier = createPlansIfNeededAndReorderBPOrStandaloneSpecFirstWithSanity(subscriptionBaseWithAddOnsSpecifier,
+                                                                                                                        catalog,
+                                                                                                                        billingRequestedDate,
+                                                                                                                        reorderedSpecifiers,
+                                                                                                                        createdOrRetrievedPlans,
+                                                                                                                        callContext);
+
+        if (hasBaseOrStandalonePlanSpecifier && baseSubscription != null) {
+            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundle.getExternalKey());
+        }
+
+        if (bundle == null && hasBaseOrStandalonePlanSpecifier) {
+            bundle = createBundleForAccount(callContext.getAccountId(),
+                                            subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(),
+                                            renameCancelledBundleIfExist,
+                                            catalog,
+                                            accountIdCacheController,
+                                            context);
+        } else if (bundle == null) {
+            log.warn("Invalid specifier: {}", subscriptionBaseWithAddOnsSpecifier);
+            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey());
+        }
+
+        final List<SubscriptionSpecifier> subscriptionSpecifiers = verifyAndBuildSubscriptionSpecifiers(bundle,
+                                                                                                        hasBaseOrStandalonePlanSpecifier,
+                                                                                                        reorderedSpecifiers,
+                                                                                                        createdOrRetrievedPlans,
+                                                                                                        subscriptionBaseWithAddOnsSpecifier.isMigrated(),
+                                                                                                        billingRequestedDate,
+                                                                                                        catalog,
+                                                                                                        addonUtils,
+                                                                                                        callContext,
+                                                                                                        context);
+        final SubscriptionAndAddOnsSpecifier subscriptionAndAddOnsSpecifier = new SubscriptionAndAddOnsSpecifier(bundle,
+                                                                                                                 billingRequestedDate,
+                                                                                                                 subscriptionSpecifiers);
+        subscriptionAndAddOns.add(subscriptionAndAddOnsSpecifier);
+    }
+
+    private DateTime computeActualBillingRequestedDate(final SubscriptionBaseBundle bundle,
+                                                       final DateTime billingRequestedDateRaw,
+                                                       @Nullable final SubscriptionBase baseSubscription,
+                                                       final Catalog catalog,
+                                                       final InternalCallContext context) throws CatalogApiException, SubscriptionBaseApiException {
+        DateTime billingRequestedDate = billingRequestedDateRaw;
+        if (baseSubscription != null) {
+            final DateTime baseSubscriptionStartDate = getBaseSubscription(bundle.getId(), catalog, context).getStartDate();
+            billingRequestedDate = billingRequestedDateRaw.isBefore(baseSubscriptionStartDate) ? baseSubscriptionStartDate : billingRequestedDateRaw;
+        }
+        return billingRequestedDate;
+    }
+
+    private SubscriptionBaseBundle getBundleWithSanity(final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier,
+                                                       final Catalog catalog,
+                                                       final TenantContext callContext,
+                                                       final InternalCallContext context) throws SubscriptionBaseApiException, CatalogApiException {
+        SubscriptionBaseBundle bundle = null;
+        if (subscriptionBaseWithAddOnsSpecifier.getBundleId() != null) {
+            bundle = dao.getSubscriptionBundleFromId(subscriptionBaseWithAddOnsSpecifier.getBundleId(), context);
+            if (bundle == null ||
+                (subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey() != null && !subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey().equals(bundle.getExternalKey()))) {
+                throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+            }
+        } else if (subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey() != null) {
+            final SubscriptionBaseBundle tmp = getActiveBundleForKey(subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(), catalog, context);
+            if (tmp != null && !tmp.getAccountId().equals(callContext.getAccountId())) {
+                throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey());
+            } else {
+                bundle = tmp;
+            }
+        }
+        return bundle;
+    }
+
+    private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final SubscriptionBaseBundle bundle,
+                                                                             final boolean hasBaseOrStandalonePlanSpecifier,
+                                                                             final List<EntitlementSpecifier> entitlements,
+                                                                             final List<Plan> entitlementsPlans,
+                                                                             final boolean isMigrated,
+                                                                             final DateTime effectiveDate,
+                                                                             final Catalog catalog,
+                                                                             final AddonUtils addonUtils,
+                                                                             final TenantContext callContext,
+                                                                             final InternalCallContext context) throws SubscriptionBaseApiException, CatalogApiException {
+        final List<SubscriptionSpecifier> subscriptions = new ArrayList<SubscriptionSpecifier>();
+        for (int i = 0; i < entitlements.size(); i++) {
+            final EntitlementSpecifier entitlement = entitlements.get(i);
+            final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
+            if (spec == null) {
+                // BP already exists
+                continue;
+            }
+
+            final Plan plan = entitlementsPlans.get(i);
+            final PlanPhase phase = plan.getAllPhases()[0];
+            if (phase == null) {
+                throw new SubscriptionBaseError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog",
+                                                              spec.getProductName(), spec.getBillingPeriod().toString(), plan.getPriceListName()));
+            }
+
+            // verify the number of subscriptions (of the same kind) allowed per bundle and the existing ones
+            if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
+                if (plan.getPlansAllowedInBundle() != -1 && plan.getPlansAllowedInBundle() > 0) {
+                    // TODO We should also look to the specifiers being created for validation
+                    final List<DefaultSubscriptionBase> subscriptionsForBundle = getSubscriptionsForBundle(bundle.getId(), null, catalog, addonUtils, callContext, context);
+                    final int existingAddOnsWithSamePlanName = addonUtils.countExistingAddOnsWithSamePlanName(subscriptionsForBundle, plan.getName());
+                    final int currentAddOnsWithSamePlanName = countCurrentAddOnsWithSamePlanName(entitlementsPlans, plan);
+                    if ((existingAddOnsWithSamePlanName + currentAddOnsWithSamePlanName) > plan.getPlansAllowedInBundle()) {
+                        // a new ADD_ON subscription of the same plan can't be added because it has reached its limit by bundle
+                        throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE, plan.getName());
+                    }
+                }
+            }
+
+            final DateTime bundleStartDate;
+            if (hasBaseOrStandalonePlanSpecifier) {
+                bundleStartDate = effectiveDate;
+            } else {
+                final SubscriptionBase baseSubscription = dao.getBaseSubscription(bundle.getId(), catalog, context);
+                bundleStartDate = getBundleStartDateWithSanity(bundle.getId(), baseSubscription, plan, effectiveDate, addonUtils, context);
+            }
+
+            final SubscriptionSpecifier subscription = new SubscriptionSpecifier();
+            subscription.setRealPriceList(plan.getPriceListName());
+            subscription.setEffectiveDate(effectiveDate);
+            subscription.setProcessedDate(context.getCreatedDate());
+            subscription.setPlan(plan);
+            subscription.setInitialPhase(spec.getPhaseType());
+            subscription.setBuilder(new SubscriptionBuilder()
+                                            .setId(UUIDs.randomUUID())
+                                            .setBundleId(bundle.getId())
+                                            .setBundleExternalKey(bundle.getExternalKey())
+                                            .setCategory(plan.getProduct().getCategory())
+                                            .setBundleStartDate(bundleStartDate)
+                                            .setAlignStartDate(effectiveDate)
+                                            .setMigrated(isMigrated));
+
+            subscriptions.add(subscription);
+        }
+
+        return subscriptions;
+    }
+
+    private boolean createPlansIfNeededAndReorderBPOrStandaloneSpecFirstWithSanity(final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier,
+                                                                                   final Catalog catalog,
+                                                                                   final DateTime effectiveDate,
+                                                                                   final Collection<EntitlementSpecifier> outputEntitlementSpecifier,
+                                                                                   final Collection<Plan> outputEntitlementPlans,
+                                                                                   final CallContext callContext) throws SubscriptionBaseApiException, CatalogApiException {
+        EntitlementSpecifier basePlanSpecifier = null;
+        Plan basePlan = null;
+        final Collection<EntitlementSpecifier> addOnSpecifiers = new ArrayList<EntitlementSpecifier>();
+        final Collection<EntitlementSpecifier> standaloneSpecifiers = new ArrayList<EntitlementSpecifier>();
+        final Collection<Plan> addOnsPlans = new ArrayList<Plan>();
+        final Collection<Plan> standalonePlans = new ArrayList<Plan>();
+
+        for (final EntitlementSpecifier cur : subscriptionBaseWithAddOnsSpecifier.getEntitlementSpecifiers()) {
+            final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(cur.getOverrides(), callContext);
+            // Called by createBaseSubscriptionsWithAddOns only -- no need for subscription start date
+            final Plan plan = catalog.createOrFindPlan(cur.getPlanPhaseSpecifier(), overridesWithContext, effectiveDate);
+
+            final boolean isBase = isBaseSpecifier(plan);
+            final boolean isStandalone = isStandaloneSpecifier(plan);
+            if (isStandalone) {
+                standaloneSpecifiers.add(cur);
+                standalonePlans.add(plan);
+            } else if (isBase) {
+                if (basePlanSpecifier == null) {
+                    basePlanSpecifier = cur;
+                    basePlan = plan;
+                } else {
+                    throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+                }
+            } else {
+                addOnSpecifiers.add(cur);
+                addOnsPlans.add(plan);
+            }
+        }
+
+        if (basePlanSpecifier != null) {
+            outputEntitlementSpecifier.add(basePlanSpecifier);
+            outputEntitlementPlans.add(basePlan);
+        }
+        outputEntitlementSpecifier.addAll(addOnSpecifiers);
+        outputEntitlementPlans.addAll(addOnsPlans);
+
+        if (!outputEntitlementSpecifier.isEmpty() && !standaloneSpecifiers.isEmpty()) {
+            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+        }
+
+        if (standaloneSpecifiers.isEmpty()) {
+            return basePlanSpecifier != null;
+        } else {
+            outputEntitlementSpecifier.addAll(standaloneSpecifiers);
+            outputEntitlementPlans.addAll(standalonePlans);
+            return true;
+        }
+    }
+
+    private boolean isBaseSpecifier(final Plan inputPlan) {
+        return inputPlan.getProduct().getCategory() == ProductCategory.BASE;
+    }
+
+    private boolean isStandaloneSpecifier(final Plan inputPlan) {
+        return inputPlan.getProduct().getCategory() == ProductCategory.STANDALONE;
+    }
+
+    private int countCurrentAddOnsWithSamePlanName(final Iterable<Plan> entitlementsPlans, final Plan currentPlan) {
+        int countCurrentAddOns = 0;
+        for (final Plan plan : entitlementsPlans) {
+            if (plan.getName().equalsIgnoreCase(currentPlan.getName())
+                && plan.getProduct().getCategory() != null
+                && ProductCategory.ADD_ON.equals(plan.getProduct().getCategory())) {
+                countCurrentAddOns++;
+            }
+        }
+        return countCurrentAddOns;
+    }
+}
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 d5c4bb6..dfdb63e 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
@@ -20,7 +20,6 @@ package org.killbill.billing.subscription.api.svcs;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -41,19 +40,16 @@ 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.Plan;
-import org.killbill.billing.catalog.api.PlanChangeResult;
-import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
 import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun.DryRunChangeReason;
-import org.killbill.billing.entitlement.api.EntitlementSpecifier;
 import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
 import org.killbill.billing.invoice.api.DryRunArguments;
-import org.killbill.billing.subscription.api.SubscriptionApiBase;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseApiService;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
@@ -63,21 +59,17 @@ import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEv
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionStatusDryRun;
-import org.killbill.billing.subscription.api.user.SubscriptionAndAddOnsSpecifier;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
 import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
-import org.killbill.billing.subscription.api.user.SubscriptionSpecifier;
 import org.killbill.billing.subscription.engine.addon.AddonUtils;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
 import org.killbill.billing.subscription.events.bcd.BCDEvent;
 import org.killbill.billing.subscription.events.bcd.BCDEventData;
-import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
-import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.bcd.BillCycleDayCalculator;
 import org.killbill.billing.util.cache.AccountIdFromBundleIdCacheLoader;
 import org.killbill.billing.util.cache.BundleIdFromSubscriptionIdCacheLoader;
@@ -93,7 +85,6 @@ import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
 import org.killbill.clock.Clock;
 import org.killbill.clock.DefaultClock;
-import org.killbill.notificationq.api.NotificationQueueService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -106,7 +97,7 @@ import com.google.inject.Inject;
 
 import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException;
 
-public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implements SubscriptionBaseInternalApi {
+public class DefaultSubscriptionInternalApi extends DefaultSubscriptionBaseCreateApi implements SubscriptionBaseInternalApi {
 
     private static final Logger log = LoggerFactory.getLogger(DefaultSubscriptionInternalApi.class);
 
@@ -133,7 +124,6 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
     @Inject
     public DefaultSubscriptionInternalApi(final SubscriptionDao dao,
                                           final SubscriptionBaseApiService apiService,
-                                          final NotificationQueueService notificationQueueService,
                                           final Clock clock,
                                           final CatalogInternalApi catalogInternalApi,
                                           final AddonUtils addonUtils,
@@ -147,230 +137,25 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         this.bundleIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.BUNDLE_ID_FROM_SUBSCRIPTION_ID);
     }
 
-    private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final SubscriptionBaseBundle bundle,
-                                                                             final boolean hasBaseOrStandalonePlanSpecifier,
-                                                                             final Iterable<EntitlementSpecifier> entitlements,
-                                                                             final boolean isMigrated,
-                                                                             final InternalCallContext context,
-                                                                             final DateTime now,
-                                                                             final DateTime effectiveDate,
-                                                                             final Catalog catalog,
-                                                                             final CallContext callContext) throws SubscriptionBaseApiException, CatalogApiException {
-        final List<SubscriptionSpecifier> subscriptions = new ArrayList<SubscriptionSpecifier>();
-        for (final EntitlementSpecifier entitlement : entitlements) {
-            final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
-            if (spec == null) {
-                // BP already exists
-                continue;
-            }
-
-            final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(entitlement.getOverrides(), callContext);
-
-            final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, effectiveDate);
-            final PlanPhase phase = plan.getAllPhases()[0];
-            if (phase == null) {
-                throw new SubscriptionBaseError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog",
-                                                              spec.getProductName(), spec.getBillingPeriod().toString(), plan.getPriceListName()));
-            }
-
-            // verify the number of subscriptions (of the same kind) allowed per bundle and the existing ones
-            if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
-                if (plan.getPlansAllowedInBundle() != -1 && plan.getPlansAllowedInBundle() > 0) {
-                    // TODO We should also look to the specifiers being created for validation
-                    final List<SubscriptionBase> subscriptionsForBundle = getSubscriptionsForBundle(bundle.getId(), null, context);
-                    final int existingAddOnsWithSamePlanName = addonUtils.countExistingAddOnsWithSamePlanName(subscriptionsForBundle, plan.getName());
-                    final int currentAddOnsWithSamePlanName = countCurrentAddOnsWithSamePlanName(entitlements, catalog, plan.getName(), effectiveDate, callContext);
-                    if ((existingAddOnsWithSamePlanName + currentAddOnsWithSamePlanName) > plan.getPlansAllowedInBundle()) {
-                        // a new ADD_ON subscription of the same plan can't be added because it has reached its limit by bundle
-                        throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE, plan.getName());
-                    }
-                }
-            }
-
-            final DateTime bundleStartDate;
-            if (hasBaseOrStandalonePlanSpecifier) {
-                bundleStartDate = effectiveDate;
-            } else {
-                final SubscriptionBase baseSubscription = dao.getBaseSubscription(bundle.getId(), catalog, context);
-                bundleStartDate = getBundleStartDateWithSanity(bundle.getId(), baseSubscription, plan, effectiveDate, catalog, context);
-            }
-
-            final SubscriptionSpecifier subscription = new SubscriptionSpecifier();
-            subscription.setRealPriceList(plan.getPriceListName());
-            subscription.setEffectiveDate(effectiveDate);
-            subscription.setProcessedDate(now);
-            subscription.setPlan(plan);
-            subscription.setInitialPhase(spec.getPhaseType());
-            subscription.setBuilder(new SubscriptionBuilder()
-                                            .setId(UUIDs.randomUUID())
-                                            .setBundleId(bundle.getId())
-                                            .setBundleExternalKey(bundle.getExternalKey())
-                                            .setCategory(plan.getProduct().getCategory())
-                                            .setBundleStartDate(bundleStartDate)
-                                            .setAlignStartDate(effectiveDate)
-                                            .setMigrated(isMigrated));
-
-            subscriptions.add(subscription);
-        }
-        return subscriptions;
-    }
-
-    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 Collection<EntitlementSpecifier> standaloneSpecifiers = new ArrayList<EntitlementSpecifier>();
-        try {
-            for (final EntitlementSpecifier cur : subscriptionBaseWithAddOnsSpecifier.getEntitlementSpecifiers()) {
-                final boolean isBase = isBaseSpecifier(catalog, effectiveDate, cur);
-                final boolean isStandalone = isStandaloneSpecifier(catalog, effectiveDate, cur);
-                if (isStandalone) {
-                    standaloneSpecifiers.add(cur);
-                } else if (isBase) {
-                    if (basePlanSpecifier == null) {
-                        basePlanSpecifier = cur;
-                    } else {
-                        throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
-                    }
-                } else {
-                    addOnSpecifiers.add(cur);
-                }
-            }
-        } catch (final CatalogApiException e) {
-            throw new SubscriptionBaseApiException(e);
-        }
-
-        if (basePlanSpecifier != null) {
-            outputEntitlementSpecifier.add(basePlanSpecifier);
-        }
-        outputEntitlementSpecifier.addAll(addOnSpecifiers);
-
-        if (!outputEntitlementSpecifier.isEmpty() && !standaloneSpecifiers.isEmpty()) {
-            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
-        }
-
-        if (standaloneSpecifiers.isEmpty()) {
-            return basePlanSpecifier != null;
-        } else {
-            outputEntitlementSpecifier.addAll(standaloneSpecifiers);
-            return true;
-        }
-    }
-
-    private boolean isBaseSpecifier(final Catalog catalog, final DateTime effectiveDate, final EntitlementSpecifier cur) throws CatalogApiException {
-        final Plan inputPlan = catalog.createOrFindPlan(cur.getPlanPhaseSpecifier(), null, effectiveDate);
-        return inputPlan.getProduct().getCategory() == ProductCategory.BASE;
-    }
-
-    private boolean isStandaloneSpecifier(final Catalog catalog, final DateTime effectiveDate, final EntitlementSpecifier cur) throws CatalogApiException {
-        final Plan inputPlan = catalog.createOrFindPlan(cur.getPlanPhaseSpecifier(), null, effectiveDate);
-        return inputPlan.getProduct().getCategory() == ProductCategory.STANDALONE;
-    }
-
     @Override
     public List<SubscriptionBaseWithAddOns> createBaseSubscriptionsWithAddOns(final Iterable<SubscriptionBaseWithAddOnsSpecifier> subscriptionWithAddOnsSpecifiers, final boolean renameCancelledBundleIfExist, final InternalCallContext context) throws SubscriptionBaseApiException {
         try {
             final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
             final CallContext callContext = internalCallContextFactory.createCallContext(context);
-            final UUID accountId = callContext.getAccountId();
-
-            final Collection<SubscriptionAndAddOnsSpecifier> subscriptionAndAddOns = new ArrayList<SubscriptionAndAddOnsSpecifier>();
-            for (final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier : subscriptionWithAddOnsSpecifiers) {
-                final DateTime billingRequestedDateRaw = (subscriptionBaseWithAddOnsSpecifier.getBillingEffectiveDate() != null) ?
-                                                         context.toUTCDateTime(subscriptionBaseWithAddOnsSpecifier.getBillingEffectiveDate()) : context.getCreatedDate();
-
-                final Collection<EntitlementSpecifier> reorderedSpecifiers = new ArrayList<EntitlementSpecifier>();
-                // Note: billingRequestedDateRaw might not be accurate here (add-on with a too early date passed)?
-                final boolean hasBaseOrStandalonePlanSpecifier = sanityAndReorderBPOrStandaloneSpecFirst(catalog, subscriptionBaseWithAddOnsSpecifier, billingRequestedDateRaw, reorderedSpecifiers);
-
-                DateTime billingRequestedDate = billingRequestedDateRaw;
-                SubscriptionBaseBundle bundle = null;
-                if (subscriptionBaseWithAddOnsSpecifier.getBundleId() != null) {
-                    bundle = dao.getSubscriptionBundleFromId(subscriptionBaseWithAddOnsSpecifier.getBundleId(), context);
-                    if (bundle == null ||
-                        (subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey() != null && !subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey().equals(bundle.getExternalKey()))) {
-                        throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
-                    }
-                } else if (subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey() != null &&
-                           !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)) {
-                        throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey());
-                    } else {
-                        bundle = tmp;
-                    }
-                }
 
-                SubscriptionBase baseSubscription = null;
-                if (bundle != null) {
-                    baseSubscription = dao.getBaseSubscription(bundle.getId(), catalog, context);
-                    if (baseSubscription != null) {
-                        final DateTime baseSubscriptionStartDate = getBaseSubscription(bundle.getId(), context).getStartDate();
-                        billingRequestedDate = billingRequestedDateRaw.isBefore(baseSubscriptionStartDate) ? baseSubscriptionStartDate : billingRequestedDateRaw;
-                    }
-                }
-
-                if (bundle == null && hasBaseOrStandalonePlanSpecifier) {
-                    bundle = createBundleForAccount(accountId,
-                                                    subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(),
-                                                    renameCancelledBundleIfExist,
-                                                    context);
-                } 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);
-                    throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
-                }
-
-                final SubscriptionAndAddOnsSpecifier subscriptionAndAddOnsSpecifier = new SubscriptionAndAddOnsSpecifier(bundle,
-                                                                                                                         billingRequestedDate,
-                                                                                                                         verifyAndBuildSubscriptionSpecifiers(bundle,
-                                                                                                                                                              hasBaseOrStandalonePlanSpecifier,
-                                                                                                                                                              reorderedSpecifiers,
-                                                                                                                                                              subscriptionBaseWithAddOnsSpecifier.isMigrated(),
-                                                                                                                                                              context,
-                                                                                                                                                              context.getCreatedDate(),
-                                                                                                                                                              billingRequestedDate,
-                                                                                                                                                              catalog,
-                                                                                                                                                              callContext));
-                subscriptionAndAddOns.add(subscriptionAndAddOnsSpecifier);
-            }
-
-            final List<SubscriptionBaseWithAddOns> subscriptionBaseWithAddOns = apiService.createPlansWithAddOns(accountId, subscriptionAndAddOns, catalog, callContext);
-            for (final SubscriptionBaseWithAddOns subscriptionBaseWithAO : subscriptionBaseWithAddOns) {
-                for (final SubscriptionBase subscriptionBase : subscriptionBaseWithAO.getSubscriptionBaseList()) {
-                    bundleIdCacheController.putIfAbsent(subscriptionBase.getId(), subscriptionBaseWithAO.getBundle().getId());
-                }
-            }
-            return subscriptionBaseWithAddOns;
+            return super.createBaseSubscriptionsWithAddOns(subscriptionWithAddOnsSpecifiers,
+                                                           renameCancelledBundleIfExist,
+                                                           catalog,
+                                                           addonUtils,
+                                                           accountIdCacheController,
+                                                           bundleIdCacheController,
+                                                           callContext,
+                                                           context);
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
     }
 
-    private int countCurrentAddOnsWithSamePlanName(final Iterable<EntitlementSpecifier> entitlements,
-                                                   final Catalog catalog, final String planName,
-                                                   final DateTime effectiveDate, final CallContext callContext) throws CatalogApiException {
-        int countCurrentAddOns = 0;
-        for (final EntitlementSpecifier entitlement : entitlements) {
-            final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
-            final PlanPhasePriceOverridesWithCallContext overridesWithContext =
-                    new DefaultPlanPhasePriceOverridesWithCallContext(entitlement.getOverrides(), callContext);
-            final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, effectiveDate);
-
-            if (plan.getName().equalsIgnoreCase(planName)
-                && plan.getProduct().getCategory() != null
-                && ProductCategory.ADD_ON.equals(plan.getProduct().getCategory())) {
-                countCurrentAddOns++;
-            }
-        }
-        return countCurrentAddOns;
-    }
-
     @Override
     public void cancelBaseSubscriptions(final Iterable<SubscriptionBase> subscriptions, final BillingActionPolicy policy, int accountBillCycleDayLocal, final InternalCallContext context) throws SubscriptionBaseApiException {
 
@@ -407,9 +192,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         }
         try {
             final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
-            final SubscriptionBaseBundle subscriptionBundle = dao.createSubscriptionBundle(bundle, catalog, renameCancelledBundleIfExist, context);
-            accountIdCacheController.putIfAbsent(bundle.getId(), accountId);
-            return subscriptionBundle;
+            return super.createBundleForAccount(accountId, bundleKey, renameCancelledBundleIfExist, catalog, accountIdCacheController, context);
         } catch (final CatalogApiException e) {
             throw new  SubscriptionBaseApiException(e);
         }
@@ -475,48 +258,23 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
 
     @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 {
-                subscriptions = dao.getSubscriptions(cur.getId(), ImmutableList.<SubscriptionBaseEvent>of(), catalog, context);
-                for (final SubscriptionBase s : subscriptions) {
-                    if (s.getCategory() == ProductCategory.ADD_ON) {
-                        continue;
-                    }
-                    if (s.getEndDate() == null || s.getEndDate().compareTo(clock.getUTCNow()) > 0) {
-                        return cur;
-                    }
-                }
-            } catch (final CatalogApiException e) {
-                log.warn("Failed to get subscriptions for bundleId='{}'", cur.getId(), e);
-                return null;
-            }
+        try {
+            return super.getActiveBundleForKey(bundleKey, catalog, context);
+        } catch (final CatalogApiException e) {
+            log.warn("Failed to get subscriptions", e);
+            return null;
         }
-        return null;
     }
 
     @Override
     public List<SubscriptionBase> getSubscriptionsForBundle(final UUID bundleId,
                                                             @Nullable final DryRunArguments dryRunArguments,
                                                             final InternalTenantContext context) throws SubscriptionBaseApiException {
-
         try {
-
             final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
-
-            final List<SubscriptionBaseEvent> outputDryRunEvents = new ArrayList<SubscriptionBaseEvent>();
-            final List<SubscriptionBase> outputSubscriptions = new ArrayList<SubscriptionBase>();
-
-            populateDryRunEvents(bundleId, dryRunArguments, outputDryRunEvents, outputSubscriptions, catalog, context);
-            final List<SubscriptionBase> result;
-            result = dao.getSubscriptions(bundleId, outputDryRunEvents, catalog, context);
-            if (result != null && !result.isEmpty()) {
-                outputSubscriptions.addAll(result);
-            }
-            Collections.sort(outputSubscriptions, DefaultSubscriptionInternalApi.SUBSCRIPTIONS_COMPARATOR);
-
-            return createSubscriptionsForApiUse(outputSubscriptions);
+            final TenantContext tenantContext = internalCallContextFactory.createTenantContext(context);
+            final List<DefaultSubscriptionBase> subscriptionsForBundle = super.getSubscriptionsForBundle(bundleId, dryRunArguments, catalog, addonUtils, tenantContext, context);
+            return new ArrayList<SubscriptionBase>(subscriptionsForBundle);
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
@@ -525,10 +283,11 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
     @Override
     public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, final InternalTenantContext context) throws SubscriptionBaseApiException {
         try {
-            final Map<UUID, List<SubscriptionBase>> internalSubscriptions = dao.getSubscriptionsForAccount(catalog, context);
+            final Map<UUID, List<DefaultSubscriptionBase>> internalSubscriptions = dao.getSubscriptionsForAccount(catalog, context);
             final Map<UUID, List<SubscriptionBase>> result = new HashMap<UUID, List<SubscriptionBase>>();
             for (final UUID bundleId : internalSubscriptions.keySet()) {
-                result.put(bundleId, createSubscriptionsForApiUse(internalSubscriptions.get(bundleId)));
+                final List<DefaultSubscriptionBase> subscriptionsForApiUse = createSubscriptionsForApiUse(internalSubscriptions.get(bundleId));
+                result.put(bundleId, new ArrayList<SubscriptionBase>(subscriptionsForApiUse));
             }
             return result;
         } catch (final CatalogApiException e) {
@@ -540,12 +299,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
     public SubscriptionBase getBaseSubscription(final UUID bundleId, final InternalTenantContext context) throws SubscriptionBaseApiException {
         try {
             final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
-
-            final SubscriptionBase result = dao.getBaseSubscription(bundleId, catalog, context);
-            if (result == null) {
-                throw new SubscriptionBaseApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
-            }
-            return createSubscriptionForApiUse(result);
+            return super.getBaseSubscription(bundleId, catalog, context);
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
@@ -619,11 +373,11 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : null;
         final DateTime effectiveCatalogDate = effectiveDate != null ? effectiveDate : context.getCreatedDate();
         final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, callContext);
-        final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, effectiveCatalogDate);
+        final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, effectiveCatalogDate, subscription.getStartDate());
         if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
             if (plan.getPlansAllowedInBundle() != -1
                 && plan.getPlansAllowedInBundle() > 0
-                && addonUtils.countExistingAddOnsWithSamePlanName(getSubscriptionsForBundle(subscription.getBundleId(), null, context), plan.getName())
+                && addonUtils.countExistingAddOnsWithSamePlanName(getSubscriptionsForBundle(subscription.getBundleId(), null, catalog, addonUtils, callContext, context), plan.getName())
                    >= plan.getPlansAllowedInBundle()) {
                 // the plan can be changed to the new value, because it has reached its limit by bundle
                 throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE, plan.getName());
@@ -648,7 +402,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
 
             final List<EntitlementAOStatusDryRun> result = new LinkedList<EntitlementAOStatusDryRun>();
 
-            final List<SubscriptionBase> bundleSubscriptions = dao.getSubscriptions(subscription.getBundleId(), ImmutableList.<SubscriptionBaseEvent>of(), catalog, context);
+            final List<DefaultSubscriptionBase> bundleSubscriptions = dao.getSubscriptions(subscription.getBundleId(), ImmutableList.<SubscriptionBaseEvent>of(), catalog, context);
             for (final SubscriptionBase cur : bundleSubscriptions) {
                 if (cur.getId().equals(subscriptionId)) {
                     continue;
@@ -659,11 +413,13 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                     continue;
                 }
 
+                final Product baseProduct = baseProductName != null ? catalog.findProduct(baseProductName, requestedDate) : null;
+
                 final DryRunChangeReason reason;
                 // If baseProductName is null, it's a cancellation dry-run. In this case, return all addons, so they are cancelled
-                if (baseProductName != null && addonUtils.isAddonIncludedFromProdName(baseProductName, cur.getCurrentPlan(), requestedDate, catalog, context)) {
+                if (baseProduct != null && addonUtils.isAddonIncluded(baseProduct, cur.getCurrentPlan())) {
                     reason = DryRunChangeReason.AO_INCLUDED_IN_NEW_PLAN;
-                } else if (baseProductName != null && addonUtils.isAddonAvailableFromProdName(baseProductName, cur.getCurrentPlan(), requestedDate, catalog, context)) {
+                } else if (baseProduct != null && addonUtils.isAddonAvailable(baseProduct, cur.getCurrentPlan())) {
                     reason = DryRunChangeReason.AO_AVAILABLE_IN_NEW_PLAN;
                 } else {
                     reason = DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN;
@@ -679,7 +435,6 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
-
     }
 
     @Override
@@ -687,111 +442,6 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         dao.updateBundleExternalKey(bundleId, newExternalKey, context);
     }
 
-    private void populateDryRunEvents(@Nullable final UUID bundleId,
-                                      @Nullable final DryRunArguments dryRunArguments,
-                                      final Collection<SubscriptionBaseEvent> outputDryRunEvents,
-                                      final Collection<SubscriptionBase> outputSubscriptions,
-                                      final Catalog catalog,
-                                      final InternalTenantContext context) throws SubscriptionBaseApiException {
-        if (dryRunArguments == null || dryRunArguments.getAction() == null) {
-            return;
-        }
-
-        final DateTime utcNow = clock.getUTCNow();
-        List<SubscriptionBaseEvent> dryRunEvents = null;
-        try {
-            final PlanPhaseSpecifier inputSpec = dryRunArguments.getPlanPhaseSpecifier();
-            final boolean isInputSpecNullOrEmpty = inputSpec == null ||
-                                                   (inputSpec.getPlanName() == null && inputSpec.getProductName() == null && inputSpec.getBillingPeriod() == null);
-
-            // Create an overridesWithContext with a null context to indicate this is dryRun and no price overriden plan should be created.
-            final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(dryRunArguments.getPlanPhasePriceOverrides(), null);
-            final Plan plan = isInputSpecNullOrEmpty ?
-                              null :
-                              catalog.createOrFindPlan(inputSpec, overridesWithContext, utcNow);
-            final TenantContext tenantContext = internalCallContextFactory.createTenantContext(context);
-
-            switch (dryRunArguments.getAction()) {
-                case START_BILLING:
-
-                    final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, catalog, context);
-                    final DateTime startEffectiveDate = dryRunArguments.getEffectiveDate() != null ? context.toUTCDateTime(dryRunArguments.getEffectiveDate()) : utcNow;
-                    final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, startEffectiveDate, catalog, context);
-                    final UUID subscriptionId = UUIDs.randomUUID();
-                    dryRunEvents = apiService.getEventsOnCreation(subscriptionId, startEffectiveDate, bundleStartDate, plan, inputSpec.getPhaseType(), plan.getPriceListName(),
-                                                                  startEffectiveDate, catalog, context);
-                    final SubscriptionBuilder builder = new SubscriptionBuilder()
-                            .setId(subscriptionId)
-                            .setBundleId(bundleId)
-                            .setBundleExternalKey(null)
-                            .setCategory(plan.getProduct().getCategory())
-                            .setBundleStartDate(bundleStartDate)
-                            .setAlignStartDate(startEffectiveDate);
-                    final DefaultSubscriptionBase newSubscription = new DefaultSubscriptionBase(builder, apiService, clock);
-                    newSubscription.rebuildTransitions(dryRunEvents, catalog);
-                    outputSubscriptions.add(newSubscription);
-                    break;
-
-                case CHANGE:
-                    final DefaultSubscriptionBase subscriptionForChange = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), catalog, context);
-
-                    DateTime changeEffectiveDate = getDryRunEffectiveDate(dryRunArguments.getEffectiveDate(), subscriptionForChange, context);
-                    if (changeEffectiveDate == null) {
-                        BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
-                        if (policy == null) {
-                            final PlanChangeResult planChangeResult = apiService.getPlanChangeResult(subscriptionForChange, inputSpec, utcNow, tenantContext);
-                            policy = planChangeResult.getPolicy();
-                        }
-                        // We pass null for billingAlignment, accountTimezone, account BCD because this is not available which means that dryRun with START_OF_TERM BillingPolicy will fail
-                        changeEffectiveDate = subscriptionForChange.getPlanChangeEffectiveDate(policy, null, -1, context);
-                    }
-                    dryRunEvents = apiService.getEventsOnChangePlan(subscriptionForChange, plan, plan.getPriceListName(), changeEffectiveDate, true, catalog, context);
-                    break;
-
-                case STOP_BILLING:
-                    final DefaultSubscriptionBase subscriptionForCancellation = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), catalog, context);
-
-                    DateTime cancelEffectiveDate = getDryRunEffectiveDate(dryRunArguments.getEffectiveDate(), subscriptionForCancellation, context);
-                    if (dryRunArguments.getEffectiveDate() == null) {
-                        BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
-                        if (policy == null) {
-                            final Plan currentPlan = subscriptionForCancellation.getCurrentPlan();
-                            final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(currentPlan.getName(),
-                                                                                   subscriptionForCancellation.getCurrentPhase().getPhaseType());
-                            policy = catalog.planCancelPolicy(spec, subscriptionForCancellation.getStartDate());
-                        }
-                        // We pass null for billingAlignment, accountTimezone, account BCD because this is not available which means that dryRun with START_OF_TERM BillingPolicy will fail
-                        cancelEffectiveDate = subscriptionForCancellation.getPlanChangeEffectiveDate(policy, null, -1, context);
-                    }
-                    dryRunEvents = apiService.getEventsOnCancelPlan(subscriptionForCancellation, cancelEffectiveDate, true, catalog, context);
-                    break;
-
-                default:
-                    throw new IllegalArgumentException("Unexpected dryRunArguments action " + dryRunArguments.getAction());
-            }
-        } catch (final CatalogApiException e) {
-            throw new SubscriptionBaseApiException(e);
-        }
-        if (dryRunEvents != null && !dryRunEvents.isEmpty()) {
-            outputDryRunEvents.addAll(dryRunEvents);
-        }
-    }
-
-    private DateTime getDryRunEffectiveDate(@Nullable final LocalDate inputDate, final DefaultSubscriptionBase subscription, final InternalTenantContext context) {
-        if (inputDate == null) {
-            return null;
-        }
-
-        // We first use context account reference time to get a candidate)
-        final DateTime tmp = context.toUTCDateTime(inputDate);
-        // If we realize that the candidate is on the same LocalDate boundary as the subscription startDate but a bit prior we correct it to avoid weird things down the line
-        if (inputDate.compareTo(context.toLocalDate(subscription.getStartDate())) == 0 && tmp.compareTo(subscription.getStartDate()) < 0) {
-            return subscription.getStartDate();
-        } else {
-            return tmp;
-        }
-    }
-
     @Override
     public void updateBCD(final UUID subscriptionId, final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException {
 
@@ -810,7 +460,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
     @Override
     public int getDefaultBillCycleDayLocal(final Map<UUID, Integer> bcdCache, final SubscriptionBase subscription, final SubscriptionBase baseSubscription, final PlanPhaseSpecifier planPhaseSpecifier, final int accountBillCycleDayLocal, final Catalog catalog, final InternalTenantContext context) throws SubscriptionBaseApiException {
         try {
-            final BillingAlignment alignment = catalog.billingAlignment(planPhaseSpecifier, subscription.getStartDate());
+            final BillingAlignment alignment = catalog.billingAlignment(planPhaseSpecifier, clock.getUTCNow(), subscription.getStartDate());
             return BillCycleDayCalculator.calculateBcdForAlignment(bcdCache, subscription, baseSubscription, alignment, context, accountBillCycleDayLocal);
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
@@ -903,39 +553,6 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         return requestedDate == null ? internalCallContext.getCreatedDate() : internalCallContext.toUTCDateTime(requestedDate);
     }
 
-    private DateTime getBundleStartDateWithSanity(final UUID bundleId, @Nullable final SubscriptionBase baseSubscription, final Plan plan,
-                                                  final DateTime effectiveDate, final Catalog catalog, final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
-        switch (plan.getProduct().getCategory()) {
-            case BASE:
-                if (baseSubscription != null &&
-                    (baseSubscription.getState() == EntitlementState.ACTIVE || baseSubscription.getState() == EntitlementState.PENDING)) {
-                    throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
-                }
-                return effectiveDate;
-
-            case ADD_ON:
-                if (baseSubscription == null) {
-                    throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, bundleId);
-                }
-                if (effectiveDate.isBefore(baseSubscription.getStartDate())) {
-                    throw new SubscriptionBaseApiException(ErrorCode.SUB_INVALID_REQUESTED_DATE, effectiveDate.toString(), baseSubscription.getStartDate().toString());
-                }
-                addonUtils.checkAddonCreationRights(baseSubscription, plan, effectiveDate, catalog, context);
-                return baseSubscription.getStartDate();
-
-            case STANDALONE:
-                if (baseSubscription != null) {
-                    throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
-                }
-                // Not really but we don't care, there is no alignment for STANDALONE subscriptions
-                return effectiveDate;
-
-            default:
-                throw new SubscriptionBaseError(String.format("Can't create subscription of type %s",
-                                                              plan.getProduct().getCategory().toString()));
-        }
-    }
-
     private List<EffectiveSubscriptionInternalEvent> convertEffectiveSubscriptionInternalEventFromSubscriptionTransitions(final SubscriptionBase subscription,
                                                                                                                           final InternalTenantContext context, final Collection<SubscriptionBaseTransition> transitions) {
         return ImmutableList.<EffectiveSubscriptionInternalEvent>copyOf(Collections2.transform(transitions, new Function<SubscriptionBaseTransition, EffectiveSubscriptionInternalEvent>() {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java
index 2ee1a66..b75e77f 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java
@@ -68,10 +68,10 @@ public class DefaultSubscriptionBaseTimelineApi extends SubscriptionApiBase impl
 
             final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(bundle.getAccountId(), context);
             final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalTenantContext);
-            final List<SubscriptionBase> subscriptions = dao.getSubscriptions(bundle.getId(),
-                                                                              ImmutableList.<SubscriptionBaseEvent>of(),
-                                                                              fullCatalog,
-                                                                              internalTenantContext);
+            final List<DefaultSubscriptionBase> subscriptions = dao.getSubscriptions(bundle.getId(),
+                                                                                     ImmutableList.<SubscriptionBaseEvent>of(),
+                                                                                     fullCatalog,
+                                                                                     internalTenantContext);
             if (subscriptions.size() == 0) {
                 throw new SubscriptionBaseRepairException(ErrorCode.SUB_NO_ACTIVE_SUBSCRIPTIONS, bundle.getId());
             }
@@ -83,7 +83,7 @@ public class DefaultSubscriptionBaseTimelineApi extends SubscriptionApiBase impl
         }
     }
 
-    private String getViewId(final DateTime lastUpdateBundleDate, final List<SubscriptionBase> subscriptions) {
+    private String getViewId(final DateTime lastUpdateBundleDate, final List<DefaultSubscriptionBase> subscriptions) {
         final StringBuilder tmp = new StringBuilder();
         long lastOrderedId = -1;
         for (final SubscriptionBase cur : subscriptions) {
@@ -130,7 +130,7 @@ public class DefaultSubscriptionBaseTimelineApi extends SubscriptionApiBase impl
         };
     }
 
-    private List<SubscriptionBaseTimeline> createGetSubscriptionRepairList(final List<SubscriptionBase> subscriptions, final List<SubscriptionBaseTimeline> inRepair, final Catalog fullCatalog, final InternalTenantContext tenantContext) throws CatalogApiException {
+    private List<SubscriptionBaseTimeline> createGetSubscriptionRepairList(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseTimeline> inRepair, final Catalog fullCatalog, final InternalTenantContext tenantContext) throws CatalogApiException {
 
         final List<SubscriptionBaseTimeline> result = new LinkedList<SubscriptionBaseTimeline>();
         final Set<UUID> repairIds = new TreeSet<UUID>();
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
index 6f3b516..9273757 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
@@ -174,7 +174,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         try {
             final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
             final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
-            final BillingActionPolicy policy = fullCatalog.planCancelPolicy(planPhase, subscription.getStartDate());
+            final BillingActionPolicy policy = fullCatalog.planCancelPolicy(planPhase, clock.getUTCNow(), subscription.getStartDate());
 
             Preconditions.checkState(policy != BillingActionPolicy.START_OF_TERM, "A default START_OF_TERM policy is not availaible");
 
@@ -227,7 +227,9 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
 
         try {
             for (final DefaultSubscriptionBase subscription : subscriptions) {
-                final BillingAlignment billingAlignment = (subscription.getState() == EntitlementState.PENDING ? null : catalog.billingAlignment(new PlanPhaseSpecifier(subscription.getLastActivePlan().getName(), subscription.getLastActivePhase().getPhaseType()), subscription.getStartDate()));
+                final BillingAlignment billingAlignment = (subscription.getState() == EntitlementState.PENDING ? null : catalog.billingAlignment(new PlanPhaseSpecifier(subscription.getLastActivePlan().getName(), subscription.getLastActivePhase().getPhaseType()),
+                                                                                                                                                 clock.getUTCNow(),
+                                                                                                                                                 subscription.getStartDate()));
                 final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, billingAlignment, accountBillCycleDayLocal, context);
                 subscriptionsWithEffectiveDate.put(subscription, effectiveDate);
             }
@@ -389,7 +391,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
 
             final PlanPhaseSpecifier fromPlanPhase = new PlanPhaseSpecifier(currentPlan.getName(),
                                                                             subscription.getCurrentOrPendingPhase().getPhaseType());
-            planChangeResult = catalogInternalApi.getFullCatalog(true, true, internalCallContext).planChange(fromPlanPhase, toPlanPhase, subscription.getStartDate());
+            planChangeResult = catalogInternalApi.getFullCatalog(true, true, internalCallContext).planChange(fromPlanPhase, toPlanPhase, effectiveDate, subscription.getStartDate());
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
@@ -606,7 +608,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         }
     }
 
-    private List<DefaultSubscriptionBase> computeAddOnsToCancel(final Collection<SubscriptionBaseEvent> cancelEvents, final CatalogEntity baseProduct, final UUID bundleId, final DateTime effectiveDate, final Catalog catalog, final InternalCallContext internalCallContext) throws CatalogApiException {
+    private List<DefaultSubscriptionBase> computeAddOnsToCancel(final Collection<SubscriptionBaseEvent> cancelEvents, final Product baseProduct, final UUID bundleId, final DateTime effectiveDate, final Catalog catalog, final InternalCallContext internalCallContext) throws CatalogApiException {
         // If cancellation/change occur in the future, there is nothing to do
         if (effectiveDate.compareTo(internalCallContext.getCreatedDate()) > 0) {
             return ImmutableList.<DefaultSubscriptionBase>of();
@@ -615,12 +617,12 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         }
     }
 
-    private List<DefaultSubscriptionBase> addCancellationAddOnForEventsIfRequired(final Collection<SubscriptionBaseEvent> events, final CatalogEntity baseProduct, final UUID bundleId,
+    private List<DefaultSubscriptionBase> addCancellationAddOnForEventsIfRequired(final Collection<SubscriptionBaseEvent> events, final Product baseProduct, final UUID bundleId,
                                                                                   final DateTime effectiveDate, final Catalog catalog, final InternalTenantContext internalTenantContext) throws CatalogApiException {
 
         final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = new ArrayList<DefaultSubscriptionBase>();
 
-        final List<SubscriptionBase> subscriptions = dao.getSubscriptions(bundleId, ImmutableList.<SubscriptionBaseEvent>of(), catalog, internalTenantContext);
+        final List<DefaultSubscriptionBase> subscriptions = dao.getSubscriptions(bundleId, ImmutableList.<SubscriptionBaseEvent>of(), catalog, internalTenantContext);
 
         for (final SubscriptionBase subscription : subscriptions) {
             final DefaultSubscriptionBase cur = (DefaultSubscriptionBase) subscription;
@@ -631,8 +633,8 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
 
             final Plan addonCurrentPlan = cur.getCurrentPlan();
             if (baseProduct == null ||
-                addonUtils.isAddonIncludedFromProdName(baseProduct.getName(), addonCurrentPlan, effectiveDate, catalog, internalTenantContext) ||
-                !addonUtils.isAddonAvailableFromProdName(baseProduct.getName(), addonCurrentPlan, effectiveDate, catalog, internalTenantContext)) {
+                addonUtils.isAddonIncluded(baseProduct, addonCurrentPlan) ||
+                !addonUtils.isAddonAvailable(baseProduct, addonCurrentPlan)) {
                 //
                 // Perform AO cancellation using the effectiveDate of the BP
                 //
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/addon/AddonUtils.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/addon/AddonUtils.java
index 587e1b7..da8dd4c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/addon/AddonUtils.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/addon/AddonUtils.java
@@ -19,14 +19,10 @@
 package org.killbill.billing.subscription.engine.addon;
 
 import java.util.Collection;
-import java.util.List;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.callcontext.InternalTenantContext;
-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.Plan;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
@@ -34,27 +30,18 @@ import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
-import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
-
-import com.google.inject.Inject;
 
 public class AddonUtils {
 
-
-    @Inject
-    public AddonUtils() {
-    }
-
-    public void checkAddonCreationRights(final SubscriptionBase baseSubscription, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context)
-            throws SubscriptionBaseApiException, CatalogApiException {
-
+    public void checkAddonCreationRights(final SubscriptionBase baseSubscription, final Plan targetAddOnPlan, final DateTime requestedDate, final InternalTenantContext context)
+            throws SubscriptionBaseApiException {
         if (baseSubscription.getState() == EntitlementState.CANCELLED ||
             (baseSubscription.getState() == EntitlementState.PENDING && context.toLocalDate(baseSubscription.getStartDate()).compareTo(context.toLocalDate(requestedDate)) < 0)) {
             throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
         }
 
         final Plan currentOrPendingPlan = baseSubscription.getCurrentOrPendingPlan();
-        final Product baseProduct = catalog.findProduct(currentOrPendingPlan.getProduct().getName(), requestedDate);
+        final Product baseProduct = currentOrPendingPlan.getProduct();
         if (isAddonIncluded(baseProduct, targetAddOnPlan)) {
             throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_ALREADY_INCLUDED,
                                                    targetAddOnPlan.getName(), currentOrPendingPlan.getProduct().getName());
@@ -66,46 +53,7 @@ public class AddonUtils {
         }
     }
 
-    public boolean isAddonAvailableFromProdName(final String baseProductName, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context) {
-        try {
-            final Product product = catalog.findProduct(baseProductName, requestedDate);
-            return isAddonAvailable(product, targetAddOnPlan);
-        } catch (CatalogApiException e) {
-            throw new SubscriptionBaseError(e);
-        }
-    }
-
-    public boolean isAddonAvailableFromPlanName(final String basePlanName, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context) {
-        try {
-            final Plan plan = catalog.findPlan(basePlanName, requestedDate);
-            final Product product = plan.getProduct();
-            return isAddonAvailable(product, targetAddOnPlan);
-        } catch (CatalogApiException e) {
-            throw new SubscriptionBaseError(e);
-        }
-    }
-
-    public boolean isAddonIncludedFromProdName(final String baseProductName, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context) {
-        try {
-            final Product product = catalog.findProduct(baseProductName, requestedDate);
-            return isAddonIncluded(product, targetAddOnPlan);
-        } catch (CatalogApiException e) {
-            throw new SubscriptionBaseError(e);
-        }
-
-    }
-
-    public boolean isAddonIncludedFromPlanName(final String basePlanName, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context) {
-        try {
-            final Plan plan = catalog.findPlan(basePlanName, requestedDate);
-            final Product product = plan.getProduct();
-            return isAddonIncluded(product, targetAddOnPlan);
-        } catch (CatalogApiException e) {
-            throw new SubscriptionBaseError(e);
-        }
-    }
-
-    private boolean isAddonAvailable(final Product baseProduct, final Plan targetAddOnPlan) {
+    public boolean isAddonAvailable(final Product baseProduct, final Plan targetAddOnPlan) {
         final Product targetAddonProduct = targetAddOnPlan.getProduct();
         final Collection<Product> availableAddOns = baseProduct.getAvailable();
 
@@ -117,7 +65,7 @@ public class AddonUtils {
         return false;
     }
 
-    private boolean isAddonIncluded(final Product baseProduct, final Plan targetAddOnPlan) {
+    public boolean isAddonIncluded(final Product baseProduct, final Plan targetAddOnPlan) {
         final Product targetAddonProduct = targetAddOnPlan.getProduct();
         final Collection<Product> includedAddOns = baseProduct.getIncluded();
         for (final Product curAv : includedAddOns) {
@@ -128,7 +76,7 @@ public class AddonUtils {
         return false;
     }
 
-    public int countExistingAddOnsWithSamePlanName(final Iterable<SubscriptionBase> subscriptionsForBundle, final String planName) {
+    public int countExistingAddOnsWithSamePlanName(final Iterable<DefaultSubscriptionBase> subscriptionsForBundle, final String planName) {
         int countExistingAddOns = 0;
         for (final SubscriptionBase subscription : subscriptionsForBundle) {
             if (subscription.getCurrentPlan().getName().equalsIgnoreCase(planName)
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 98ed638..2277355 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -43,6 +43,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.entitlement.api.SubscriptionApiException;
@@ -351,9 +352,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
     @Override
     public SubscriptionBase getSubscriptionFromId(final UUID subscriptionId, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
-        final SubscriptionBase shellSubscription = transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<SubscriptionBase>() {
+        final DefaultSubscriptionBase shellSubscription = transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<DefaultSubscriptionBase>() {
             @Override
-            public SubscriptionBase inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+            public DefaultSubscriptionBase inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
                 final SubscriptionModelDao subscriptionModel = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getById(subscriptionId.toString(), context);
                 final SubscriptionBundleModelDao bundleModel = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getById(subscriptionModel.getBundleId().toString(), context);
                 return SubscriptionModelDao.toSubscription(subscriptionModel, bundleModel.getExternalKey());
@@ -374,21 +375,21 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
     }
 
     @Override
-    public List<SubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+    public List<DefaultSubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
         return buildBundleSubscriptions(getSubscriptionFromBundleId(bundleId, context), null, dryRunEvents, catalog, context);
     }
 
-    private List<SubscriptionBase> getSubscriptionFromBundleId(final UUID bundleId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<List<SubscriptionBase>>() {
+    private List<DefaultSubscriptionBase> getSubscriptionFromBundleId(final UUID bundleId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<List<DefaultSubscriptionBase>>() {
             @Override
-            public List<SubscriptionBase> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+            public List<DefaultSubscriptionBase> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
 
                 final SubscriptionBundleModelDao bundleModel = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getById(bundleId.toString(), context);
 
                 final List<SubscriptionModelDao> models = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getSubscriptionsFromBundleId(bundleId.toString(), context);
-                return new ArrayList<SubscriptionBase>(Collections2.transform(models, new Function<SubscriptionModelDao, SubscriptionBase>() {
+                return new ArrayList<DefaultSubscriptionBase>(Collections2.transform(models, new Function<SubscriptionModelDao, DefaultSubscriptionBase>() {
                     @Override
-                    public SubscriptionBase apply(@Nullable final SubscriptionModelDao input) {
+                    public DefaultSubscriptionBase apply(@Nullable final SubscriptionModelDao input) {
                         return SubscriptionModelDao.toSubscription(input, bundleModel.getExternalKey());
                     }
                 }));
@@ -397,15 +398,15 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
     }
 
     @Override
-    public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
-        final Map<UUID, List<SubscriptionBase>> subscriptionsFromAccountId = getSubscriptionsFromAccountId(context);
+    public Map<UUID, List<DefaultSubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+        final Map<UUID, List<DefaultSubscriptionBase>> subscriptionsFromAccountId = getSubscriptionsFromAccountId(context);
 
         final List<SubscriptionBaseEvent> eventsForAccount = getEventsForAccountId(context);
 
-        final Map<UUID, List<SubscriptionBase>> result = new HashMap<UUID, List<SubscriptionBase>>();
+        final Map<UUID, List<DefaultSubscriptionBase>> result = new HashMap<UUID, List<DefaultSubscriptionBase>>();
         for (final UUID bundleId : subscriptionsFromAccountId.keySet()) {
 
-            final List<SubscriptionBase> subscriptionsForBundle = subscriptionsFromAccountId.get(bundleId);
+            final List<DefaultSubscriptionBase> subscriptionsForBundle = subscriptionsFromAccountId.get(bundleId);
             final Multimap<UUID, SubscriptionBaseEvent> eventsForSubscriptions = ArrayListMultimap.create();
 
             for (final SubscriptionBase cur : subscriptionsForBundle) {
@@ -424,17 +425,17 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         return result;
     }
 
-    private Map<UUID, List<SubscriptionBase>> getSubscriptionsFromAccountId(final InternalTenantContext context) {
-        final List<SubscriptionBase> allSubscriptions = transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<List<SubscriptionBase>>() {
+    private Map<UUID, List<DefaultSubscriptionBase>> getSubscriptionsFromAccountId(final InternalTenantContext context) {
+        final List<DefaultSubscriptionBase> allSubscriptions = transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<List<DefaultSubscriptionBase>>() {
             @Override
-            public List<SubscriptionBase> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+            public List<DefaultSubscriptionBase> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
 
                 final List<SubscriptionBundleModelDao> bundleModels = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getByAccountRecordId(context);
 
                 final List<SubscriptionModelDao> subscriptionModels = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getByAccountRecordId(context);
-                return new ArrayList<SubscriptionBase>(Collections2.transform(subscriptionModels, new Function<SubscriptionModelDao, SubscriptionBase>() {
+                return new ArrayList<DefaultSubscriptionBase>(Collections2.transform(subscriptionModels, new Function<SubscriptionModelDao, DefaultSubscriptionBase>() {
                     @Override
-                    public SubscriptionBase apply(final SubscriptionModelDao input) {
+                    public DefaultSubscriptionBase apply(final SubscriptionModelDao input) {
                         final SubscriptionBundleModelDao bundleModel = Iterables.find(bundleModels, new Predicate<SubscriptionBundleModelDao>() {
                             @Override
                             public boolean apply(final SubscriptionBundleModelDao bundleInput) {
@@ -447,10 +448,10 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
             }
         });
 
-        final Map<UUID, List<SubscriptionBase>> result = new HashMap<UUID, List<SubscriptionBase>>();
-        for (final SubscriptionBase subscriptionBase : allSubscriptions) {
+        final Map<UUID, List<DefaultSubscriptionBase>> result = new HashMap<UUID, List<DefaultSubscriptionBase>>();
+        for (final DefaultSubscriptionBase subscriptionBase : allSubscriptions) {
             if (result.get(subscriptionBase.getBundleId()) == null) {
-                result.put(subscriptionBase.getBundleId(), new LinkedList<SubscriptionBase>());
+                result.put(subscriptionBase.getBundleId(), new LinkedList<DefaultSubscriptionBase>());
             }
             result.get(subscriptionBase.getBundleId()).add(subscriptionBase);
         }
@@ -836,14 +837,14 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         }
     }
 
-    private SubscriptionBase buildSubscription(final SubscriptionBase input, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+    private DefaultSubscriptionBase buildSubscription(final DefaultSubscriptionBase input, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
 
         if (input == null) {
             return null;
         }
-        final List<SubscriptionBase> bundleInput = new ArrayList<SubscriptionBase>();
+        final List<DefaultSubscriptionBase> bundleInput = new ArrayList<DefaultSubscriptionBase>();
         if (input.getCategory() == ProductCategory.ADD_ON) {
-            final SubscriptionBase baseSubscription = getBaseSubscription(input.getBundleId(), false, catalog, context);
+            final DefaultSubscriptionBase baseSubscription = getBaseSubscription(input.getBundleId(), false, catalog, context);
             if (baseSubscription == null) {
                 return null;
             }
@@ -854,8 +855,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
             bundleInput.add(input);
         }
 
-        final List<SubscriptionBase> reloadedSubscriptions = buildBundleSubscriptions(bundleInput, null, null, catalog, context);
-        for (final SubscriptionBase cur : reloadedSubscriptions) {
+        final List<DefaultSubscriptionBase> reloadedSubscriptions = buildBundleSubscriptions(bundleInput, null, null, catalog, context);
+        for (final DefaultSubscriptionBase cur : reloadedSubscriptions) {
             if (cur.getId().equals(input.getId())) {
                 return cur;
             }
@@ -864,7 +865,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         throw new SubscriptionBaseError("Unexpected code path in buildSubscription");
     }
 
-    private List<SubscriptionBase> buildBundleSubscriptions(final List<SubscriptionBase> input, @Nullable final Multimap<UUID, SubscriptionBaseEvent> eventsForSubscription,
+    private List<DefaultSubscriptionBase> buildBundleSubscriptions(final List<DefaultSubscriptionBase> input, @Nullable final Multimap<UUID, SubscriptionBaseEvent> eventsForSubscription,
                                                             @Nullable final Collection<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
         if (input == null || input.isEmpty()) {
             return Collections.emptyList();
@@ -875,14 +876,14 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
         final Collection<ApiEventChange> baseChangeEvents = new LinkedList<ApiEventChange>();
         ApiEventCancel baseCancellationEvent = null;
-        final List<SubscriptionBase> result = new ArrayList<SubscriptionBase>(input.size());
-        for (final SubscriptionBase cur : input) {
+        final List<DefaultSubscriptionBase> result = new ArrayList<DefaultSubscriptionBase>(input.size());
+        for (final DefaultSubscriptionBase cur : input) {
             final List<SubscriptionBaseEvent> events = eventsForSubscription != null ?
                                                        (List<SubscriptionBaseEvent>) eventsForSubscription.get(cur.getId()) :
                                                        getEventsForSubscription(cur.getId(), context);
             mergeDryRunEvents(cur.getId(), events, dryRunEvents);
 
-            SubscriptionBase reloaded = createSubscriptionForInternalUse(cur, events, catalog, context);
+            DefaultSubscriptionBase reloaded = createSubscriptionForInternalUse(cur, events, catalog, context);
 
             switch (cur.getCategory()) {
                 case BASE:
@@ -908,10 +909,11 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
                     SubscriptionBaseEvent baseTriggerEventForAddOnCancellation = baseCancellationEvent;
                     for (final ApiEventChange baseChangeEvent : baseChangeEvents) {
-                        final String baseProductName = baseChangeEvent.getEventPlan();
+                        final Plan basePlan = catalog.findPlan(baseChangeEvent.getEventPlan(), baseChangeEvent.getEffectiveDate(), cur.getAlignStartDate());
+                        final Product baseProduct = basePlan.getProduct();
 
-                        if ((!addonUtils.isAddonAvailableFromPlanName(baseProductName, targetAddOnPlan, baseChangeEvent.getEffectiveDate(), catalog, context)) ||
-                            (addonUtils.isAddonIncludedFromPlanName(baseProductName, targetAddOnPlan, baseChangeEvent.getEffectiveDate(), catalog, context))) {
+                        if ((!addonUtils.isAddonAvailable(baseProduct, targetAddOnPlan)) ||
+                            (addonUtils.isAddonIncluded(baseProduct, targetAddOnPlan))) {
                             if (baseTriggerEventForAddOnCancellation != null) {
                                 if (baseTriggerEventForAddOnCancellation.getEffectiveDate().isAfter(baseChangeEvent.getEffectiveDate())) {
                                     baseTriggerEventForAddOnCancellation = baseChangeEvent;
@@ -1051,7 +1053,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
     }
 
-    private SubscriptionBase createSubscriptionForInternalUse(final SubscriptionBase shellSubscription, final List<SubscriptionBaseEvent> events, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+    private DefaultSubscriptionBase createSubscriptionForInternalUse(final SubscriptionBase shellSubscription, final List<SubscriptionBaseEvent> events, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
         final DefaultSubscriptionBase result = new DefaultSubscriptionBase(new SubscriptionBuilder(((DefaultSubscriptionBase) shellSubscription)), null, clock);
 
         if (!events.isEmpty()) {
@@ -1060,9 +1062,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         return result;
     }
 
-    private SubscriptionBase getBaseSubscription(final UUID bundleId, final boolean rebuildSubscription, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
-        final List<SubscriptionBase> subscriptions = getSubscriptionFromBundleId(bundleId, context);
-        for (final SubscriptionBase cur : subscriptions) {
+    private DefaultSubscriptionBase getBaseSubscription(final UUID bundleId, final boolean rebuildSubscription, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+        final List<DefaultSubscriptionBase> subscriptions = getSubscriptionFromBundleId(bundleId, context);
+        for (final DefaultSubscriptionBase cur : subscriptions) {
             if (cur.getCategory() == ProductCategory.BASE) {
                 return rebuildSubscription ? buildSubscription(cur, catalog, context) : cur;
             }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java
index e0761b9..d7adf33 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java
@@ -105,7 +105,7 @@ public class SubscriptionModelDao extends EntityModelDaoBase implements EntityMo
         this.migrated = migrated;
     }
 
-    public static SubscriptionBase toSubscription(final SubscriptionModelDao src, final String externalKey) {
+    public static DefaultSubscriptionBase toSubscription(final SubscriptionModelDao src, final String externalKey) {
         if (src == null) {
             return null;
         }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
index 242676b..64fdc9a 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
@@ -64,9 +64,9 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
     // SubscriptionBase retrieval
     public SubscriptionBase getBaseSubscription(UUID bundleId, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
 
-    public List<SubscriptionBase> getSubscriptions(UUID bundleId, List<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
+    public List<DefaultSubscriptionBase> getSubscriptions(UUID bundleId, List<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
 
-    public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
+    public Map<UUID, List<DefaultSubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
 
     // Update
     public void updateChargedThroughDate(DefaultSubscriptionBase subscription, InternalCallContext context);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java
index 4503630..4999f00 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -398,10 +400,11 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
         final PlanSpecifier planSpecifier = new PlanSpecifier(aoProduct,
                                                               aoTerm,
                                                               aoPriceList);
-        final PlanAlignmentCreate alignement = catalog.planCreateAlignment(planSpecifier, clock.getUTCNow());
-        assertEquals(alignement, PlanAlignmentCreate.START_OF_BUNDLE);
+        final DateTime utcNow = clock.getUTCNow();
+        final PlanAlignmentCreate alignment = catalog.planCreateAlignment(planSpecifier, utcNow, utcNow);
+        assertEquals(alignment, PlanAlignmentCreate.START_OF_BUNDLE);
 
-        testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
+        testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignment);
     }
 
     @Test(groups = "slow")
@@ -414,10 +417,11 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
         final PlanSpecifier planSpecifier = new PlanSpecifier(aoProduct,
                                                               aoTerm,
                                                               aoPriceList);
-        final PlanAlignmentCreate alignement = catalog.planCreateAlignment(planSpecifier, clock.getUTCNow());
-        assertEquals(alignement, PlanAlignmentCreate.START_OF_SUBSCRIPTION);
+        final DateTime utcNow = clock.getUTCNow();
+        final PlanAlignmentCreate alignment = catalog.planCreateAlignment(planSpecifier, utcNow, utcNow);
+        assertEquals(alignment, PlanAlignmentCreate.START_OF_SUBSCRIPTION);
 
-        testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
+        testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignment);
     }
 
     private void testAddonCreateInternal(final String aoProduct, final BillingPeriod aoTerm, final String aoPriceList, final PlanAlignmentCreate expAlignement) throws SubscriptionBaseApiException {
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index 8b5a74e..36b05f0 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -78,7 +78,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
     protected static final Logger log = LoggerFactory.getLogger(SubscriptionDao.class);
 
     private final List<SubscriptionBaseBundle> bundles;
-    private final List<SubscriptionBase> subscriptions;
+    private final List<DefaultSubscriptionBase> subscriptions;
     private final TreeSet<SubscriptionBaseEvent> events;
 
     private final MockNonEntityDao mockNonEntityDao;
@@ -100,7 +100,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
         this.notificationQueueService = notificationQueueService;
         this.eventBus = eventBus;
         this.bundles = new ArrayList<SubscriptionBaseBundle>();
-        this.subscriptions = new ArrayList<SubscriptionBase>();
+        this.subscriptions = new ArrayList<DefaultSubscriptionBase>();
         this.events = new TreeSet<SubscriptionBaseEvent>();
     }
 
@@ -216,7 +216,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
                     for (final SubscriptionBaseEvent cur : initialEvents) {
                         recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new SubscriptionNotificationKey(cur.getId()), context);
                     }
-                    final SubscriptionBase updatedSubscription = buildSubscription((DefaultSubscriptionBase) subscriptionBase, context);
+                    final DefaultSubscriptionBase updatedSubscription = buildSubscription((DefaultSubscriptionBase) subscriptionBase, context);
                     this.subscriptions.add(updatedSubscription);
                     mockNonEntityDao.addTenantRecordIdMapping(updatedSubscription.getId(), context);
 
@@ -229,24 +229,24 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
     }
 
     @Override
-    public List<SubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents,  final Catalog catalog, final InternalTenantContext context) {
-        final List<SubscriptionBase> results = new ArrayList<SubscriptionBase>();
-        for (final SubscriptionBase cur : subscriptions) {
+    public List<DefaultSubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, final InternalTenantContext context) {
+        final List<DefaultSubscriptionBase> results = new ArrayList<DefaultSubscriptionBase>();
+        for (final DefaultSubscriptionBase cur : subscriptions) {
             if (cur.getBundleId().equals(bundleId)) {
-                results.add(buildSubscription((DefaultSubscriptionBase) cur, context));
+                results.add(buildSubscription(cur, context));
             }
         }
         return results;
     }
 
     @Override
-    public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, final InternalTenantContext context) {
-        final Map<UUID, List<SubscriptionBase>> results = new HashMap<UUID, List<SubscriptionBase>>();
-        for (final SubscriptionBase cur : subscriptions) {
+    public Map<UUID, List<DefaultSubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, final InternalTenantContext context) {
+        final Map<UUID, List<DefaultSubscriptionBase>> results = new HashMap<UUID, List<DefaultSubscriptionBase>>();
+        for (final DefaultSubscriptionBase cur : subscriptions) {
             if (results.get(cur.getBundleId()) == null) {
-                results.put(cur.getBundleId(), new LinkedList<SubscriptionBase>());
+                results.put(cur.getBundleId(), new LinkedList<DefaultSubscriptionBase>());
             }
-            results.get(cur.getBundleId()).add(buildSubscription((DefaultSubscriptionBase) cur, context));
+            results.get(cur.getBundleId()).add(buildSubscription(cur, context));
         }
         return results;
     }
@@ -297,9 +297,9 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
         notifyBusOfEffectiveImmediateChange(subscription, readyPhaseEvent, 0, context);
     }
 
-    private SubscriptionBase buildSubscription(final DefaultSubscriptionBase in, final InternalTenantContext context) {
+    private DefaultSubscriptionBase buildSubscription(final DefaultSubscriptionBase in, final InternalTenantContext context) {
         final DefaultSubscriptionBase subscription = new DefaultSubscriptionBase(new SubscriptionBuilder(in), null, clock);
-        if (events.size() > 0) {
+        if (!events.isEmpty()) {
             try {
                 subscription.rebuildTransitions(getEventsForSubscription(in.getId(), context), catalogService.getFullCatalog(true, true, context));
             } catch (final CatalogApiException e) {
@@ -307,13 +307,12 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
             }
         }
         return subscription;
-
     }
 
     @Override
     public void updateChargedThroughDate(final DefaultSubscriptionBase subscription, final InternalCallContext context) {
         boolean found = false;
-        final Iterator<SubscriptionBase> it = subscriptions.iterator();
+        final Iterator<DefaultSubscriptionBase> it = subscriptions.iterator();
         while (it.hasNext()) {
             final SubscriptionBase cur = it.next();
             if (cur.getId().equals(subscription.getId())) {
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 c3bb42a..7ca3747 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
@@ -77,6 +77,9 @@ public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
     @Override // to ignore events
     @AfterMethod(groups = "slow")
     public void afterMethod() throws Exception {
+        if (hasFailed()) {
+            return;
+        }
         subscriptionTestInitializer.stopTestFramework(testListener, busService, subscriptionBaseService);
     }
 
diff --git a/util/src/test/java/org/killbill/billing/api/TestApiListener.java b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
index 83cb4c2..b431636 100644
--- a/util/src/test/java/org/killbill/billing/api/TestApiListener.java
+++ b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
@@ -324,8 +324,12 @@ public class TestApiListener {
                 } catch (final Exception ignore) {
                     // Rerun one more time to provide details
                     final long pending = idbi.withHandle(new PendingBusOrNotificationCallback(clock));
-                    log.error("isCompleted : Received all events but found remaining unprocessed bus events/notifications =  {}", pending);
-                    return false;
+                    if (pending != 0) {
+                        log.error("isCompleted : Received all events but found remaining unprocessed bus events/notifications = {}", pending);
+                        return false;
+                    } else {
+                        return completed;
+                    }
                 }
             } while (waitTimeMs > 0 && !completed);
         }