killbill-memoizeit

#219 - Added new method to SubscriptionResource to allow the

10/20/2016 4:48:36 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 fada42e..f4427c1 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,11 +30,11 @@ 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;
@@ -52,6 +52,9 @@ public interface SubscriptionBaseInternalApi {
     public List<SubscriptionBase> createBaseSubscriptionWithAddOns(UUID bundleId, Iterable<EntitlementSpecifier> entitlements, DateTime requestedDateWithMs,
                                                                    final boolean isMigrated, InternalCallContext context) throws SubscriptionBaseApiException;
 
+    public List<SubscriptionBase> createBaseSubscriptionsWithAddOns(UUID accountId, Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifier,
+                                                                    InternalCallContext contextWithValidAccountRecordId) throws SubscriptionBaseApiException;
+
     public void cancelBaseSubscriptions(Iterable<SubscriptionBase> subscriptions, BillingActionPolicy policy, InternalCallContext context) throws SubscriptionBaseApiException;
 
     public SubscriptionBaseBundle createBundleForAccount(UUID accountId, String bundleName, InternalCallContext context)
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..862c735 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
@@ -34,6 +34,8 @@ 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 +138,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().get(0).getEntitlementSpecifier().iterator().next().getPlanPhaseSpecifier(), planPhasePriceOverride);
                 final List<EntitlementSpecifier> entitlementSpecifiers = new ArrayList<EntitlementSpecifier>();
                 entitlementSpecifiers.add(entitlementSpecifier);
 
+                final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier =
+                        new DefaultBaseEntitlementWithAddOnsSpecifier(
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().get(0).getBundleId(),
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().get(0).getExternalKey(),
+                                entitlementSpecifiers,
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().get(0).getEntitlementEffectiveDate(),
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().get(0).getBillingEffectiveDate(),
+                                entitlementContext.getBaseEntitlementWithAddOnsSpecifiers().get(0).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..cdc77ad 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().get(0).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..31e6957 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
@@ -37,7 +37,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 +46,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;
@@ -74,6 +72,7 @@ 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 +132,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 +162,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().get(0).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().get(0).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,11 +181,13 @@ 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 List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers) throws SubscriptionBaseApiException {
+        if ((baseEntitlementWithAddOnsSpecifiers == null) || baseEntitlementWithAddOnsSpecifiers.isEmpty()) {
+            if ((baseEntitlementWithAddOnsSpecifiers.get(0).getEntitlementSpecifier() == null) || !baseEntitlementWithAddOnsSpecifiers.get(0).getEntitlementSpecifier().iterator().hasNext()) {
+                throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+            }
         }
-        return entitlementSpecifiers.get(0);
+        return baseEntitlementWithAddOnsSpecifiers.get(0).getEntitlementSpecifier().iterator().next();
     }
 
     @Override
@@ -193,15 +201,20 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
 
         final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
         Iterables.addAll(entitlementSpecifierList, entitlementSpecifiers);
+        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_SUBSCRIPTIONS_WITH_AO,
                                                                                accountId,
                                                                                null,
-                                                                               null,
-                                                                               externalKey,
-                                                                               entitlementSpecifierList,
-                                                                               entitlementEffectiveDate,
-                                                                               billingEffectiveDate,
+                                                                               baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
                                                                                properties,
                                                                                callContext);
@@ -218,10 +231,10 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
 
                     final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, contextWithValidAccountRecordId);
 
-                    final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBillingEffectiveDate(), contextWithValidAccountRecordId);
+                    final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().get(0).getBillingEffectiveDate(), contextWithValidAccountRecordId);
                     final List<SubscriptionBase> subscriptionBases = subscriptionBaseInternalApi.createBaseSubscriptionWithAddOns(bundle.getId(), entitlementSpecifiers, billingRequestedDate, isMigrated, contextWithValidAccountRecordId);
 
-                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEntitlementEffectiveDate(), contextWithValidAccountRecordId);
+                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().get(0).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);
@@ -245,6 +258,58 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
     }
 
     @Override
