killbill-uncached

Merge branch 'javier-gomez-github-jgomez-issue-219' into

11/11/2016 10:20:07 PM

Changes

pom.xml 2(+1 -1)

Details

diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index 9badfbc..8599350 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -30,13 +30,12 @@ import org.joda.time.LocalDate;
 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.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
-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.user.SubscriptionBaseApiException;
@@ -49,8 +48,8 @@ public interface SubscriptionBaseInternalApi {
     public SubscriptionBase createSubscription(UUID bundleId, PlanPhaseSpecifier spec, List<PlanPhasePriceOverride> overrides, DateTime requestedDateWithMs,
                                                final boolean isMigrated, InternalCallContext context) throws SubscriptionBaseApiException;
 
-    public List<SubscriptionBase> createBaseSubscriptionWithAddOns(UUID bundleId, Iterable<EntitlementSpecifier> entitlements, DateTime requestedDateWithMs,
-                                                                   final boolean isMigrated, InternalCallContext context) throws SubscriptionBaseApiException;
+    public List<SubscriptionBaseWithAddOns> createBaseSubscriptionsWithAddOns(UUID accountId, Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifier,
+                                                                              InternalCallContext contextWithValidAccountRecordId) throws SubscriptionBaseApiException;
 
     public void cancelBaseSubscriptions(Iterable<SubscriptionBase> subscriptions, BillingActionPolicy policy, InternalCallContext context) throws SubscriptionBaseApiException;
 
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseWithAddOns.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseWithAddOns.java
new file mode 100644
index 0000000..5753c1a
--- /dev/null
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseWithAddOns.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.api;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.subscription.api.SubscriptionBase;
+
+public interface SubscriptionBaseWithAddOns {
+
+    public UUID getBundleId();
+
+    public List<SubscriptionBase> getSubscriptionBaseList();
+
+    public DateTime getEffectiveDate();
+}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestMigrationSubscriptions.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestMigrationSubscriptions.java
index 3069314..16eadea 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestMigrationSubscriptions.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestMigrationSubscriptions.java
@@ -20,6 +20,7 @@ package org.killbill.billing.beatrix.integration;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.Account;
@@ -30,6 +31,7 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PriceListSet;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
 import org.killbill.billing.entitlement.api.Entitlement;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
@@ -235,14 +237,117 @@ public class TestMigrationSubscriptions extends TestIntegrationBase {
         specifierList.add(addOnEntitlementSpecifier1);
 
         busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.BLOCK);
-        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, entitlementMigrationDate, billingMigrationDate, false, ImmutableList.<PluginProperty>of(), callContext);
+        BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = buildBaseEntitlementWithAddOnsSpecifier(entitlementMigrationDate, billingMigrationDate, externalKey, specifierList);
+        List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
+
+        final List<Entitlement> baseEntitlements = entitlementApi.createBaseEntitlementsWithAddOns(
+                account.getId(),
+                baseEntitlementWithAddOnsSpecifierList,
+                ImmutableList.<PluginProperty>of(),
+                callContext);
         assertListenerStatus();
-        Assert.assertEquals(baseEntitlement.getState(), EntitlementState.ACTIVE);
+        Assert.assertEquals(baseEntitlements.get(0).getState(), EntitlementState.ACTIVE);
 
         // Billing starts straight on EVERGREEN
-        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.NULL_INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
         clock.addMonths(1);
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.NULL_INVOICE,
+                                      NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
         assertListenerStatus();
     }
 
+
+    @Test(groups = "slow")
+    public void testMigrationWithMultipleBundlesAndDifferentDates() throws Exception {
+
+        clock.setDay(new LocalDate(2016, 1, 1));
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        // We set both entitlement and billing date with desired value
+        final LocalDate entitlementMigrationDateBundle1 = new LocalDate(2015, 12, 20);
+        final LocalDate billingMigrationDateBundle1 = new LocalDate(2016, 2, 1);
+
+        // We set both entitlement and billing date with desired value
+        final LocalDate entitlementMigrationDateBundle2 = new LocalDate(2015, 12, 20);
+        final LocalDate billingMigrationDateBundle2 = new LocalDate(2016, 3, 1);
+
+        final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+        final PlanPhaseSpecifier addOnSpec1 = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+
+        final String externalKey = "baseExternalKey";
+        EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
+        EntitlementSpecifier addOnEntitlementSpecifier1 = new DefaultEntitlementSpecifier(addOnSpec1, null);
+
+        final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
+        specifierList.add(baseEntitlementSpecifier);
+        specifierList.add(addOnEntitlementSpecifier1);
+
+        busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK);
+        BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifierBundle1 =
+                buildBaseEntitlementWithAddOnsSpecifier(entitlementMigrationDateBundle1, billingMigrationDateBundle1, externalKey, specifierList);
+        List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifierBundle2 =
+                buildBaseEntitlementWithAddOnsSpecifier(entitlementMigrationDateBundle2, billingMigrationDateBundle2, externalKey, specifierList);
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifierBundle1);
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifierBundle2);
+
+        final List<Entitlement> baseEntitlements = entitlementApi.createBaseEntitlementsWithAddOns(
+                account.getId(),
+                baseEntitlementWithAddOnsSpecifierList,
+                ImmutableList.<PluginProperty>of(),
+                callContext);
+        assertListenerStatus();
+        Assert.assertEquals(baseEntitlements.get(0).getState(), EntitlementState.ACTIVE);
+        Assert.assertEquals(baseEntitlements.get(1).getState(), EntitlementState.ACTIVE);
+
+        // Billing starts straight on EVERGREEN for Bundle 1 after 1 month
+        clock.addMonths(1);
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.NULL_INVOICE,
+                                      NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        assertListenerStatus();
+
+        // Billing starts straight on EVERGREEN for Bundle 2 after 2 months
+        clock.addMonths(1);
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE, NextEvent.INVOICE,
+                                      NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE,
+                                      NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        assertListenerStatus();
+
+        // Next month we should still get one single invoice and payment / invoice payment
+        clock.addMonths(1);
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        assertListenerStatus();
+    }
+
+    private BaseEntitlementWithAddOnsSpecifier buildBaseEntitlementWithAddOnsSpecifier(final LocalDate entitlementMigrationDate, final LocalDate billingMigrationDate, final String externalKey, final List<EntitlementSpecifier> specifierList) {
+        return new BaseEntitlementWithAddOnsSpecifier() {
+                @Override
+                public UUID getBundleId() {
+                    return null;
+                }
+                @Override
+                public String getExternalKey() {
+                    return externalKey;
+                }
+                @Override
+                public Iterable<EntitlementSpecifier> getEntitlementSpecifier() {
+                    return specifierList;
+                }
+                @Override
+                public LocalDate getEntitlementEffectiveDate() {
+                    return entitlementMigrationDate;
+                }
+                @Override
+                public LocalDate getBillingEffectiveDate() {
+                    return billingMigrationDate;
+                }
+                @Override
+                public boolean isMigrated() {
+                    return false;
+                }
+            };
+    }
+
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
index 17d84b4..8b5ca40 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
@@ -22,12 +22,10 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
-import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
@@ -35,6 +33,8 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
+import org.killbill.billing.entitlement.api.DefaultBaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
 import org.killbill.billing.entitlement.api.Entitlement;
@@ -42,8 +42,6 @@ import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.entitlement.api.EntitlementSpecifier;
-import org.killbill.billing.entitlement.api.Subscription;
-import org.killbill.billing.entitlement.api.SubscriptionApiException;
 import org.killbill.billing.entitlement.api.SubscriptionEventType;
 import org.killbill.billing.invoice.api.DryRunType;
 import org.killbill.billing.invoice.api.Invoice;