+    public List<Entitlement> createBaseEntitlementsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementSpecifiersWithAddOns, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+
+        logCreateEntitlementsWithAOs(log, baseEntitlementSpecifiersWithAddOns);
+
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementSpecifiersWithAddOnsList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        Iterables.addAll(baseEntitlementSpecifiersWithAddOnsList, baseEntitlementSpecifiersWithAddOns);
+
+        final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SHOPPING_CART_SUBSCRIPTIONS,
+                                                                               accountId,
+                                                                               null,
+                                                                               baseEntitlementSpecifiersWithAddOnsList,
+                                                                               null,
+                                                                               properties,
+                                                                               callContext);
+
+        final WithEntitlementPlugin<List<Entitlement>> createBaseEntitlementsWithAddOns = new WithEntitlementPlugin<List<Entitlement>>() {
+            @Override
+            public List<Entitlement> doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+                final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
+
+                try {
+                    final List<SubscriptionBase> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(accountId, baseEntitlementSpecifiersWithAddOns, contextWithValidAccountRecordId);
+
+                    final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().get(0).getEntitlementEffectiveDate(), contextWithValidAccountRecordId);
+                    final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
+                    for (final SubscriptionBase cur : subscriptionsWithAddOns) {
+                        final BlockingState blockingState = new DefaultBlockingState(cur.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
+                        blockingStates.add(blockingState);
+                    }
+                    entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(blockingStates, subscriptionsWithAddOns.get(0).getBundleId(), contextWithValidAccountRecordId);
+
+                    return buildEntitlementList(accountId, subscriptionsWithAddOns, callContext);
+                } catch (final SubscriptionBaseApiException e) {
+                    throw new EntitlementApiException(e);
+                }
+            }
+        };
+        return pluginExecution.executeWithPlugin(createBaseEntitlementsWithAddOns, pluginContext);
+    }
+
+    private List<Entitlement> buildEntitlementList(final UUID accountId, final List<SubscriptionBase> subscriptionsWithAddOns, final CallContext callContext) throws EntitlementApiException {
+        List<Entitlement> result = new ArrayList<Entitlement>();
+        for (SubscriptionBase subscriptionWithAddOns : subscriptionsWithAddOns) {
+            Entitlement entitlement = new DefaultEntitlement(accountId, subscriptionWithAddOns.getId(), eventsStreamBuilder, entitlementApi, pluginExecution,
+                                                             blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
+                                                             entitlementUtils, dateHelper, clock, securityApi, internalCallContextFactory, callContext);
+            result.add(entitlement);
+        }
+        return result;
+    }
+
+    @Override
     public Entitlement addEntitlement(final UUID bundleId, final PlanPhaseSpecifier planPhaseSpecifier, final List<PlanPhasePriceOverride> overrides, @Nullable final LocalDate entitlementEffectiveDate, @Nullable final LocalDate billingEffectiveDate,
             final boolean isMigrated, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
 
@@ -254,14 +319,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 +352,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().get(0).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().get(0).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 +473,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 +519,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().get(0).getBillingEffectiveDate(), contextWithSourceAccountRecordId);
                     final SubscriptionBaseBundle newBundle = subscriptionBaseTransferApi.transferBundle(sourceAccountId, destAccountId, externalKey, requestedDate, true, cancelImm, context);
 
 
@@ -460,7 +537,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().get(0).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..6f6033f 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 List<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 List<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 List<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 List<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..557c568 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().get(0).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/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/SubscriptionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
index 8a9a799..4e1a83f 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
@@ -53,6 +53,7 @@ import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.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 +73,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;
@@ -357,6 +359,147 @@ public class SubscriptionResource extends JaxRsResourceBase {
     }
 
     @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.");
+
+        logDeprecationParameterWarningIfNeeded(QUERY_REQUESTED_DT, QUERY_ENTITLEMENT_REQUESTED_DT, QUERY_BILLING_REQUESTED_DT);
+
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+
+        final SubscriptionJson baseEntitlement = Iterables.tryFind(entitlementsWithAddOns.get(0).getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
+            @Override
+            public boolean apply(final SubscriptionJson subscription) {
+                return ProductCategory.BASE.toString().equalsIgnoreCase(subscription.getProductCategory());
+            }
+        }).orNull();
+        verifyNonNull(baseEntitlement.getAccountId(), "SubscriptionJson accountId needs to be set for BASE product.");
+
+        final Account account = getAccountFromSubscriptionJson(baseEntitlement, callContext);
+
+        final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+        for (BulkBaseSubscriptionAndAddOnsJson bulkBaseEntitlementWithAddOns : entitlementsWithAddOns) {
+            final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
+
+            // verify the number of BASE subscriptions
+            final int baseSubscriptionsSize = Iterables.size(Iterables.filter(bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
+                @Override
+                public boolean apply(final SubscriptionJson subscription) {
+                    return subscription.getProductCategory().equals(ProductCategory.BASE.toString());
+                }
+            }));
+            verifyNumberOfElements(baseSubscriptionsSize, 1, "Only one BASE product is allowed.");
+
+            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, entitlementsWithAddOns.size() - 1, "It should be " + (entitlementsWithAddOns.size() - 1) + " ADD_ON products.");
+
+            for (final SubscriptionJson entitlement : bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns()) {
+                // 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, account.getCurrency());
+
+                EntitlementSpecifier specifier = new EntitlementSpecifier() {
+                    @Override
+                    public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                        return planPhaseSpecifier;
+                    }
+                    @Override
+                    public List<PlanPhasePriceOverride> getOverrides() {
+                        return overrides;
+                    }
+                };
+                entitlementSpecifierList.add(specifier);
+            }
+
+            // create the baseEntitlementSpecifierWithAddOns
+            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 = 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;
+                }
+            };
+            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 List<Entitlement> entitlement) {
+                return Response.status(Status.CREATED).build();
+            }
+        };
+        final EntitlementCallCompletion<List<Entitlement>> callCompletionCreation = new EntitlementCallCompletion<List<Entitlement>>();
+        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
+    }
+
+    @TimedResource
     @PUT
     @Path("/{subscriptionId:" + UUID_PATTERN + "}/uncancel")
     @Produces(APPLICATION_JSON)

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 1e442c2..250ee1d 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.133</version>
+        <version>0.134-SNAPSHOT</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.17.6-SNAPSHOT</version>
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..3067e37 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
@@ -33,6 +33,7 @@ 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.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;
@@ -50,6 +51,9 @@ public interface SubscriptionBaseApiService {
     public List<DefaultSubscriptionBase> createPlans(Iterable<SubscriptionSpecifier> subscriptions, CallContext context)
             throws SubscriptionBaseApiException;
 
+    public List<List<DefaultSubscriptionBase>> createPlansWithAddOns(UUID accountId, Iterable<SubscriptionAndAddOnsSpecifier> subscriptionsAndAddOns, CallContext context)
+            throws SubscriptionBaseApiException;
+
     public boolean cancel(DefaultSubscriptionBase subscription, CallContext context)
             throws SubscriptionBaseApiException;
 
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 4b18a23..48391d5 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;
@@ -63,6 +64,7 @@ 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.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;
@@ -275,6 +277,99 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         }
     }
 
+    @Override
+    public List<SubscriptionBase> 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 List<SubscriptionAndAddOnsSpecifier> subscriptionAndAddOns = new ArrayList<SubscriptionAndAddOnsSpecifier>();
+            for (BaseEntitlementWithAddOnsSpecifier entitlementWithAddOnsSpecifier : baseEntitlementWithAddOnsSpecifier) {
+                final List<SubscriptionSpecifier> subscriptions = new ArrayList<SubscriptionSpecifier>();
+                final DateTime now = clock.getUTCNow();
+                final DateTime effectiveDate = (entitlementWithAddOnsSpecifier.getEntitlementEffectiveDate() != null) ?
+                                               DefaultClock.truncateMs(entitlementWithAddOnsSpecifier.getEntitlementEffectiveDate().toDateTimeAtCurrentTime()) : now;
+
+                final SubscriptionBaseBundle bundle = createBundleForAccount(accountId, entitlementWithAddOnsSpecifier.getExternalKey(), context);
+                final List<SubscriptionBase> subscriptionWithAddOns = createBaseSubscriptionWithAddOns(bundle.getId(), entitlementWithAddOnsSpecifier.getEntitlementSpecifier(),
+                                                                                                       entitlementWithAddOnsSpecifier.getEntitlementEffectiveDate().toDateTimeAtCurrentTime(),
+                                                                                                       entitlementWithAddOnsSpecifier.isMigrated(), context);
+                boolean first = true;
+                for (EntitlementSpecifier entitlement : entitlementWithAddOnsSpecifier.getEntitlementSpecifier()) {
+                    final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
+
+                    final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(entitlement.getOverrides(), callContext);
+
+                    final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, entitlementWithAddOnsSpecifier.getEntitlementEffectiveDate().toDateTimeAtCurrentTime());
+                    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.");
+                        }
+                    }
+
+                    // 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(subscriptionWithAddOns, plan.getName());
+                            int currentAddOnsWithSamePlanName = countCurrentAddOnsWithSamePlanName(entitlementWithAddOnsSpecifier.getEntitlementSpecifier(), 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(bundle.getId())
+                                                    .setCategory(plan.getProduct().getCategory())
+                                                    .setBundleStartDate(effectiveDate)
+                                                    .setAlignStartDate(effectiveDate)
+                                                    .setMigrated(entitlementWithAddOnsSpecifier.isMigrated()));
+
+                    subscriptions.add(subscription);
+                }
+                SubscriptionAndAddOnsSpecifier subscriptionAndAddOnsSpecifier = new SubscriptionAndAddOnsSpecifier();
+                subscriptionAndAddOnsSpecifier.setSubscriptionSpecifiers(subscriptions);
+                subscriptionAndAddOns.add(subscriptionAndAddOnsSpecifier);
+            }
+
+            final List<List<DefaultSubscriptionBase>> result = apiService.createPlansWithAddOns(accountId, subscriptionAndAddOns, callContext);
+            return buildSubscriptionBaseList(result);
+
+        } catch (CatalogApiException e) {
+            throw new SubscriptionBaseApiException(e);
+        }
+    }
+
+    private List<SubscriptionBase> buildSubscriptionBaseList(final List<List<DefaultSubscriptionBase>> defaultSubscriptionBaseList) {
+        List<SubscriptionBase> result = new ArrayList<SubscriptionBase>();
+
+        for (List<DefaultSubscriptionBase> defaultSubscriptionBase : defaultSubscriptionBaseList) {
+            final ImmutableList<SubscriptionBase> subscriptionBases = ImmutableList.copyOf(Iterables.transform(defaultSubscriptionBase, new Function<DefaultSubscriptionBase, SubscriptionBase>() {
+                @Override
+                public SubscriptionBase apply(final DefaultSubscriptionBase input) {
+                    return input;
+                }
+            }));
+            result.addAll(subscriptionBases);
+        }
+        return result;
+    }
+
     private int countCurrentAddOnsWithSamePlanName(final Iterable<EntitlementSpecifier> entitlements,
                                                    final Catalog catalog, final String planName,
                                                    final DateTime effectiveDate, final CallContext callContext) throws CatalogApiException {
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..4137398 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
@@ -118,6 +118,57 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
     }
 
     @Override