@@ -56,7 +54,6 @@ import com.google.common.collect.ImmutableList;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
 
 public class TestSubscription extends TestIntegrationBase {
 
@@ -192,7 +189,7 @@ public class TestSubscription extends TestIntegrationBase {
     }
 
     @Test(groups = "slow")
-    public void testCreateSubscriptionWithAddOns() throws Exception {
+    public void testCreateMultipleSubscriptionsWithAddOns() throws Exception {
         final LocalDate initialDate = new LocalDate(2015, 10, 1);
         clock.setDay(initialDate);
 
@@ -202,29 +199,51 @@ public class TestSubscription extends TestIntegrationBase {
         final PlanPhaseSpecifier addOnSpec1 = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
         final PlanPhaseSpecifier addOnSpec2 = new PlanPhaseSpecifier("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
 
-        final String externalKey = "baseExternalKey";
-        EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
-        EntitlementSpecifier addOnEntitlementSpecifier1 = new DefaultEntitlementSpecifier(addOnSpec1, null);
-        EntitlementSpecifier addOnEntitlementSpecifier2 = new DefaultEntitlementSpecifier(addOnSpec2, null);
-
-        final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
-        specifierList.add(baseEntitlementSpecifier);
-        specifierList.add(addOnEntitlementSpecifier1);
-        specifierList.add(addOnEntitlementSpecifier2);
-
-        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, initialDate, false, ImmutableList.<PluginProperty>of(), callContext);
+        final String externalKeyA = "baseExternalKeyAAA";
+        final EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
+        final EntitlementSpecifier addOnEntitlementSpecifier1 = new DefaultEntitlementSpecifier(addOnSpec1, null);
+        final EntitlementSpecifier addOnEntitlementSpecifier2 = new DefaultEntitlementSpecifier(addOnSpec2, null);
+
+        final List<EntitlementSpecifier> specifierListA = new ArrayList<EntitlementSpecifier>();
+        specifierListA.add(baseEntitlementSpecifier);
+        specifierListA.add(addOnEntitlementSpecifier1);
+        specifierListA.add(addOnEntitlementSpecifier2);
+
+        final String externalKeyB = "baseExternalKeyBBB";
+
+        final List<EntitlementSpecifier> specifierListB = new ArrayList<EntitlementSpecifier>();
+        specifierListB.add(baseEntitlementSpecifier);
+        specifierListB.add(addOnEntitlementSpecifier1);
+        specifierListB.add(addOnEntitlementSpecifier2);
+
+        final List<BaseEntitlementWithAddOnsSpecifier> entitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        final BaseEntitlementWithAddOnsSpecifier cartSpecifierA = new DefaultBaseEntitlementWithAddOnsSpecifier(null, externalKeyA, specifierListA, null, null, false);
+        final BaseEntitlementWithAddOnsSpecifier cartSpecifierB = new DefaultBaseEntitlementWithAddOnsSpecifier(null, externalKeyB, specifierListB, null, null, false);
+        entitlementWithAddOnsSpecifierList.add(cartSpecifierA);
+        entitlementWithAddOnsSpecifierList.add(cartSpecifierB);
+
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK,
+                                      NextEvent.CREATE, NextEvent.BLOCK,
+                                      NextEvent.CREATE, NextEvent.BLOCK,
+                                      NextEvent.CREATE, NextEvent.BLOCK,
+                                      NextEvent.CREATE, NextEvent.BLOCK,
+                                      NextEvent.CREATE, NextEvent.BLOCK,
+                                      NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE,
+                                      NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE,
+                                      NextEvent.NULL_INVOICE,
+                                      NextEvent.INVOICE,
+                                      NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT
+                                     );
+        final List<Entitlement> allEntitlements = entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, ImmutableList.<PluginProperty>of(), callContext);
         assertListenerStatus();
         checkNoMoreInvoiceToGenerate(account);
 
-        assertNotNull(entitlement);
+        assertNotNull(allEntitlements);
+        assertEquals(allEntitlements.size(), 6);
 
-        final List<Entitlement> allEntitlementsForBundle = entitlementApi.getAllEntitlementsForBundle(entitlement.getBundleId(), callContext);
-        assertTrue(allEntitlementsForBundle.size() == 3);
-
-        final Entitlement baseEntitlement = allEntitlementsForBundle.get(0);
-        final Entitlement addOnEntitlement1 = allEntitlementsForBundle.get(1);
-        final Entitlement addOnEntitlement2 = allEntitlementsForBundle.get(2);
+        final Entitlement baseEntitlement = allEntitlements.get(0);
+        final Entitlement addOnEntitlement1 = allEntitlements.get(1);
+        final Entitlement addOnEntitlement2 = allEntitlements.get(2);
 
         assertEquals(baseEntitlement.getLastActiveProduct().getName(), "Shotgun");
         assertEquals(baseEntitlement.getLastActiveProductCategory(), ProductCategory.BASE);
@@ -236,47 +255,84 @@ public class TestSubscription extends TestIntegrationBase {
         assertEquals(addOnEntitlement2.getLastActiveProductCategory(), ProductCategory.ADD_ON);
 
         final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
-        assertTrue(invoices.size() == 1); // ONLY ONE INVOICE
-        assertTrue(invoices.get(0).getInvoiceItems().size() == 3);
+        assertEquals(invoices.size(), 1); // ONLY ONE INVOICE
+        assertEquals(invoices.get(0).getInvoiceItems().size(), 6);
 
         final ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
                 new ExpectedInvoiceItemCheck(initialDate, new LocalDate(2015, 10, 31), InvoiceItemType.RECURRING, new BigDecimal("387.05")), // amount=387.05, rate=399.95 -> Telescopic-Scope
                 new ExpectedInvoiceItemCheck(initialDate, new LocalDate(2015, 10, 31), InvoiceItemType.RECURRING, new BigDecimal("967.69")), // amount=967.69, rate=999.95 -> Laser-Scope
+                new ExpectedInvoiceItemCheck(initialDate, null, InvoiceItemType.FIXED, new BigDecimal("0")), // Shotgun
+                new ExpectedInvoiceItemCheck(initialDate, new LocalDate(2015, 10, 31), InvoiceItemType.RECURRING, new BigDecimal("387.05")), // amount=387.05, rate=399.95 -> Telescopic-Scope
+                new ExpectedInvoiceItemCheck(initialDate, new LocalDate(2015, 10, 31), InvoiceItemType.RECURRING, new BigDecimal("967.69")), // amount=967.69, rate=999.95 -> Laser-Scope
                 new ExpectedInvoiceItemCheck(initialDate, null, InvoiceItemType.FIXED, new BigDecimal("0"))); // Shotgun
 
         invoiceChecker.checkInvoice(invoices.get(0).getId(), callContext, toBeChecked);
     }
 
-    @Test(groups = "slow")
-    public void testCreateSubscriptionWithAddOnsWithLimitException() throws Exception {
+    @Test(groups = "slow", expectedExceptions = EntitlementApiException.class, expectedExceptionsMessageRegExp = "Missing Base Subscription.")
+    public void testCreateMultipleSubscriptionsWithoutBase() throws Exception {
         final LocalDate initialDate = new LocalDate(2015, 10, 1);
         clock.setDay(initialDate);
 
         final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
 
         final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final PlanPhaseSpecifier addOnSpec1 = new PlanPhaseSpecifier("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+        final PlanPhaseSpecifier addOnSpec1 = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
         final PlanPhaseSpecifier addOnSpec2 = new PlanPhaseSpecifier("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final PlanPhaseSpecifier addOnSpec3 = new PlanPhaseSpecifier("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
 
-        final String externalKey = "baseExternalKey";
-        EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
-        EntitlementSpecifier addOnEntitlementSpecifier1 = new DefaultEntitlementSpecifier(addOnSpec1, null);
-        EntitlementSpecifier addOnEntitlementSpecifier2 = new DefaultEntitlementSpecifier(addOnSpec2, null);
-        EntitlementSpecifier addOnEntitlementSpecifier3 = new DefaultEntitlementSpecifier(addOnSpec3, null);
+        final String externalKeyA = "baseExternalKeyAAA";
+        final EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
+        final EntitlementSpecifier addOnEntitlementSpecifier1 = new DefaultEntitlementSpecifier(addOnSpec1, null);
+        final EntitlementSpecifier addOnEntitlementSpecifier2 = new DefaultEntitlementSpecifier(addOnSpec2, null);
 
-        final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
-        specifierList.add(baseEntitlementSpecifier);
-        specifierList.add(addOnEntitlementSpecifier1);
-        specifierList.add(addOnEntitlementSpecifier2);
-        specifierList.add(addOnEntitlementSpecifier3);
+        final List<EntitlementSpecifier> specifierListA = new ArrayList<EntitlementSpecifier>();
+        specifierListA.add(baseEntitlementSpecifier);
+        specifierListA.add(addOnEntitlementSpecifier1);
+        specifierListA.add(addOnEntitlementSpecifier2);
 
-        // Trying to add the third add_on with the same plan should throw an exception (the limit is 2 for this plan)
-        try {
-            entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, initialDate, false, ImmutableList.<PluginProperty>of(), callContext);
-        } catch (final EntitlementApiException e) {
-            assertEquals(e.getCode(), ErrorCode.SUB_CREATE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE.getCode());
-        }
+        final String externalKeyB = "baseExternalKeyBBB";
+        final List<EntitlementSpecifier> specifierListB = new ArrayList<EntitlementSpecifier>();
+        specifierListB.add(addOnEntitlementSpecifier1);
+        specifierListB.add(addOnEntitlementSpecifier2);
+
+        final List<BaseEntitlementWithAddOnsSpecifier> entitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        final BaseEntitlementWithAddOnsSpecifier cartSpecifierA = new DefaultBaseEntitlementWithAddOnsSpecifier(null, externalKeyA, specifierListA, null, null, false);
+        final BaseEntitlementWithAddOnsSpecifier cartSpecifierB = new DefaultBaseEntitlementWithAddOnsSpecifier(null, externalKeyB, specifierListB, null, null, false);
+        entitlementWithAddOnsSpecifierList.add(cartSpecifierA);
+        entitlementWithAddOnsSpecifierList.add(cartSpecifierB);
+
+        entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, ImmutableList.<PluginProperty>of(), callContext);
+    }
+
+    @Test(groups = "slow", expectedExceptions = EntitlementApiException.class,
+            expectedExceptionsMessageRegExp = "Can't create AddOn laser-scope-monthly, the number of Subscriptions allowed by this Plan and Bundle has reached its limit")
+    public void testCreateMultipleSubscriptionsExceedLimit() throws Exception {
+        final LocalDate initialDate = new LocalDate(2015, 10, 1);
+        clock.setDay(initialDate);
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+        final PlanPhaseSpecifier addOnSpec1 = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+        final PlanPhaseSpecifier addOnSpec2 = new PlanPhaseSpecifier("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+        final String externalKeyA = "baseExternalKeyAAA";
+        final EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
+        final EntitlementSpecifier addOnEntitlementSpecifier1 = new DefaultEntitlementSpecifier(addOnSpec1, null);
+        final EntitlementSpecifier addOnEntitlementSpecifier2 = new DefaultEntitlementSpecifier(addOnSpec2, null);
+
+        final List<EntitlementSpecifier> specifierListA = new ArrayList<EntitlementSpecifier>();
+        specifierListA.add(baseEntitlementSpecifier);
+        specifierListA.add(addOnEntitlementSpecifier1);
+        specifierListA.add(addOnEntitlementSpecifier2);
+        specifierListA.add(addOnEntitlementSpecifier2);
+        specifierListA.add(addOnEntitlementSpecifier2);
+
+        final List<BaseEntitlementWithAddOnsSpecifier> entitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        final BaseEntitlementWithAddOnsSpecifier cartSpecifierA = new DefaultBaseEntitlementWithAddOnsSpecifier(null, externalKeyA, specifierListA, null, null, false);
+        entitlementWithAddOnsSpecifierList.add(cartSpecifierA);
+
+        entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, ImmutableList.<PluginProperty>of(), callContext);
     }
 
     @Test(groups = "slow")
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java
index e6f7499..b19e101 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java
@@ -32,8 +32,9 @@ import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
-import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
+import org.killbill.billing.entitlement.api.DefaultBaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
 import org.killbill.billing.entitlement.api.EntitlementSpecifier;
@@ -136,30 +137,34 @@ public class TestWithEntilementPlugin extends TestIntegrationBase {
         @Override
         public PriorEntitlementResult priorCall(final EntitlementContext entitlementContext, final Iterable<PluginProperty> properties) throws EntitlementPluginApiException {
             if (planPhasePriceOverride != null) {
-                final EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(entitlementContext.getEntitlementSpecifiers().get(0).getPlanPhaseSpecifier(), planPhasePriceOverride);
+                final EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getEntitlementSpecifier().iterator().next().getPlanPhaseSpecifier(), planPhasePriceOverride);
                 final List<EntitlementSpecifier> entitlementSpecifiers = new ArrayList<EntitlementSpecifier>();
                 entitlementSpecifiers.add(entitlementSpecifier);
 
+                final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier =
+                        new DefaultBaseEntitlementWithAddOnsSpecifier(
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getBundleId(),
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getExternalKey(),
+                                entitlementSpecifiers,
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getEntitlementEffectiveDate(),
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getBillingEffectiveDate(),
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().isMigrated()
+                        );
+                final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiersList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+                baseEntitlementWithAddOnsSpecifiersList.add(baseEntitlementWithAddOnsSpecifier);
+
                 return new PriorEntitlementResult() {
                     @Override
                     public boolean isAborted() {
                         return false;
                     }
                     @Override
-                    public LocalDate getAdjustedEntitlementEffectiveDate() {
-                        return null;
-                    }
-                    @Override
-                    public LocalDate getAdjustedBillingEffectiveDate() {
-                        return null;
-                    }
-                    @Override
                     public BillingActionPolicy getAdjustedBillingActionPolicy() {
                         return null;
                     }
                     @Override
-                    public List<EntitlementSpecifier> getAdjustedEntitlementSpecifiers() {
-                        return entitlementSpecifiers;
+                    public List<BaseEntitlementWithAddOnsSpecifier> getAdjustedBaseEntitlementWithAddOnsSpecifiers() {
+                        return baseEntitlementWithAddOnsSpecifiersList;
                     }
                     @Override
                     public Iterable<PluginProperty> getAdjustedPluginProperties() {
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultBaseEntitlementWithAddOnsSpecifier.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultBaseEntitlementWithAddOnsSpecifier.java
new file mode 100644
index 0000000..265d0b9
--- /dev/null
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultBaseEntitlementWithAddOnsSpecifier.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.entitlement.api;
+
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+
+public class DefaultBaseEntitlementWithAddOnsSpecifier implements BaseEntitlementWithAddOnsSpecifier {
+
+    private final UUID bundleId;
+    private final String externalKey;
+    private final Iterable<EntitlementSpecifier> entitlementSpecifier;
+    private final LocalDate entitlementEffectiveDate;
+    private final LocalDate billingEffectiveDate;
+    private final boolean isMigrated;
+
+    public DefaultBaseEntitlementWithAddOnsSpecifier(final UUID bundleId,
+                                                     final String externalKey,
+                                                     final Iterable<EntitlementSpecifier> entitlementSpecifier,
+                                                     final LocalDate entitlementEffectiveDate,
+                                                     final LocalDate billingEffectiveDate,
+                                                     final boolean isMigrated) {
+        this.bundleId = bundleId;
+        this.externalKey = externalKey;
+        this.entitlementSpecifier = entitlementSpecifier;
+        this.entitlementEffectiveDate = entitlementEffectiveDate;
+        this.billingEffectiveDate = billingEffectiveDate;
+        this.isMigrated = isMigrated;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    @Override
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    @Override
+    public Iterable<EntitlementSpecifier> getEntitlementSpecifier() {
+        return entitlementSpecifier;
+    }
+
+    @Override
+    public LocalDate getEntitlementEffectiveDate() {
+        return entitlementEffectiveDate;
+    }
+
+    @Override
+    public LocalDate getBillingEffectiveDate() {
+        return billingEffectiveDate;
+    }
+
+    @Override
+    public boolean isMigrated() {
+        return isMigrated;
+    }
+}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
index 1f84737..2cf1acc 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
@@ -34,7 +34,6 @@ 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.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
@@ -322,14 +321,19 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         }
 
         final LocalDate billingEffectiveDate = overrideBillingEffectiveDate ? entitlementEffectiveDate : null;
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                getBundleId(),
+                getExternalKey(),
+                null,
+                entitlementEffectiveDate,
+                billingEffectiveDate,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CANCEL_SUBSCRIPTION,
                                                                                getAccountId(),
                                                                                null,
-                                                                               getBundleId(),
-                                                                               getExternalKey(),
-                                                                               null,
-                                                                               entitlementEffectiveDate,
-                                                                               billingEffectiveDate,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                properties,
                                                                                callContext);
@@ -382,14 +386,19 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         // Get the latest state from disk
         refresh(callContext);
 
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                getBundleId(),
+                getExternalKey(),
+                null,
+                null,
+                null,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UNCANCEL_SUBSCRIPTION,
                                                                                getAccountId(),
                                                                                null,
-                                                                               getBundleId(),
-                                                                               getExternalKey(),
-                                                                               null,
-                                                                               null,
-                                                                               null,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                properties,
                                                                                callContext);
@@ -460,14 +469,19 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         // Get the latest state from disk
         refresh(callContext);
 
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                getBundleId(),
+                getExternalKey(),
+                null,
+                entitlementEffectiveDate,
+                entitlementEffectiveDate,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CANCEL_SUBSCRIPTION,
                                                                                getAccountId(),
                                                                                null,
-                                                                               getBundleId(),
-                                                                               getExternalKey(),
-                                                                               null,
-                                                                               entitlementEffectiveDate,
-                                                                               null,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                billingPolicy,
                                                                                properties,
                                                                                callContext);
@@ -537,14 +551,19 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         // Get the latest state from disk
         refresh(callContext);
 
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                getBundleId(),
+                getExternalKey(),
+                null,
+                null,
+                null,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CHANGE_PLAN,
                                                                                getAccountId(),
                                                                                null,
-                                                                               getBundleId(),
-                                                                               getExternalKey(),
-                                                                               null,
-                                                                               null,
-                                                                               null,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                properties,
                                                                                callContext);
@@ -603,14 +622,19 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         // Get the latest state from disk
         refresh(callContext);
 
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                getBundleId(),
+                getExternalKey(),
+                null,
+                effectiveDate,
+                effectiveDate,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CHANGE_PLAN,
                                                                                getAccountId(),
                                                                                null,
-                                                                               getBundleId(),
-                                                                               getExternalKey(),
-                                                                               null,
-                                                                               effectiveDate,
-                                                                               effectiveDate,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                properties,
                                                                                callContext);
@@ -623,7 +647,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
                 }
 
                 final InternalCallContext context = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
-                final DateTime effectiveChangeDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBillingEffectiveDate(), context);
+                final DateTime effectiveChangeDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getBillingEffectiveDate(), context);
 
                 final DateTime resultingEffectiveDate;
                 try {
@@ -671,14 +695,19 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         // Get the latest state from disk
         refresh(callContext);
 
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                getBundleId(),
+                getExternalKey(),
+                null,
+                entitlementEffectiveDate,
+                null,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CHANGE_PLAN,
                                                                                getAccountId(),
                                                                                null,
-                                                                               getBundleId(),
-                                                                               getExternalKey(),
-                                                                               null,
-                                                                               entitlementEffectiveDate,
-                                                                               null,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                actionPolicy,
                                                                                properties,
                                                                                callContext);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