+    public List<List<DefaultSubscriptionBase>> createPlansWithAddOns(final UUID accountId, final Iterable<SubscriptionAndAddOnsSpecifier> subscriptionsAndAddOns, final CallContext context) throws SubscriptionBaseApiException {
+
+        Map<UUID, List<SubscriptionBaseEvent>> eventsMap = new HashMap<UUID, List<SubscriptionBaseEvent>>();
+        List<List<DefaultSubscriptionBase>> subscriptionBaseAndAddOnsList = new ArrayList<List<DefaultSubscriptionBase>>();
+
+        for (SubscriptionAndAddOnsSpecifier subscriptionAndAddOns : subscriptionsAndAddOns) {
+            List<DefaultSubscriptionBase> subscriptionBaseList = new ArrayList<DefaultSubscriptionBase>();
+            for (SubscriptionSpecifier subscription : subscriptionAndAddOns.getSubscriptionSpecifiers()) {
+
+                try {
+                    final DefaultSubscriptionBase subscriptionBase = new DefaultSubscriptionBase(subscription.getBuilder(), this, clock);
+                    final InternalCallContext internalCallContext = createCallContextFromBundleId(subscriptionBase.getBundleId(), context);
+                    final List<SubscriptionBaseEvent> events = getEventsOnCreation(subscriptionBase.getBundleId(), subscriptionBase.getId(), subscriptionBase.getAlignStartDate(),
+                                                                                   subscriptionBase.getBundleStartDate(), subscription.getPlan(),
+                                                                                   subscription.getInitialPhase(), subscription.getRealPriceList(),
+                                                                                   subscription.getEffectiveDate(), subscription.getProcessedDate(), internalCallContext);
+                    eventsMap.put(subscriptionBase.getId(), events);
+                    subscriptionBaseList.add(subscriptionBase);
+
+                } catch (final CatalogApiException e) {
+                    throw new SubscriptionBaseApiException(e);
+                }
+            }
+            subscriptionBaseAndAddOnsList.add(subscriptionBaseList);
+        }
+
+        final InternalCallContext internalCallContext = createCallContextFromAccountId(accountId, context);
+        dao.createSubscriptionsWithAddOns(subscriptionBaseAndAddOnsList, eventsMap, internalCallContext);
+
+        for (List<DefaultSubscriptionBase> subscriptionBase : subscriptionBaseAndAddOnsList) {
+            final DefaultSubscriptionBase baseSubscription = findBaseSubscription(subscriptionBase);
+            try {
+                baseSubscription.rebuildTransitions(dao.getEventsForSubscription(baseSubscription.getId(), internalCallContext),
+                                                    catalogService.getFullCatalog(true, true, internalCallContext));
+
+                for (final DefaultSubscriptionBase input : subscriptionBase) {
+                    if (input.getId().equals(baseSubscription.getId())) {
+                        continue;
+                    }
+
+                    input.rebuildTransitions(dao.getEventsForSubscription(input.getId(), internalCallContext),
+                                             catalogService.getFullCatalog(true, true, internalCallContext));
+                }
+            } catch (CatalogApiException e) {
+                throw new SubscriptionBaseApiException(e);
+            }
+        }
+        return subscriptionBaseAndAddOnsList;
+    }
+
+    @Override
     public List<DefaultSubscriptionBase> createPlans(final Iterable<SubscriptionSpecifier> subscriptions, final CallContext context) throws SubscriptionBaseApiException {
 
         Map<UUID, List<SubscriptionBaseEvent>> eventsMap = new HashMap<UUID, List<SubscriptionBaseEvent>>();
@@ -592,6 +643,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/SubscriptionAndAddOnsSpecifier.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionAndAddOnsSpecifier.java
new file mode 100644
index 0000000..9d9af7c
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionAndAddOnsSpecifier.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+public class SubscriptionAndAddOnsSpecifier {
+
+    private List<SubscriptionSpecifier> subscriptionSpecifiers;
+
+    public SubscriptionAndAddOnsSpecifier() {
+    }
+
+    public SubscriptionAndAddOnsSpecifier(final List<SubscriptionSpecifier> subscriptionSpecifiers) {
+        this.subscriptionSpecifiers = subscriptionSpecifiers;
+    }
+
+    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..cd3a84a 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
@@ -513,34 +513,57 @@ 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<List<DefaultSubscriptionBase>> subscriptionsWithAddOns,
+                                              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 (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);
+                for (List<DefaultSubscriptionBase> subscriptionWithAddOns : subscriptionsWithAddOns) {
+                    createSubscription(entitySqlDaoWrapperFactory, transactional, eventsDaoFromSameTransaction, subscriptionWithAddOns, context, initialEventsMap);
+                }
+                return null;
+            }
+        });
+    }
 
-                        final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
-                        recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
+    @Override
+    public void createSubscriptionWithAddOns(final List<DefaultSubscriptionBase> 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);
 
-                    }
-                    // 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);
-                    }
-                }
+                createSubscription(entitySqlDaoWrapperFactory, transactional, eventsDaoFromSameTransaction, subscriptions, context, initialEventsMap);
                 return null;
             }
         });
     }
 
+    private void createSubscription(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final SubscriptionSqlDao transactional, final SubscriptionEventSqlDao eventsDaoFromSameTransaction, final List<DefaultSubscriptionBase> subscriptions, final InternalCallContext context, final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap) throws EntityPersistenceException {
+        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 boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
+                recordBusOrFutureNotificationFromTransaction(subscription, 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);
+            }
+        }
+    }
+
     @Override
     public void cancelSubscriptionsOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
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..d08cd40 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
@@ -83,6 +83,8 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
 
     public void createSubscriptionWithAddOns(List<DefaultSubscriptionBase> subscriptions, Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, InternalCallContext context);
 
+    public void createSubscriptionsWithAddOns(List<List<DefaultSubscriptionBase>> subscriptionsWithAddOns, Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, InternalCallContext context);
+
     public void cancelSubscriptionsOnBasePlanEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent event, List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
 
     public void cancelSubscriptions(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..6291b97 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
@@ -239,6 +239,26 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
     }
 
     @Override
+    public void createSubscriptionsWithAddOns(final List<List<DefaultSubscriptionBase>> subscriptionsWithAddOns,
+                                              final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap,
+                                              final InternalCallContext context) {
+        synchronized (events) {
+            for (List<DefaultSubscriptionBase> subscriptionAndAddOns : subscriptionsWithAddOns) {
+                for (DefaultSubscriptionBase subscription : subscriptionAndAddOns) {
+                    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);
+                    }
+                    final SubscriptionBase updatedSubscription = buildSubscription(subscription, context);
+                    this.subscriptions.add(updatedSubscription);
+                    mockNonEntityDao.addTenantRecordIdMapping(updatedSubscription.getId(), context);
+                }
+            }
+        }
+    }
+
+    @Override
     public List<SubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents, final InternalTenantContext context) {
         final List<SubscriptionBase> results = new ArrayList<SubscriptionBase>();
         for (final SubscriptionBase cur : subscriptions) {