index f26c167..bd890dc 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
@@ -20,6 +20,7 @@ package org.killbill.billing.entitlement.api;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -37,7 +38,6 @@ import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.AccountEventsStreams;
 import org.killbill.billing.entitlement.EntitlementService;
 import org.killbill.billing.entitlement.EventsStream;
@@ -47,7 +47,6 @@ import org.killbill.billing.entitlement.block.BlockingChecker;
 import org.killbill.billing.entitlement.dao.BlockingStateDao;
 import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
 import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
-import org.killbill.billing.entitlement.logging.EntitlementLoggingHelper;
 import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
 import org.killbill.billing.entitlement.plugin.api.OperationType;
 import org.killbill.billing.junction.DefaultBlockingState;
@@ -55,6 +54,7 @@ import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.security.api.SecurityApi;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
+import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
 import org.killbill.billing.subscription.api.transfer.SubscriptionBaseTransferApi;
 import org.killbill.billing.subscription.api.transfer.SubscriptionBaseTransferApiException;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
@@ -73,7 +73,7 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
 import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCreateEntitlement;
-import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCreateEntitlementWithAOs;
+import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCreateEntitlementsWithAOs;
 import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logPauseResumeEntitlement;
 import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logTransferEntitlement;
 
@@ -133,14 +133,21 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
         final EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(planPhaseSpecifier, overrides);
         final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
         entitlementSpecifierList.add(entitlementSpecifier);
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                null,
+                externalKey,
+                entitlementSpecifierList,
+                entitlementEffectiveDate,
+                billingEffectiveDate,
+                isMigrated);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
+
+
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
                                                                                accountId,
                                                                                null,
-                                                                               null,
-                                                                               externalKey,
-                                                                               entitlementSpecifierList,
-                                                                               entitlementEffectiveDate,
-                                                                               billingEffectiveDate,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                properties,
                                                                                callContext);
@@ -156,11 +163,11 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
 
                     final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, contextWithValidAccountRecordId);
 
-                    final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBillingEffectiveDate(), contextWithValidAccountRecordId);
-                    final EntitlementSpecifier specifier = getFirstEntitlementSpecifier(updatedPluginContext.getEntitlementSpecifiers());
+                    final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getBillingEffectiveDate(), contextWithValidAccountRecordId);
+                    final EntitlementSpecifier specifier = getFirstEntitlementSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
                     final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundle.getId(), specifier.getPlanPhaseSpecifier(), specifier.getOverrides(), billingRequestedDate, isMigrated, contextWithValidAccountRecordId);
 
-                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEntitlementEffectiveDate(), contextWithValidAccountRecordId);
+                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getEntitlementEffectiveDate(), contextWithValidAccountRecordId);
                     final BlockingState newBlockingState = new DefaultBlockingState(subscription.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
                     entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(newBlockingState), subscription.getBundleId(), contextWithValidAccountRecordId);
 
@@ -175,73 +182,69 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
         return pluginExecution.executeWithPlugin(createBaseEntitlementWithPlugin, pluginContext);
     }
 
-    private EntitlementSpecifier getFirstEntitlementSpecifier(final List<EntitlementSpecifier> entitlementSpecifiers) throws SubscriptionBaseApiException {
-        if ((entitlementSpecifiers == null) || entitlementSpecifiers.isEmpty()) {
-            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+    private EntitlementSpecifier getFirstEntitlementSpecifier(final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers) throws SubscriptionBaseApiException {
+        if ((baseEntitlementWithAddOnsSpecifiers == null) || baseEntitlementWithAddOnsSpecifiers.iterator().hasNext()) {
+            if ((baseEntitlementWithAddOnsSpecifiers.iterator().next().getEntitlementSpecifier() == null) || !baseEntitlementWithAddOnsSpecifiers.iterator().next().getEntitlementSpecifier().iterator().hasNext()) {
+                throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+            }
         }
-        return entitlementSpecifiers.get(0);
+        return baseEntitlementWithAddOnsSpecifiers.iterator().next().getEntitlementSpecifier().iterator().next();
     }
 
     @Override
-    public Entitlement createBaseEntitlementWithAddOns(final UUID accountId, final String externalKey, final Iterable<EntitlementSpecifier> entitlementSpecifiers,
-                                                       @Nullable LocalDate entitlementEffectiveDate,  @Nullable LocalDate billingEffectiveDate, final boolean isMigrated,
-                                                       final Iterable<PluginProperty> properties, final CallContext callContext)
-            throws EntitlementApiException {
+    public List<Entitlement> createBaseEntitlementsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
 
-        logCreateEntitlementWithAOs(log, externalKey, entitlementSpecifiers, entitlementEffectiveDate, billingEffectiveDate);
+        logCreateEntitlementsWithAOs(log, baseEntitlementWithAddOnsSpecifiers);
 
-
-        final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
-        Iterables.addAll(entitlementSpecifierList, entitlementSpecifiers);
-
-        final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTIONS_WITH_AO,
+        final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SHOPPING_CART_SUBSCRIPTIONS,
                                                                                accountId,
                                                                                null,
-                                                                               null,
-                                                                               externalKey,
-                                                                               entitlementSpecifierList,
-                                                                               entitlementEffectiveDate,
-                                                                               billingEffectiveDate,
+                                                                               baseEntitlementWithAddOnsSpecifiers,
                                                                                null,
                                                                                properties,
                                                                                callContext);
 
-        final WithEntitlementPlugin<Entitlement> createBaseEntitlementWithAddOn = new WithEntitlementPlugin<Entitlement>() {
+        final WithEntitlementPlugin<List<Entitlement>> createBaseEntitlementsWithAddOns = new WithEntitlementPlugin<List<Entitlement>>() {
             @Override
-            public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+            public List<Entitlement> doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
                 final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
 
                 try {
-                    if (entitlementUtils.getFirstActiveSubscriptionIdForKeyOrNull(externalKey, contextWithValidAccountRecordId) != null) {
-                        throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, externalKey));
-                    }
-
-                    final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, contextWithValidAccountRecordId);
-
-                    final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBillingEffectiveDate(), contextWithValidAccountRecordId);
-                    final List<SubscriptionBase> subscriptionBases = subscriptionBaseInternalApi.createBaseSubscriptionWithAddOns(bundle.getId(), entitlementSpecifiers, billingRequestedDate, isMigrated, contextWithValidAccountRecordId);
-
-                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEntitlementEffectiveDate(), contextWithValidAccountRecordId);
-                    final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
-                    for (final SubscriptionBase cur : subscriptionBases) {
-                        final BlockingState blockingState = new DefaultBlockingState(cur.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
-                        blockingStates.add(blockingState);
+                    final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(accountId, baseEntitlementWithAddOnsSpecifiers, contextWithValidAccountRecordId);
+                    Map<BlockingState, UUID> blockingStateMap = new HashMap<BlockingState, UUID>();
+                    int i = 0;
+                    for (Iterator<BaseEntitlementWithAddOnsSpecifier> it = baseEntitlementWithAddOnsSpecifiers.iterator(); it.hasNext(); i++) {
+                        BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = it.next();
+                        for (SubscriptionBase subscriptionBase : subscriptionsWithAddOns.get(i).getSubscriptionBaseList()) {
+                            final BlockingState blockingState = new DefaultBlockingState(subscriptionBase.getId(), BlockingStateType.SUBSCRIPTION,
+                                                                                         DefaultEntitlementApi.ENT_STATE_START,
+                                                                                         EntitlementService.ENTITLEMENT_SERVICE_NAME,
+                                                                                         false, false, false,
+                                                                                         dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), contextWithValidAccountRecordId));
+                            blockingStateMap.put(blockingState, subscriptionsWithAddOns.get(i).getBundleId());
+                        }
                     }
-                    final SubscriptionBase subscriptionBaseBP = subscriptionBases.get(0);
-                    entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(blockingStates, subscriptionBaseBP.getBundleId(), contextWithValidAccountRecordId);
-
-
-                    return new DefaultEntitlement(accountId, subscriptionBaseBP.getId(), eventsStreamBuilder, entitlementApi, pluginExecution,
-                                                  blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
-                                                  entitlementUtils, dateHelper, clock, securityApi, internalCallContextFactory, callContext);
-
+                    entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(blockingStateMap, contextWithValidAccountRecordId);
+                    return buildEntitlementList(accountId, subscriptionsWithAddOns, callContext);
                 } catch (final SubscriptionBaseApiException e) {
                     throw new EntitlementApiException(e);
                 }
-
             }
         };
-        return pluginExecution.executeWithPlugin(createBaseEntitlementWithAddOn, pluginContext);
+        return pluginExecution.executeWithPlugin(createBaseEntitlementsWithAddOns, pluginContext);
+    }
+
+    private List<Entitlement> buildEntitlementList(final UUID accountId, final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns, final CallContext callContext) throws EntitlementApiException {
+        List<Entitlement> result = new ArrayList<Entitlement>();
+        for (SubscriptionBaseWithAddOns subscriptionWithAddOns : subscriptionsWithAddOns) {
+            for (SubscriptionBase subscriptionBase : subscriptionWithAddOns.getSubscriptionBaseList()) {
+                Entitlement entitlement = new DefaultEntitlement(accountId, subscriptionBase.getId(), eventsStreamBuilder, entitlementApi, pluginExecution,
+                                                                 blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
+                                                                 entitlementUtils, dateHelper, clock, securityApi, internalCallContextFactory, callContext);
+                result.add(entitlement);
+            }
+        }
+        return result;
     }
 
     @Override
@@ -254,14 +257,20 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
         final EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(planPhaseSpecifier, overrides);
         final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
         entitlementSpecifierList.add(entitlementSpecifier);
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                bundleId,
+                null,
+                entitlementSpecifierList,
+                entitlementEffectiveDate,
+                billingEffectiveDate,
+                isMigrated);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
+
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
                                                                                null,
                                                                                null,
-                                                                               bundleId,
-                                                                               null,
-                                                                               entitlementSpecifierList,
-                                                                               entitlementEffectiveDate,
-                                                                               billingEffectiveDate,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                properties,
                                                                                callContext);
@@ -281,13 +290,13 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
                     throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
                 }
 
-                final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBillingEffectiveDate(), eventsStreamForBaseSubscription.getInternalTenantContext());
+                final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getBillingEffectiveDate(), eventsStreamForBaseSubscription.getInternalTenantContext());
                 try {
                     final InternalCallContext context = internalCallContextFactory.createInternalCallContext(eventsStreamForBaseSubscription.getAccountId(), callContext);
-                    final EntitlementSpecifier specifier = getFirstEntitlementSpecifier(updatedPluginContext.getEntitlementSpecifiers());
+                    final EntitlementSpecifier specifier = getFirstEntitlementSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
                     final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundleId, specifier.getPlanPhaseSpecifier(), specifier.getOverrides(), billingRequestedDate, isMigrated, context);
 
-                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEntitlementEffectiveDate(), eventsStreamForBaseSubscription.getInternalTenantContext());
+                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getEntitlementEffectiveDate(), eventsStreamForBaseSubscription.getInternalTenantContext());
                     final BlockingState newBlockingState = new DefaultBlockingState(subscription.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
                     entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(newBlockingState), subscription.getBundleId(), context);
 
@@ -402,15 +411,21 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
 
         logTransferEntitlement(log, sourceAccountId, destAccountId, externalKey, effectiveDate, billingPolicy);
 
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                null,
+                externalKey,
+                new ArrayList<EntitlementSpecifier>(),
+                effectiveDate,
+                effectiveDate,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
+
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.TRANSFER_BUNDLE,
                                                                                sourceAccountId,
                                                                                destAccountId,
-                                                                               null,
-                                                                               externalKey,
-                                                                               new ArrayList<EntitlementSpecifier>(),
-                                                                               effectiveDate,
-                                                                               effectiveDate,
-                                                                               null,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
+                                                                               billingPolicy,
                                                                                properties,
                                                                                context);
 
@@ -442,7 +457,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
                         throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_KEY, externalKey));
                     }
 
-                    final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBillingEffectiveDate(), contextWithSourceAccountRecordId);
+                    final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getBillingEffectiveDate(), contextWithSourceAccountRecordId);
                     final SubscriptionBaseBundle newBundle = subscriptionBaseTransferApi.transferBundle(sourceAccountId, destAccountId, externalKey, requestedDate, true, cancelImm, context);
 
 
@@ -460,7 +475,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
                     final InternalCallContext contextWithDestAccountRecordId = internalCallContextFactory.createInternalCallContext(destAccountId, context);
 
                     blockingStates.clear();
-                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEntitlementEffectiveDate(), contextWithDestAccountRecordId);
+                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getEntitlementEffectiveDate(), contextWithDestAccountRecordId);
                     for (final SubscriptionBase subscriptionBase : subscriptionBaseInternalApi.getSubscriptionsForBundle(newBundle.getId(), null, contextWithDestAccountRecordId)) {
                         final BlockingState newBlockingState = new DefaultBlockingState(subscriptionBase.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
                         blockingStates.put(newBlockingState, subscriptionBase.getBundleId());
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
index d5ec969..c868eff 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
@@ -23,7 +23,6 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
-import org.joda.time.LocalDate;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
 import org.killbill.billing.entitlement.plugin.api.OperationType;
@@ -39,11 +38,7 @@ public class DefaultEntitlementContext implements EntitlementContext {
     private final OperationType operationType;
     private final UUID accountId;
     private final UUID destinationAccountId;
-    private final UUID bundleId;
-    private final String externalKey;
-    private final List<EntitlementSpecifier> entitlementSpecifiers;
-    private final LocalDate entitlementEffectiveDate;
-    private final LocalDate billingEffectiveDate;
+    private final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers;
     private final BillingActionPolicy billingActionPolicy;
     private final Iterable<PluginProperty> pluginProperties;
     private final UUID userToken;
@@ -62,11 +57,7 @@ public class DefaultEntitlementContext implements EntitlementContext {
         this(prev.getOperationType(),
              prev.getAccountId(),
              prev.getDestinationAccountId(),
-             prev.getBundleId(),
-             prev.getExternalKey(),
-             pluginResult != null && pluginResult.getAdjustedEntitlementSpecifiers() != null ? pluginResult.getAdjustedEntitlementSpecifiers() : prev.getEntitlementSpecifiers(),
-             pluginResult != null && pluginResult.getAdjustedEntitlementEffectiveDate() != null ? pluginResult.getAdjustedEntitlementEffectiveDate() : prev.getEntitlementEffectiveDate(),
-             pluginResult != null && pluginResult.getAdjustedBillingEffectiveDate() != null ? pluginResult.getAdjustedBillingEffectiveDate() : prev.getBillingEffectiveDate(),
+             pluginResult != null && pluginResult.getAdjustedBaseEntitlementWithAddOnsSpecifiers() != null ? pluginResult.getAdjustedBaseEntitlementWithAddOnsSpecifiers() : prev.getBaseEntitlementWithAddOnsSpecifiers(),
              pluginResult != null && pluginResult.getAdjustedBillingActionPolicy() != null ? pluginResult.getAdjustedBillingActionPolicy() : prev.getBillingActionPolicy(),
              pluginResult != null && pluginResult.getAdjustedPluginProperties() != null ? pluginResult.getAdjustedPluginProperties() : prev.getPluginProperties(),
              prev);
@@ -75,15 +66,11 @@ public class DefaultEntitlementContext implements EntitlementContext {
     public DefaultEntitlementContext(final OperationType operationType,
                                      final UUID accountId,
                                      final UUID destinationAccountId,
-                                     final UUID bundleId,
-                                     final String externalKey,
-                                     final List<EntitlementSpecifier> entitlementSpecifiers,
-                                     @Nullable final LocalDate entitlementEffectiveDate,
-                                     @Nullable final LocalDate billingEffectiveDate,
+                                     final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers,
                                      @Nullable final BillingActionPolicy actionPolicy,
                                      final Iterable<PluginProperty> pluginProperties,
                                      final CallContext callContext) {
-        this(operationType, accountId, destinationAccountId, bundleId, externalKey, entitlementSpecifiers, entitlementEffectiveDate, billingEffectiveDate, actionPolicy, pluginProperties,
+        this(operationType, accountId, destinationAccountId, baseEntitlementWithAddOnsSpecifiers, actionPolicy, pluginProperties,
              callContext.getUserToken(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(),
              callContext.getComments(), callContext.getCreatedDate(), callContext.getUpdatedDate(), callContext.getTenantId());
     }
@@ -92,11 +79,7 @@ public class DefaultEntitlementContext implements EntitlementContext {
     public DefaultEntitlementContext(final OperationType operationType,
                                      final UUID accountId,
                                      final UUID destinationAccountId,
-                                     final UUID bundleId,
-                                     final String externalKey,
-                                     final List<EntitlementSpecifier> entitlementSpecifiers,
-                                     @Nullable final LocalDate entitlementEffectiveDate,
-                                     @Nullable final LocalDate billingEffectiveDate,
+                                     final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers,
                                      @Nullable final BillingActionPolicy actionPolicy,
                                      final Iterable<PluginProperty> pluginProperties,
                                      final UUID userToken,
@@ -111,11 +94,7 @@ public class DefaultEntitlementContext implements EntitlementContext {
         this.operationType = operationType;
         this.accountId = accountId;
         this.destinationAccountId = destinationAccountId;
-        this.bundleId = bundleId;
-        this.externalKey = externalKey;
-        this.entitlementSpecifiers = entitlementSpecifiers;
-        this.entitlementEffectiveDate = entitlementEffectiveDate;
-        this.billingEffectiveDate = billingEffectiveDate;
+        this.baseEntitlementWithAddOnsSpecifiers = baseEntitlementWithAddOnsSpecifiers;
         this.billingActionPolicy = actionPolicy;
         this.pluginProperties = pluginProperties;
         this.userToken = userToken;
@@ -145,29 +124,8 @@ public class DefaultEntitlementContext implements EntitlementContext {
     }
 
     @Override
-    public UUID getBundleId() {
-        return bundleId;
-    }
-
-    @Override
-    public String getExternalKey() {
-        return externalKey;
-    }
-
-    @Override
-    public List<EntitlementSpecifier> getEntitlementSpecifiers() {
-        return entitlementSpecifiers;
-    }
-
-
-    @Override
-    public LocalDate getEntitlementEffectiveDate() {
-        return entitlementEffectiveDate;
-    }
-
-    @Override
-    public LocalDate getBillingEffectiveDate() {
-        return billingEffectiveDate;
+    public Iterable<BaseEntitlementWithAddOnsSpecifier> getBaseEntitlementWithAddOnsSpecifiers() {
+        return baseEntitlementWithAddOnsSpecifiers;
     }
 
     @Override
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
index a6fbfb9..1ea62df 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
@@ -279,15 +279,21 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
             throw new EntitlementApiException(e);
         }
 
+
         final LocalDate effectiveDate = new LocalDate(clock.getUTCNow(), account.getTimeZone());
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                bundleId,
+                newExternalKey,
+                new ArrayList<EntitlementSpecifier>(),
+                effectiveDate,
+                effectiveDate,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UPDATE_BUNDLE_EXTERNAL_KEY,
                                                                                bundle.getAccountId(),
                                                                                null,
-                                                                               bundleId,
-                                                                               newExternalKey,
-                                                                               new ArrayList<EntitlementSpecifier>(),
-                                                                               effectiveDate,
-                                                                               effectiveDate,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                ImmutableList.<PluginProperty>of(),
                                                                                callContext);
@@ -365,14 +371,19 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
         final DateTime effectiveDate = inputEffectiveDate == null ? clock.getUTCNow() : internalCallContextWithValidAccountId.toUTCDateTime(inputEffectiveDate);
         final DefaultBlockingState blockingState = new DefaultBlockingState(inputBlockingState, effectiveDate);
 
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                bundleId,
+                externalKey,
+                new ArrayList<EntitlementSpecifier>(),
+                internalCallContextWithValidAccountId.toLocalDate(effectiveDate),
+                internalCallContextWithValidAccountId.toLocalDate(effectiveDate),
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.ADD_BLOCKING_STATE,
                                                                                accountId,
                                                                                null,
-                                                                               bundleId,
-                                                                               externalKey,
-                                                                               new ArrayList<EntitlementSpecifier>(),
-                                                                               internalCallContextWithValidAccountId.toLocalDate(effectiveDate),
-                                                                               null,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                properties,
                                                                                callContext);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
index 3044aca..f1c8e57 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
@@ -17,9 +17,11 @@
 
 package org.killbill.billing.entitlement.api.svcs;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
@@ -34,8 +36,10 @@ import org.killbill.billing.entitlement.AccountEntitlements;
 import org.killbill.billing.entitlement.AccountEventsStreams;
 import org.killbill.billing.entitlement.EntitlementService;
 import org.killbill.billing.entitlement.EventsStream;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.DefaultBaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.entitlement.api.DefaultEntitlementApi;
 import org.killbill.billing.entitlement.api.DefaultEntitlementContext;
@@ -138,15 +142,20 @@ public class DefaultEntitlementApiBase {
 
     public void pause(final UUID bundleId, @Nullable final LocalDate localEffectiveDate, final Iterable<PluginProperty> properties, final InternalCallContext internalCallContext) throws EntitlementApiException {
 
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                bundleId,
+                null,
+                null,
+                localEffectiveDate,
+                localEffectiveDate,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.PAUSE_BUNDLE,
                                                                                null,
                                                                                null,
-                                                                               bundleId,
                                                                                null,
                                                                                null,
-                                                                               localEffectiveDate,
-                                                                               localEffectiveDate,
-                                                                               null,
                                                                                properties,
                                                                                internalCallContextFactory.createCallContext(internalCallContext));
 
@@ -167,14 +176,19 @@ public class DefaultEntitlementApiBase {
 
     public void resume(final UUID bundleId, @Nullable final LocalDate localEffectiveDate, final Iterable<PluginProperty> properties, final InternalCallContext internalCallContext) throws EntitlementApiException {
 
+        final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                bundleId,
+                null,
+                null,
+                localEffectiveDate,
+                localEffectiveDate,
+                false);
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.RESUME_BUNDLE,
                                                                                null,
                                                                                null,
-                                                                               bundleId,
-                                                                               null,
-                                                                               null,
-                                                                               localEffectiveDate,
-                                                                               localEffectiveDate,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                properties,
                                                                                internalCallContextFactory.createCallContext(internalCallContext));
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
index 3fc96da..2f7ad68 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
@@ -40,8 +40,10 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.entitlement.DefaultEntitlementService;
 import org.killbill.billing.entitlement.EntitlementInternalApi;
 import org.killbill.billing.entitlement.EntitlementService;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.DefaultBaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.entitlement.api.DefaultEntitlementApi;
 import org.killbill.billing.entitlement.api.DefaultEntitlementContext;
@@ -107,14 +109,20 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
                 continue;
             }
 
+            final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+                    entitlement.getBundleId(),
+                    entitlement.getExternalKey(),
+                    null,
+                    effectiveDate,
+                    null,
+                    false);
+            final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+            baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
+
             final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CANCEL_SUBSCRIPTION,
                                                                                    entitlement.getAccountId(),
                                                                                    null,
-                                                                                   entitlement.getBundleId(),
-                                                                                   entitlement.getExternalKey(),
-                                                                                   null,
-                                                                                   effectiveDate,
-                                                                                   null,
+                                                                                   baseEntitlementWithAddOnsSpecifierList,
                                                                                    billingPolicy,
                                                                                    properties,
                                                                                    callContext);
@@ -219,7 +227,7 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
 
         @Override
         public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
-            DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEntitlementEffectiveDate(), internalCallContext);
+            DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getEntitlementEffectiveDate(), internalCallContext);
             // Avoid timing issues for IMM cancellations (we don't want an entitlement cancel date one second or so after the subscription cancel date or
             // add-ons cancellations computations won't work).
             if (effectiveDate.compareTo(entitlement.getSubscriptionBase().getEndDate()) > 0) {
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java b/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
index 7e7f2e2..8f47820 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
@@ -22,10 +22,10 @@ import java.util.UUID;
 
 import org.joda.time.LocalDate;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
-import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.Entitlement;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
@@ -69,6 +69,22 @@ public abstract class EntitlementLoggingHelper {
         }
     }
 
+    public static void logCreateEntitlementsWithAOs(final Logger log, final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementSpecifiersWithAddOns) {
+        if (log.isInfoEnabled()) {
+            final StringBuilder logLine = new StringBuilder("Create Entitlements with AddOns: ");
+
+            if (baseEntitlementSpecifiersWithAddOns != null && baseEntitlementSpecifiersWithAddOns.iterator().hasNext()) {
+                for (BaseEntitlementWithAddOnsSpecifier cur : baseEntitlementSpecifiersWithAddOns) {
+                    logCreateEntitlementWithAOs(log, cur.getExternalKey(),
+                                                cur.getEntitlementSpecifier(),
+                                                cur.getEntitlementEffectiveDate(),
+                                                cur.getBillingEffectiveDate());
+                }
+            }
+            log.info(logLine.toString());
+        }
+    }
+
     public static void logCreateEntitlementWithAOs(final Logger log,
                                                    final String externalKey,
                                                    final Iterable<EntitlementSpecifier> entitlementSpecifiers,
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
index fe59e7d..979dde2 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -500,104 +500,6 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
     }
 
     @Test(groups = "slow")
-    public void testCreateBaseEntitlementWithAddOns() throws AccountApiException, EntitlementApiException, SubscriptionBaseApiException {
-        final LocalDate initialDate = new LocalDate(2013, 8, 7);
-        clock.setDay(initialDate);
-
-        final Account account = createAccount(getAccountData(7));
-
-        final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Cleaning", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-
-        final String externalKey = "baseExternalKey";
-        EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
-        EntitlementSpecifier addOnEntitlementSpecifier = new DefaultEntitlementSpecifier(addOnSpec, null);
-
-        final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
-        specifierList.add(baseEntitlementSpecifier);
-        specifierList.add(addOnEntitlementSpecifier);
-
-        testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, initialDate, false, ImmutableList.<PluginProperty>of(), callContext);
-        assertListenerStatus();
-
-        assertNotNull(entitlement);
-
-        final List<Entitlement> allEntitlementsForBundle = entitlementApi.getAllEntitlementsForBundle(entitlement.getBundleId(), callContext);
-        assertTrue(allEntitlementsForBundle.size() == 2);
-
-        final Entitlement baseEntitlement = allEntitlementsForBundle.get(0);
-        final Entitlement addOnEntitlement = allEntitlementsForBundle.get(1);
-
-        assertEquals(baseEntitlement.getLastActiveProduct().getName(), "Pistol");
-        assertEquals(baseEntitlement.getLastActiveProductCategory(), ProductCategory.BASE);
-
-        assertEquals(addOnEntitlement.getLastActiveProduct().getName(), "Cleaning");
-        assertEquals(addOnEntitlement.getLastActiveProductCategory(), ProductCategory.ADD_ON);
-
-    }
-
-    @Test(groups = "slow")
-    public void testCreateBaseEntitlementWithInvalidAddOn() throws AccountApiException, EntitlementApiException {
-        final LocalDate initialDate = new LocalDate(2013, 8, 7);
-        clock.setDay(initialDate);
-
-        final Account account = createAccount(getAccountData(7));
-
-        final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Invalid",  BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-
-        final String externalKey = "baseExternalKey";
-        EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
-        EntitlementSpecifier addOnEntitlementSpecifier = new DefaultEntitlementSpecifier(addOnSpec, null);
-
-        final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
-        specifierList.add(baseEntitlementSpecifier);
-        specifierList.add(addOnEntitlementSpecifier);
-
-        try {
-            entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, initialDate, false, ImmutableList.<PluginProperty>of(), callContext);
-            fail();
-        } catch (EntitlementApiException e) {
-            assertEquals(e.getMessage(), "Could not find any product named 'Invalid'");
-        }
-
-        final List<Entitlement> allEntitlementsForAccount = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
-        assertTrue(allEntitlementsForAccount.size() == 0);
-
-    }
-
-    @Test(groups = "slow")
-    public void testCreateBaseEntitlementWithoutBaseEntitlement() throws AccountApiException, EntitlementApiException {
-        final LocalDate initialDate = new LocalDate(2013, 8, 7);
-        clock.setDay(initialDate);
-
-        final Account account = createAccount(getAccountData(7));
-
-        final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Cleaning",  BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Bullets",  BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-
-        final String externalKey = "addOnExternalKey";
-        EntitlementSpecifier addOnEntitlementSpecifier1 = new DefaultEntitlementSpecifier(baseSpec, null);
-        EntitlementSpecifier addOnEntitlementSpecifier2 = new DefaultEntitlementSpecifier(addOnSpec, null);
-
-        final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
-        specifierList.add(addOnEntitlementSpecifier1);
-        specifierList.add(addOnEntitlementSpecifier2);
-
-        try {
-            entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, initialDate, false, ImmutableList.<PluginProperty>of(), callContext);
-            fail();
-        } catch (EntitlementApiException e) {
-            assertEquals(e.getMessage(), "Missing Base Subscription.");
-        }
-
-        final List<Entitlement> allEntitlementsForAccount = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
-        assertTrue(allEntitlementsForAccount.size() == 0);
-    }
-
-
-    @Test(groups = "slow")
     public void testCreateBaseWithDifferentInTheFuture() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
         final LocalDate initialDate = new LocalDate(2013, 8, 7);
         clock.setDay(initialDate);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BulkBaseSubscriptionAndAddOnsJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BulkBaseSubscriptionAndAddOnsJson.java
new file mode 100644
index 0000000..b5367d1
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BulkBaseSubscriptionAndAddOnsJson.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
+
+public class BulkBaseSubscriptionAndAddOnsJson {
+
+    @ApiModelProperty(required = true)
+    private final List<SubscriptionJson> baseEntitlementAndAddOns;
+
+    @JsonCreator
+    public BulkBaseSubscriptionAndAddOnsJson(
+            @JsonProperty("baseEntitlementAndAddOns") @Nullable final List<SubscriptionJson> baseEntitlementAndAddOns) {
+        this.baseEntitlementAndAddOns = baseEntitlementAndAddOns;
+    }
+
+    public List<SubscriptionJson> getBaseEntitlementAndAddOns() {
+        return baseEntitlementAndAddOns;
+    }
+
+    @Override
+    public String toString() {
+        return "BulkBaseSubscriptionAndAddOnsJson{" +
+               "baseEntitlementAndAddOns=" + baseEntitlementAndAddOns +
+               '}';
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BulkBaseSubscriptionAndAddOnsJson that = (BulkBaseSubscriptionAndAddOnsJson) o;
+
+        return baseEntitlementAndAddOns != null ? baseEntitlementAndAddOns.equals(that.baseEntitlementAndAddOns) : that.baseEntitlementAndAddOns == null;
+
+    }
+
+    @Override
+    public int hashCode() {
+        return baseEntitlementAndAddOns != null ? baseEntitlementAndAddOns.hashCode() : 0;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index 265a1fa..fe76cbf 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -21,6 +21,7 @@ package org.killbill.billing.jaxrs.resources;
 import java.math.BigDecimal;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -265,17 +266,21 @@ public class AccountResource extends JaxRsResourceBase {
                            @ApiResponse(code = 404, message = "Account not found")})
     public Response getAccountBundles(@PathParam("accountId") final String accountId,
                                       @QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
+                                      @QueryParam(QUERY_BUNDLES_FILTER) final String bundlesFilter,
                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, SubscriptionApiException {
         final TenantContext tenantContext = context.createContext(request);
 
         final UUID uuid = UUID.fromString(accountId);
-       final Account account = accountUserApi.getAccountById(uuid, tenantContext);
+        final Account account = accountUserApi.getAccountById(uuid, tenantContext);
 
         final List<SubscriptionBundle> bundles = (externalKey != null) ?
                                                  subscriptionApi.getSubscriptionBundlesForAccountIdAndExternalKey(uuid, externalKey, tenantContext) :
                                                  subscriptionApi.getSubscriptionBundlesForAccountId(uuid, tenantContext);
 
-        final Collection<BundleJson> result = Collections2.transform(bundles, new Function<SubscriptionBundle, BundleJson>() {
+        boolean filter = (null != bundlesFilter && !bundlesFilter.isEmpty());
+
+        final Collection<BundleJson> result = Collections2.transform(
+                (filter) ? filterBundles(bundles, Arrays.asList(bundlesFilter.split(","))) : bundles, new Function<SubscriptionBundle, BundleJson>() {
             @Override
             public BundleJson apply(final SubscriptionBundle input) {
                 try {
@@ -289,6 +294,16 @@ public class AccountResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(result).build();
     }
 
+    private List<SubscriptionBundle> filterBundles(final List<SubscriptionBundle> subscriptionBundlesForAccountId, final List<String> bundlesFilter) {
+        List<SubscriptionBundle> result = new ArrayList<SubscriptionBundle>();
+        for (SubscriptionBundle subscriptionBundle : subscriptionBundlesForAccountId) {
+            if (bundlesFilter.contains(subscriptionBundle.getId().toString())) {
+                result.add(subscriptionBundle);
+            }
+        }
+        return result;
+    }
+
     @TimedResource
     @GET
     @Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index 071fbd0..cecf4f7 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -132,6 +132,7 @@ public interface JaxrsResource {
 
     public static final String QUERY_BUNDLE_TRANSFER_ADDON = "transferAddOn";
     public static final String QUERY_BUNDLE_TRANSFER_CANCEL_IMM = "cancelImmediately";
+    public static final String QUERY_BUNDLES_FILTER = "bundlesFilter";
 
     public static final String QUERY_DELETE_DEFAULT_PM_WITH_AUTO_PAY_OFF = "deleteDefaultPmWithAutoPayOff";
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index ce30ab9..591dbfc 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -609,7 +609,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
     }
 
     private URI getPaymentLocation(final UriInfo uriInfo, final Payment payment) {
-        return uriBuilder.buildLocation(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+        return uriBuilder.buildLocation(uriInfo, PaymentResource.class, "getPayment", payment.getId(), null);
     }
 
     private PaymentTransaction findCreatedTransaction(final Payment payment, final TransactionType transactionType, @Nullable final String transactionExternalKey) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
index 8a9a799..6532449 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
@@ -20,7 +20,9 @@ package org.killbill.billing.jaxrs.resources;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.TimeoutException;
 
@@ -48,11 +50,13 @@ import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.api.Entitlement;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
@@ -72,6 +76,7 @@ import org.killbill.billing.events.PaymentErrorInternalEvent;
 import org.killbill.billing.events.PaymentInfoInternalEvent;
 import org.killbill.billing.events.PaymentPluginErrorInternalEvent;
 import org.killbill.billing.jaxrs.json.BlockingStateJson;
+import org.killbill.billing.jaxrs.json.BulkBaseSubscriptionAndAddOnsJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.PhasePriceOverrideJson;
 import org.killbill.billing.jaxrs.json.SubscriptionJson;
@@ -262,16 +267,6 @@ public class SubscriptionResource extends JaxRsResourceBase {
 
         Preconditions.checkArgument(Iterables.size(entitlements) > 0, "Subscription list mustn't be null or empty.");
 
-        for (SubscriptionJson entitlement : entitlements) {
-            verifyNonNullOrEmpty(entitlement, "SubscriptionJson body should be specified for each element");
-            if (entitlement.getPlanName() == null) {
-                verifyNonNullOrEmpty(entitlement.getProductName(), "SubscriptionJson productName needs to be set for each element",
-                                     entitlement.getProductCategory(), "SubscriptionJson productCategory needs to be set for each element",
-                                     entitlement.getBillingPeriod(), "SubscriptionJson billingPeriod needs to be set for each element",
-                                     entitlement.getPriceList(), "SubscriptionJson priceList needs to be set for each element");
-            }
-        }
-
         logDeprecationParameterWarningIfNeeded(QUERY_REQUESTED_DT, QUERY_ENTITLEMENT_REQUESTED_DT, QUERY_BILLING_REQUESTED_DT);
 
         final int baseSubscriptionsSize = Iterables.size(Iterables.filter(entitlements, new Predicate<SubscriptionJson>() {
@@ -291,6 +286,8 @@ public class SubscriptionResource extends JaxRsResourceBase {
         verifyNumberOfElements(addOnSubscriptionsSize, entitlements.size() - 1, "It should be " + (entitlements.size() - 1) + " ADD_ON products.");
 
         final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+
         final SubscriptionJson baseEntitlement = Iterables.tryFind(entitlements, new Predicate<SubscriptionJson>() {
             @Override
             public boolean apply(final SubscriptionJson subscription) {
@@ -300,60 +297,210 @@ public class SubscriptionResource extends JaxRsResourceBase {
 
         verifyNonNull(baseEntitlement.getAccountId(), "SubscriptionJson accountId needs to be set for BASE product.");
 
-        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final EntitlementCallCompletionCallback<List<Entitlement>> callback = new EntitlementCallCompletionCallback<List<Entitlement>>() {
+            @Override
+            public List<Entitlement> doOperation(final CallContext ctx) throws InterruptedException, TimeoutException, EntitlementApiException, SubscriptionApiException, AccountApiException {
 
-        final EntitlementCallCompletionCallback<Entitlement> callback = new EntitlementCallCompletionCallback<Entitlement>() {
+                final Account account = getAccountFromSubscriptionJson(baseEntitlement, callContext);
+
+                final List<EntitlementSpecifier> entitlementSpecifierList = buildEntitlementSpecifierList(entitlements, account.getCurrency());
+
+                final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(entitlementDate);
+                final LocalDate resolvedBillingDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(billingDate);
 
+                final UUID bundleId = baseEntitlement.getBundleId() != null ? UUID.fromString(baseEntitlement.getBundleId()) : null;
+
+                BaseEntitlementWithAddOnsSpecifier baseEntitlementSpecifierWithAddOns = buildBaseEntitlementWithAddOnsSpecifier(entitlementSpecifierList, resolvedEntitlementDate, resolvedBillingDate, bundleId, baseEntitlement, isMigrated);
+                final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+                baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementSpecifierWithAddOns);
+
+                return entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifierList, pluginProperties, callContext);
+            }
             @Override
-            public Entitlement doOperation(final CallContext ctx) throws InterruptedException, TimeoutException, EntitlementApiException, SubscriptionApiException, AccountApiException {
+            public boolean isImmOperation() {
+                return true;
+            }
+            @Override
+            public Response doResponseOk(final List<Entitlement> entitlements) {
+                return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", entitlements.get(0).getBundleId());
+            }
+        };
+        final EntitlementCallCompletion<List<Entitlement>> callCompletionCreation = new EntitlementCallCompletion<List<Entitlement>>();
+        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
+    }
 
-                List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
-                final Account account = getAccountFromSubscriptionJson(baseEntitlement, callContext);
+    @TimedResource
+    @POST
+    @Path("/createEntitlementsWithAddOns")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Create multiple entitlements with addOn products")
+    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid entitlements supplied")})
+    public Response createEntitlementsWithAddOns(final List<BulkBaseSubscriptionAndAddOnsJson> entitlementsWithAddOns,
+                                                @QueryParam(QUERY_REQUESTED_DT) final String requestedDate, /* This is deprecated, only used for backward compatibility */
+                                                @QueryParam(QUERY_ENTITLEMENT_REQUESTED_DT) final String entitlementDate,
+                                                @QueryParam(QUERY_BILLING_REQUESTED_DT) final String billingDate,
+                                                @QueryParam(QUERY_MIGRATED) @DefaultValue("false") final Boolean isMigrated,
+                                                @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+                                                @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+                                                @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                                @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                                @HeaderParam(HDR_REASON) final String reason,
+                                                @HeaderParam(HDR_COMMENT) final String comment,
+                                                @javax.ws.rs.core.Context final HttpServletRequest request,
+                                                @javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException, AccountApiException, SubscriptionApiException {
+
+        Preconditions.checkArgument(Iterables.size(entitlementsWithAddOns) > 0, "Subscription bulk list mustn't be null or empty.");
 
-                for (final SubscriptionJson entitlement : entitlements) {
+        logDeprecationParameterWarningIfNeeded(QUERY_REQUESTED_DT, QUERY_ENTITLEMENT_REQUESTED_DT, QUERY_BILLING_REQUESTED_DT);
 
-                    final PlanPhaseSpecifier planPhaseSpecifier = entitlement.getPlanName() != null ?
-                                                                  new PlanPhaseSpecifier(entitlement.getPlanName(), null) :
-                                                                  new PlanPhaseSpecifier(entitlement.getProductName(),
-                                                                                         BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), null);
-                    final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(entitlement.getPriceOverrides(), planPhaseSpecifier, account.getCurrency());
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
-                    EntitlementSpecifier specifier = new EntitlementSpecifier() {
+        final Account account = accountUserApi.getAccountById(UUID.fromString(entitlementsWithAddOns.get(0).getBaseEntitlementAndAddOns().get(0).getAccountId()), callContext);
 
-                        @Override
-                        public PlanPhaseSpecifier getPlanPhaseSpecifier() {
-                            return planPhaseSpecifier;
-                        }
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        for (BulkBaseSubscriptionAndAddOnsJson bulkBaseEntitlementWithAddOns : entitlementsWithAddOns) {
+            final Iterable<SubscriptionJson> baseEntitlements = Iterables.filter(
+                    bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
+                @Override
+                public boolean apply(final SubscriptionJson subscription) {
+                    return ProductCategory.BASE.toString().equalsIgnoreCase(subscription.getProductCategory());
+                }
+            });
+            Preconditions.checkArgument(Iterables.size(baseEntitlements) > 0, "SubscriptionJson Base Entitlement needs to be provided");
+            verifyNumberOfElements(Iterables.size(baseEntitlements), 1, "Only one BASE product is allowed per bundle.");
+
+            final Iterable<SubscriptionJson> entitlementsWithBundleSpecified = Iterables.filter(
+                bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
+                    @Override
+                    public boolean apply(final SubscriptionJson subscription) {
+                        return subscription.getBundleId() != null;
+                    }
+                }
+            );
+            Preconditions.checkArgument(Iterables.size(entitlementsWithBundleSpecified) == 0, "BundleId must not be specified when creating new bulks");
 
-                        @Override
-                        public List<PlanPhasePriceOverride> getOverrides() {
-                            return overrides;
-                        }
-                    };
+            SubscriptionJson baseEntitlement = baseEntitlements.iterator().next();
 
-                    entitlementSpecifierList.add(specifier);
+            final int addOnSubscriptionsSize = Iterables.size(Iterables.filter(bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
+                @Override
+                public boolean apply(final SubscriptionJson subscription) {
+                    return subscription.getProductCategory().equals(ProductCategory.ADD_ON.toString());
                 }
+            }));
+            verifyNumberOfElements(addOnSubscriptionsSize, bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns().size() - 1, "It should be " + (bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns().size() - 1) + " ADD_ON products.");
 
-                final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(entitlementDate);
-                final LocalDate resolvedBillingDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(billingDate);
-                return entitlementApi.createBaseEntitlementWithAddOns(account.getId(), baseEntitlement.getExternalKey(), entitlementSpecifierList,
-                                                                      resolvedEntitlementDate, resolvedBillingDate, isMigrated, pluginProperties, callContext);
-            }
+            final List<EntitlementSpecifier> entitlementSpecifierList = buildEntitlementSpecifierList(bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), account.getCurrency());
+
+            // create the baseEntitlementSpecifierWithAddOns
+            final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(entitlementDate);
+            final LocalDate resolvedBillingDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(billingDate);
+
+            BaseEntitlementWithAddOnsSpecifier baseEntitlementSpecifierWithAddOns = buildBaseEntitlementWithAddOnsSpecifier(entitlementSpecifierList, resolvedEntitlementDate, resolvedBillingDate, null, baseEntitlement, isMigrated);
+            baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementSpecifierWithAddOns);
+        }
 
+        final EntitlementCallCompletionCallback<List<Entitlement>> callback = new EntitlementCallCompletionCallback<List<Entitlement>>() {
+            @Override
+            public List<Entitlement> doOperation(final CallContext ctx) throws InterruptedException, TimeoutException, EntitlementApiException, SubscriptionApiException, AccountApiException {
+                return entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifierList, pluginProperties, callContext);
+            }
             @Override
             public boolean isImmOperation() {
                 return true;
             }
-
             @Override
-            public Response doResponseOk(final Entitlement entitlement) {
-                return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", entitlement.getBundleId());
+            public Response doResponseOk(final List<Entitlement> entitlements) {
+                return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccountBundles", entitlements.get(0).getAccountId(), buildQueryParams(buildBundleIdList(entitlements)));
             }
+        };
+        final EntitlementCallCompletion<List<Entitlement>> callCompletionCreation = new EntitlementCallCompletion<List<Entitlement>>();
+        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
+    }
 
+    private List<EntitlementSpecifier> buildEntitlementSpecifierList(final List<SubscriptionJson> entitlements, final Currency currency) {
+        final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
+        for (final SubscriptionJson entitlement : entitlements) {
+            // verifications
+            verifyNonNullOrEmpty(entitlement, "SubscriptionJson body should be specified for each element");
+            if (entitlement.getPlanName() == null) {
+                verifyNonNullOrEmpty(entitlement.getProductName(), "SubscriptionJson productName needs to be set for each element",
+                                     entitlement.getProductCategory(), "SubscriptionJson productCategory needs to be set for each element",
+                                     entitlement.getBillingPeriod(), "SubscriptionJson billingPeriod needs to be set for each element",
+                                     entitlement.getPriceList(), "SubscriptionJson priceList needs to be set for each element");
+            }
+            // create the entitlementSpecifier
+            final PlanPhaseSpecifier planPhaseSpecifier = entitlement.getPlanName() != null ?
+                                                          new PlanPhaseSpecifier(entitlement.getPlanName(), null) :
+                                                          new PlanPhaseSpecifier(entitlement.getProductName(),
+                                                                                 BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), null);
+            final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(entitlement.getPriceOverrides(), planPhaseSpecifier, currency);
+
+            EntitlementSpecifier specifier = new EntitlementSpecifier() {
+                @Override
+                public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                    return planPhaseSpecifier;
+                }
+                @Override
+                public List<PlanPhasePriceOverride> getOverrides() {
+                    return overrides;
+                }
+            };
+            entitlementSpecifierList.add(specifier);
+        }
+        return entitlementSpecifierList;
+    }
+
+    private BaseEntitlementWithAddOnsSpecifier buildBaseEntitlementWithAddOnsSpecifier(final List<EntitlementSpecifier> entitlementSpecifierList, final LocalDate resolvedEntitlementDate, final LocalDate resolvedBillingDate, final UUID bundleId, final SubscriptionJson baseEntitlement, final @QueryParam(QUERY_MIGRATED) @DefaultValue("false") Boolean isMigrated) {
+        return new BaseEntitlementWithAddOnsSpecifier() {
+            @Override
+            public UUID getBundleId() {
+                return bundleId;
+            }
+            @Override
+            public String getExternalKey() {
+                return baseEntitlement.getExternalKey();
+            }
+            @Override
+            public Iterable<EntitlementSpecifier> getEntitlementSpecifier() {
+                return entitlementSpecifierList;
+            }
+            @Override
+            public LocalDate getEntitlementEffectiveDate() {
+                return resolvedEntitlementDate;
+            }
+            @Override
+            public LocalDate getBillingEffectiveDate() {
+                return resolvedBillingDate;
+            }
+            @Override
+            public boolean isMigrated() {
+                return isMigrated;
+            }
         };
+    }
 
-        final EntitlementCallCompletion<Entitlement> callCompletionCreation = new EntitlementCallCompletion<Entitlement>();
-        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
+    private List<String> buildBundleIdList(final List<Entitlement> entitlements) {
+        List<String> result = new ArrayList<String>();
+        for (Entitlement entitlement : entitlements) {
+            if (!result.contains(entitlement.getBundleId().toString())) {
+                result.add(entitlement.getBundleId().toString());
+            }
+        }
+        return result;
+    }
+
+    private Map<String, String> buildQueryParams(final List<String> bundleIdList) {
+        Map<String, String> queryParams = new HashMap<String, String>();
+        String value = "";
+        for (String bundleId : bundleIdList) {
+            if (value.equals("")) {
+                value += bundleId;
+            } else value+="," + bundleId;
+        }
+        queryParams.put(QUERY_BUNDLES_FILTER, value);
+        return queryParams;
     }
 
     @TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
index ee09c1a..23d28b4 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -46,16 +46,30 @@ public class JaxrsUriBuilder {
 
     public Response buildResponse(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass,
                                   final String getMethodName, final Object objectId) {
-        final URI location = buildLocation(uriInfo, theClass, getMethodName, objectId);
+        final URI location = buildLocation(uriInfo, theClass, getMethodName, objectId, null);
+        return !jaxrsConfig.isJaxrsLocationFullUrl() ?
+               Response.status(Response.Status.CREATED).header("Location", location.getPath()).build() :
+               Response.created(location).build();
+    }
+
+    public Response buildResponse(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass,
+                                  final String getMethodName, final Object objectId, final Map<String, String> params) {
+        final URI location = buildLocation(uriInfo, theClass, getMethodName, objectId, params);
         return !jaxrsConfig.isJaxrsLocationFullUrl() ?
                Response.status(Response.Status.CREATED).header("Location", location.getPath()).build() :
                Response.created(location).build();
     }
 
     public URI buildLocation(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass,
-                             final String getMethodName, final Object objectId) {
+                             final String getMethodName, final Object objectId, final Map<String, String> params) {
         final UriBuilder uriBuilder = getUriBuilder(uriInfo.getBaseUri().getPath(), theClass, getMethodName);
 
+        if (null != params && !params.isEmpty()) {
+            for (final String key : params.keySet()) {
+                uriBuilder.queryParam(key, params.get(key));
+            }
+        }
+
         if (jaxrsConfig.isJaxrsLocationFullUrl()) {
             uriBuilder.scheme(uriInfo.getAbsolutePath().getScheme())
                       .host(uriInfo.getAbsolutePath().getHost())

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 2254f5b..cfa80b9 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.135</version>
+        <version>0.136-SNAPSHOT</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.17.9-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
index a298dc3..fe19bb1 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
@@ -26,15 +26,17 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.joda.time.Interval;
 import org.joda.time.LocalDate;
-import org.killbill.billing.catalog.DefaultPriceList;
 import org.killbill.billing.catalog.DefaultPriceListSet;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.client.KillBillClientException;
 import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.BulkBaseSubscriptionAndAddOns;
 import org.killbill.billing.client.model.Bundle;
+import org.killbill.billing.client.model.Bundles;
 import org.killbill.billing.client.model.Invoice;
 import org.killbill.billing.client.model.PhasePriceOverride;
 import org.killbill.billing.client.model.Subscription;
@@ -44,9 +46,8 @@ import org.killbill.billing.util.api.AuditLevel;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
-import com.ning.http.client.Response;
-
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
@@ -268,7 +269,7 @@ public class TestEntitlement extends TestJaxrsBase {
         subscriptions.add(base);
         subscriptions.add(addOn1);
         subscriptions.add(addOn2);
-        final Bundle bundle = killBillClient.createSubscriptionWithAddOns(subscriptions, initialDate.toLocalDate(), 10, "createdBy", "", "");
+        final Bundle bundle = killBillClient.createSubscriptionWithAddOns(subscriptions, null, 10, "createdBy", "", "");
         assertNotNull(bundle);
         assertEquals(bundle.getExternalKey(), "base");
         assertEquals(bundle.getSubscriptions().size(), 3);
@@ -277,6 +278,78 @@ public class TestEntitlement extends TestJaxrsBase {
         assertEquals(invoices.size(), 1);
     }
 
+    @Test(groups = "slow", description = "Create a bulk of base entitlement and addOns under the same transaction")
+    public void testCreateEntitlementsWithAddOns() throws Exception {
+        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account accountJson = createAccountWithDefaultPaymentMethod();
+
+        final Subscription base = new Subscription();
+        base.setAccountId(accountJson.getAccountId());
+        base.setProductName("Shotgun");
+        base.setProductCategory(ProductCategory.BASE);
+        base.setBillingPeriod(BillingPeriod.MONTHLY);
+        base.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        final Subscription addOn1 = new Subscription();
+        addOn1.setAccountId(accountJson.getAccountId());
+        addOn1.setProductName("Telescopic-Scope");
+        addOn1.setProductCategory(ProductCategory.ADD_ON);
+        addOn1.setBillingPeriod(BillingPeriod.MONTHLY);
+        addOn1.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        final Subscription addOn2 = new Subscription();
+        addOn2.setAccountId(accountJson.getAccountId());
+        addOn2.setProductName("Laser-Scope");
+        addOn2.setProductCategory(ProductCategory.ADD_ON);
+        addOn2.setBillingPeriod(BillingPeriod.MONTHLY);
+        addOn2.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        final List<Subscription> subscriptions = new ArrayList<Subscription>();
+        subscriptions.add(base);
+        subscriptions.add(addOn1);
+        subscriptions.add(addOn2);
+
+        final List<BulkBaseSubscriptionAndAddOns> bulkList = new ArrayList<BulkBaseSubscriptionAndAddOns>();
+        bulkList.add(new BulkBaseSubscriptionAndAddOns(subscriptions));
+        bulkList.add(new BulkBaseSubscriptionAndAddOns(subscriptions));
+
+        final Bundles bundles = killBillClient.createSubscriptionsWithAddOns(bulkList, null, 10, requestOptions);
+
+        assertNotNull(bundles);
+        assertEquals(bundles.size(), 2);
+        assertFalse(bundles.get(0).getExternalKey().equals(bundles.get(1).getExternalKey()));
+
+        final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, requestOptions);
+        assertEquals(invoices.size(), 1);
+    }
+
+    @Test(groups = "slow", description = "Create a bulk of base entitlements and addOns under the same transaction",
+            expectedExceptions = KillBillClientException.class, expectedExceptionsMessageRegExp = "SubscriptionJson Base Entitlement needs to be provided")
+    public void testCreateEntitlementsWithoutBase() throws Exception {
+        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account accountJson = createAccountWithDefaultPaymentMethod();
+
+        final Subscription addOn1 = new Subscription();
+        addOn1.setAccountId(accountJson.getAccountId());
+        addOn1.setProductName("Telescopic-Scope");
+        addOn1.setProductCategory(ProductCategory.ADD_ON);
+        addOn1.setBillingPeriod(BillingPeriod.MONTHLY);
+        addOn1.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        final List<Subscription> subscriptions = new ArrayList<Subscription>();
+        subscriptions.add(addOn1);
+
+        final List<BulkBaseSubscriptionAndAddOns> bulkList = new ArrayList<BulkBaseSubscriptionAndAddOns>();
+        bulkList.add(new BulkBaseSubscriptionAndAddOns(subscriptions));
+        bulkList.add(new BulkBaseSubscriptionAndAddOns(subscriptions));
+
+        killBillClient.createSubscriptionsWithAddOns(bulkList, null, 10, requestOptions);
+    }
+
     @Test(groups = "slow", description = "Can create an entitlement in the future")
     public void testCreateEntitlementInTheFuture() throws Exception {
         final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
index dc345c6..d1991cd 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
@@ -25,7 +25,6 @@ import org.joda.time.DateTime;
 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.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
@@ -33,9 +32,10 @@ import org.killbill.billing.catalog.api.PlanChangeResult;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseWithAddOns;
+import org.killbill.billing.subscription.api.user.SubscriptionAndAddOnsSpecifier;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
 import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
-import org.killbill.billing.subscription.api.user.SubscriptionSpecifier;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
@@ -47,7 +47,7 @@ public interface SubscriptionBaseApiService {
                                               CallContext context)
             throws SubscriptionBaseApiException;
 
-    public List<DefaultSubscriptionBase> createPlans(Iterable<SubscriptionSpecifier> subscriptions, CallContext context)
+    public List<DefaultSubscriptionBaseWithAddOns> createPlansWithAddOns(UUID accountId, Iterable<SubscriptionAndAddOnsSpecifier> subscriptionsAndAddOns, CallContext context)
             throws SubscriptionBaseApiException;
 
     public boolean cancel(DefaultSubscriptionBase subscription, CallContext context)
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 843e114..9ccf2a8 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
@@ -48,6 +48,7 @@ import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
 import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun.DryRunChangeReason;
@@ -58,11 +59,14 @@ import org.killbill.billing.subscription.api.SubscriptionApiBase;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
+import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
 import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseApiService;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseWithAddOns;
 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;
@@ -195,82 +199,112 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         }
     }
 
-    @Override
-    public List<SubscriptionBase> createBaseSubscriptionWithAddOns(final UUID bundleId, final Iterable<EntitlementSpecifier> entitlements, final DateTime requestedDateWithMs, final boolean isMigrated, final InternalCallContext context) throws SubscriptionBaseApiException {
-
-        final DateTime now = clock.getUTCNow();
-        final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
+    private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final UUID bundleId, 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>();
+        boolean first = true;
+        final List<SubscriptionBase> subscriptionsForBundle = getSubscriptionsForBundle(bundleId, null, context);
 
-        try {
-            final List<SubscriptionSpecifier> subscriptions = new ArrayList<SubscriptionSpecifier>();
-            final Catalog catalog = catalogService.getFullCatalog(true, true, context);
-            final CallContext callContext = internalCallContextFactory.createCallContext(context);
-
-            final SubscriptionBaseBundle bundle = dao.getSubscriptionBundleFromId(bundleId, context);
-            if (bundle == null) {
-                throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleId);
-            }
-
-            boolean first = true;
-            final List<SubscriptionBase> subscriptionsForBundle = getSubscriptionsForBundle(bundleId, null, context);
+        for (EntitlementSpecifier entitlement : entitlements) {
 
-            for (EntitlementSpecifier entitlement : entitlements) {
+            final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
 
-                final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
+            final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(entitlement.getOverrides(), callContext);
 
-                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()));
+            }
 
-                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()));
+            if (first) {
+                first = false;
+                if (plan.getProduct().getCategory() != ProductCategory.BASE) {
+                    throw new SubscriptionBaseApiException(new IllegalArgumentException(), ErrorCode.SUB_CREATE_NO_BP.getCode(), "Missing Base Subscription.");
                 }
+            }
 
-                if (first) {
-                    first = false;
-                    if (plan.getProduct().getCategory() != ProductCategory.BASE) {
-                        throw new SubscriptionBaseApiException(new IllegalArgumentException(), ErrorCode.SUB_CREATE_NO_BP.getCode(), "Missing Base Subscription.");
+            // 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) {
+                    int existingAddOnsWithSamePlanName = addonUtils.countExistingAddOnsWithSamePlanName(subscriptionsForBundle, plan.getName());
+                    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());
                     }
                 }
+            }
 
-                // 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) {
-                        int existingAddOnsWithSamePlanName = addonUtils.countExistingAddOnsWithSamePlanName(subscriptionsForBundle, plan.getName());
-                        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());
-                        }
-                    }
-                }
+            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(bundleId)
+                                            .setCategory(plan.getProduct().getCategory())
+                                            .setBundleStartDate(effectiveDate)
+                                            .setAlignStartDate(effectiveDate)
+                                            .setMigrated(isMigrated));
+
+            subscriptions.add(subscription);
+        }
+        return subscriptions;
+    }
 
-                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(bundleId)
-                                                .setCategory(plan.getProduct().getCategory())
-                                                .setBundleStartDate(effectiveDate)
-                                                .setAlignStartDate(effectiveDate)
-                                                .setMigrated(isMigrated));
-
-                subscriptions.add(subscription);
+    @Override
+    public List<SubscriptionBaseWithAddOns> createBaseSubscriptionsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifier, final InternalCallContext context) throws SubscriptionBaseApiException {
+        try {
+            final Catalog catalog = catalogService.getFullCatalog(true, true, context);
+            final CallContext callContext = internalCallContextFactory.createCallContext(context);
+            final DateTime now = clock.getUTCNow();
+
+            final List<SubscriptionAndAddOnsSpecifier> subscriptionAndAddOns = new ArrayList<SubscriptionAndAddOnsSpecifier>();
+            for (BaseEntitlementWithAddOnsSpecifier entitlementWithAddOnsSpecifier : baseEntitlementWithAddOnsSpecifier) {
+                final DateTime effectiveDate = (entitlementWithAddOnsSpecifier.getBillingEffectiveDate() != null) ?
+                                               DefaultClock.truncateMs(entitlementWithAddOnsSpecifier.getBillingEffectiveDate().toDateTimeAtStartOfDay()) : now;
+
+                final SubscriptionBaseBundle bundle = createBundleForAccount(accountId, entitlementWithAddOnsSpecifier.getExternalKey(), context);
+
+                SubscriptionAndAddOnsSpecifier subscriptionAndAddOnsSpecifier = new SubscriptionAndAddOnsSpecifier(
+                        bundle.getId(),
+                        effectiveDate,
+                        verifyAndBuildSubscriptionSpecifiers(bundle.getId(),
+                                                             entitlementWithAddOnsSpecifier.getEntitlementSpecifier(),
+                                                             entitlementWithAddOnsSpecifier.isMigrated(),
+                                                             context,
+                                                             now,
+                                                             effectiveDate,
+                                                             catalog,
+                                                             callContext)
+                );
+                subscriptionAndAddOns.add(subscriptionAndAddOnsSpecifier);
             }
 
-            final List<DefaultSubscriptionBase> result = apiService.createPlans(subscriptions, callContext);
-            return ImmutableList.copyOf(Iterables.transform(result, new Function<DefaultSubscriptionBase, SubscriptionBase>() {
-                @Override
-                public SubscriptionBase apply(final DefaultSubscriptionBase input) {
-                    return (SubscriptionBase) input;
-                }
-            }));
-        } catch (final CatalogApiException e) {
+            final List<DefaultSubscriptionBaseWithAddOns> defaultSubscriptionBaseWithAddOnsList = apiService.createPlansWithAddOns(accountId, subscriptionAndAddOns, callContext);
+            List<SubscriptionBaseWithAddOns> subscriptionBaseWithAddOnsList = new ArrayList<SubscriptionBaseWithAddOns>();
+            for (final DefaultSubscriptionBaseWithAddOns cur : defaultSubscriptionBaseWithAddOnsList) {
+                SubscriptionBaseWithAddOns subscriptionBaseWithAddOns = new SubscriptionBaseWithAddOns() {
+                    @Override
+                    public UUID getBundleId() {
+                        return cur.getBundleId();
+                    }
+                    @Override
+                    public List<SubscriptionBase> getSubscriptionBaseList() {
+                        return cur.getSubscriptionBaseList();
+                    }
+                    @Override
+                    public DateTime getEffectiveDate() {
+                        return cur.getEffectiveDate();
+                    }
+                };
+                subscriptionBaseWithAddOnsList.add(subscriptionBaseWithAddOns);
+            }
+           return subscriptionBaseWithAddOnsList;
+        } catch (CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
     }
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 0fee918..0cd9ce2 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
@@ -34,7 +34,6 @@ import org.killbill.billing.ObjectType;
 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.BillingPeriod;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.CatalogService;
@@ -71,6 +70,7 @@ import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.clock.Clock;
 import org.killbill.clock.DefaultClock;
 
+import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -118,12 +118,41 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
     }
 
     @Override
-    public List<DefaultSubscriptionBase> createPlans(final Iterable<SubscriptionSpecifier> subscriptions, final CallContext context) throws SubscriptionBaseApiException {
-
+    public List<DefaultSubscriptionBaseWithAddOns> createPlansWithAddOns(final UUID accountId, final Iterable<SubscriptionAndAddOnsSpecifier> subscriptionsAndAddOns, final CallContext context) throws SubscriptionBaseApiException {
         Map<UUID, List<SubscriptionBaseEvent>> eventsMap = new HashMap<UUID, List<SubscriptionBaseEvent>>();
-        List<DefaultSubscriptionBase> subscriptionBaseList = new ArrayList<DefaultSubscriptionBase>();
-        for (SubscriptionSpecifier subscription : subscriptions) {
+        List<List<DefaultSubscriptionBase>> subscriptionBaseAndAddOnsList = new ArrayList<List<DefaultSubscriptionBase>>();
+        List<DefaultSubscriptionBaseWithAddOns> allSubscriptions = new ArrayList<DefaultSubscriptionBaseWithAddOns>();
+
+        for (SubscriptionAndAddOnsSpecifier subscriptionAndAddOns : subscriptionsAndAddOns) {
+            List<DefaultSubscriptionBase> subscriptionBaseList = new ArrayList<DefaultSubscriptionBase>();
+            createEvents(subscriptionAndAddOns.getSubscriptionSpecifiers(), context, eventsMap, subscriptionBaseList);
+
+            subscriptionBaseAndAddOnsList.add(subscriptionBaseList);
+
+            DefaultSubscriptionBaseWithAddOns defaultSubscriptionBaseWithAddOns = new DefaultSubscriptionBaseWithAddOns(
+                    subscriptionAndAddOns.getBundleId(),
+                    ImmutableList.copyOf(Iterables.transform(subscriptionBaseList, new Function<DefaultSubscriptionBase, SubscriptionBase>() {
+                        @Override
+                        public SubscriptionBase apply(final DefaultSubscriptionBase input) {
+                            return input;
+                        }
+                    })),
+                    subscriptionAndAddOns.getEffectiveDate());
+            allSubscriptions.add(defaultSubscriptionBaseWithAddOns);
+        }
+
+        final InternalCallContext internalCallContext = createCallContextFromAccountId(accountId, context);
+        dao.createSubscriptionsWithAddOns(allSubscriptions, eventsMap, internalCallContext);
+
+        for (List<DefaultSubscriptionBase> subscriptions : subscriptionBaseAndAddOnsList) {
+            final DefaultSubscriptionBase baseSubscription = findBaseSubscription(subscriptions);
+            rebuildTransitions(internalCallContext, subscriptions, baseSubscription);
+        }
+        return allSubscriptions;
+    }
 
+    private void createEvents(final Iterable<SubscriptionSpecifier> subscriptions, final CallContext context, final Map<UUID, List<SubscriptionBaseEvent>> eventsMap, final List<DefaultSubscriptionBase> subscriptionBaseList) throws SubscriptionBaseApiException {
+        for (SubscriptionSpecifier subscription : subscriptions) {
             try {
                 final DefaultSubscriptionBase subscriptionBase = new DefaultSubscriptionBase(subscription.getBuilder(), this, clock);
                 final InternalCallContext internalCallContext = createCallContextFromBundleId(subscriptionBase.getBundleId(), context);
@@ -138,16 +167,14 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
                 throw new SubscriptionBaseApiException(e);
             }
         }
+    }
 
-        final InternalCallContext internalCallContext = createCallContextFromBundleId(subscriptionBaseList.get(0).getBundleId(), context);
-        dao.createSubscriptionWithAddOns(subscriptionBaseList, eventsMap, internalCallContext);
-
-        final DefaultSubscriptionBase baseSubscription = findBaseSubscription(subscriptionBaseList);
+    private void rebuildTransitions(final InternalCallContext internalCallContext, final List<DefaultSubscriptionBase> subscriptions, final DefaultSubscriptionBase baseSubscription) throws SubscriptionBaseApiException {
         try {
             baseSubscription.rebuildTransitions(dao.getEventsForSubscription(baseSubscription.getId(), internalCallContext),
                                                 catalogService.getFullCatalog(true, true, internalCallContext));
 
-            for (final DefaultSubscriptionBase input : subscriptionBaseList) {
+            for (final DefaultSubscriptionBase input : subscriptions) {
                 if (input.getId().equals(baseSubscription.getId())) {
                     continue;
                 }
@@ -158,13 +185,12 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         } catch (CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
-        return subscriptionBaseList;
     }
 
     private DefaultSubscriptionBase findBaseSubscription(final List<DefaultSubscriptionBase> subscriptionBaseList) {
-        return Iterables.tryFind(subscriptionBaseList, new Predicate<DefaultSubscriptionBase>() {
+        return Iterables.tryFind(subscriptionBaseList, new Predicate<SubscriptionBase>() {
             @Override
-            public boolean apply(final DefaultSubscriptionBase subscription) {
+            public boolean apply(final SubscriptionBase subscription) {
                 return ProductCategory.BASE.equals(subscription.getCategory());
             }
         }).orNull();
@@ -592,6 +618,10 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         return internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context);
     }
 
+    private InternalCallContext createCallContextFromAccountId(final UUID accountId, final CallContext context) {
+        return internalCallContextFactory.createInternalCallContext(accountId, ObjectType.ACCOUNT, context);
+    }
+
     private InternalTenantContext createTenantContextFromBundleId(final UUID bundleId, final TenantContext context) {
         return internalCallContextFactory.createInternalTenantContext(bundleId, ObjectType.BUNDLE, context);
     }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseWithAddOns.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseWithAddOns.java
new file mode 100644
index 0000000..0050afd
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseWithAddOns.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.api.user;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.subscription.api.SubscriptionBase;
+import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultSubscriptionBaseWithAddOns implements SubscriptionBaseWithAddOns {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultSubscriptionBaseWithAddOns.class);
+
+    private final UUID bundleId;
+    private final List<SubscriptionBase> subscriptionBaseList;
+    private final DateTime effectiveDate;
+
+    public DefaultSubscriptionBaseWithAddOns(final UUID bundleId, final List<SubscriptionBase> subscriptionBaseList, final DateTime effectiveDate) {
+        this.bundleId = bundleId;
+        this.subscriptionBaseList = subscriptionBaseList;
+        this.effectiveDate = effectiveDate;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    @Override
+    public List<SubscriptionBase> getSubscriptionBaseList() {
+        return subscriptionBaseList;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionAndAddOnsSpecifier.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionAndAddOnsSpecifier.java
new file mode 100644
index 0000000..549335e
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionAndAddOnsSpecifier.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.api.user;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+public class SubscriptionAndAddOnsSpecifier {
+
+    private UUID bundleId;
+    private DateTime effectiveDate;
+    private List<SubscriptionSpecifier> subscriptionSpecifiers;
+
+    public SubscriptionAndAddOnsSpecifier() {
+    }
+
+    public SubscriptionAndAddOnsSpecifier(final UUID bundleId, final DateTime effectiveDate,
+                                          final List<SubscriptionSpecifier> subscriptionSpecifiers) {
+        this.bundleId = bundleId;
+        this.effectiveDate = effectiveDate;
+        this.subscriptionSpecifiers = subscriptionSpecifiers;
+    }
+
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public List<SubscriptionSpecifier> getSubscriptionSpecifiers() {
+        return subscriptionSpecifiers;
+    }
+
+    public void setSubscriptionSpecifiers(final List<SubscriptionSpecifier> subscriptionSpecifiers) {
+        this.subscriptionSpecifiers = subscriptionSpecifiers;
+    }
+}
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 8899098..81df1b3 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
@@ -54,6 +54,7 @@ import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEv
 import org.killbill.billing.subscription.api.user.DefaultRequestedSubscriptionEvent;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseWithAddOns;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
 import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
@@ -513,27 +514,34 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
     }
 
     @Override
-    public void createSubscriptionWithAddOns(final List<DefaultSubscriptionBase> subscriptions, final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, final InternalCallContext context) {
+    public void createSubscriptionsWithAddOns(final List<DefaultSubscriptionBaseWithAddOns> subscriptions, final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, final InternalCallContext context) {
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
                 final SubscriptionSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class);
                 final SubscriptionEventSqlDao eventsDaoFromSameTransaction = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
+                for (DefaultSubscriptionBaseWithAddOns subscription : subscriptions) {
+                    List<DefaultSubscriptionBase> currentSubscriptionBaseList = ImmutableList.copyOf(Iterables.transform(subscription.getSubscriptionBaseList(), new Function<SubscriptionBase, DefaultSubscriptionBase>() {
+                        @Override
+                        public DefaultSubscriptionBase apply(final SubscriptionBase input) {
+                            return (DefaultSubscriptionBase) input;
+                        }
+                    }));
+                    for (DefaultSubscriptionBase subscriptionBase : currentSubscriptionBaseList) {
+                        transactional.create(new SubscriptionModelDao(subscriptionBase), context);
 
-                for (DefaultSubscriptionBase subscription : subscriptions) {
-                    transactional.create(new SubscriptionModelDao(subscription), context);
-
-                    final List<SubscriptionBaseEvent> initialEvents = initialEventsMap.get(subscription.getId());
-                    for (final SubscriptionBaseEvent cur : initialEvents) {
-                        eventsDaoFromSameTransaction.create(new SubscriptionEventModelDao(cur), context);
+                        final List<SubscriptionBaseEvent> initialEvents = initialEventsMap.get(subscriptionBase.getId());
+                        for (final SubscriptionBaseEvent cur : initialEvents) {
+                            eventsDaoFromSameTransaction.create(new SubscriptionEventModelDao(cur), context);
 
-                        final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
-                        recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
+                            final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
+                            recordBusOrFutureNotificationFromTransaction(subscriptionBase, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
 
-                    }
-                    // Notify the Bus of the latest requested change, if needed
-                    if (initialEvents.size() > 0) {
-                        notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, initialEvents.get(initialEvents.size() - 1), SubscriptionBaseTransitionType.CREATE, context);
+                        }
+                        // Notify the Bus of the latest requested change, if needed
+                        if (initialEvents.size() > 0) {
+                            notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscriptionBase, initialEvents.get(initialEvents.size() - 1), SubscriptionBaseTransitionType.CREATE, context);
+                        }
                     }
                 }
                 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 846be18..7c5743d 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
@@ -29,6 +29,7 @@ import org.killbill.billing.subscription.api.transfer.BundleTransferData;
 import org.killbill.billing.subscription.api.transfer.TransferCancelData;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseWithAddOns;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
 import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
@@ -81,7 +82,7 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
     // SubscriptionBase creation, cancellation, changePlanWithRequestedDate apis
     public void createSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> initialEvents, InternalCallContext context);
 
-    public void createSubscriptionWithAddOns(List<DefaultSubscriptionBase> subscriptions, Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, InternalCallContext context);
+    public void createSubscriptionsWithAddOns(List<DefaultSubscriptionBaseWithAddOns> subscriptions, Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, InternalCallContext context);
 
     public void cancelSubscriptionsOnBasePlanEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent event, List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
 
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 b3bde6d..d2306f1 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
@@ -43,6 +43,7 @@ import org.killbill.billing.subscription.api.transfer.TransferCancelData;
 import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseWithAddOns;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
 import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
@@ -68,6 +69,9 @@ import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificatio
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
 public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBundleModelDao, SubscriptionBaseBundle, SubscriptionApiException> implements SubscriptionDao {
@@ -220,20 +224,27 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
         mockNonEntityDao.addTenantRecordIdMapping(updatedSubscription.getId(), context);
     }
 
-    @Override
-    public void createSubscriptionWithAddOns(final List<DefaultSubscriptionBase> subscriptions,
-                                             final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap,
-                                             final InternalCallContext context) {
+    public void createSubscriptionsWithAddOns(final List<DefaultSubscriptionBaseWithAddOns> subscriptions,
+                                              final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap,
+                                              final InternalCallContext context) {
         synchronized (events) {
-            for (DefaultSubscriptionBase subscription : subscriptions) {
-                final List<SubscriptionBaseEvent> initialEvents = initialEventsMap.get(subscription.getId());
-                events.addAll(initialEvents);
-                for (final SubscriptionBaseEvent cur : initialEvents) {
-                    recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new SubscriptionNotificationKey(cur.getId()), context);
+            for (DefaultSubscriptionBaseWithAddOns subscription : subscriptions) {
+                List<DefaultSubscriptionBase> currentSubscriptionBaseList = ImmutableList.copyOf(Iterables.transform(subscription.getSubscriptionBaseList(), new Function<SubscriptionBase, DefaultSubscriptionBase>() {
+                    @Override
+                    public DefaultSubscriptionBase apply(final SubscriptionBase input) {
+                        return (DefaultSubscriptionBase) input;
+                    }
+                }));
+                for (DefaultSubscriptionBase subscriptionBase : currentSubscriptionBaseList) {
+                    final List<SubscriptionBaseEvent> initialEvents = initialEventsMap.get(subscriptionBase.getId());
+                    events.addAll(initialEvents);
+                    for (final SubscriptionBaseEvent cur : initialEvents) {
+                        recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new SubscriptionNotificationKey(cur.getId()), context);
+                    }
+                    final SubscriptionBase updatedSubscription = buildSubscription(subscriptionBase, context);
+                    this.subscriptions.add(updatedSubscription);
+                    mockNonEntityDao.addTenantRecordIdMapping(updatedSubscription.getId(), context);
                 }
-                final SubscriptionBase updatedSubscription = buildSubscription(subscription, context);
-                this.subscriptions.add(updatedSubscription);
-                mockNonEntityDao.addTenantRecordIdMapping(updatedSubscription.getId(), context);
             }
         }
     }