killbill-aplcache

Merge remote-tracking branch 'origin/catalog' into kares-performance Signed-off-by:

3/27/2015 8:42:37 AM

Changes

Details

diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java
index fca316c..02d6c5b 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java
@@ -25,6 +25,7 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
@@ -51,15 +52,15 @@ public interface SubscriptionBase extends Entity, Blockable {
             throws SubscriptionBaseApiException;
 
     // Return the effective date of the change
-    public DateTime changePlan(final String productName, final BillingPeriod term, final String priceList, final CallContext context)
+    public DateTime changePlan(final String productName, final BillingPeriod term, final String priceList, final List<PlanPhasePriceOverride> overrides, final CallContext context)
             throws SubscriptionBaseApiException;
 
     // Return the effective date of the change
-    public DateTime changePlanWithDate(final String productName, final BillingPeriod term, final String priceList, final DateTime requestedDate, final CallContext context)
+    public DateTime changePlanWithDate(final String productName, final BillingPeriod term, final String priceList, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDate, final CallContext context)
             throws SubscriptionBaseApiException;
 
     // Return the effective date of the change
-    public DateTime changePlanWithPolicy(final String productName, final BillingPeriod term, final String priceList,
+    public DateTime changePlanWithPolicy(final String productName, final BillingPeriod term, final String priceList, final List<PlanPhasePriceOverride> overrides,
                                          final BillingActionPolicy policy, final CallContext context)
             throws SubscriptionBaseApiException;
 
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 44b315d..329873b 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
@@ -26,6 +26,7 @@ import org.joda.time.DateTime;
 
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
 import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
@@ -36,7 +37,7 @@ import org.killbill.billing.util.entity.Pagination;
 
 public interface SubscriptionBaseInternalApi {
 
-    public SubscriptionBase createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDateWithMs,
+    public SubscriptionBase createSubscription(UUID bundleId, PlanPhaseSpecifier spec, List<PlanPhasePriceOverride> overrides, DateTime requestedDateWithMs,
                                                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/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index d202719..249a757 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -985,7 +985,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
     private void checkChangePlanWithOverdueState(final Entitlement entitlement, final boolean shouldFail, final boolean expectedPayment) {
         if (shouldFail) {
             try {
-                entitlement.changePlan("Pistol", term, PriceListSet.DEFAULT_PRICELIST_NAME, callContext);
+                entitlement.changePlan("Pistol", term, PriceListSet.DEFAULT_PRICELIST_NAME, null, callContext);
             } catch (EntitlementApiException e) {
                 assertTrue(e.getCause() instanceof BlockingApiException || e.getCode() == ErrorCode.SUB_CHANGE_NON_ACTIVE.getCode(),
                            String.format("Cause is %s, message is %s", e.getCause(), e.getMessage()));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index a5c19e3..9ef034f 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -50,6 +50,7 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
@@ -551,11 +552,12 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         }, events);
     }
 
-    protected DefaultEntitlement createBaseEntitlementAndCheckForCompletion(final UUID accountId,
+    protected DefaultEntitlement createBaseEntitlementWithPriceOverrideAndCheckForCompletion(final UUID accountId,
                                                                             final String bundleExternalKey,
                                                                             final String productName,
                                                                             final ProductCategory productCategory,
                                                                             final BillingPeriod billingPeriod,
+                                                                            final List<PlanPhasePriceOverride> overrides,
                                                                             final NextEvent... events) {
         if (productCategory == ProductCategory.ADD_ON) {
             throw new RuntimeException("Unxepected Call for creating ADD_ON");
@@ -567,7 +569,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                 try {
                     final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, productCategory, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null);
                     final LocalDate effectiveDate = new LocalDate(clock.getUTCNow());
-                    final Entitlement entitlement = entitlementApi.createBaseEntitlement(accountId, spec, bundleExternalKey, effectiveDate, callContext);
+                    final Entitlement entitlement = entitlementApi.createBaseEntitlement(accountId, spec, bundleExternalKey, overrides, effectiveDate, callContext);
                     assertNotNull(entitlement);
                     return entitlement;
                 } catch (final EntitlementApiException e) {
@@ -578,6 +580,16 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         }, events);
     }
 
+
+    protected DefaultEntitlement createBaseEntitlementAndCheckForCompletion(final UUID accountId,
+                                                                            final String bundleExternalKey,
+                                                                            final String productName,
+                                                                            final ProductCategory productCategory,
+                                                                            final BillingPeriod billingPeriod,
+                                                                            final NextEvent... events) {
+        return createBaseEntitlementWithPriceOverrideAndCheckForCompletion(accountId, bundleExternalKey, productName, productCategory, billingPeriod, null, events);
+    }
+
     protected DefaultEntitlement addAOEntitlementAndCheckForCompletion(final UUID bundleId,
                                                                        final String productName,
                                                                        final ProductCategory productCategory,
@@ -593,7 +605,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                 try {
                     final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, productCategory, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null);
                     final LocalDate effectiveDate = new LocalDate(clock.getUTCNow());
-                    final Entitlement entitlement = entitlementApi.addEntitlement(bundleId, spec, effectiveDate, callContext);
+                    final Entitlement entitlement = entitlementApi.addEntitlement(bundleId, spec, null, effectiveDate, callContext);
                     assertNotNull(entitlement);
                     return entitlement;
                 } catch (final EntitlementApiException e) {
@@ -617,9 +629,9 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                     // Need to fetch again to get latest CTD updated from the system
                     Entitlement refreshedEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
                     if (billingPolicy == null) {
-                        refreshedEntitlement = refreshedEntitlement.changePlan(productName, billingPeriod, priceList, callContext);
+                        refreshedEntitlement = refreshedEntitlement.changePlan(productName, billingPeriod, priceList, null, callContext);
                     } else {
-                        refreshedEntitlement = refreshedEntitlement.changePlanOverrideBillingPolicy(productName, billingPeriod, priceList, clock.getUTCNow().toLocalDate(), billingPolicy, callContext);
+                        refreshedEntitlement = refreshedEntitlement.changePlanOverrideBillingPolicy(productName, billingPeriod, priceList, null, clock.getUTCNow().toLocalDate(), billingPolicy, callContext);
                     }
                     return refreshedEntitlement;
                 } catch (final EntitlementApiException e) {
@@ -781,5 +793,10 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         public BillingActionPolicy getBillingActionPolicy() {
             return billingPolicy;
         }
+
+        @Override
+        public List<PlanPhasePriceOverride> getPlanPhasePriceoverrides() {
+            return null;
+        }
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
new file mode 100644
index 0000000..5309ad9
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.beatrix.integration;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
+import org.killbill.billing.catalog.DefaultPriceListSet;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PriceListSet;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.testng.annotations.Test;
+
+public class TestWithPriceOverride extends TestIntegrationBase {
+
+    @Test(groups = "slow")
+    public void testCreatWithFixedPriceOverride() throws Exception {
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        // We take april as it has 30 days (easier to play with BCD)
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2012, 4, 1));
+
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        overrides.add(new DefaultPlanPhasePriceOverride("shotgun-monthly-trial", account.getCurrency(), BigDecimal.ONE, null));
+
+        final DefaultEntitlement bpSubscription = createBaseEntitlementWithPriceOverrideAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, overrides, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+        // Check bundle after BP got created otherwise we get an error from auditApi.
+        subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(clock.getUTCToday(), null, InvoiceItemType.FIXED, new BigDecimal("1")));
+    }
+
+
+    @Test(groups = "slow")
+    public void testCreateWithRecurringPriceOverride() throws Exception {
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        // We take april as it has 30 days (easier to play with BCD)
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2012, 4, 1));
+
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        overrides.add(new DefaultPlanPhasePriceOverride("shotgun-monthly-evergreen", account.getCurrency(), null, BigDecimal.TEN));
+
+        final DefaultEntitlement bpSubscription = createBaseEntitlementWithPriceOverrideAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, overrides, NextEvent.CREATE, NextEvent.INVOICE);
+        // Check bundle after BP got created otherwise we get an error from auditApi.
+        subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, BigDecimal.TEN));
+
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.RECURRING, BigDecimal.TEN));
+    }
+
+
+
+
+    @Test(groups = "slow")
+    public void testChangePlanWithRecurringPriceOverride() throws Exception {
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        // We take april as it has 30 days (easier to play with BCD)
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2012, 4, 1));
+
+
+        final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        // Check bundle after BP got created otherwise we get an error from auditApi.
+        subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        overrides.add(new DefaultPlanPhasePriceOverride("shotgun-monthly-evergreen", account.getCurrency(), null, new BigDecimal("279.95")));
+
+        busHandler.pushExpectedEvent(NextEvent.CHANGE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        bpSubscription.changePlanOverrideBillingPolicy("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, overrides, new LocalDate(2012, 5, 1), BillingActionPolicy.IMMEDIATE, callContext);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("279.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-249.95")));
+
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 4, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.RECURRING, new BigDecimal("279.95")));
+    }
+
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
index c3351c9..b43e99d 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
@@ -55,7 +55,7 @@ public class EhCacheCatalogCache implements CatalogCache {
 
     @Override
     public void loadDefaultCatalog(final String url) throws CatalogApiException {
-        defaultCatalog = (url != null) ? loader.load(url) : null;
+        defaultCatalog = (url != null) ? loader.loadDefaultCatalog(url) : null;
     }
 
     @Override
@@ -92,8 +92,8 @@ public class EhCacheCatalogCache implements CatalogCache {
     private CacheLoaderArgument initializeCacheLoaderArgument(final EhCacheCatalogCache parentCache) {
         final LoaderCallback loaderCallback = new LoaderCallback() {
             @Override
-            public Object loadCatalog(final List<String> catalogXMLs) throws CatalogApiException {
-                return loader.load(catalogXMLs);
+            public Object loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException {
+                return loader.load(catalogXMLs, tenantRecordId);
             }
         };
         final Object[] args = new Object[1];
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideDao.java
new file mode 100644
index 0000000..eaef9b1
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideDao.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+
+public interface CatalogOverrideDao {
+
+    public CatalogOverridePlanDefinitionModelDao getOrCreateOverridePlanDefinition(String parentPlanName, DateTime catalogEffectiveDate, PlanPhasePriceOverride[] resolvedOverride, InternalCallContext context);
+
+    public List<CatalogOverridePhaseDefinitionModelDao> getOverriddenPlanPhases(final Long planDefRecordId, final InternalTenantContext context);
+
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionModelDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionModelDao.java
new file mode 100644
index 0000000..5d73a1c
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionModelDao.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.math.BigDecimal;
+
+import org.joda.time.DateTime;
+
+public class CatalogOverridePhaseDefinitionModelDao {
+
+    private Long recordId;
+    private String parentPhaseName;
+    private String currency;
+    private BigDecimal fixedPrice;
+    private BigDecimal recurringPrice;
+    private DateTime effectiveDate;
+    private DateTime createdDate;
+    private String createdBy;
+    private Long tenantRecordId;
+
+    public CatalogOverridePhaseDefinitionModelDao() {
+    }
+
+    public CatalogOverridePhaseDefinitionModelDao(final String parentPhaseName, final String currency, final BigDecimal fixedPrice, final BigDecimal recurringPrice, final DateTime effectiveDate) {
+        this.parentPhaseName = parentPhaseName;
+        this.currency = currency;
+        this.fixedPrice = fixedPrice;
+        this.recurringPrice = recurringPrice;
+        this.effectiveDate = effectiveDate;
+    }
+
+    public Long getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(final Long recordId) {
+        this.recordId = recordId;
+    }
+
+    public String getParentPhaseName() {
+        return parentPhaseName;
+    }
+
+    public void setParentPhaseName(final String parentPhaseName) {
+        this.parentPhaseName = parentPhaseName;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public void setCurrency(final String currency) {
+        this.currency = currency;
+    }
+
+    public BigDecimal getFixedPrice() {
+        return fixedPrice;
+    }
+
+    public void setFixedPrice(final BigDecimal fixedPrice) {
+        this.fixedPrice = fixedPrice;
+    }
+
+    public BigDecimal getRecurringPrice() {
+        return recurringPrice;
+    }
+
+    public void setRecurringPrice(final BigDecimal recurringPrice) {
+        this.recurringPrice = recurringPrice;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public void setEffectiveDate(final DateTime effectiveDate) {
+        this.effectiveDate = effectiveDate;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(final DateTime createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(final String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public Long getTenantRecordId() {
+        return tenantRecordId;
+    }
+
+    public void setTenantRecordId(final Long tenantRecordId) {
+        this.tenantRecordId = tenantRecordId;
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.java
new file mode 100644
index 0000000..2e70525
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
+
+@UseStringTemplate3StatementLocator
+public interface CatalogOverridePhaseDefinitionSqlDao extends Transactional<CatalogOverridePhaseDefinitionSqlDao>, CloseMe {
+
+    @SqlUpdate
+    public void create(@SmartBindBean final CatalogOverridePhaseDefinitionModelDao entity,
+                       @SmartBindBean final InternalCallContext context);
+
+    @SqlQuery
+    public CatalogOverridePhaseDefinitionModelDao getByRecordId(@Bind("recordId") final Long recordId,
+                                                                @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
+    public CatalogOverridePhaseDefinitionModelDao getByAttributes(@Bind("parentPhaseName") String parentPhaseName,
+                                                                  @Bind("currency") String currency,
+                                                                  @Bind("fixedPrice") BigDecimal fixedPrice,
+                                                                  @Bind("recurringPrice") BigDecimal recurringPrice,
+                                                                  @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
+    public List<CatalogOverridePhaseDefinitionModelDao> getOverriddenPlanPhases(@Bind("targetPlanDefRecordId") Long targetPlanDefRecordId,
+                                                                                @SmartBindBean final InternalTenantContext context);
+
+
+    @SqlQuery
+    public Long getLastInsertId();
+
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionModelDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionModelDao.java
new file mode 100644
index 0000000..6efad3d
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionModelDao.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import org.joda.time.DateTime;
+
+public class CatalogOverridePlanDefinitionModelDao {
+
+    private Long recordId;
+    private String parentPlanName;
+    private Boolean isActive;
+    private DateTime effectiveDate;
+    private DateTime createdDate;
+    private String createdBy;
+    private Long tenantRecordId;
+
+    public CatalogOverridePlanDefinitionModelDao() {
+    }
+
+    public CatalogOverridePlanDefinitionModelDao(final String parentPlanName, final Boolean isActive, final DateTime effectiveDate) {
+        this.recordId = 0L;
+        this.parentPlanName = parentPlanName;
+        this.isActive = isActive;
+        this.effectiveDate = effectiveDate;
+    }
+
+    public Long getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(final Long recordId) {
+        this.recordId = recordId;
+    }
+
+    public String getParentPlanName() {
+        return parentPlanName;
+    }
+
+    public void setParentPlanName(final String parentPlanName) {
+        this.parentPlanName = parentPlanName;
+    }
+
+    public Boolean getIsActive() {
+        return isActive;
+    }
+
+    public void setIsActive(final Boolean isActive) {
+        this.isActive = isActive;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public void setEffectiveDate(final DateTime effectiveDate) {
+        this.effectiveDate = effectiveDate;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(final DateTime createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public Long getTenantRecordId() {
+        return tenantRecordId;
+    }
+
+    public void setTenantRecordId(final Long tenantRecordId) {
+        this.tenantRecordId = tenantRecordId;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(final String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    @Override
+    public String toString() {
+        return "CatalogOverridePlanDefinitionModelDao{" +
+               "recordId=" + recordId +
+               ", parentPlanName='" + parentPlanName + '\'' +
+               ", isActive=" + isActive +
+               ", effectiveDate=" + effectiveDate +
+               ", createdDate=" + createdDate +
+               ", createdBy='" + createdBy + '\'' +
+               ", tenantRecordId=" + tenantRecordId +
+               '}';
+    }
+}
+
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.java
new file mode 100644
index 0000000..6c73970
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
+
+@UseStringTemplate3StatementLocator
+public interface CatalogOverridePlanDefinitionSqlDao extends Transactional<CatalogOverridePlanDefinitionSqlDao>, CloseMe {
+
+    @SqlUpdate
+    public void create(@SmartBindBean final CatalogOverridePlanDefinitionModelDao entity,
+                       @SmartBindBean final InternalCallContext context);
+
+    @SqlQuery
+    public CatalogOverridePlanDefinitionModelDao getByRecordId(@Bind("recordId") final Long recordId,
+                                                               @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
+    public Long getLastInsertId();
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseModelDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseModelDao.java
new file mode 100644
index 0000000..e4e9c65
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseModelDao.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import org.joda.time.DateTime;
+
+public class CatalogOverridePlanPhaseModelDao {
+
+    private Long recordId;
+    private Short phaseNumber;
+    private Long phaseDefRecordId;
+    private Long targetPlanDefRecordId;
+    private DateTime createdDate;
+    private String createdBy;
+    private Long tenantRecordId;
+
+    public CatalogOverridePlanPhaseModelDao() {
+    }
+
+    public CatalogOverridePlanPhaseModelDao(final Short phaseNumber, final Long phaseDefRecordId, final Long targetPlanDefRecordId) {
+        this.phaseNumber = phaseNumber;
+        this.phaseDefRecordId = phaseDefRecordId;
+        this.targetPlanDefRecordId = targetPlanDefRecordId;
+    }
+
+    public Long getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(final Long recordId) {
+        this.recordId = recordId;
+    }
+
+    public Short getPhaseNumber() {
+        return phaseNumber;
+    }
+
+    public void setPhaseNumber(final Short phaseNumber) {
+        this.phaseNumber = phaseNumber;
+    }
+
+    public Long getPhaseDefRecordId() {
+        return phaseDefRecordId;
+    }
+
+    public void setPhaseDefRecordId(final Long phaseDefRecordId) {
+        this.phaseDefRecordId = phaseDefRecordId;
+    }
+
+    public Long getTargetPlanDefRecordId() {
+        return targetPlanDefRecordId;
+    }
+
+    public void setTargetPlanDefRecordId(final Long targetPlanDefRecordId) {
+        this.targetPlanDefRecordId = targetPlanDefRecordId;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(final DateTime createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(final String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public Long getTenantRecordId() {
+        return tenantRecordId;
+    }
+
+    public void setTenantRecordId(final Long tenantRecordId) {
+        this.tenantRecordId = tenantRecordId;
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.java
new file mode 100644
index 0000000..e4f0e55
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.tag.dao.UUIDCollectionBinder;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
+
+@UseStringTemplate3StatementLocator
+public interface CatalogOverridePlanPhaseSqlDao extends Transactional<CatalogOverridePlanPhaseSqlDao>, CloseMe {
+
+    @SqlUpdate
+    public void create(@SmartBindBean final CatalogOverridePlanPhaseModelDao entity,
+                       @SmartBindBean final InternalCallContext context);
+
+    @SqlQuery
+    public CatalogOverridePlanPhaseModelDao getByRecordId(@Bind("recordId") final Long recordId,
+                                                          @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
+    public Long getTargetPlanDefinition(@PlanPhaseKeysCollectionBinder final Collection<String> concatPhaseNumAndPhaseDefRecordId,
+                                        @Bind("targetCount") final Integer targetCount,
+                                        @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
+    public Long getLastInsertId();
+
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/DefaultCatalogOverrideDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/DefaultCatalogOverrideDao.java
new file mode 100644
index 0000000..3494b7b
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/DefaultCatalogOverrideDao.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.clock.Clock;
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+
+import com.google.inject.Inject;
+
+public class DefaultCatalogOverrideDao implements CatalogOverrideDao {
+
+    private final IDBI dbi;
+    private final Clock clock;
+
+    @Inject
+    public DefaultCatalogOverrideDao(final IDBI dbi, final Clock clock) {
+        this.dbi = dbi;
+        this.clock = clock;
+        // There is no real good place to do that but here (since the sqlDao are NOT EntitySqlDao and DBPProvider belongs in common)... oh well..
+        ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(CatalogOverridePlanDefinitionModelDao.class));
+        ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(CatalogOverridePhaseDefinitionModelDao.class));
+        ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(CatalogOverridePlanPhaseModelDao.class));
+    }
+
+    @Override
+    public CatalogOverridePlanDefinitionModelDao getOrCreateOverridePlanDefinition(final String parentPlanName, final DateTime catalogEffectiveDate, final PlanPhasePriceOverride[] resolvedOverride, final InternalCallContext context) {
+
+        return dbi.inTransaction(new TransactionCallback<CatalogOverridePlanDefinitionModelDao>() {
+            @Override
+            public CatalogOverridePlanDefinitionModelDao inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+
+                final CatalogOverridePhaseDefinitionModelDao[] overridePhaseDefinitionModelDaos = new CatalogOverridePhaseDefinitionModelDao[resolvedOverride.length];
+                for (int i = 0; i < resolvedOverride.length; i++) {
+                    final PlanPhasePriceOverride curOverride = resolvedOverride[i];
+                    if (curOverride != null) {
+                        final CatalogOverridePhaseDefinitionModelDao createdOverridePhaseDefinitionModelDao = getOrCreateOverridePhaseDefinitionFromTransaction(curOverride.getPhaseName(), catalogEffectiveDate, curOverride, handle, context);
+                        overridePhaseDefinitionModelDaos[i] = createdOverridePhaseDefinitionModelDao;
+                    }
+                }
+
+                final CatalogOverridePlanDefinitionSqlDao sqlDao = handle.attach(CatalogOverridePlanDefinitionSqlDao.class);
+                final Long targetPlanDefinitionRecordId = getOverridePlanDefinitionFromTransaction(overridePhaseDefinitionModelDaos, handle, context);
+                if (targetPlanDefinitionRecordId != null) {
+                    return sqlDao.getByRecordId(targetPlanDefinitionRecordId, context);
+                }
+
+                final CatalogOverridePlanDefinitionModelDao inputPlanDef = new CatalogOverridePlanDefinitionModelDao(parentPlanName, true, catalogEffectiveDate);
+                sqlDao.create(inputPlanDef, context);
+                final Long recordId = sqlDao.getLastInsertId();
+                final CatalogOverridePlanDefinitionModelDao resultPlanDef = sqlDao.getByRecordId(recordId, context);
+
+                for (short i = 0; i < overridePhaseDefinitionModelDaos.length; i++) {
+                    if (overridePhaseDefinitionModelDaos[i] != null) {
+                        createCatalogOverridePlanPhaseFromTransaction(i, overridePhaseDefinitionModelDaos[i], resultPlanDef, handle, context);
+                    }
+                }
+                return resultPlanDef;
+            }
+        });
+    }
+
+    @Override
+    public List<CatalogOverridePhaseDefinitionModelDao> getOverriddenPlanPhases(final Long planDefRecordId, final InternalTenantContext context) {
+        return dbi.inTransaction(new TransactionCallback<List<CatalogOverridePhaseDefinitionModelDao>>() {
+            @Override
+            public List<CatalogOverridePhaseDefinitionModelDao> inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+                final CatalogOverridePhaseDefinitionSqlDao sqlDao = handle.attach(CatalogOverridePhaseDefinitionSqlDao.class);
+                return sqlDao.getOverriddenPlanPhases(planDefRecordId, context);
+            }
+        });
+    }
+
+    private Long getOverridePlanDefinitionFromTransaction(final CatalogOverridePhaseDefinitionModelDao[] overridePhaseDefinitionModelDaos, final Handle inTransactionHandle, final InternalCallContext context) {
+        final CatalogOverridePlanPhaseSqlDao sqlDao = inTransactionHandle.attach(CatalogOverridePlanPhaseSqlDao.class);
+
+        final List<String> keys = new ArrayList<String>();
+        for (int i = 0; i < overridePhaseDefinitionModelDaos.length; i++) {
+            final CatalogOverridePhaseDefinitionModelDao cur = overridePhaseDefinitionModelDaos[i];
+            if (cur != null) {
+                // Each key is the concatenation of the phase_number, phase_definition_record_id
+                final StringBuffer key = new StringBuffer();
+                key.append(i);
+                key.append(",");
+                key.append(cur.getRecordId());
+                keys.add(key.toString());
+            }
+        }
+        return sqlDao.getTargetPlanDefinition(keys, keys.size(), context);
+    }
+
+    private void createCatalogOverridePlanPhaseFromTransaction(final short phaseNum, final CatalogOverridePhaseDefinitionModelDao phaseDef, final CatalogOverridePlanDefinitionModelDao planDef, final Handle inTransactionHandle, final InternalCallContext context) {
+        final CatalogOverridePlanPhaseSqlDao sqlDao = inTransactionHandle.attach(CatalogOverridePlanPhaseSqlDao.class);
+        final CatalogOverridePlanPhaseModelDao modelDao = new CatalogOverridePlanPhaseModelDao(phaseNum, phaseDef.getRecordId(), planDef.getRecordId());
+        sqlDao.create(modelDao, context);
+    }
+
+    private CatalogOverridePhaseDefinitionModelDao getOrCreateOverridePhaseDefinitionFromTransaction(final String parentPhaseName, final DateTime catalogEffectiveDate, final PlanPhasePriceOverride override, final Handle inTransactionHandle, final InternalCallContext context) {
+        final CatalogOverridePhaseDefinitionSqlDao sqlDao = inTransactionHandle.attach(CatalogOverridePhaseDefinitionSqlDao.class);
+        CatalogOverridePhaseDefinitionModelDao result = sqlDao.getByAttributes(parentPhaseName, override.getCurrency().name(), override.getFixedPrice(), override.getRecurringPrice(), context);
+        if (result == null) {
+            final CatalogOverridePhaseDefinitionModelDao phaseDef = new CatalogOverridePhaseDefinitionModelDao(parentPhaseName, override.getCurrency().name(), override.getFixedPrice(), override.getRecurringPrice(),
+                                                                                                               catalogEffectiveDate);
+            sqlDao.create(phaseDef, context);
+            final Long recordId = sqlDao.getLastInsertId();
+            result = sqlDao.getByRecordId(recordId, context);
+        }
+        return result;
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/PlanPhaseKeysCollectionBinder.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/PlanPhaseKeysCollectionBinder.java
new file mode 100644
index 0000000..0835ffd
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/PlanPhaseKeysCollectionBinder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Collection;
+
+import org.killbill.billing.catalog.dao.PlanPhaseKeysCollectionBinder.PlanPhaseKeysCollectionBinderFactory;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+@BindingAnnotation(PlanPhaseKeysCollectionBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface PlanPhaseKeysCollectionBinder {
+
+    public static class PlanPhaseKeysCollectionBinderFactory implements BinderFactory {
+
+        @Override
+        public Binder build(Annotation annotation) {
+            return new Binder<PlanPhaseKeysCollectionBinder, Collection<String>>() {
+
+                @Override
+                public void bind(SQLStatement<?> query, PlanPhaseKeysCollectionBinder bind, Collection<String> keys) {
+                    query.define("keys", keys);
+
+                    int idx = 0;
+                    for (String state : keys) {
+                        query.bind("key_" + idx, state);
+                        idx++;
+                    }
+                }
+            };
+        }
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
index 8480115..b791185 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
@@ -27,6 +27,7 @@ import org.killbill.billing.catalog.api.Fixed;
 import org.killbill.billing.catalog.api.FixedType;
 import org.killbill.billing.catalog.api.InternationalPrice;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
@@ -54,6 +55,13 @@ public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements
     }
 
 
+    public DefaultFixed() {}
+
+    public DefaultFixed(final DefaultFixed in, final PlanPhasePriceOverride override) {
+        this.type = in.getType();
+        this.fixedPrice = in.getPrice() != null ? new DefaultInternationalPrice((DefaultInternationalPrice) in.getPrice(), override, true) : null;
+    }
+
     @Override
     public void initialize(final StandaloneCatalog root, final URI uri) {
         if (fixedPrice != null) {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
index 2607cfd..ec6e80f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
@@ -27,6 +27,7 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.CurrencyValueNull;
 import org.killbill.billing.catalog.api.InternationalPrice;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.Price;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
@@ -48,8 +49,20 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
         return prices;
     }
 
-
-
+    public DefaultInternationalPrice() {}
+
+    public DefaultInternationalPrice(final DefaultInternationalPrice in, final PlanPhasePriceOverride override, final boolean fixed) {
+        this.prices = in.getPrices() != null ? new DefaultPrice[in.getPrices().length] : null;
+        // There is a question on whether we keep the other prices that were not overridden or only have one entry for the overridden price on that currency.
+        for (int i = 0; i < in.getPrices().length; i++) {
+            final DefaultPrice curPrice = (DefaultPrice)  in.getPrices()[i];
+            if (curPrice.getCurrency().equals(override.getCurrency())) {
+                prices[i] = new DefaultPrice(fixed ? override.getFixedPrice() : override.getRecurringPrice(), override.getCurrency());
+            } else {
+                prices[i] = curPrice;
+            }
+        }
+    }
 
     /* (non-Javadoc)
       * @see org.killbill.billing.catalog.IInternationalPrice#getPrice(org.killbill.billing.catalog.api.Currency)
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index 505414c..a6a0079 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.Iterator;
+import java.util.List;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -38,6 +39,7 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.Recurring;
 import org.killbill.xmlloader.ValidatingConfig;
@@ -76,6 +78,22 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
     @XmlElement(required = false)
     private Integer plansAllowedInBundle = 1;
 
+
+    public DefaultPlan() {}
+
+    public DefaultPlan(final String planName, final DefaultPlan in, final PlanPhasePriceOverride[] overrides) {
+        this.name = planName;
+        this.retired = in.isRetired();
+        this.effectiveDateForExistingSubscriptons = in.getEffectiveDateForExistingSubscriptons();
+        this.product = (DefaultProduct) in.getProduct();
+        this.initialPhases = new DefaultPlanPhase[in.getInitialPhases().length];
+        for (int i = 0; i< overrides.length - 1; i++) {
+            final DefaultPlanPhase newPhase = new DefaultPlanPhase(this, in.getInitialPhases()[i], overrides[i]);
+            initialPhases[i] = newPhase;
+        }
+        this.finalPhase = new DefaultPlanPhase(this, in.getFinalPhase(), overrides[overrides.length - 1]);
+
+    }
     /* (non-Javadoc)
       * @see org.killbill.billing.catalog.IPlan#getEffectiveDateForExistingSubscriptons()
       */
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
index ec69d44..28499d2 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
@@ -20,6 +20,7 @@ package org.killbill.billing.catalog;
 
 import java.net.URI;
 
+import javax.annotation.Nullable;
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAttribute;
@@ -33,6 +34,7 @@ import org.killbill.billing.catalog.api.Fixed;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.Recurring;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.xmlloader.ValidatingConfig;
@@ -61,6 +63,20 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
     //Not exposed in XML
     private Plan plan;
 
+    public DefaultPlanPhase() {}
+
+    public DefaultPlanPhase(final DefaultPlan parentPlan, final DefaultPlanPhase in, @Nullable final PlanPhasePriceOverride override) {
+        this.type = in.getPhaseType();
+        this.duration = (DefaultDuration) in.getDuration();
+        this.fixed = override != null && override.getFixedPrice() != null ? new DefaultFixed((DefaultFixed) in.getFixed(), override) : (DefaultFixed) in.getFixed();
+        this.recurring = override != null && override.getRecurringPrice() != null ? new DefaultRecurring((DefaultRecurring) in.getRecurring(), override) : (DefaultRecurring) in.getRecurring();
+        this.usages = new DefaultUsage[in.getUsages().length];
+        for (int i = 0; i < in.getUsages().length; i++) {
+            usages[i] = (DefaultUsage) in.getUsages()[i];
+        }
+        this.plan = parentPlan;
+    }
+
     public static String phaseName(final String planName, final PhaseType phasetype) {
         return planName + "-" + phasetype.toString().toLowerCase();
     }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhasePriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhasePriceOverride.java
new file mode 100644
index 0000000..2a1ee1c
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhasePriceOverride.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog;
+
+import java.math.BigDecimal;
+
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+
+public class DefaultPlanPhasePriceOverride implements PlanPhasePriceOverride {
+
+    private final String phaseName;
+    private final PlanPhaseSpecifier planPhaseSpecifier;
+    private final Currency currency;
+    private final BigDecimal fixedPrice;
+    private final BigDecimal recurringPrice;
+
+    public DefaultPlanPhasePriceOverride(final PlanPhaseSpecifier planPhaseSpecifier, final Currency currency, final BigDecimal fixedPrice, final BigDecimal recurringPrice) {
+        this.phaseName = null;
+        this.planPhaseSpecifier = planPhaseSpecifier;
+        this.currency = currency;
+        this.fixedPrice = fixedPrice;
+        this.recurringPrice = recurringPrice;
+    }
+
+    public DefaultPlanPhasePriceOverride(final String phaseName, final Currency currency, final BigDecimal fixedPrice, final BigDecimal recurringPrice) {
+        this.phaseName = phaseName;
+        this.planPhaseSpecifier = null;
+        this.currency = currency;
+        this.fixedPrice = fixedPrice;
+        this.recurringPrice = recurringPrice;
+    }
+
+    @Override
+    public String getPhaseName() {
+        return phaseName;
+    }
+
+    @Override
+    public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+        return planPhaseSpecifier;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public BigDecimal getFixedPrice() {
+        return fixedPrice;
+    }
+
+    @Override
+    public BigDecimal getRecurringPrice() {
+        return recurringPrice;
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
index 644052e..02b7840 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
@@ -26,6 +26,7 @@ import javax.xml.bind.annotation.XmlElement;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.Recurring;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationError;
@@ -44,6 +45,13 @@ public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implem
     private Plan plan;
     private PlanPhase phase;
 
+    public DefaultRecurring() {};
+
+    public DefaultRecurring(final DefaultRecurring in, final PlanPhasePriceOverride override) {
+        this.billingPeriod = in.getBillingPeriod();
+        this.recurringPrice = in.getRecurringPrice() != null ? new DefaultInternationalPrice(in.getRecurringPrice(), override, false) : null;
+    }
+
     @Override
     public BillingPeriod getBillingPeriod() {
         return billingPeriod;
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
index 7118e5f..32685e9 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
@@ -25,8 +25,12 @@ import org.killbill.billing.catalog.api.user.DefaultCatalogUserApi;
 import org.killbill.billing.catalog.caching.CatalogCache;
 import org.killbill.billing.catalog.caching.CatalogCacheInvalidationCallback;
 import org.killbill.billing.catalog.caching.EhCacheCatalogCache;
+import org.killbill.billing.catalog.dao.CatalogOverrideDao;
+import org.killbill.billing.catalog.dao.DefaultCatalogOverrideDao;
 import org.killbill.billing.catalog.io.CatalogLoader;
 import org.killbill.billing.catalog.io.VersionedCatalogLoader;
+import org.killbill.billing.catalog.override.DefaultPriceOverride;
+import org.killbill.billing.catalog.override.PriceOverride;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
 import org.killbill.billing.util.config.CatalogConfig;
@@ -51,6 +55,11 @@ public class CatalogModule extends KillBillModule {
     protected void installCatalog() {
         bind(CatalogService.class).to(DefaultCatalogService.class).asEagerSingleton();
         bind(CatalogLoader.class).to(VersionedCatalogLoader.class).asEagerSingleton();
+        bind(PriceOverride.class).to(DefaultPriceOverride.class).asEagerSingleton();
+    }
+
+    protected void installCatalogDao() {
+        bind(CatalogOverrideDao.class).to(DefaultCatalogOverrideDao.class).asEagerSingleton();
     }
 
     protected void installCatalogUserApi() {
@@ -62,9 +71,12 @@ public class CatalogModule extends KillBillModule {
         bind(CacheInvalidationCallback.class).annotatedWith(Names.named(CATALOG_INVALIDATION_CALLBACK)).to(CatalogCacheInvalidationCallback.class).asEagerSingleton();
     }
 
+
+
     @Override
     protected void configure() {
         installConfig();
+        installCatalogDao();
         installCatalog();
         installCatalogUserApi();
         installCatalogConfigCache();
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/io/CatalogLoader.java b/catalog/src/main/java/org/killbill/billing/catalog/io/CatalogLoader.java
index 642cf6f..953dfe3 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/io/CatalogLoader.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/io/CatalogLoader.java
@@ -23,6 +23,6 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 
 public interface CatalogLoader {
 
-    public abstract VersionedCatalog load(String urlString)
+    public abstract VersionedCatalog loadDefaultCatalog(String urlString)
             throws CatalogApiException;
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
index 460b9a6..632d393 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
@@ -24,36 +24,43 @@ import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.google.common.io.Resources;
-import com.google.inject.Inject;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.StandaloneCatalog;
+import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
 import org.killbill.billing.catalog.VersionedCatalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
-import org.killbill.billing.platform.api.KillbillService.ServiceException;
+import org.killbill.billing.catalog.override.PriceOverride;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.clock.Clock;
 import org.killbill.xmlloader.UriAccessor;
 import org.killbill.xmlloader.XMLLoader;
 
+import com.google.common.io.Resources;
+import com.google.inject.Inject;
+
 public class VersionedCatalogLoader implements CatalogLoader {
+
     private static final Object PROTOCOL_FOR_FILE = "file";
-    private final String XML_EXTENSION = ".xml";
+    private static final String XML_EXTENSION = ".xml";
+
     private final Clock clock;
+    private final PriceOverride priceOverride;
+    private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
-    public VersionedCatalogLoader(final Clock clock) {
+    public VersionedCatalogLoader(final Clock clock, final PriceOverride priceOverride, final InternalCallContextFactory internalCallContextFactory) {
         this.clock = clock;
+        this.priceOverride = priceOverride;
+        this.internalCallContextFactory = internalCallContextFactory;
     }
 
     /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.io.ICatalogLoader#load(java.lang.String)
+      * @see org.killbill.billing.catalog.io.ICatalogLoader#loadDefaultCatalog(java.lang.String)
       */
     @Override
-    public VersionedCatalog load(final String uriString) throws CatalogApiException {
+    public VersionedCatalog loadDefaultCatalog(final String uriString) throws CatalogApiException {
         try {
-            List<URI> xmlURIs = null;
-
+            List<URI> xmlURIs;
             if (uriString.endsWith(XML_EXTENSION)) { // Assume its an xml file
                 xmlURIs = new ArrayList<URI>();
                 URI uri = new URI(uriString);
@@ -75,10 +82,10 @@ public class VersionedCatalogLoader implements CatalogLoader {
                 xmlURIs = findXmlReferences(directoryContents, new URL(uriString));
             }
 
-            final VersionedCatalog result = new VersionedCatalog(clock);
+            final VersionedCatalog result = new VersionedCatalog(clock, InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID);
             for (final URI u : xmlURIs) {
                 final StandaloneCatalog catalog = XMLLoader.getObjectFromUri(u, StandaloneCatalog.class);
-                result.add(catalog);
+                result.add(new StandaloneCatalogWithPriceOverride(catalog, priceOverride, InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID, internalCallContextFactory));
             }
             return result;
         } catch (Exception e) {
@@ -86,15 +93,15 @@ public class VersionedCatalogLoader implements CatalogLoader {
         }
     }
 
-    public VersionedCatalog load(final List<String> catalogXMLs) throws CatalogApiException {
-        final VersionedCatalog result = new VersionedCatalog(clock);
+    public VersionedCatalog load(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException {
+        final VersionedCatalog result = new VersionedCatalog(clock, tenantRecordId);
         final URI uri;
         try {
             uri = new URI("/tenantCatalog");
             for (final String cur : catalogXMLs) {
                 final InputStream curCatalogStream = new ByteArrayInputStream(cur.getBytes());
                 final StandaloneCatalog catalog = XMLLoader.getObjectFromStream(uri, curCatalogStream, StandaloneCatalog.class);
-                result.add(catalog);
+                result.add(new StandaloneCatalogWithPriceOverride(catalog, priceOverride, tenantRecordId, internalCallContextFactory));
             }
             return result;
         } catch (Exception e) {
@@ -179,6 +186,4 @@ public class VersionedCatalogLoader implements CatalogLoader {
         }
         return new URI(url.toString() + f);
     }
-
-
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
new file mode 100644
index 0000000..897af0e
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.override;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.DefaultPlan;
+import org.killbill.billing.catalog.DefaultPlanPhase;
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.StaticCatalog;
+import org.killbill.billing.catalog.dao.CatalogOverrideDao;
+import org.killbill.billing.catalog.dao.CatalogOverridePhaseDefinitionModelDao;
+import org.killbill.billing.catalog.dao.CatalogOverridePlanDefinitionModelDao;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+
+public class DefaultPriceOverride implements PriceOverride {
+
+    public static final Pattern CUSTOM_PLAN_NAME_PATTERN = Pattern.compile("(.*)-(\\d+)$");
+
+    private final CatalogOverrideDao overrideDao;
+
+    @Inject
+    public DefaultPriceOverride(final CatalogOverrideDao overrideDao) {
+        this.overrideDao = overrideDao;
+    }
+
+    @Override
+    public DefaultPlan getOrCreateOverriddenPlan(final Plan parentPlan, final DateTime catalogEffectiveDate, final List<PlanPhasePriceOverride> overrides, final InternalCallContext context) throws CatalogApiException {
+
+        final PlanPhasePriceOverride[] resolvedOverride = new PlanPhasePriceOverride[parentPlan.getAllPhases().length];
+        int index = 0;
+        for (final PlanPhase curPhase : parentPlan.getAllPhases()) {
+            final PlanPhasePriceOverride curOverride = Iterables.tryFind(overrides, new Predicate<PlanPhasePriceOverride>() {
+                @Override
+                public boolean apply(final PlanPhasePriceOverride input) {
+                    if (input.getPhaseName() != null) {
+                        return input.getPhaseName().equals(curPhase.getName());
+                    }
+                    // If the phaseName was not passed, we infer by matching the phaseType. This obvously would not work in a case where
+                    // a plan is defined with multiple phases of the same type.
+                    final PlanPhaseSpecifier curPlanPhaseSpecifier = input.getPlanPhaseSpecifier();
+                    if (curPlanPhaseSpecifier.getPhaseType().equals(curPhase.getPhaseType())) {
+                        return true;
+                    }
+                    return false;
+                }
+            }).orNull();
+            resolvedOverride[index++] = curOverride != null ?
+                                        new DefaultPlanPhasePriceOverride(curPhase.getName(), curOverride.getCurrency(), curOverride.getFixedPrice(), curOverride.getRecurringPrice()) :
+                                        null;
+        }
+
+        for (int i = 0; i < resolvedOverride.length; i++) {
+            final PlanPhasePriceOverride curOverride = resolvedOverride[i];
+            if (curOverride != null) {
+                final DefaultPlanPhase curPhase = (DefaultPlanPhase) parentPlan.getAllPhases()[i];
+
+                if (curPhase.getFixed() == null && curOverride.getFixedPrice() != null) {
+                    final String error = String.format("There is no existing fixed price for the phase %s", curPhase.getName());
+                    throw new CatalogApiException(ErrorCode.CAT_INVALID_INVALID_PRICE_OVERRIDE, parentPlan.getName(), error);
+                }
+
+                if (curPhase.getRecurring() == null && curOverride.getRecurringPrice() != null) {
+                    final String error = String.format("There is no existing recurring price for the phase %s", curPhase.getName());
+                    throw new CatalogApiException(ErrorCode.CAT_INVALID_INVALID_PRICE_OVERRIDE, parentPlan.getName(), error);
+                }
+            }
+        }
+
+        final CatalogOverridePlanDefinitionModelDao overriddenPlan = overrideDao.getOrCreateOverridePlanDefinition(parentPlan.getName(), catalogEffectiveDate, resolvedOverride, context);
+        final String planName = new StringBuffer(parentPlan.getName()).append("-").append(overriddenPlan.getRecordId()).toString();
+        final DefaultPlan result = new DefaultPlan(planName, (DefaultPlan) parentPlan, resolvedOverride);
+        return result;
+    }
+
+    @Override
+    public DefaultPlan getOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
+
+        final Matcher m = CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
+        if (!m.matches()) {
+            throw new CatalogApiException(ErrorCode.CAT_NO_SUCH_PLAN, planName);
+        }
+        final String parentPlanName = m.group(1);
+        final Long planDefRecordId = Long.parseLong(m.group(2));
+
+        final List<CatalogOverridePhaseDefinitionModelDao> phaseDefs = overrideDao.getOverriddenPlanPhases(planDefRecordId, context);
+        final DefaultPlan defaultPlan = (DefaultPlan) catalog.findCurrentPlan(parentPlanName);
+
+        final PlanPhasePriceOverride[] overrides = createOverrides(defaultPlan, phaseDefs);
+        return new DefaultPlan(planName, defaultPlan, overrides);
+    }
+
+    private PlanPhasePriceOverride[] createOverrides(final Plan defaultPlan, final List<CatalogOverridePhaseDefinitionModelDao> phaseDefs) {
+
+        final PlanPhasePriceOverride[] result = new PlanPhasePriceOverride[defaultPlan.getAllPhases().length];
+
+        for (int i = 0; i < defaultPlan.getAllPhases().length; i++) {
+
+            final PlanPhase curPhase = defaultPlan.getAllPhases()[i];
+            final CatalogOverridePhaseDefinitionModelDao overriddenPhase = Iterables.tryFind(phaseDefs, new Predicate<CatalogOverridePhaseDefinitionModelDao>() {
+                @Override
+                public boolean apply(final CatalogOverridePhaseDefinitionModelDao input) {
+                    return input.getParentPhaseName().equals(curPhase.getName());
+                }
+            }).orNull();
+            result[i] = (overriddenPhase != null) ?
+                        new DefaultPlanPhasePriceOverride(curPhase.getName(), Currency.valueOf(overriddenPhase.getCurrency()), overriddenPhase.getFixedPrice(), overriddenPhase.getRecurringPrice()) :
+                        null;
+        }
+        return result;
+    }
+
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/override/PriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/override/PriceOverride.java
new file mode 100644
index 0000000..49cdbc8
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/override/PriceOverride.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.override;
+
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.DefaultPlan;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.StaticCatalog;
+
+public interface PriceOverride {
+
+    DefaultPlan getOrCreateOverriddenPlan(final Plan parentPlan, final DateTime catalogEffectiveDate, final List<PlanPhasePriceOverride> overrides, final InternalCallContext context) throws CatalogApiException;
+
+
+    DefaultPlan getOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
index 7b654c7..0acb75d 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
@@ -41,6 +41,8 @@ import org.killbill.billing.catalog.api.PlanAlignmentChange;
 import org.killbill.billing.catalog.api.PlanAlignmentCreate;
 import org.killbill.billing.catalog.api.PlanChangeResult;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.PriceList;
@@ -51,9 +53,12 @@ import org.killbill.billing.catalog.rules.PlanRules;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
+import com.google.common.collect.ImmutableList;
+
 @XmlRootElement(name = "catalog")
 @XmlAccessorType(XmlAccessType.NONE)
 public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> implements StaticCatalog {
+
     @XmlElement(required = true)
     private Date effectiveDate;
 
@@ -63,8 +68,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
     @XmlElement(required = true)
     private BillingMode recurringBillingMode;
 
-    private URI catalogURI;
-
     @XmlElementWrapper(name = "currencies", required = true)
     @XmlElement(name = "currency", required = true)
     private Currency[] supportedCurrencies;
@@ -87,6 +90,8 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
     @XmlElement(name = "priceLists", required = true)
     private DefaultPriceListSet priceLists;
 
+    private URI catalogURI;
+
     public StandaloneCatalog() {
     }
 
@@ -115,14 +120,14 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
     /* (non-Javadoc)
      * @see org.killbill.billing.catalog.ICatalog#getProducts()
      */
-   @Override
-   public DefaultProduct[] getCurrentProducts() {
-       return products;
-   }
-
-   /* (non-Javadoc)
-    * @see org.killbill.billing.catalog.ICatalog#getProducts()
-    */
+    @Override
+    public DefaultProduct[] getCurrentProducts() {
+        return products;
+    }
+
+    /* (non-Javadoc)
+     * @see org.killbill.billing.catalog.ICatalog#getProducts()
+     */
     @Override
     public DefaultUnit[] getCurrentUnits() {
         return units;
@@ -158,7 +163,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
       * @see org.killbill.billing.catalog.ICatalog#getPlan(java.lang.String, java.lang.String)
       */
     @Override
-    public DefaultPlan findCurrentPlan(final String productName, final BillingPeriod period, final String priceListName) throws CatalogApiException {
+    public DefaultPlan createOrFindCurrentPlan(final String productName, final BillingPeriod period, final String priceListName, final PlanPhasePriceOverridesWithCallContext unused) throws CatalogApiException {
         if (productName == null) {
             throw new CatalogApiException(ErrorCode.CAT_NULL_PRODUCT_NAME);
         }
@@ -221,7 +226,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return priceLists.findPriceListFrom(name);
     }
 
-
     //////////////////////////////////////////////////////////////////////////////
     //
     // RULES
@@ -267,7 +271,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return errors;
     }
 
-
     @Override
     public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
         catalogURI = sourceURI;
@@ -283,13 +286,12 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
 
     }
 
-
     //////////////////////////////////////////////////////////////////////////////
     //
     // UNIT LIMIT
     //
     //////////////////////////////////////////////////////////////////////////////
-    
+
     @Override
     public boolean compliesWithLimits(final String phaseName, final String unit, final double value) throws CatalogApiException {
         PlanPhase phase = findCurrentPhase(phaseName);
@@ -344,12 +346,12 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
     @Override
     public boolean canCreatePlan(final PlanSpecifier specifier) throws CatalogApiException {
         final Product product = findCurrentProduct(specifier.getProductName());
-        final Plan plan = findCurrentPlan(specifier.getProductName(), specifier.getBillingPeriod(), specifier.getPriceListName());
+        final Plan plan = createOrFindCurrentPlan(specifier.getProductName(), specifier.getBillingPeriod(), specifier.getPriceListName(), null);
         final DefaultPriceList priceList = findCurrentPriceList(specifier.getPriceListName());
 
         return (!product.isRetired()) &&
-                (!plan.isRetired()) &&
-                (!priceList.isRetired());
+               (!plan.isRetired()) &&
+               (!priceList.isRetired());
     }
 
     @Override
@@ -358,13 +360,13 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
 
         try {
             Product product = findCurrentProduct(baseProductName);
-            if ( product != null ) {
-                for ( Product availAddon : product.getAvailable() ) {
-                    for ( BillingPeriod billingPeriod : BillingPeriod.values()) {
-                        for( PriceList priceList : getPriceLists().getAllPriceLists()) {
+            if (product != null) {
+                for (Product availAddon : product.getAvailable()) {
+                    for (BillingPeriod billingPeriod : BillingPeriod.values()) {
+                        for (PriceList priceList : getPriceLists().getAllPriceLists()) {
                             if (priceListName == null || priceListName.equals(priceList.getName())) {
                                 Plan addonInList = priceList.findPlan(availAddon, billingPeriod);
-                                if ( (addonInList != null) ) {
+                                if ((addonInList != null)) {
                                     availAddons.add(new DefaultListing(addonInList, priceList));
                                 }
                             }
@@ -395,7 +397,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
                 }
             }
         }
-
         return availBasePlans;
     }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
new file mode 100644
index 0000000..11b1bfe
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog;
+
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Matcher;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingAlignment;
+import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.Listing;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanAlignmentChange;
+import org.killbill.billing.catalog.api.PlanAlignmentCreate;
+import org.killbill.billing.catalog.api.PlanChangeResult;
+import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.PlanSpecifier;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.StaticCatalog;
+import org.killbill.billing.catalog.api.Unit;
+import org.killbill.billing.catalog.override.DefaultPriceOverride;
+import org.killbill.billing.catalog.override.PriceOverride;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
+
+public class StandaloneCatalogWithPriceOverride extends ValidatingConfig<StandaloneCatalogWithPriceOverride> implements StaticCatalog {
+
+    private final StandaloneCatalog standaloneCatalog;
+    private final PriceOverride priceOverride;
+    private final Long tenantRecordId;
+
+    private final InternalCallContextFactory internalCallContextFactory;
+
+    public StandaloneCatalogWithPriceOverride(final StandaloneCatalog staticCatalog, final PriceOverride priceOverride, final Long tenantRecordId, final InternalCallContextFactory internalCallContextFactory) {
+        this.tenantRecordId = tenantRecordId;
+        this.standaloneCatalog = staticCatalog;
+        this.priceOverride = priceOverride;
+        this.internalCallContextFactory = internalCallContextFactory;
+    }
+
+    @Override
+    public String getCatalogName() {
+        return standaloneCatalog.getCatalogName();
+    }
+
+    @Override
+    public BillingMode getRecurringBillingMode() {
+        return standaloneCatalog.getRecurringBillingMode();
+    }
+
+    @Override
+    public Date getEffectiveDate() {
+        return standaloneCatalog.getEffectiveDate();
+    }
+
+    @Override
+    public Currency[] getCurrentSupportedCurrencies() throws CatalogApiException {
+        return standaloneCatalog.getCurrentSupportedCurrencies();
+    }
+
+    @Override
+    public DefaultProduct[] getCurrentProducts() throws CatalogApiException {
+        return standaloneCatalog.getCurrentProducts();
+    }
+
+    @Override
+    public Unit[] getCurrentUnits() throws CatalogApiException {
+        return standaloneCatalog.getCurrentUnits();
+    }
+
+    @Override
+    public DefaultPlan[] getCurrentPlans() throws CatalogApiException {
+        return standaloneCatalog.getCurrentPlans();
+    }
+
+    @Override
+    public Plan createOrFindCurrentPlan(final String productName, final BillingPeriod period, final String priceListName, final PlanPhasePriceOverridesWithCallContext overrides) throws CatalogApiException {
+        final Plan defaultPlan = standaloneCatalog.createOrFindCurrentPlan(productName, period, priceListName, null);
+
+        if (overrides == null ||
+            overrides.getOverrides() == null ||
+            overrides.getOverrides().isEmpty()) {
+            return defaultPlan;
+        }
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(overrides.getCallContext());
+        return priceOverride.getOrCreateOverriddenPlan(defaultPlan, new DateTime(getEffectiveDate()), overrides.getOverrides(), internalCallContext);
+    }
+
+    @Override
+    public Plan findCurrentPlan(final String planName) throws CatalogApiException {
+
+        final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
+        if (m.matches()) {
+            final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(tenantRecordId, null);
+            return priceOverride.getOverriddenPlan(planName, standaloneCatalog, internalTenantContext);
+        }
+        return standaloneCatalog.findCurrentPlan(planName);
+    }
+
+    @Override
+    public Product findCurrentProduct(final String productName) throws CatalogApiException {
+        return standaloneCatalog.findCurrentProduct(productName);
+    }
+
+    @Override
+    public PlanPhase findCurrentPhase(final String phaseName) throws CatalogApiException {
+        final String planName = DefaultPlanPhase.planName(phaseName);
+        final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
+        if (m.matches()) {
+            final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(tenantRecordId, null);
+            Plan plan = priceOverride.getOverriddenPlan(planName, standaloneCatalog, internalTenantContext);
+            return plan.findPhase(phaseName);
+        }
+        return standaloneCatalog.findCurrentPhase(phaseName);
+    }
+
+    @Override
+    public PriceList findCurrentPricelist(final String priceListName) throws CatalogApiException {
+        return standaloneCatalog.findCurrentPricelist(priceListName);
+    }
+
+    @Override
+    public BillingActionPolicy planChangePolicy(final PlanPhaseSpecifier planPhaseSpecifier, final PlanSpecifier planSpecifier) throws CatalogApiException {
+        return standaloneCatalog.planChangePolicy(planPhaseSpecifier, planSpecifier);
+    }
+
+    @Override
+    public PlanChangeResult planChange(final PlanPhaseSpecifier planPhaseSpecifier, final PlanSpecifier planSpecifier) throws CatalogApiException {
+        return standaloneCatalog.planChange(planPhaseSpecifier, planSpecifier);
+    }
+
+    @Override
+    public BillingActionPolicy planCancelPolicy(final PlanPhaseSpecifier planPhaseSpecifier) throws CatalogApiException {
+        return standaloneCatalog.planCancelPolicy(planPhaseSpecifier);
+    }
+
+    @Override
+    public PlanAlignmentCreate planCreateAlignment(final PlanSpecifier planSpecifier) throws CatalogApiException {
+        return standaloneCatalog.planCreateAlignment(planSpecifier);
+    }
+
+    @Override
+    public BillingAlignment billingAlignment(final PlanPhaseSpecifier planPhaseSpecifier) throws CatalogApiException {
+        return standaloneCatalog.billingAlignment(planPhaseSpecifier);
+    }
+
+    @Override
+    public PlanAlignmentChange planChangeAlignment(final PlanPhaseSpecifier planPhaseSpecifier, final PlanSpecifier planSpecifier) throws CatalogApiException {
+        return standaloneCatalog.planChangeAlignment(planPhaseSpecifier, planSpecifier);
+    }
+
+    @Override
+    public boolean canCreatePlan(final PlanSpecifier planSpecifier) throws CatalogApiException {
+        return standaloneCatalog.canCreatePlan(planSpecifier);
+    }
+
+    @Override
+    public List<Listing> getAvailableBasePlanListings() throws CatalogApiException {
+        return standaloneCatalog.getAvailableBasePlanListings();
+    }
+
+    @Override
+    public List<Listing> getAvailableAddOnListings(final String baseProductName, @Nullable final String priceListName) throws CatalogApiException {
+        return standaloneCatalog.getAvailableAddOnListings(baseProductName, priceListName);
+    }
+
+    @Override
+    public boolean compliesWithLimits(final String phaseName, final String unit, final double value) throws CatalogApiException {
+        return standaloneCatalog.compliesWithLimits(phaseName, unit, value);
+    }
+
+    @Override
+    public ValidationErrors validate(final StandaloneCatalogWithPriceOverride root, final ValidationErrors errors) {
+        return standaloneCatalog.validate(root.standaloneCatalog, errors);
+    }
+
+    @Override
+    public void initialize(final StandaloneCatalogWithPriceOverride root, final URI sourceURI) {
+        standaloneCatalog.initialize(root.standaloneCatalog, sourceURI);
+    }
+
+    public DefaultPriceList findCurrentPriceList(final String priceListName) throws CatalogApiException {
+        return standaloneCatalog.findCurrentPriceList(priceListName);
+    }
+
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index 9d9cfea..77de272 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
@@ -47,6 +47,8 @@ import org.killbill.billing.catalog.api.PlanAlignmentChange;
 import org.killbill.billing.catalog.api.PlanAlignmentCreate;
 import org.killbill.billing.catalog.api.PlanChangeResult;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.PriceList;
@@ -59,34 +61,37 @@ import org.killbill.xmlloader.ValidationErrors;
 
 @XmlRootElement(name = "catalog")
 @XmlAccessorType(XmlAccessType.NONE)
-public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implements Catalog, StaticCatalog {
+public class VersionedCatalog extends ValidatingConfig<StandaloneCatalogWithPriceOverride> implements Catalog, StaticCatalog {
 
     private final Clock clock;
     private String catalogName;
     private BillingMode recurringBillingMode;
+    private final Long tenantRecordId;
 
     @XmlElement(name = "catalogVersion", required = true)
-    private final List<StandaloneCatalog> versions = new ArrayList<StandaloneCatalog>();
+    private final List<StandaloneCatalogWithPriceOverride> versions = new ArrayList<StandaloneCatalogWithPriceOverride>();
 
-    // Default CTOR for XMLWriter.writeXML
+    // Required for JAXB deserialization
     public VersionedCatalog() {
         this.clock = null;
+        this.tenantRecordId = null;
     }
 
-    public VersionedCatalog(final Clock clock) {
+    public VersionedCatalog(final Clock clock, final Long tenantRecordId) {
         this.clock = clock;
+        this.tenantRecordId = tenantRecordId;
     }
 
 
     //
     // Private methods
     //
-    private StandaloneCatalog versionForDate(final DateTime date) throws CatalogApiException {
+    private StandaloneCatalogWithPriceOverride versionForDate(final DateTime date) throws CatalogApiException {
         return versions.get(indexOfVersionForDate(date.toDate()));
     }
 
-    private List<StandaloneCatalog> versionsBeforeDate(final Date date) throws CatalogApiException {
-        final List<StandaloneCatalog> result = new ArrayList<StandaloneCatalog>();
+    private List<StandaloneCatalogWithPriceOverride> versionsBeforeDate(final Date date) throws CatalogApiException {
+        final List<StandaloneCatalogWithPriceOverride> result = new ArrayList<StandaloneCatalogWithPriceOverride>();
         final int index = indexOfVersionForDate(date);
         for (int i = 0; i <= index; i++) {
             result.add(versions.get(i));
@@ -96,7 +101,7 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 
     private int indexOfVersionForDate(final Date date) throws CatalogApiException {
         for (int i = versions.size() - 1; i >= 0; i--) {
-            final StandaloneCatalog c = versions.get(i);
+            final StandaloneCatalogWithPriceOverride c = versions.get(i);
             if (c.getEffectiveDate().getTime() <= date.getTime()) {
                 return i;
             }
@@ -110,25 +115,25 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
         String productName;
         BillingPeriod bp;
         String priceListName;
+        PlanPhasePriceOverridesWithCallContext overrides;
 
         public PlanRequestWrapper(final String name) {
-            super();
             this.name = name;
         }
 
         public PlanRequestWrapper(final String productName, final BillingPeriod bp,
-                                  final String priceListName) {
-            super();
+                                  final String priceListName, final PlanPhasePriceOverridesWithCallContext overrides) {
             this.productName = productName;
             this.bp = bp;
             this.priceListName = priceListName;
+            this.overrides = overrides;
         }
 
-        public Plan findPlan(final StandaloneCatalog catalog) throws CatalogApiException {
+        public Plan findPlan(final StandaloneCatalogWithPriceOverride catalog) throws CatalogApiException {
             if (name != null) {
                 return catalog.findCurrentPlan(name);
             } else {
-                return catalog.findCurrentPlan(productName, bp, priceListName);
+                return catalog.createOrFindCurrentPlan(productName, bp, priceListName, overrides);
             }
         }
     }
@@ -137,14 +142,14 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
                           final DateTime requestedDate,
                           final DateTime subscriptionStartDate)
             throws CatalogApiException {
-        final List<StandaloneCatalog> catalogs = versionsBeforeDate(requestedDate.toDate());
+        final List<StandaloneCatalogWithPriceOverride> catalogs = versionsBeforeDate(requestedDate.toDate());
         if (catalogs.size() == 0) {
             throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, requestedDate.toDate().toString());
         }
 
         for (int i = catalogs.size() - 1; i >= 0; i--) { // Working backwards to find the latest applicable plan
-            final StandaloneCatalog c = catalogs.get(i);
-            Plan plan = null;
+            final StandaloneCatalogWithPriceOverride c = catalogs.get(i);
+            Plan plan;
             try {
                 plan = wrapper.findPlan(c);
             } catch (CatalogApiException e) {
@@ -175,7 +180,7 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
     //
     // Public methods not exposed in interface
     //
-    public void add(final StandaloneCatalog e) throws CatalogApiException {
+    public void add(final StandaloneCatalogWithPriceOverride e) throws CatalogApiException {
         if (catalogName == null) {
             catalogName = e.getCatalogName();
         } else {
@@ -191,15 +196,15 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
             }
         }
         versions.add(e);
-        Collections.sort(versions, new Comparator<StandaloneCatalog>() {
+        Collections.sort(versions, new Comparator<StandaloneCatalogWithPriceOverride>() {
             @Override
-            public int compare(final StandaloneCatalog c1, final StandaloneCatalog c2) {
+            public int compare(final StandaloneCatalogWithPriceOverride c1, final StandaloneCatalogWithPriceOverride c2) {
                 return c1.getEffectiveDate().compareTo(c2.getEffectiveDate());
             }
         });
     }
 
-    public Iterator<StandaloneCatalog> iterator() {
+    public Iterator<StandaloneCatalogWithPriceOverride> iterator() {
         return versions.iterator();
     }
 
@@ -241,12 +246,13 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
     }
 
     @Override
-    public Plan findPlan(final String productName,
+    public Plan createOrFindPlan(final String productName,
                          final BillingPeriod term,
                          final String priceListName,
+                         final PlanPhasePriceOverridesWithCallContext overrides,
                          final DateTime requestedDate)
             throws CatalogApiException {
-        return versionForDate(requestedDate).findCurrentPlan(productName, term, priceListName);
+        return versionForDate(requestedDate).createOrFindCurrentPlan(productName, term, priceListName, overrides);
     }
 
     @Override
@@ -258,13 +264,14 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
     }
 
     @Override
-    public Plan findPlan(final String productName,
+    public Plan createOrFindPlan(final String productName,
                          final BillingPeriod term,
                          final String priceListName,
+                         final PlanPhasePriceOverridesWithCallContext overrides,
                          final DateTime requestedDate,
                          final DateTime subscriptionStartDate)
             throws CatalogApiException {
-        return findPlan(new PlanRequestWrapper(productName, term, priceListName), requestedDate, subscriptionStartDate);
+        return findPlan(new PlanRequestWrapper(productName, term, priceListName, overrides), requestedDate, subscriptionStartDate);
     }
 
     //
@@ -343,15 +350,15 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
     // VerifiableConfig API
     //
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        for (final StandaloneCatalog c : versions) {
+    public void initialize(final StandaloneCatalogWithPriceOverride catalog, final URI sourceURI) {
+        for (final StandaloneCatalogWithPriceOverride c : versions) {
             c.initialize(catalog, sourceURI);
         }
     }
 
     @Override
-    public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
-        for (final StandaloneCatalog c : versions) {
+    public ValidationErrors validate(final StandaloneCatalogWithPriceOverride catalog, final ValidationErrors errors) {
+        for (final StandaloneCatalogWithPriceOverride c : versions) {
             errors.addAll(c.validate(c, errors));
         }
         //TODO MDW validation - ensure all catalog versions have a single name
@@ -397,9 +404,9 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
     }
 
     @Override
-    public Plan findCurrentPlan(final String productName, final BillingPeriod term,
-                                final String priceList) throws CatalogApiException {
-        return versionForDate(clock.getUTCNow()).findCurrentPlan(productName, term, priceList);
+    public Plan createOrFindCurrentPlan(final String productName, final BillingPeriod term,
+                                final String priceList, PlanPhasePriceOverridesWithCallContext overrides) throws CatalogApiException {
+        return versionForDate(clock.getUTCNow()).createOrFindCurrentPlan(productName, term, priceList, overrides);
     }
 
     @Override
diff --git a/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.sql.stg b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.sql.stg
new file mode 100644
index 0000000..7f27b07
--- /dev/null
+++ b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.sql.stg
@@ -0,0 +1,86 @@
+group CatalogOverridePhaseDefinitionSqlDao;
+
+tableName() ::= "catalog_override_phase_definition"
+
+
+tableFields(prefix) ::= <<
+  <prefix>parent_phase_name
+, <prefix>currency
+, <prefix>fixed_price
+, <prefix>recurring_price
+, <prefix>effective_date
+, <prefix>created_date
+, <prefix>created_by
+, <prefix>tenant_record_id
+>>
+
+allTableFields(prefix) ::= <<
+  <prefix>record_id
+, <tableFields(prefix)>
+>>
+
+
+tableValues() ::= <<
+  :parentPhaseName
+, :currency
+, :fixedPrice
+, :recurringPrice
+, :effectiveDate
+, :createdDate
+, :createdBy
+, :tenantRecordId
+>>
+
+
+allTableValues() ::= <<
+  :recordId
+, <tableValues()>
+>>
+
+create() ::= <<
+insert into <tableName()> (
+<tableFields()>
+)
+values (
+<tableValues()>
+)
+;
+>>
+
+getByRecordId() ::= <<
+select <allTableFields()>
+from <tableName()>
+where record_id = :recordId
+and tenant_record_id = :tenantRecordId
+;
+>>
+
+getByAttributes() ::= <<
+select <allTableFields()>
+from <tableName()>
+where parent_phase_name = :parentPhaseName
+and currency = :currency
+and (fixed_price = :fixedPrice or (fixed_price is null and :fixedPrice is null))
+and (recurring_price = :recurringPrice or (recurring_price is null and :recurringPrice is null))
+and tenant_record_id = :tenantRecordId
+;
+>>
+
+
+getOverriddenPlanPhases() ::= <<
+select <allTableFields("pdef.")>
+from <tableName()> pdef
+join catalog_override_plan_phase pp
+on pdef.record_id = pp.phase_def_record_id
+where
+pp.target_plan_def_record_id = :targetPlanDefRecordId
+and pp.tenant_record_id = :tenantRecordId
+order by pp.phase_number asc
+;
+>>
+
+
+getLastInsertId() ::= <<
+select LAST_INSERT_ID();
+>>
+
diff --git a/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.sql.stg b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.sql.stg
new file mode 100644
index 0000000..3024bc0
--- /dev/null
+++ b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.sql.stg
@@ -0,0 +1,56 @@
+group CatalogOverridePlanDefinitionSqlDao;
+
+tableName() ::= "catalog_override_plan_definition"
+
+tableFields(prefix) ::= <<
+  <prefix>parent_plan_name
+, <prefix>effective_date
+, <prefix>is_active
+, <prefix>created_date
+, <prefix>created_by
+, <prefix>tenant_record_id
+>>
+
+allTableFields(prefix) ::= <<
+  <prefix>record_id
+, <tableFields(prefix)>
+>>
+
+
+tableValues() ::= <<
+  :parentPlanName
+, :effectiveDate
+, :isActive
+, :createdDate
+, :createdBy
+, :tenantRecordId
+>>
+
+
+allTableValues() ::= <<
+  :recordId
+, <tableValues()>
+>>
+
+create() ::= <<
+insert into <tableName()> (
+<tableFields()>
+)
+values (
+<tableValues()>
+)
+;
+>>
+
+getByRecordId() ::= <<
+select <allTableFields()>
+from <tableName()>
+where record_id = :recordId
+and tenant_record_id = :tenantRecordId
+;
+>>
+
+getLastInsertId() ::= <<
+    select LAST_INSERT_ID();
+>>
+
diff --git a/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.sql.stg b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.sql.stg
new file mode 100644
index 0000000..356eea2
--- /dev/null
+++ b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.sql.stg
@@ -0,0 +1,78 @@
+group CatalogOverridePlanPhaseSqlDao;
+
+
+tableName() ::= "catalog_override_plan_phase"
+
+
+tableFields(prefix) ::= <<
+  <prefix>phase_number
+, <prefix>phase_def_record_id
+, <prefix>target_plan_def_record_id
+, <prefix>created_date
+, <prefix>created_by
+, <prefix>tenant_record_id
+>>
+
+allTableFields(prefix) ::= <<
+  <prefix>record_id
+, <tableFields(prefix)>
+>>
+
+
+tableValues() ::= <<
+  :phaseNumber
+, :phaseDefRecordId
+, :targetPlanDefRecordId
+, :createdDate
+, :createdBy
+, :tenantRecordId
+>>
+
+
+allTableValues() ::= <<
+  :recordId
+, <tableValues()>
+>>
+
+create() ::= <<
+insert into <tableName()> (
+<tableFields()>
+)
+values (
+<tableValues()>
+)
+;
+>>
+
+getByRecordId() ::= <<
+select <allTableFields()>
+from
+<tableName()>
+where record_id = :recordId
+and tenant_record_id = :tenantRecordId
+;
+>>
+
+getTargetPlanDefinition(keys) ::= <<
+select
+target_plan_def_record_id
+from (select
+      target_plan_def_record_id
+      , count(*) count
+      from
+      <tableName()>
+      where
+      concat_ws(',', phase_number, phase_def_record_id) in (<keys: {key | :key_<i0>}; separator="," >)
+      and tenant_record_id = :tenantRecordId
+      group by 1) tmp
+where
+1=1
+and tmp.count = :targetCount
+;
+>>
+
+
+getLastInsertId() ::= <<
+select LAST_INSERT_ID();
+>>
+
diff --git a/catalog/src/main/resources/org/killbill/billing/catalog/ddl.sql b/catalog/src/main/resources/org/killbill/billing/catalog/ddl.sql
new file mode 100644
index 0000000..0bc969c
--- /dev/null
+++ b/catalog/src/main/resources/org/killbill/billing/catalog/ddl.sql
@@ -0,0 +1,43 @@
+/*! SET storage_engine=INNODB */;
+
+DROP TABLE IF EXISTS catalog_override_plan_definition;
+CREATE TABLE catalog_override_plan_definition (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    parent_plan_name varchar(255) NOT NULL,
+    effective_date datetime NOT NULL,
+    is_active bool DEFAULT 1,
+    created_date datetime NOT NULL,
+    created_by varchar(50) NOT NULL,
+    tenant_record_id int(11) unsigned default null,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX catalog_override_plan_definition_tenant_record_id ON catalog_override_plan_definition(tenant_record_id);
+
+
+DROP TABLE IF EXISTS catalog_override_phase_definition;
+CREATE TABLE catalog_override_phase_definition (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    parent_phase_name varchar(255) NOT NULL,
+    currency char(3) NOT NULL,
+    fixed_price numeric(15,9) NULL,
+    recurring_price numeric(15,9) NULL,
+    effective_date datetime NOT NULL,
+    created_date datetime NOT NULL,
+    created_by varchar(50) NOT NULL,
+    tenant_record_id int(11) unsigned default null,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX catalog_override_phase_definition_idx ON catalog_override_phase_definition(tenant_record_id, parent_phase_name, currency);
+
+DROP TABLE IF EXISTS catalog_override_plan_phase;
+CREATE TABLE catalog_override_plan_phase (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    phase_number tinyint(3) unsigned NOT NULL,
+    phase_def_record_id int(11) unsigned NOT NULL,
+    target_plan_def_record_id int(11) unsigned NOT NULL,
+    created_date datetime NOT NULL,
+    created_by varchar(50) NOT NULL,
+    tenant_record_id int(11) unsigned default null,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX catalog_override_plan_phase_idx ON catalog_override_plan_phase(tenant_record_id, phase_number, phase_def_record_id);
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java b/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java
new file mode 100644
index 0000000..a556b15
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog;
+
+import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
+import org.killbill.billing.catalog.dao.CatalogOverrideDao;
+import org.killbill.billing.catalog.glue.TestCatalogModuleWithEmbeddedDB;
+import org.killbill.billing.catalog.override.PriceOverride;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.annotations.BeforeClass;
+
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+public class CatalogTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEmbeddedDB {
+
+    @Inject
+    protected CatalogOverrideDao catalogOverrideDao;
+
+    @Inject
+    protected IDBI dbi;
+
+    @Inject
+    protected PriceOverride priceOverride;
+
+    @Override
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/resource.properties");
+    }
+
+    @BeforeClass(groups = "slow")
+    protected void beforeClass() throws Exception {
+        final Injector injector = Guice.createInjector(new TestCatalogModuleWithEmbeddedDB(configSource));
+        injector.injectMembers(this);
+    }
+
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverrideDao.java b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverrideDao.java
new file mode 100644
index 0000000..29188c3
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverrideDao.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.CatalogTestSuiteWithEmbeddedDB;
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
+import org.killbill.billing.catalog.StandaloneCatalog;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.xmlloader.XMLLoader;
+import org.testng.annotations.Test;
+
+import com.google.common.io.Resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class TestCatalogOverrideDao extends CatalogTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testOverrideLastPhase() throws Exception {
+
+        final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarBasic.xml").toExternalForm(), StandaloneCatalog.class);
+        final Plan plan = catalog.findCurrentPlan("standard-monthly");
+
+        final PlanPhasePriceOverride[] resolvedOverrides = new PlanPhasePriceOverride[plan.getAllPhases().length];
+        resolvedOverrides[0] = null;
+        resolvedOverrides[1] = new DefaultPlanPhasePriceOverride(plan.getFinalPhase().getName(), Currency.USD, null, new BigDecimal("128.76"));
+        final CatalogOverridePlanDefinitionModelDao newPlan = catalogOverrideDao.getOrCreateOverridePlanDefinition(plan.getName(), new DateTime(catalog.getEffectiveDate()), resolvedOverrides, internalCallContext);
+        assertEquals(newPlan.getParentPlanName(), "standard-monthly");
+        assertTrue(newPlan.getIsActive());
+    }
+
+    @Test(groups = "slow")
+    public void testOverrideTwoOutOfThreePhases() throws Exception {
+
+        final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+        final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
+
+        final PlanPhasePriceOverride[] resolvedOverrides = new PlanPhasePriceOverride[plan.getAllPhases().length];
+        resolvedOverrides[0] = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[0].getName(), Currency.USD, BigDecimal.TEN, null);
+        resolvedOverrides[1] = null;
+        resolvedOverrides[2] = new DefaultPlanPhasePriceOverride(plan.getFinalPhase().getName(), Currency.USD, null, new BigDecimal("348.64"));
+        final CatalogOverridePlanDefinitionModelDao newPlan = catalogOverrideDao.getOrCreateOverridePlanDefinition(plan.getName(), new DateTime(catalog.getEffectiveDate()), resolvedOverrides, internalCallContext);
+        assertEquals(newPlan.getParentPlanName(), "discount-standard-monthly");
+        assertTrue(newPlan.getIsActive());
+
+        final List<CatalogOverridePhaseDefinitionModelDao> phases = catalogOverrideDao.getOverriddenPlanPhases(1L, internalCallContext);
+        assertEquals(phases.size(), 2);
+    }
+
+    @Test(groups = "slow")
+    public void testGetOverriddenPlanPhases() throws Exception {
+
+        final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+        final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
+
+        final PlanPhasePriceOverride[] resolvedOverrides = new PlanPhasePriceOverride[plan.getAllPhases().length];
+        resolvedOverrides[0] = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[0].getName(), Currency.USD, BigDecimal.TEN, BigDecimal.ONE);
+        resolvedOverrides[1] = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[1].getName(), Currency.USD, BigDecimal.ONE, BigDecimal.TEN);
+        resolvedOverrides[2] = new DefaultPlanPhasePriceOverride(plan.getFinalPhase().getName(), Currency.USD, BigDecimal.ZERO, new BigDecimal("348.64"));
+
+        final CatalogOverridePlanDefinitionModelDao newPlan = catalogOverrideDao.getOrCreateOverridePlanDefinition(plan.getName(), new DateTime(catalog.getEffectiveDate()), resolvedOverrides, internalCallContext);
+
+        final List<CatalogOverridePhaseDefinitionModelDao> phases = catalogOverrideDao.getOverriddenPlanPhases(newPlan.getRecordId(), internalCallContext);
+        assertEquals(phases.size(), 3);
+        for (int i = 0; i < 3; i++) {
+            final CatalogOverridePhaseDefinitionModelDao curPhase = phases.get(i);
+            assertEquals(curPhase.getCurrency(), resolvedOverrides[i].getCurrency().name());
+            assertEquals(curPhase.getFixedPrice().compareTo(resolvedOverrides[i].getFixedPrice()), 0);
+            assertEquals(curPhase.getRecurringPrice().compareTo(resolvedOverrides[i].getRecurringPrice()), 0);
+            assertEquals(curPhase.getParentPhaseName(), resolvedOverrides[i].getPhaseName());
+        }
+    }
+
+}
+
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverridePhaseDefinitionSqlDao.java b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverridePhaseDefinitionSqlDao.java
new file mode 100644
index 0000000..aa2dc48
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverridePhaseDefinitionSqlDao.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.math.BigDecimal;
+
+import org.killbill.billing.catalog.CatalogTestSuiteWithEmbeddedDB;
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+public class TestCatalogOverridePhaseDefinitionSqlDao extends CatalogTestSuiteWithEmbeddedDB {
+
+    @BeforeClass(groups = "slow")
+    public void beforeClass() throws Exception {
+        super.beforeClass();
+        ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(CatalogOverridePhaseDefinitionModelDao.class));
+    }
+
+    @Test(groups = "slow")
+    public void testBasic() throws Exception {
+
+        final CatalogOverridePhaseDefinitionModelDao obj1 = new CatalogOverridePhaseDefinitionModelDao("p1", "EUR", BigDecimal.ONE, BigDecimal.TEN, clock.getUTCNow());
+
+        performTestInTransaction(new WithCatalogOverridePhaseDefinitionSqlDaoTransaction<Void>() {
+            @Override
+            public Void doTransaction(final CatalogOverridePhaseDefinitionSqlDao sqlDao) {
+                sqlDao.create(obj1, internalCallContext);
+                final Long lastInserted = sqlDao.getLastInsertId();
+
+                final CatalogOverridePhaseDefinitionModelDao rehydrated = sqlDao.getByRecordId(lastInserted, internalCallContext);
+                assertEquals(rehydrated.getParentPhaseName(), obj1.getParentPhaseName());
+                assertEquals(rehydrated.getFixedPrice().compareTo(obj1.getFixedPrice()), 0);
+                assertEquals(rehydrated.getRecurringPrice().compareTo(obj1.getRecurringPrice()), 0);
+                assertEquals(rehydrated.getCurrency(), obj1.getCurrency());
+                return null;
+            }
+        });
+    }
+
+    @Test(groups = "slow")
+    public void testBasicWithNullPrices() throws Exception {
+
+        final CatalogOverridePhaseDefinitionModelDao obj1 = new CatalogOverridePhaseDefinitionModelDao("p2", "USD", null, new BigDecimal("54.21"), clock.getUTCNow());
+
+        performTestInTransaction(new WithCatalogOverridePhaseDefinitionSqlDaoTransaction<Void>() {
+            @Override
+            public Void doTransaction(final CatalogOverridePhaseDefinitionSqlDao sqlDao) {
+                sqlDao.create(obj1, internalCallContext);
+                final Long lastInserted = sqlDao.getLastInsertId();
+
+                final CatalogOverridePhaseDefinitionModelDao rehydrated = sqlDao.getByRecordId(lastInserted, internalCallContext);
+                assertEquals(rehydrated.getParentPhaseName(), obj1.getParentPhaseName());
+                assertNull(rehydrated.getFixedPrice());
+                assertEquals(rehydrated.getRecurringPrice().compareTo(obj1.getRecurringPrice()), 0);
+                assertEquals(rehydrated.getCurrency(), obj1.getCurrency());
+                return null;
+            }
+        });
+    }
+
+    @Test(groups = "slow")
+    public void testGetByAttributes() throws Exception {
+
+        final CatalogOverridePhaseDefinitionModelDao objWithNoNullPrices = new CatalogOverridePhaseDefinitionModelDao("p2", "USD", BigDecimal.ZERO, new BigDecimal("12.453"), clock.getUTCNow());
+        final CatalogOverridePhaseDefinitionModelDao objWithNullFixedPrice = new CatalogOverridePhaseDefinitionModelDao("p3", "BTC", null, new BigDecimal("14.443"), clock.getUTCNow());
+        final CatalogOverridePhaseDefinitionModelDao objWithNullRecurringPrice = new CatalogOverridePhaseDefinitionModelDao("p4", "EUR", new BigDecimal("11.243"), null, clock.getUTCNow());
+
+        performTestInTransaction(new WithCatalogOverridePhaseDefinitionSqlDaoTransaction<Void>() {
+            @Override
+            public Void doTransaction(final CatalogOverridePhaseDefinitionSqlDao sqlDao) {
+                sqlDao.create(objWithNoNullPrices, internalCallContext);
+                checkRehydrated(objWithNoNullPrices, sqlDao);
+
+                sqlDao.create(objWithNullFixedPrice, internalCallContext);
+                checkRehydrated(objWithNullFixedPrice, sqlDao);
+
+                sqlDao.create(objWithNullRecurringPrice, internalCallContext);
+                checkRehydrated(objWithNullRecurringPrice, sqlDao);
+                return null;
+            }
+
+            private void checkRehydrated(final CatalogOverridePhaseDefinitionModelDao obj, final CatalogOverridePhaseDefinitionSqlDao sqlDao) {
+                final CatalogOverridePhaseDefinitionModelDao rehydrated = sqlDao.getByAttributes(obj.getParentPhaseName(), obj.getCurrency(), obj.getFixedPrice(), obj.getRecurringPrice(), internalCallContext);
+                assertEquals(rehydrated.getParentPhaseName(), obj.getParentPhaseName());
+                if (obj.getFixedPrice() != null) {
+                    assertEquals(rehydrated.getFixedPrice().compareTo(obj.getFixedPrice()), 0);
+                } else {
+                    assertNull(rehydrated.getFixedPrice());
+                }
+                if (obj.getRecurringPrice() != null) {
+                    assertEquals(rehydrated.getRecurringPrice().compareTo(obj.getRecurringPrice()), 0);
+                } else {
+                    assertNull(rehydrated.getRecurringPrice());
+                }
+                assertEquals(rehydrated.getCurrency(), obj.getCurrency());
+            }
+        });
+    }
+
+    private interface WithCatalogOverridePhaseDefinitionSqlDaoTransaction<T> {
+
+        public <T> T doTransaction(final CatalogOverridePhaseDefinitionSqlDao sqlDao);
+    }
+
+    private <T> T performTestInTransaction(final WithCatalogOverridePhaseDefinitionSqlDaoTransaction<T> callback) {
+        return dbi.inTransaction(new TransactionCallback<T>() {
+            @Override
+            public T inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+                final CatalogOverridePhaseDefinitionSqlDao sqlDao = handle.attach(CatalogOverridePhaseDefinitionSqlDao.class);
+                return callback.doTransaction(sqlDao);
+            }
+        });
+    }
+
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverridePlanDefinitionSqlDao.java b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverridePlanDefinitionSqlDao.java
new file mode 100644
index 0000000..89844d8
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverridePlanDefinitionSqlDao.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.math.BigDecimal;
+
+import org.killbill.billing.catalog.CatalogTestSuiteWithEmbeddedDB;
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestCatalogOverridePlanDefinitionSqlDao extends CatalogTestSuiteWithEmbeddedDB {
+
+    @BeforeClass(groups = "slow")
+    public void beforeClass() throws Exception {
+        super.beforeClass();
+        ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(CatalogOverridePlanDefinitionModelDao.class));
+    }
+
+    @Test(groups = "slow")
+    public void testBasic() throws Exception {
+
+        final CatalogOverridePlanDefinitionModelDao obj1 = new CatalogOverridePlanDefinitionModelDao("p1", true, clock.getUTCNow());
+
+        performTestInTransaction(new WithCatalogOverridePlanDefinitionSqlDaoTransaction<Void>() {
+            @Override
+            public Void doTransaction(final CatalogOverridePlanDefinitionSqlDao sqlDao) {
+                sqlDao.create(obj1, internalCallContext);
+                final Long lastInserted = sqlDao.getLastInsertId();
+
+                final CatalogOverridePlanDefinitionModelDao rehydrated = sqlDao.getByRecordId(lastInserted, internalCallContext);
+                assertEquals(rehydrated.getParentPlanName(), obj1.getParentPlanName());
+                assertEquals(rehydrated.getIsActive(), obj1.getIsActive());
+                return null;
+            }
+        });
+    }
+
+    private interface WithCatalogOverridePlanDefinitionSqlDaoTransaction<T> {
+
+        public <T> T doTransaction(final CatalogOverridePlanDefinitionSqlDao sqlDao);
+    }
+
+    private <T> T performTestInTransaction(final WithCatalogOverridePlanDefinitionSqlDaoTransaction<T> callback) {
+        return dbi.inTransaction(new TransactionCallback<T>() {
+            @Override
+            public T inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+                final CatalogOverridePlanDefinitionSqlDao sqlDao = handle.attach(CatalogOverridePlanDefinitionSqlDao.class);
+                return callback.doTransaction(sqlDao);
+            }
+        });
+    }
+
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverridePlanPhaseSqlDao.java b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverridePlanPhaseSqlDao.java
new file mode 100644
index 0000000..2aed21b
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverridePlanPhaseSqlDao.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.dao;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.killbill.billing.catalog.CatalogTestSuiteWithEmbeddedDB;
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestCatalogOverridePlanPhaseSqlDao extends CatalogTestSuiteWithEmbeddedDB {
+
+
+    @BeforeClass(groups = "slow")
+    public void beforeClass() throws Exception {
+        super.beforeClass();
+        ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(CatalogOverridePlanPhaseModelDao.class));
+    }
+
+    @Test(groups = "slow")
+    public void testBasic() throws Exception {
+
+        final CatalogOverridePlanPhaseModelDao obj1 = new CatalogOverridePlanPhaseModelDao((short) 1, 2L, 3L);
+
+        performTestInTransaction(new WithCatalogOverridePlanPhaseSqlDaoTransaction<Void>() {
+            @Override
+            public Void doTransaction(final CatalogOverridePlanPhaseSqlDao sqlDao) {
+                sqlDao.create(obj1, internalCallContext);
+                final Long lastInserted = sqlDao.getLastInsertId();
+
+                final CatalogOverridePlanPhaseModelDao rehydrated = sqlDao.getByRecordId(lastInserted, internalCallContext);
+                assertEquals(rehydrated.getPhaseNumber(), obj1.getPhaseNumber());
+                assertEquals(rehydrated.getPhaseDefRecordId(), obj1.getPhaseDefRecordId());
+                assertEquals(rehydrated.getTargetPlanDefRecordId(), obj1.getTargetPlanDefRecordId());
+                return null;
+            }
+        });
+    }
+
+
+    @Test(groups = "slow")
+    public void testGetTargetPlanDefinition() throws Exception {
+
+        final CatalogOverridePlanPhaseModelDao obj1 = new CatalogOverridePlanPhaseModelDao((short) 1, 2L, 3L);
+        final CatalogOverridePlanPhaseModelDao obj2 = new CatalogOverridePlanPhaseModelDao((short) 2, 5L, 3L);
+        final CatalogOverridePlanPhaseModelDao obj3 = new CatalogOverridePlanPhaseModelDao((short) 4, 7L, 3L);
+        final CatalogOverridePlanPhaseModelDao nobj1 = new CatalogOverridePlanPhaseModelDao((short) 4, 7L, 4L);
+
+        performTestInTransaction(new WithCatalogOverridePlanPhaseSqlDaoTransaction<Void>() {
+            @Override
+            public Void doTransaction(final CatalogOverridePlanPhaseSqlDao sqlDao) {
+                sqlDao.create(obj1, internalCallContext);
+                sqlDao.create(obj2, internalCallContext);
+                sqlDao.create(obj3, internalCallContext);
+                sqlDao.create(nobj1, internalCallContext);
+
+                final List<String> keys = new ArrayList<String>();
+                keys.add("1,2");
+                keys.add("2,5");
+                keys.add("4,7");
+                final Long targetPlan = sqlDao.getTargetPlanDefinition(keys, keys.size(), internalCallContext);
+                assertEquals(targetPlan, new Long(3));
+                return null;
+            }
+        });
+    }
+
+    private interface WithCatalogOverridePlanPhaseSqlDaoTransaction<T> {
+
+        public <T> T doTransaction(final CatalogOverridePlanPhaseSqlDao sqlDao);
+    }
+
+    private <T> T performTestInTransaction(final WithCatalogOverridePlanPhaseSqlDaoTransaction<T> callback) {
+        return dbi.inTransaction(new TransactionCallback<T>() {
+            @Override
+            public T inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+                final CatalogOverridePlanPhaseSqlDao sqlDao = handle.attach(CatalogOverridePlanPhaseSqlDao.class);
+                return callback.doTransaction(sqlDao);
+            }
+        });
+    }
+
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java
index 9af00a4..b6f5354 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java
@@ -33,7 +33,6 @@ public class TestCatalogModule extends CatalogModule {
     @Override
     public void configure() {
         super.configure();
-        install(new GuicyKillbillTestNoDBModule(configSource));
         install(new MockNonEntityDaoModule(configSource));
         install(new CacheModule(configSource));
         install(new MockTenantModule(configSource));
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleNoDB.java b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleNoDB.java
index 16639e2..d85629c 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleNoDB.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleNoDB.java
@@ -18,11 +18,25 @@
 
 package org.killbill.billing.catalog.glue;
 
+import org.killbill.billing.GuicyKillbillTestNoDBModule;
+import org.killbill.billing.catalog.dao.CatalogOverrideDao;
 import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.mockito.Mockito;
 
 public class TestCatalogModuleNoDB extends TestCatalogModule {
 
+    protected void installCatalogDao() {
+        final CatalogOverrideDao mockCatalogOverrideDao = Mockito.mock(CatalogOverrideDao.class);
+        bind(CatalogOverrideDao.class).toInstance(mockCatalogOverrideDao);
+    }
+
     public TestCatalogModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
+
+    @Override
+    public void configure() {
+        super.configure();
+        install(new GuicyKillbillTestNoDBModule(configSource));
+    }
 }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleWithEmbeddedDB.java b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleWithEmbeddedDB.java
new file mode 100644
index 0000000..a8fa91f
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleWithEmbeddedDB.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog.glue;
+
+import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+
+public class TestCatalogModuleWithEmbeddedDB extends TestCatalogModule {
+
+    public TestCatalogModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
+
+    @Override
+    public void configure() {
+        super.configure();
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
+    }
+
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java
index 64d5075..d6f212e 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java
@@ -31,11 +31,10 @@ import javax.xml.transform.TransformerException;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.catalog.CatalogTestSuiteNoDB;
-import org.killbill.billing.catalog.StandaloneCatalog;
+import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
 import org.killbill.billing.catalog.VersionedCatalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.InvalidConfigException;
-import org.killbill.billing.platform.api.KillbillService.ServiceException;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 import org.xml.sax.SAXException;
@@ -117,9 +116,9 @@ public class TestVersionedCatalogLoader extends CatalogTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testLoad() throws IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, CatalogApiException {
-        final VersionedCatalog c = loader.load(Resources.getResource("versionedCatalog").toString());
+        final VersionedCatalog c = loader.loadDefaultCatalog(Resources.getResource("versionedCatalog").toString());
         Assert.assertEquals(c.size(), 3);
-        final Iterator<StandaloneCatalog> it = c.iterator();
+        final Iterator<StandaloneCatalogWithPriceOverride> it = c.iterator();
         DateTime dt = new DateTime("2011-01-01T00:00:00+00:00");
         Assert.assertEquals(it.next().getEffectiveDate(), dt.toDate());
         dt = new DateTime("2011-02-02T00:00:00+00:00");
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
index 7e102ea..b6df395 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
@@ -17,6 +17,7 @@
 package org.killbill.billing.catalog;
 
 import java.util.Date;
+import java.util.List;
 
 import org.joda.time.DateTime;
 
@@ -31,6 +32,8 @@ import org.killbill.billing.catalog.api.PlanAlignmentChange;
 import org.killbill.billing.catalog.api.PlanAlignmentCreate;
 import org.killbill.billing.catalog.api.PlanChangeResult;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.PriceList;
@@ -41,6 +44,8 @@ import org.killbill.billing.catalog.rules.CaseChangePlanPolicy;
 import org.killbill.billing.catalog.rules.CaseCreateAlignment;
 import org.killbill.billing.catalog.rules.PlanRules;
 
+import com.google.common.collect.ImmutableList;
+
 public class MockCatalog extends StandaloneCatalog implements Catalog {
 
     private static final String[] PRODUCT_NAMES = new String[]{"TestProduct1", "TestProduct2", "TestProduct3"};
@@ -107,9 +112,9 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
     }
 
     @Override
-    public Plan findPlan(final String productName, final BillingPeriod term, final String priceListName, final DateTime requestedDate)
+    public Plan createOrFindPlan(final String productName, final BillingPeriod term, final String priceListName, PlanPhasePriceOverridesWithCallContext overrides, final DateTime requestedDate)
             throws CatalogApiException {
-        return findCurrentPlan(productName, term, priceListName);
+        return createOrFindCurrentPlan(productName, term, priceListName, overrides);
     }
 
     @Override
@@ -119,9 +124,9 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
     }
 
     @Override
-    public Plan findPlan(final String productName, final BillingPeriod term, final String priceListName, final DateTime requestedDate,
+    public Plan createOrFindPlan(final String productName, final BillingPeriod term, final String priceListName, PlanPhasePriceOverridesWithCallContext overrides, final DateTime requestedDate,
                          final DateTime subscriptionStartDate) throws CatalogApiException {
-        return findCurrentPlan(productName, term, priceListName);
+        return createOrFindCurrentPlan(productName, term, priceListName, overrides);
     }
 
     @Override
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockInternationalPrice.java b/catalog/src/test/java/org/killbill/billing/catalog/MockInternationalPrice.java
index 1500ce1..1e7f747 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockInternationalPrice.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockInternationalPrice.java
@@ -34,6 +34,7 @@ public class MockInternationalPrice extends DefaultInternationalPrice {
         return new MockInternationalPrice(new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(value)));
     }
 
+
     public MockInternationalPrice(final DefaultPrice... price) {
         setPrices(price);
     }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java b/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
new file mode 100644
index 0000000..abf31f7
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.catalog;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.CurrencyValueNull;
+import org.killbill.billing.catalog.api.InternationalPrice;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.Price;
+import org.killbill.billing.catalog.override.DefaultPriceOverride;
+import org.killbill.xmlloader.XMLLoader;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+public class TestDefaultPriceOverride extends CatalogTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testBasic() throws Exception {
+
+        final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+        final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
+
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        final PlanPhasePriceOverride phase1 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[0].getName(), Currency.USD, BigDecimal.ONE, null);
+        overrides.add(phase1);
+        final PlanPhasePriceOverride phase3 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[2].getName(), Currency.USD, null, new BigDecimal("142.41"));
+        overrides.add(phase3);
+
+        final DefaultPlan overriddenPlan = priceOverride.getOrCreateOverriddenPlan(plan, new DateTime(catalog.getEffectiveDate()), overrides, internalCallContext);
+
+        final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(overriddenPlan.getName());
+        assertTrue(m.matches());
+        assertEquals(m.group(1), plan.getName());
+        assertEquals(m.group(2), "1"); // first entry in the table
+
+        assertEquals(overriddenPlan.getProduct().getName(), plan.getProduct().getName());
+        assertEquals(overriddenPlan.getRecurringBillingPeriod(), plan.getRecurringBillingPeriod());
+        if (plan.getEffectiveDateForExistingSubscriptons() != null) {
+            assertEquals(overriddenPlan.getEffectiveDateForExistingSubscriptons().compareTo(plan.getEffectiveDateForExistingSubscriptons()), 0);
+        }
+        assertNotEquals(overriddenPlan.getFinalPhase().getName(), plan.getFinalPhase().getName());
+        assertEquals(overriddenPlan.getPlansAllowedInBundle(), plan.getPlansAllowedInBundle());
+
+        assertEquals(overriddenPlan.getAllPhases().length, overriddenPlan.getAllPhases().length);
+        for (int i = 0; i < overriddenPlan.getAllPhases().length; i++) {
+
+            final DefaultPlanPhase initialPhase = (DefaultPlanPhase) plan.getAllPhases()[i];
+            final DefaultPlanPhase newPhase = (DefaultPlanPhase) overriddenPlan.getAllPhases()[i];
+
+            final PlanPhasePriceOverride override = Iterables.tryFind(overrides, new Predicate<PlanPhasePriceOverride>() {
+                @Override
+                public boolean apply(final PlanPhasePriceOverride input) {
+                    return input.getPhaseName().equals(initialPhase.getName());
+                }
+            }).orNull();
+
+            assertNotEquals(newPhase.getName(), initialPhase.getName());
+            assertEquals(newPhase.getDuration(), initialPhase.getDuration());
+            assertEquals(newPhase.getPhaseType(), initialPhase.getPhaseType());
+            assertEquals(newPhase.getUsages().length, initialPhase.getUsages().length);
+            if (initialPhase.getFixed() != null) {
+                assertEquals(newPhase.getFixed().getType(), initialPhase.getFixed().getType());
+                assertInternationalPrice(newPhase.getFixed().getPrice(), initialPhase.getFixed().getPrice(), override, true);
+            }
+            if (initialPhase.getRecurring() != null) {
+                assertInternationalPrice(newPhase.getRecurring().getRecurringPrice(), initialPhase.getRecurring().getRecurringPrice(), override, false);
+            }
+        }
+    }
+
+    @Test(groups = "slow", expectedExceptions = CatalogApiException.class)
+    public void testWithInvalidPriceOverride() throws Exception {
+
+        final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+        final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
+
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        final PlanPhasePriceOverride phase1 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[0].getName(), Currency.USD, null, BigDecimal.ONE);
+        overrides.add(phase1);
+
+        priceOverride.getOrCreateOverriddenPlan(plan, new DateTime(catalog.getEffectiveDate()), overrides, internalCallContext);
+
+    }
+
+    @Test(groups = "slow")
+    public void testGetOverriddenPlan() throws Exception {
+
+        final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+        final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
+
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        final PlanPhasePriceOverride phase1 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[0].getName(), Currency.USD, BigDecimal.ONE, null);
+        overrides.add(phase1);
+        final PlanPhasePriceOverride phase3 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[2].getName(), Currency.USD, null, new BigDecimal("142.41"));
+        overrides.add(phase3);
+
+        final DefaultPlan overriddenPlanCreated = priceOverride.getOrCreateOverriddenPlan(plan, new DateTime(catalog.getEffectiveDate()), overrides, internalCallContext);
+
+        System.out.println("overriddenPlanCreated = " + overriddenPlanCreated.getName());
+
+        final DefaultPlan overriddenPlan = priceOverride.getOverriddenPlan(overriddenPlanCreated.getName(), catalog, internalCallContext);
+
+        assertEquals(overriddenPlan.getProduct().getName(), plan.getProduct().getName());
+        assertEquals(overriddenPlan.getRecurringBillingPeriod(), plan.getRecurringBillingPeriod());
+        if (plan.getEffectiveDateForExistingSubscriptons() != null) {
+            assertEquals(overriddenPlan.getEffectiveDateForExistingSubscriptons().compareTo(plan.getEffectiveDateForExistingSubscriptons()), 0);
+        }
+        assertNotEquals(overriddenPlan.getFinalPhase().getName(), plan.getFinalPhase().getName());
+        assertEquals(overriddenPlan.getPlansAllowedInBundle(), plan.getPlansAllowedInBundle());
+
+        assertEquals(overriddenPlan.getAllPhases().length, overriddenPlan.getAllPhases().length);
+        for (int i = 0; i < overriddenPlan.getAllPhases().length; i++) {
+
+            final DefaultPlanPhase initialPhase = (DefaultPlanPhase) plan.getAllPhases()[i];
+            final DefaultPlanPhase newPhase = (DefaultPlanPhase) overriddenPlan.getAllPhases()[i];
+
+            final PlanPhasePriceOverride override = Iterables.tryFind(overrides, new Predicate<PlanPhasePriceOverride>() {
+                @Override
+                public boolean apply(final PlanPhasePriceOverride input) {
+                    return input.getPhaseName().equals(initialPhase.getName());
+                }
+            }).orNull();
+
+            assertNotEquals(newPhase.getName(), initialPhase.getName());
+            assertEquals(newPhase.getName(), overriddenPlan.getName() + "-" +  initialPhase.getName().split("-")[initialPhase.getName().split("-").length -1]);
+            assertEquals(newPhase.getDuration(), initialPhase.getDuration());
+            assertEquals(newPhase.getPhaseType(), initialPhase.getPhaseType());
+            assertEquals(newPhase.getUsages().length, initialPhase.getUsages().length);
+            if (initialPhase.getFixed() != null) {
+                assertEquals(newPhase.getFixed().getType(), initialPhase.getFixed().getType());
+                assertInternationalPrice(newPhase.getFixed().getPrice(), initialPhase.getFixed().getPrice(), override, true);
+            }
+            if (initialPhase.getRecurring() != null) {
+                assertInternationalPrice(newPhase.getRecurring().getRecurringPrice(), initialPhase.getRecurring().getRecurringPrice(), override, false);
+            }
+        }
+    }
+
+    private void assertInternationalPrice(final InternationalPrice newInternationalPrice, final InternationalPrice initInternationalPrice, final PlanPhasePriceOverride override, final boolean isFixed) throws CurrencyValueNull {
+        assertEquals(newInternationalPrice.getPrices().length, initInternationalPrice.getPrices().length);
+        for (int i = 0; i < newInternationalPrice.getPrices().length; i++) {
+            final Price initPrice = initInternationalPrice.getPrices()[i];
+            final Price newPrice = newInternationalPrice.getPrices()[i];
+            if (override != null && override.getCurrency() == initPrice.getCurrency() &&
+                ((isFixed && override.getFixedPrice() != null) || (!isFixed && override.getRecurringPrice() != null))) {
+                assertEquals(newPrice.getValue().compareTo(isFixed ? override.getFixedPrice() : override.getRecurringPrice()), 0);
+            } else {
+                if (initPrice != null && initPrice.getValue() != null) {
+                    assertEquals(newPrice.getValue().compareTo(initPrice.getValue()), 0);
+                }
+            }
+        }
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestLimits.java b/catalog/src/test/java/org/killbill/billing/catalog/TestLimits.java
index 7cfe30c..bd730c4 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestLimits.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestLimits.java
@@ -30,7 +30,7 @@ public class TestLimits extends CatalogTestSuiteNoDB {
     @BeforeClass(groups = "fast")
     public void beforeClass() throws Exception {
         super.beforeClass();
-        catalog = loader.load(Resources.getResource("WeaponsHireSmall.xml").toString());
+        catalog = loader.loadDefaultCatalog(Resources.getResource("WeaponsHireSmall.xml").toString());
     }
 
     @Test(groups = "fast")
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
index b004cd8..12d60f5 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
@@ -47,12 +47,12 @@ public class TestVersionedCatalog extends CatalogTestSuiteNoDB {
     @BeforeClass(groups = "fast")
     public void beforeClass() throws Exception {
         super.beforeClass();
-        vc = loader.load(Resources.getResource("versionedCatalog").toString());
+        vc = loader.loadDefaultCatalog(Resources.getResource("versionedCatalog").toString());
     }
 
     @Test(groups = "fast")
     public void testAddCatalog() throws IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException, CatalogApiException {
-        vc.add((new StandaloneCatalog(new Date()).setCatalogName(vc.getCatalogName()).setRecurringBillingMode(vc.getRecurringBillingMode())));
+        vc.add(new StandaloneCatalogWithPriceOverride(new StandaloneCatalog(new Date()).setCatalogName(vc.getCatalogName()).setRecurringBillingMode(vc.getRecurringBillingMode()), null, 0L, null));
         Assert.assertEquals(vc.size(), 4);
     }
 
diff --git a/catalog/src/test/resources/resource.properties b/catalog/src/test/resources/resource.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/catalog/src/test/resources/resource.properties
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 da643e6..004a24f 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
@@ -18,6 +18,7 @@ package org.killbill.billing.entitlement.api;
 
 import java.io.IOException;
 import java.util.Collection;
+import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -30,6 +31,7 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
@@ -372,7 +374,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
     }
 
     @Override
-    public Entitlement changePlan(final String productName, final BillingPeriod billingPeriod, final String priceList, final CallContext callContext) throws EntitlementApiException {
+    public Entitlement changePlan(final String productName, final BillingPeriod billingPeriod, final String priceList, final List<PlanPhasePriceOverride> overrides, final CallContext callContext) throws EntitlementApiException {
         // Get the latest state from disk
         refresh(callContext);
 
@@ -389,7 +391,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
 
         final DateTime effectiveChangeDate;
         try {
-            effectiveChangeDate = getSubscriptionBase().changePlan(productName, billingPeriod, priceList, callContext);
+            effectiveChangeDate = getSubscriptionBase().changePlan(productName, billingPeriod, priceList, overrides, callContext);
         } catch (SubscriptionBaseApiException e) {
             throw new EntitlementApiException(e);
         }
@@ -400,7 +402,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
     }
 
     @Override
-    public Entitlement changePlanWithDate(final String productName, final BillingPeriod billingPeriod, final String priceList, final LocalDate localDate, final CallContext callContext) throws EntitlementApiException {
+    public Entitlement changePlanWithDate(final String productName, final BillingPeriod billingPeriod, final String priceList, final List<PlanPhasePriceOverride> overrides, final LocalDate localDate, final CallContext callContext) throws EntitlementApiException {
         // Get the latest state from disk
         refresh(callContext);
 
@@ -417,7 +419,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
 
         final DateTime effectiveChangeDate = dateHelper.fromLocalDateAndReferenceTime(localDate, getSubscriptionBase().getStartDate(), context);
         try {
-            getSubscriptionBase().changePlanWithDate(productName, billingPeriod, priceList, effectiveChangeDate, callContext);
+            getSubscriptionBase().changePlanWithDate(productName, billingPeriod, priceList, overrides, effectiveChangeDate, callContext);
         } catch (SubscriptionBaseApiException e) {
             throw new EntitlementApiException(e);
         }
@@ -428,7 +430,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
     }
 
     @Override
-    public Entitlement changePlanOverrideBillingPolicy(final String productName, final BillingPeriod billingPeriod, final String priceList, final LocalDate localDateX, final BillingActionPolicy actionPolicy, final CallContext callContext) throws EntitlementApiException {
+    public Entitlement changePlanOverrideBillingPolicy(final String productName, final BillingPeriod billingPeriod, final String priceList, final List<PlanPhasePriceOverride> overrides, final LocalDate localDate, final BillingActionPolicy actionPolicy, final CallContext callContext) throws EntitlementApiException {
         // Get the latest state from disk
         refresh(callContext);
 
@@ -445,7 +447,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
 
         final DateTime effectiveChangeDate;
         try {
-            effectiveChangeDate = getSubscriptionBase().changePlanWithPolicy(productName, billingPeriod, priceList, actionPolicy, callContext);
+            effectiveChangeDate = getSubscriptionBase().changePlanWithPolicy(productName, billingPeriod, priceList, overrides, actionPolicy, callContext);
         } catch (SubscriptionBaseApiException e) {
             throw new EntitlementApiException(e);
         }
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 d74f611..3874218 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
@@ -24,6 +24,7 @@ import javax.inject.Inject;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -114,7 +115,7 @@ public class DefaultEntitlementApi implements EntitlementApi {
     }
 
     @Override
-    public Entitlement createBaseEntitlement(final UUID accountId, final PlanPhaseSpecifier planPhaseSpecifier, final String externalKey, final LocalDate effectiveDate, final CallContext callContext) throws EntitlementApiException {
+    public Entitlement createBaseEntitlement(final UUID accountId, final PlanPhaseSpecifier planPhaseSpecifier, final String externalKey, final List<PlanPhasePriceOverride> overrides, final LocalDate effectiveDate, final CallContext callContext) throws EntitlementApiException {
         final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
         try {
 
@@ -126,7 +127,7 @@ public class DefaultEntitlementApi implements EntitlementApi {
 
             final DateTime referenceTime = clock.getUTCNow();
             final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, referenceTime, contextWithValidAccountRecordId);
-            final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundle.getId(), planPhaseSpecifier, requestedDate, contextWithValidAccountRecordId);
+            final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundle.getId(), planPhaseSpecifier, overrides, requestedDate, contextWithValidAccountRecordId);
 
             return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, this,
                                           blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
@@ -137,7 +138,7 @@ public class DefaultEntitlementApi implements EntitlementApi {
     }
 
     @Override
-    public Entitlement addEntitlement(final UUID bundleId, final PlanPhaseSpecifier planPhaseSpecifier, final LocalDate effectiveDate, final CallContext callContext) throws EntitlementApiException {
+    public Entitlement addEntitlement(final UUID bundleId, final PlanPhaseSpecifier planPhaseSpecifier, final List<PlanPhasePriceOverride> overrides, final LocalDate effectiveDate, final CallContext callContext) throws EntitlementApiException {
         final EventsStream eventsStreamForBaseSubscription = eventsStreamBuilder.buildForBaseSubscription(bundleId, callContext);
 
         // Check the base entitlement state is active
@@ -154,7 +155,7 @@ public class DefaultEntitlementApi implements EntitlementApi {
 
         try {
             final InternalCallContext context = internalCallContextFactory.createInternalCallContext(callContext);
-            final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundleId, planPhaseSpecifier, requestedDate, context);
+            final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundleId, planPhaseSpecifier, overrides, requestedDate, context);
 
             return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, this,
                                           blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
index 89d16e9..7fa6a31 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
@@ -47,7 +47,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
         assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
 
@@ -74,7 +74,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
         assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
 
@@ -108,7 +108,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
         assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
 
@@ -141,7 +141,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.BLOCK);
@@ -166,7 +166,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         final DateTime ctd = clock.getUTCNow().plusDays(30).plusMonths(1);
@@ -209,7 +209,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         final DateTime ctd = clock.getUTCNow().plusDays(30).plusMonths(1);
@@ -249,12 +249,12 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         // Immediate change during trial
         testListener.pushExpectedEvent(NextEvent.CHANGE);
-        entitlement.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, callContext);
+        entitlement.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, callContext);
         assertListenerStatus();
 
         // Verify the change is immediate
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
index 77f5457..044bf37 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -54,14 +54,14 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
 
         // Keep the same object for the whole test, to make sure we refresh its state before r/w calls
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         // Add ADD_ON
         // Keep the same object for the whole test, to make sure we refresh its state before r/w calls
         final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement addOnEntitlement = entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, initialDate, callContext);
+        final Entitlement addOnEntitlement = entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, initialDate, callContext);
         assertListenerStatus();
         try {
             entitlement.uncancelEntitlement(callContext);
@@ -118,7 +118,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
 
         // Keep the same object for the whole test, to make sure we refresh its state before r/w calls
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         testListener.pushExpectedEvent(NextEvent.PHASE);
@@ -151,7 +151,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
         assertEquals(entitlement.getAccountId(), account.getId());
         assertEquals(entitlement.getExternalKey(), account.getExternalKey());
@@ -243,13 +243,13 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         // Add ADD_ON
         final PlanPhaseSpecifier spec1 = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement telescopicEntitlement = entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec1, initialDate, callContext);
+        final Entitlement telescopicEntitlement = entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec1, null, initialDate, callContext);
         assertListenerStatus();
 
         assertEquals(telescopicEntitlement.getAccountId(), account.getId());
@@ -282,14 +282,14 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         clock.addDays(1);
         final LocalDate effectiveDateSpec1 = new LocalDate(clock.getUTCNow(), account.getTimeZone());
         final PlanPhaseSpecifier spec1 = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement telescopicEntitlement = entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec1, effectiveDateSpec1, callContext);
+        final Entitlement telescopicEntitlement = entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec1, null, effectiveDateSpec1, callContext);
         assertListenerStatus();
 
         // Block all entitlement in the bundle
@@ -323,7 +323,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
         // Try to add an ADD_ON, it should fail
         try {
             final PlanPhaseSpecifier spec3 = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-            final Entitlement telescopicEntitlement3 = entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec1, effectiveDateSpec1, callContext);
+            final Entitlement telescopicEntitlement3 = entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec1, null, effectiveDateSpec1, callContext);
         } catch (EntitlementApiException e) {
             assertEquals(e.getCode(), ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION.getCode());
         }
@@ -361,7 +361,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
         // Create entitlement
         testListener.pushExpectedEvent(NextEvent.CREATE);
         final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         // Get the phase event out of the way
@@ -408,7 +408,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
 
         // Create entitlement
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(accountSrc.getId(), spec, accountSrc.getExternalKey(), initialDate, callContext);
+        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(accountSrc.getId(), spec, accountSrc.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         final DateTime ctd = clock.getUTCNow().plusDays(30).plusMonths(1);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
index 3ca1389..92ba55d 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
@@ -52,10 +52,10 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
         final Account account = accountApi.createAccount(getAccountData(7), callContext);
         final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
         testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE, NextEvent.BLOCK);
-        final Entitlement entitlement1 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), initialDate, callContext);
+        final Entitlement entitlement1 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, initialDate, callContext);
         // Sleep 1 sec so created date are apparts from each other and ordering in the bundle does not default on the UUID which is random.
         try {Thread.sleep(1000); } catch (InterruptedException ignore) {};
-        final Entitlement entitlement2 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), initialDate, callContext);
+        final Entitlement entitlement2 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, initialDate, callContext);
         entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "stateName", "service", false, false, false, clock.getUTCNow()),
                                                                         internalCallContextFactory.createInternalCallContext(account.getId(), callContext));
         assertListenerStatus();
@@ -98,7 +98,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, externalKey, initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, externalKey, null, initialDate, callContext);
         assertListenerStatus();
         assertEquals(entitlement.getAccountId(), account.getId());
         assertEquals(entitlement.getExternalKey(), externalKey);
@@ -132,7 +132,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
 
         // Create entitlement and check each field
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement2 = entitlementApi.createBaseEntitlement(account.getId(), spec2, externalKey, new LocalDate(clock.getUTCNow(), account.getTimeZone()), callContext);
+        final Entitlement entitlement2 = entitlementApi.createBaseEntitlement(account.getId(), spec2, externalKey, null, new LocalDate(clock.getUTCNow(), account.getTimeZone()), callContext);
         assertListenerStatus();
         assertEquals(entitlement2.getAccountId(), account.getId());
         assertEquals(entitlement2.getExternalKey(), externalKey);
@@ -203,7 +203,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
         // Create entitlement
         testListener.pushExpectedEvent(NextEvent.CREATE);
         final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         assertListenerStatus();
 
         // Get the phase event out of the way
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
index 6ced61d..92cb81c 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
@@ -56,7 +56,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
         // See TestEntitlementUtils for a more comprehensive test
         final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
         testListener.pushExpectedEvent(NextEvent.CREATE);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), clock.getUTCToday(), callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, clock.getUTCToday(), callContext);
         assertListenerStatus();
 
         final BlockingStateType type = BlockingStateType.SUBSCRIPTION;
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java b/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
index 37e1924..3f32c16 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
@@ -79,11 +79,11 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
 
         // Create base entitlement
         final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        baseEntitlement = (DefaultEntitlement) entitlementApi.createBaseEntitlement(account.getId(), baseSpec, account.getExternalKey(), initialDate, callContext);
+        baseEntitlement = (DefaultEntitlement) entitlementApi.createBaseEntitlement(account.getId(), baseSpec, account.getExternalKey(), null, initialDate, callContext);
 
         // Add ADD_ON
         final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        addOnEntitlement = (DefaultEntitlement) entitlementApi.addEntitlement(baseEntitlement.getBundleId(), addOnSpec, initialDate, callContext);
+        addOnEntitlement = (DefaultEntitlement) entitlementApi.addEntitlement(baseEntitlement.getBundleId(), addOnSpec, null, initialDate, callContext);
 
         // Verify the initial state
         checkFutureBlockingStatesToCancel(baseEntitlement, null, null);
@@ -235,7 +235,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
     @Test(groups = "slow", description = "Verify add-ons blocking states are added for EOT change plans")
     public void testChangePlanEOT() throws Exception {
         // Change plan EOT to Assault-Rifle (Telescopic-Scope is included)
-        final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, new LocalDate(2013, 10, 7), callContext);
+        final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, new LocalDate(2013, 10, 7), callContext);
         // No blocking event (EOT)
         assertListenerStatus();
 
@@ -270,11 +270,11 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
         // Add a second ADD_ON (Laser-Scope is available, not included)
         testListener.pushExpectedEvents(NextEvent.CREATE);
         final PlanPhaseSpecifier secondAddOnSpec = new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final DefaultEntitlement secondAddOnEntitlement = (DefaultEntitlement) entitlementApi.addEntitlement(baseEntitlement.getBundleId(), secondAddOnSpec, clock.getUTCToday(), callContext);
+        final DefaultEntitlement secondAddOnEntitlement = (DefaultEntitlement) entitlementApi.addEntitlement(baseEntitlement.getBundleId(), secondAddOnSpec, null, clock.getUTCToday(),  callContext);
         assertListenerStatus();
 
         // Change plan EOT to Assault-Rifle (Telescopic-Scope is included)
-        final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, new LocalDate(2013, 10, 7), callContext);
+        final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, new LocalDate(2013, 10, 7), callContext);
         // No blocking event (EOT)
         assertListenerStatus();
 
@@ -293,7 +293,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
 
         // Change plan IMM (upgrade) to Assault-Rifle (Telescopic-Scope is included)
         testListener.pushExpectedEvents(NextEvent.CHANGE, NextEvent.CANCEL, NextEvent.BLOCK);
-        final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, callContext);
+        final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, callContext);
         assertListenerStatus();
 
         // Refresh the add-on state
@@ -324,7 +324,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
         // Add a second ADD_ON
         testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.PHASE);
         final PlanPhaseSpecifier addOn2Spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final Entitlement addOn2Entitlement = entitlementApi.addEntitlement(baseEntitlement.getBundleId(), addOn2Spec, initialDate, callContext);
+        final Entitlement addOn2Entitlement = entitlementApi.addEntitlement(baseEntitlement.getBundleId(), addOn2Spec, null, initialDate, callContext);
         assertListenerStatus();
 
         // Date prior to the base cancellation date to verify it is not impacted by the base cancellation (in contrary to the second add-on)
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/bundles/DefaultResourceBundleFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/template/bundles/DefaultResourceBundleFactory.java
index 3a6fc9f..f989471 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/bundles/DefaultResourceBundleFactory.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/bundles/DefaultResourceBundleFactory.java
@@ -82,11 +82,11 @@ public class DefaultResourceBundleFactory implements ResourceBundleFactory {
 
     private ResourceBundle getGlobalBundle(final Locale locale, final String bundlePath) {
         try {
-            // Try to load the bundle from the classpath first
+            // Try to loadDefaultCatalog the bundle from the classpath first
             return ResourceBundle.getBundle(bundlePath, locale);
         } catch (MissingResourceException ignored) {
         }
-        // Try to load it from a properties file
+        // Try to loadDefaultCatalog it from a properties file
         final String propertiesFileNameWithCountry = bundlePath + "_" + locale.getLanguage() + "_" + locale.getCountry() + ".properties";
         ResourceBundle bundle = getBundleFromPropertiesFile(propertiesFileNameWithCountry);
         if (bundle != null) {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index 61e5082..5086d46 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -44,6 +44,7 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.entitlement.api.SubscriptionEventType;
@@ -451,5 +452,10 @@ public class TestInvoiceHelper {
         public BillingActionPolicy getBillingActionPolicy() {
             return null;
         }
+
+        @Override
+        public List<PlanPhasePriceOverride> getPlanPhasePriceoverrides() {
+            return null;
+        }
     }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java
index 3dbd829..9bc33ac 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java
@@ -17,6 +17,8 @@
 
 package org.killbill.billing.jaxrs.json;
 
+import java.util.List;
+
 import javax.annotation.Nullable;
 
 import org.joda.time.LocalDate;
@@ -36,6 +38,7 @@ public class InvoiceDryRunJson {
     private final String subscriptionId;
     private final String bundleId;
     private final String billingPolicy;
+    private final List<PhasePriceOverrideJson> priceOverrides;
 
     @JsonCreator
     public InvoiceDryRunJson(@JsonProperty("dryRunAction") @Nullable final String dryRunAction,
@@ -47,7 +50,8 @@ public class InvoiceDryRunJson {
                              @JsonProperty("subscriptionId") @Nullable final String subscriptionId,
                              @JsonProperty("bundleId") @Nullable final String bundleId,
                              @JsonProperty("effectiveDate") @Nullable final LocalDate effectiveDate,
-                             @JsonProperty("billingPolicy") @Nullable final String billingPolicy) {
+                             @JsonProperty("billingPolicy") @Nullable final String billingPolicy,
+                             @JsonProperty("priceOverrides") @Nullable final List<PhasePriceOverrideJson> priceOverrides) {
         this.dryRunAction = dryRunAction;
         this.phaseType = phaseType;
         this.productName = productName;
@@ -58,6 +62,7 @@ public class InvoiceDryRunJson {
         this.bundleId = bundleId;
         this.effectiveDate = effectiveDate;
         this.billingPolicy = billingPolicy;
+        this.priceOverrides = priceOverrides;
     }
 
     public String getDryRunAction() {
@@ -100,6 +105,10 @@ public class InvoiceDryRunJson {
         return billingPolicy;
     }
 
+    public List<PhasePriceOverrideJson> getPriceOverrides() {
+        return priceOverrides;
+    }
+
     @Override
     public boolean equals(final Object o) {
         if (this == o) {
@@ -141,6 +150,9 @@ public class InvoiceDryRunJson {
         if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
             return false;
         }
+        if (priceOverrides != null ? !priceOverrides.equals(that.priceOverrides) : that.priceOverrides != null) {
+            return false;
+        }
 
         return true;
     }
@@ -157,6 +169,7 @@ public class InvoiceDryRunJson {
         result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
         result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
         result = 31 * result + (billingPolicy != null ? billingPolicy.hashCode() : 0);
+        result = 31 * result + (priceOverrides != null ? priceOverrides.hashCode() : 0);
         return result;
     }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PhasePriceOverrideJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PhasePriceOverrideJson.java
new file mode 100644
index 0000000..25c4de8
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PhasePriceOverrideJson.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.math.BigDecimal;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.PlanSpecifier;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class PhasePriceOverrideJson {
+
+    private final String phaseName;
+    private final String phaseType;
+    private final BigDecimal fixedPrice;
+    private final BigDecimal recurringPrice;
+
+    @JsonCreator
+    public PhasePriceOverrideJson(@JsonProperty("phaseName") final String phaseName,
+                                  @JsonProperty("phaseType") final String phaseType,
+                                  @Nullable @JsonProperty("fixedPrice") final BigDecimal fixedPrice,
+                                  @Nullable @JsonProperty("recurringPrice") final BigDecimal recurringPrice) {
+        this.phaseName = phaseName;
+        this.phaseType = phaseType;
+        this.fixedPrice = fixedPrice;
+        this.recurringPrice = recurringPrice;
+    }
+
+    public BigDecimal getFixedPrice() {
+        return fixedPrice;
+    }
+
+    public BigDecimal getRecurringPrice() {
+        return recurringPrice;
+    }
+
+    public String getPhaseName() {
+        return phaseName;
+    }
+
+    public String getPhaseType() {
+        return phaseType;
+    }
+
+    @Override
+    public String toString() {
+        return "PhasePriceOverrideJson{" +
+               "phaseName='" + phaseName + '\'' +
+               "phaseType='" + phaseType + '\'' +
+               ", fixedPrice=" + fixedPrice +
+               ", recurringPrice=" + recurringPrice +
+               '}';
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PhasePriceOverrideJson)) {
+            return false;
+        }
+
+        final PhasePriceOverrideJson that = (PhasePriceOverrideJson) o;
+
+        if (fixedPrice != null ? !fixedPrice.equals(that.fixedPrice) : that.fixedPrice != null) {
+            return false;
+        }
+        if (phaseName != null ? !phaseName.equals(that.phaseName) : that.phaseName != null) {
+            return false;
+        }
+        if (phaseType != null ? !phaseType.equals(that.phaseType) : that.phaseType != null) {
+            return false;
+        }
+        if (recurringPrice != null ? !recurringPrice.equals(that.recurringPrice) : that.recurringPrice != null) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = phaseName != null ? phaseName.hashCode() : 0;
+        result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0);
+        result = 31 * result + (phaseType != null ? phaseType.hashCode() : 0);
+        result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0);
+        return result;
+    }
+
+    public static List<PlanPhasePriceOverride> toPlanPhasePriceOverrides(final List<PhasePriceOverrideJson> input, final PlanSpecifier spec, final Currency currency) {
+        if (input == null || input.isEmpty()) {
+            return ImmutableList.<PlanPhasePriceOverride>of();
+        }
+        return ImmutableList.copyOf(Iterables.transform(input, new Function<PhasePriceOverrideJson, PlanPhasePriceOverride>() {
+            @Nullable
+            @Override
+            public PlanPhasePriceOverride apply(@Nullable final PhasePriceOverrideJson input) {
+                final PhaseType phaseType = input.getPhaseType() != null ? PhaseType.valueOf(input.getPhaseType()) : null;
+                if (input.getPhaseName() != null) {
+                    return new DefaultPlanPhasePriceOverride(input.getPhaseName(), currency, input.getFixedPrice(), input.getRecurringPrice());
+                } else {
+                    final PlanPhaseSpecifier planPhaseSpecifier = new PlanPhaseSpecifier(spec.getProductName(), spec.getProductCategory(), spec.getBillingPeriod(), spec.getPriceListName(), phaseType);
+                    return new DefaultPlanPhasePriceOverride(planPhaseSpecifier, currency, input.getFixedPrice(), input.getRecurringPrice());
+                }
+            }
+        }));
+
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionJson.java
index 24e5eb3..e0ddda5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionJson.java
@@ -65,6 +65,8 @@ public class SubscriptionJson extends JsonBase {
     private final LocalDate billingStartDate;
     private final LocalDate billingEndDate;
     private final List<EventSubscriptionJson> events;
+    private final List<PhasePriceOverrideJson> priceOverrides;
+
 
     public static class EventSubscriptionJson extends JsonBase {
 
@@ -285,6 +287,7 @@ public class SubscriptionJson extends JsonBase {
                             @JsonProperty("billingStartDate") @Nullable final LocalDate billingStartDate,
                             @JsonProperty("billingEndDate") @Nullable final LocalDate billingEndDate,
                             @JsonProperty("events") @Nullable final List<EventSubscriptionJson> events,
+                            @JsonProperty("priceOverrides") final List<PhasePriceOverrideJson> priceOverrides,
                             @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
         super(auditLogs);
         this.startDate = startDate;
@@ -304,6 +307,7 @@ public class SubscriptionJson extends JsonBase {
         this.subscriptionId = subscriptionId;
         this.externalKey = externalKey;
         this.events = events;
+        this.priceOverrides = priceOverrides;
     }
 
     public SubscriptionJson(final Subscription subscription, @Nullable final AccountAuditLogs accountAuditLogs) {
@@ -329,6 +333,8 @@ public class SubscriptionJson extends JsonBase {
         for (final SubscriptionEvent subscriptionEvent : subscription.getSubscriptionEvents()) {
             this.events.add(new EventSubscriptionJson(subscriptionEvent, accountAuditLogs));
         }
+        this.priceOverrides = new LinkedList<PhasePriceOverrideJson>();
+        // STEPH_PO
     }
 
     public String getAccountId() {
@@ -399,6 +405,10 @@ public class SubscriptionJson extends JsonBase {
         return events;
     }
 
+    public List<PhasePriceOverrideJson> getPriceOverrides() {
+        return priceOverrides;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("SubscriptionJson{");
@@ -419,6 +429,7 @@ public class SubscriptionJson extends JsonBase {
         sb.append(", billingStartDate=").append(billingStartDate);
         sb.append(", billingEndDate=").append(billingEndDate);
         sb.append(", events=").append(events);
+        sb.append(", priceOverrides=").append(priceOverrides);
         sb.append('}');
         return sb.toString();
     }
@@ -485,7 +496,9 @@ public class SubscriptionJson extends JsonBase {
         if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
             return false;
         }
-
+        if (priceOverrides != null ? !priceOverrides.equals(that.priceOverrides) : that.priceOverrides != null) {
+            return false;
+        }
         return true;
     }
 
@@ -508,6 +521,8 @@ public class SubscriptionJson extends JsonBase {
         result = 31 * result + (billingStartDate != null ? billingStartDate.hashCode() : 0);
         result = 31 * result + (billingEndDate != null ? billingEndDate.hashCode() : 0);
         result = 31 * result + (events != null ? events.hashCode() : 0);
+        result = 31 * result + (priceOverrides != null ? priceOverrides.hashCode() : 0);
         return result;
     }
+
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index 4411446..497756c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -58,10 +58,12 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.SubscriptionApiException;
@@ -78,6 +80,7 @@ import org.killbill.billing.jaxrs.json.InvoiceDryRunJson;
 import org.killbill.billing.jaxrs.json.InvoiceItemJson;
 import org.killbill.billing.jaxrs.json.InvoiceJson;
 import org.killbill.billing.jaxrs.json.InvoicePaymentJson;
+import org.killbill.billing.jaxrs.json.PhasePriceOverrideJson;
 import org.killbill.billing.jaxrs.json.TagJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
@@ -340,7 +343,7 @@ public class InvoiceResource extends JaxRsResourceBase {
 
         final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), callContext);
 
-        final DryRunArguments dryRunArguments = new DefaultDryRunArguments(dryRunSubscriptionSpec, account.getTimeZone(), clock);
+        final DryRunArguments dryRunArguments = new DefaultDryRunArguments(dryRunSubscriptionSpec, account.getTimeZone(), account.getCurrency(), clock);
         try {
             final Invoice generatedInvoice = invoiceApi.triggerInvoiceGeneration(UUID.fromString(accountId), inputDate, dryRunArguments,
                                                                                  callContext);
@@ -890,18 +893,9 @@ public class InvoiceResource extends JaxRsResourceBase {
         private final PlanPhaseSpecifier specifier;
         private final UUID bundleId;
         private final BillingActionPolicy billingPolicy;
+        private final List<PlanPhasePriceOverride> overrides;
 
-        public DefaultDryRunArguments(final SubscriptionEventType action, final UUID subscriptionId, final UUID bundleId,
-                                      final PlanPhaseSpecifier specifier, final DateTime effectiveDate, final BillingActionPolicy billingPolicy) {
-            this.action = action;
-            this.subscriptionId = subscriptionId;
-            this.bundleId = bundleId;
-            this.effectiveDate = effectiveDate;
-            this.billingPolicy = billingPolicy;
-            this.specifier = specifier;
-        }
-
-        public DefaultDryRunArguments(final InvoiceDryRunJson input, final DateTimeZone accountTimeZone, final Clock clock) {
+        public DefaultDryRunArguments(final InvoiceDryRunJson input, final DateTimeZone accountTimeZone, final Currency currency, final Clock clock) {
             if (input == null) {
                 this.action = null;
                 this.subscriptionId = null;
@@ -909,21 +903,35 @@ public class InvoiceResource extends JaxRsResourceBase {
                 this.specifier = null;
                 this.bundleId = null;
                 this.billingPolicy = null;
+                this.overrides = null;
             } else {
                 this.action = input.getDryRunAction() != null ? SubscriptionEventType.valueOf(input.getDryRunAction()) : null;
                 this.subscriptionId = input.getSubscriptionId() != null ? UUID.fromString(input.getSubscriptionId()) : null;
                 this.bundleId = input.getBundleId() != null ? UUID.fromString(input.getBundleId()) : null;
                 this.effectiveDate = input.getEffectiveDate() != null ? ClockUtil.computeDateTimeWithUTCReferenceTime(input.getEffectiveDate(), clock.getUTCNow().toLocalTime(), accountTimeZone, clock) : null;
                 this.billingPolicy = input.getBillingPolicy() != null ? BillingActionPolicy.valueOf(input.getBillingPolicy()) : null;
-                this.specifier = (input.getProductName() != null &&
-                                  input.getProductCategory() != null &&
-                                  input.getBillingPeriod() != null) ?
-                                 new PlanPhaseSpecifier(input.getProductName(),
-                                                        ProductCategory.valueOf(input.getProductCategory()),
-                                                        BillingPeriod.valueOf(input.getBillingPeriod()),
-                                                        input.getPriceListName(),
-                                                        input.getPhaseType() != null ? PhaseType.valueOf(input.getPhaseType()) : null) :
-                                 null;
+                final PlanPhaseSpecifier planPhaseSpecifier  = (input.getProductName() != null &&
+                                     input.getProductCategory() != null &&
+                                     input.getBillingPeriod() != null) ?
+                                    new PlanPhaseSpecifier(input.getProductName(),
+                                                           ProductCategory.valueOf(input.getProductCategory()),
+                                                           BillingPeriod.valueOf(input.getBillingPeriod()),
+                                                           input.getPriceListName(),
+                                                           input.getPhaseType() != null ? PhaseType.valueOf(input.getPhaseType()) : null) :
+                                    null;
+                this.specifier = planPhaseSpecifier;
+                this.overrides = input.getPriceOverrides() != null ?
+                                 ImmutableList.copyOf(Iterables.transform(input.getPriceOverrides(), new Function<PhasePriceOverrideJson, PlanPhasePriceOverride>() {
+                    @Nullable
+                    @Override
+                    public PlanPhasePriceOverride apply(@Nullable final PhasePriceOverrideJson input) {
+                        if (input.getPhaseName() != null) {
+                            return new DefaultPlanPhasePriceOverride(input.getPhaseName(), currency, input.getFixedPrice(), input.getRecurringPrice());
+                        } else {
+                            return new DefaultPlanPhasePriceOverride(planPhaseSpecifier, currency, input.getFixedPrice(), input.getRecurringPrice());
+                        }
+                    }
+                })) : ImmutableList.<PlanPhasePriceOverride>of();
             }
         }
 
@@ -956,6 +964,11 @@ public class InvoiceResource extends JaxRsResourceBase {
         public BillingActionPolicy getBillingActionPolicy() {
             return billingPolicy;
         }
+
+        @Override
+        public List<PlanPhasePriceOverride> getPlanPhasePriceoverrides() {
+            return overrides;
+        }
     }
 
 }
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 f272f2c..e1eb4dc 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
@@ -41,11 +41,14 @@ import javax.ws.rs.core.UriInfo;
 
 import org.joda.time.LocalDate;
 import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.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.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.Entitlement;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
@@ -61,6 +64,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.CustomFieldJson;
+import org.killbill.billing.jaxrs.json.PhasePriceOverrideJson;
 import org.killbill.billing.jaxrs.json.SubscriptionJson;
 import org.killbill.billing.jaxrs.json.TagJson;
 import org.killbill.billing.jaxrs.util.Context;
@@ -161,6 +165,9 @@ public class SubscriptionResource extends JaxRsResourceBase {
         }
 
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID accountId = entitlement.getAccountId() != null ? UUID.fromString(entitlement.getAccountId()) : null;
+        final Account account = accountUserApi.getAccountById(accountId, callContext);
+
         final EntitlementCallCompletionCallback<Entitlement> callback = new EntitlementCallCompletionCallback<Entitlement>() {
             @Override
             public Entitlement doOperation(final CallContext ctx) throws InterruptedException, TimeoutException, EntitlementApiException {
@@ -169,12 +176,18 @@ public class SubscriptionResource extends JaxRsResourceBase {
                                                                        ProductCategory.valueOf(entitlement.getProductCategory()),
                                                                        BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), null);
 
-                final UUID accountId = entitlement.getAccountId() != null ? UUID.fromString(entitlement.getAccountId()) : null;
                 final LocalDate inputLocalDate = toLocalDate(accountId, requestedDate, callContext);
+
+
                 final UUID bundleId = entitlement.getBundleId() != null ? UUID.fromString(entitlement.getBundleId()) : null;
+
+                final PlanSpecifier planSpec = new PlanSpecifier(entitlement.getProductName(),
+                                                                 ProductCategory.valueOf(entitlement.getProductCategory()),
+                                                                 BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList());
+                final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(entitlement.getPriceOverrides(), planSpec, account.getCurrency());
                 return createAddOnEntitlement ?
-                       entitlementApi.addEntitlement(bundleId, spec, inputLocalDate, callContext) :
-                       entitlementApi.createBaseEntitlement(accountId, spec, entitlement.getExternalKey(), inputLocalDate, callContext);
+                       entitlementApi.addEntitlement(bundleId, spec, overrides, inputLocalDate, callContext) :
+                       entitlementApi.createBaseEntitlement(accountId, spec, entitlement.getExternalKey(), overrides, inputLocalDate, callContext);
             }
 
             @Override
@@ -235,6 +248,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
 
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
+        final UUID accountId = entitlement.getAccountId() != null ? UUID.fromString(entitlement.getAccountId()) : null;
         final EntitlementCallCompletionCallback<Response> callback = new EntitlementCallCompletionCallback<Response>() {
 
             private boolean isImmediateOp = true;
@@ -247,13 +261,20 @@ public class SubscriptionResource extends JaxRsResourceBase {
                 final Entitlement current = entitlementApi.getEntitlementForId(uuid, callContext);
                 final LocalDate inputLocalDate = toLocalDate(current.getAccountId(), requestedDate, callContext);
                 final Entitlement newEntitlement;
+
+                final Account account = accountUserApi.getAccountById(accountId, callContext);
+                final PlanSpecifier planSpec = new PlanSpecifier(entitlement.getProductName(),
+                                                                 ProductCategory.valueOf(entitlement.getProductCategory()),
+                                                                 BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList());
+                final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(entitlement.getPriceOverrides(), planSpec, account.getCurrency());
+
                 if (requestedDate == null && policyString == null) {
-                    newEntitlement = current.changePlan(entitlement.getProductName(), BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), ctx);
+                    newEntitlement = current.changePlan(entitlement.getProductName(), BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), overrides, ctx);
                 } else if (policyString == null) {
-                    newEntitlement = current.changePlanWithDate(entitlement.getProductName(), BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), inputLocalDate, ctx);
+                    newEntitlement = current.changePlanWithDate(entitlement.getProductName(), BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), overrides, inputLocalDate, ctx);
                 } else {
                     final BillingActionPolicy policy = BillingActionPolicy.valueOf(policyString.toUpperCase());
-                    newEntitlement = current.changePlanOverrideBillingPolicy(entitlement.getProductName(), BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), inputLocalDate, policy, ctx);
+                    newEntitlement = current.changePlanOverrideBillingPolicy(entitlement.getProductName(), BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), overrides, inputLocalDate, policy, ctx);
                 }
                 isImmediateOp = newEntitlement.getLastActiveProduct().getName().equals(entitlement.getProductName()) &&
                                 newEntitlement.getLastActivePlan().getRecurringBillingPeriod() == BillingPeriod.valueOf(entitlement.getBillingPeriod()) &&
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleJsonWithSubscriptions.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleJsonWithSubscriptions.java
index c8323cc..201c5fc 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleJsonWithSubscriptions.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleJsonWithSubscriptions.java
@@ -18,6 +18,7 @@
 
 package org.killbill.billing.jaxrs.json;
 
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
@@ -54,6 +55,9 @@ public class TestBundleJsonWithSubscriptions extends JaxrsTestSuiteNoDB {
                                                                       UUID.randomUUID().toString(),
                                                                       UUID.randomUUID().toString(),
                                                                       null);
+
+        final PhasePriceOverrideJson priceOverride = new PhasePriceOverrideJson(null, "somePhaseType", BigDecimal.ONE, null);
+
         final SubscriptionJson subscription = new SubscriptionJson(UUID.randomUUID().toString(),
                                                                    UUID.randomUUID().toString(),
                                                                    UUID.randomUUID().toString(),
@@ -71,6 +75,7 @@ public class TestBundleJsonWithSubscriptions extends JaxrsTestSuiteNoDB {
                                                                    new LocalDate(),
                                                                    new LocalDate(),
                                                                    ImmutableList.<EventSubscriptionJson>of(event),
+                                                                   ImmutableList.of(priceOverride),
                                                                    auditLogs);
 
         final BundleJson bundleJson = new BundleJson(someUUID, bundleId.toString(), externalKey, ImmutableList.<SubscriptionJson>of(subscription), null, auditLogs);
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestEntitlementJsonWithEvents.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestEntitlementJsonWithEvents.java
index eb2f7d4..6026c77 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestEntitlementJsonWithEvents.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestEntitlementJsonWithEvents.java
@@ -18,6 +18,7 @@
 
 package org.killbill.billing.jaxrs.json;
 
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
@@ -62,6 +63,9 @@ public class TestEntitlementJsonWithEvents extends JaxrsTestSuiteNoDB {
                                                                          UUID.randomUUID().toString(),
                                                                          PhaseType.DISCOUNT.toString(),
                                                                          auditLogs);
+
+        final PhasePriceOverrideJson priceOverride = new PhasePriceOverrideJson("bar", null, BigDecimal.TEN, BigDecimal.ONE);
+
         final SubscriptionJson entitlementJsonWithEvents = new SubscriptionJson(accountId,
                                                                                 bundleId,
                                                                                 subscriptionId,
@@ -79,6 +83,7 @@ public class TestEntitlementJsonWithEvents extends JaxrsTestSuiteNoDB {
                                                                                 new LocalDate(),
                                                                                 new LocalDate(),
                                                                                 ImmutableList.<EventSubscriptionJson>of(newEvent),
+                                                                                ImmutableList.of(priceOverride),
                                                                                 null);
 
         final String asJson = mapper.writeValueAsString(entitlementJsonWithEvents);
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
index 3eca07b..7088620 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
@@ -58,7 +58,7 @@ public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbedded
 
         testListener.pushExpectedEvent(NextEvent.CREATE);
         final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         final SubscriptionBase subscription = subscriptionInternalApi.getSubscriptionFromId(entitlement.getId(), internalCallContext);
         assertListenerStatus();
 
@@ -195,7 +195,7 @@ public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbedded
 
         testListener.pushExpectedEvent(NextEvent.CREATE);
         final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, callContext);
         final SubscriptionBase subscription = subscriptionInternalApi.getSubscriptionFromId(entitlement.getId(), internalCallContext);
         assertListenerStatus();
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java b/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
index 011eb4f..87cc174 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
@@ -91,7 +91,7 @@ public class DefaultOverdueService implements OverdueService {
                 overdueConfigCache.loadDefaultOverdueConfig(properties.getConfigURI());
                 isConfigLoaded = true;
             } catch (OverdueApiException e) {
-                log.warn("Overdue system disabled: unable to load the overdue config from " + properties.getConfigURI(), e);
+                log.warn("Overdue system disabled: unable to loadDefaultCatalog the overdue config from " + properties.getConfigURI(), e);
                 e.printStackTrace();
             }
         }
diff --git a/profiles/killbill/src/main/resources/CatalogMos.xml b/profiles/killbill/src/main/resources/CatalogMos.xml
new file mode 100644
index 0000000..f86c487
--- /dev/null
+++ b/profiles/killbill/src/main/resources/CatalogMos.xml
@@ -0,0 +1,515 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="catalog.xsd ">
+
+    <effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
+    <catalogName>MOSCatalog</catalogName>
+
+    <recurringBillingMode>IN_ADVANCE</recurringBillingMode>
+
+    <currencies>
+        <currency>USD</currency>
+    </currencies>
+
+    <products>
+        <product name="cafe">
+            <category>BASE</category>
+        </product>
+        <product name="custom">
+            <category>BASE</category>
+        </product>
+        <product name="flagship">
+            <category>BASE</category>
+        </product>
+        <product name="free">
+            <category>BASE</category>
+        </product>
+        <product name="mx60mr12_2_license">
+            <category>BASE</category>
+        </product>
+        <product name="mx60mr12_3_license">
+            <category>BASE</category>
+        </product>
+        <product name="mx60mr12_4_license">
+            <category>BASE</category>
+        </product>
+        <product name="mx60mr12_5_license">
+            <category>BASE</category>
+        </product>
+        <product name="mx60_license">
+            <category>BASE</category>
+        </product>
+        <product name="mx60mr12_license">
+            <category>BASE</category>
+        </product>
+        <product name="mx80mr12_6_license">
+            <category>BASE</category>
+        </product>
+        <product name="z1_mr12">
+            <category>BASE</category>
+        </product>
+        <product name="z1_license">
+            <category>BASE</category>
+        </product>
+        <product name="midsize">
+            <category>BASE</category>
+        </product>
+        <product name="mr12_license">
+            <category>BASE</category>
+        </product>
+        <product name="solo">
+            <category>BASE</category>
+        </product>
+    </products>
+
+    <rules>
+        <changePolicy>
+            <changePolicyCase>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
+        </changePolicy>
+        <changeAlignment>
+            <changeAlignmentCase>
+                <alignment>START_OF_BUNDLE</alignment>
+            </changeAlignmentCase>
+        </changeAlignment>
+        <cancelPolicy>
+            <cancelPolicyCase>
+                <policy>END_OF_TERM</policy>
+            </cancelPolicyCase>
+        </cancelPolicy>
+        <createAlignment>
+            <createAlignmentCase>
+                <alignment>START_OF_BUNDLE</alignment>
+            </createAlignmentCase>
+        </createAlignment>
+        <billingAlignment>
+            <billingAlignmentCase>
+                <alignment>SUBSCRIPTION</alignment>
+            </billingAlignmentCase>
+        </billingAlignment>
+        <priceList>
+            <priceListCase>
+                <toPriceList>DEFAULT</toPriceList>
+            </priceListCase>
+        </priceList>
+    </rules>
+
+    <plans>
+        <plan name="cafe-monthly">
+            <product>cafe</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>199.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="custom-monthly">
+            <product>custom</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>450.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="flagship-monthly">
+            <product>flagship</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>399.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="free-monthly"> <!-- May be alternative ways to model this - not sure if it matters though -->
+            <product>free</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>0.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="mx60mr12_2_license-annual">
+            <product>mx60mr12_2_license</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>206.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="mx60mr12_3_license-annual">
+            <product>mx60mr12_3_license</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>262.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="mx60mr12_4_license-annual">
+            <product>mx60mr12_4_license</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>318.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="mx60mr12_5_license-annual">
+            <product>mx60mr12_5_license</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>374.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="mx60_license-annual">
+            <product>mx60_license</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>94.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="mx60mr12_license-annual">
+            <product>mx60mr12_license</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>150.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="mx80mr12_6_license-annual">
+            <product>mx80mr12_6_license</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>696.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="z1_mr12-annual">
+            <product>z1_mr12</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>71.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="z1_license-annual">
+            <product>z1_license</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>15.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="midsize-monthly">
+            <product>midsize</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>299.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="mr12_license-annual">
+            <product>mr12_license</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>YEARS</unit>
+                        <number>1</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>56.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="solo-monthly">
+            <product>solo</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>99.00</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+    </plans>
+    <priceLists>
+        <defaultPriceList name="DEFAULT">
+            <plans>
+                <plan>cafe-monthly</plan>
+                <plan>custom-monthly</plan>
+                <plan>flagship-monthly</plan>
+                <plan>free-monthly</plan>
+                <plan>mx60mr12_2_license-annual</plan>
+                <plan>mx60mr12_3_license-annual</plan>
+                <plan>mx60mr12_4_license-annual</plan>
+                <plan>mx60mr12_5_license-annual</plan>
+                <plan>mx60_license-annual</plan>
+                <plan>mx60mr12_license-annual</plan>
+                <plan>mx80mr12_6_license-annual</plan>
+                <plan>z1_mr12-annual</plan>
+                <plan>z1_license-annual</plan>
+                <plan>midsize-monthly</plan>
+                <plan>mr12_license-annual</plan>
+                <plan>solo-monthly</plan>
+            </plans>
+        </defaultPriceList>
+    </priceLists>
+</catalog>
diff --git a/profiles/killbill/src/main/resources/killbill-server.properties.mos b/profiles/killbill/src/main/resources/killbill-server.properties.mos
new file mode 100644
index 0000000..735f895
--- /dev/null
+++ b/profiles/killbill/src/main/resources/killbill-server.properties.mos
@@ -0,0 +1,88 @@
+#
+# Copyright 2010-2013 Ning, Inc.
+# Copyright 2014 Groupon, Inc
+# Copyright 2014 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.
+#
+
+#
+# KILLBILL GENERIC PROPERTIES
+#
+# Database config
+#org.killbill.dao.url=jdbc:mysql://127.0.0.1:3306/killbill
+org.killbill.dao.url=jdbc:mysql://127.0.0.1:3306/killbil_analytics_mos
+org.killbill.dao.user=root
+org.killbill.dao.password=root
+org.killbill.dao.logLevel=DEBUG
+
+# Use the SpyCarAdvanced.xml catalog
+#org.killbill.catalog.uri=SpyCarAdvanced.xml
+org.killbill.catalog.uri=CatalogMos.xml
+
+# NotificationQ, Bus, ExtBus config
+org.killbill.notificationq.main.sleep=1000
+org.killbill.notificationq.main.claimed=10
+org.killbill.notificationq.main.sticky=true
+
+org.killbill.persistent.bus.external.sticky=true
+org.killbill.persistent.bus.external.inMemory=true
+
+org.killbill.persistent.bus.main.sticky=true
+org.killbill.persistent.bus.main.claimed=1
+org.killbill.persistent.bus.main.inflight.claimed=1
+org.killbill.persistent.bus.main.nbThreads=1
+org.killbill.persistent.bus.main.sleep=0
+org.killbill.persistent.bus.main.useInflightQ=true
+org.killbill.persistent.bus.main.queue.capacity=100
+
+# Start KB in multi-tenant
+org.killbill.server.multitenant=true
+
+# Override polling from Tenant Broadcast Task
+org.killbill.tenant.broadcast.rate=1s
+
+#
+# PLUGIN SPECIFIC PROPERTIES
+#
+# Database config (OSGI plugins)
+#org.killbill.billing.osgi.dao.url=jdbc:mysql://127.0.0.1:3306/killbill
+org.killbill.billing.osgi.dao.url=jdbc:mysql://127.0.0.1:3306/killbil_analytics_mos
+org.killbill.billing.osgi.dao.user=root
+org.killbill.billing.osgi.dao.password=root
+
+# Allow jruby concurrency
+org.killbill.jruby.context.scope=THREADSAFE
+
+# Path for plugin config
+#org.killbill.billing.osgi.bundles.jruby.conf.dir=/var/tmp/bundles/plugins/config
+org.killbill.osgi.bundle.install.dir=/var/tmp/bundles_analytics_mos
+
+# Config property files for plugin to access
+org.killbill.server.properties=/Users/sbrossier/Src/killbill/killbill/profiles/killbill/src/main/resources/killbill-server.properties
+
+#
+# INTEGRATION TESTS ONLY
+#
+# To enable test endpoint and have Kill Bill run with a ClockMock (should not be used for production server)
+org.killbill.server.test.mode=true
+
+# Set payment calls to timeout after 5 sec -- mostly for integration tests
+org.killbill.payment.plugin.timeout=5s
+
+org.killbill.payment.retry.days=
+
+org.killbill.catalog.bundlePath=CatalogTranslation
+org.killbill.template.bundlePath=InvoiceTranslation
+org.killbill.template.name=HtmlInvoiceTemplate.mustache
+
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
index a885df7..39c2c7c 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
@@ -64,8 +64,10 @@ public class TestEntitlement extends TestJaxrsBase {
         final String newProductName = "Assault-Rifle";
 
         final Subscription newInput = new Subscription();
+        newInput.setAccountId(entitlementJson.getAccountId());
         newInput.setSubscriptionId(entitlementJson.getSubscriptionId());
         newInput.setProductName(newProductName);
+        newInput.setProductCategory(ProductCategory.BASE);
         newInput.setBillingPeriod(entitlementJson.getBillingPeriod());
         newInput.setPriceList(entitlementJson.getPriceList());
         objFromJson = killBillClient.updateSubscription(newInput, CALL_COMPLETION_TIMEOUT_SEC, createdBy, reason, comment);
@@ -128,8 +130,10 @@ public class TestEntitlement extends TestJaxrsBase {
     public void testWithNonExistentEntitlement() throws Exception {
         final UUID subscriptionId = UUID.randomUUID();
         final Subscription subscription = new Subscription();
+        subscription.setAccountId(UUID.randomUUID());
         subscription.setSubscriptionId(subscriptionId);
         subscription.setProductName("Pistol");
+        subscription.setProductCategory(ProductCategory.BASE);
         subscription.setBillingPeriod(BillingPeriod.ANNUAL);
         subscription.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
 
@@ -175,8 +179,10 @@ public class TestEntitlement extends TestJaxrsBase {
 
         // Change billing period immediately
         final Subscription newInput = new Subscription();
+        newInput.setAccountId(accountJson.getAccountId());
         newInput.setSubscriptionId(subscriptionJson.getSubscriptionId());
         newInput.setProductName(subscriptionJson.getProductName());
+        newInput.setProductCategory(ProductCategory.BASE);
         newInput.setBillingPeriod(BillingPeriod.MONTHLY);
         newInput.setPriceList(subscriptionJson.getPriceList());
         objFromJson = killBillClient.updateSubscription(newInput, BillingActionPolicy.IMMEDIATE, CALL_COMPLETION_TIMEOUT_SEC, createdBy, reason, comment);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
index 0b803c3..03b3482 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
@@ -112,7 +112,7 @@ public class TestInvoice extends TestJaxrsBase {
         // "Assault-Rifle", BillingPeriod.ANNUAL, "rescue", BillingActionPolicy.IMMEDIATE,
         final Account accountJson = createAccountWithDefaultPaymentMethod();
         final InvoiceDryRun dryRunArg = new InvoiceDryRun(SubscriptionEventType.START_BILLING,
-                                                          null, "Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, null, null, null, null, null);
+                                                          null, "Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, null, null, null, null, null, null);
         final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), initialDate, dryRunArg, createdBy, reason, comment);
         assertEquals(dryRunInvoice.getItems().size(), 1);
 
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/alignment/MigrationPlanAligner.java b/subscription/src/main/java/org/killbill/billing/subscription/alignment/MigrationPlanAligner.java
index 1996237..c7b054e 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/alignment/MigrationPlanAligner.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/alignment/MigrationPlanAligner.java
@@ -48,11 +48,11 @@ public class MigrationPlanAligner extends BaseAligner {
 
         try {
             TimedMigration[] events;
-            final Plan plan0 = catalogService.getFullCatalog(context).findPlan(input[0].getPlanPhaseSpecifier().getProductName(),
-                                                                        input[0].getPlanPhaseSpecifier().getBillingPeriod(), input[0].getPlanPhaseSpecifier().getPriceListName(), now);
+            final Plan plan0 = catalogService.getFullCatalog(context).createOrFindPlan(input[0].getPlanPhaseSpecifier().getProductName(),
+                                                                                       input[0].getPlanPhaseSpecifier().getBillingPeriod(), input[0].getPlanPhaseSpecifier().getPriceListName(), null, now);
 
-            final Plan plan1 = (input.length > 1) ? catalogService.getFullCatalog(context).findPlan(input[1].getPlanPhaseSpecifier().getProductName(),
-                                                                                             input[1].getPlanPhaseSpecifier().getBillingPeriod(), input[1].getPlanPhaseSpecifier().getPriceListName(), now) :
+            final Plan plan1 = (input.length > 1) ? catalogService.getFullCatalog(context).createOrFindPlan(input[1].getPlanPhaseSpecifier().getProductName(),
+                                                                                                            input[1].getPlanPhaseSpecifier().getBillingPeriod(), input[1].getPlanPhaseSpecifier().getPriceListName(), null, now) :
                                null;
 
             DateTime migrationStartDate = input[0].getEffectiveDate();
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
index 16a32fa..8d76cbd 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
@@ -70,7 +70,7 @@ public class PlanAligner extends BaseAligner {
      * @param plan            the current Plan
      * @param initialPhase    the initialPhase on which we should create that subscription. can be null
      * @param priceList       the priceList
-     * @param requestedDate   the requested date (only used to load the catalog)
+     * @param requestedDate   the requested date (only used to loadDefaultCatalog the catalog)
      * @param effectiveDate   the effective creation date (driven by the catalog policy, i.e. when the creation occurs)
      * @return the current and next phases
      * @throws CatalogApiException         for catalog errors
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 365224a..a96cac4 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
@@ -29,6 +29,7 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanChangeResult;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
@@ -46,7 +47,7 @@ public interface SubscriptionBaseApiService {
             throws SubscriptionBaseApiException;
 
     @Deprecated
-    public boolean recreatePlan(DefaultSubscriptionBase subscription, PlanPhaseSpecifier spec, DateTime requestedDateWithMs, CallContext context)
+    public boolean recreatePlan(DefaultSubscriptionBase subscription, PlanPhaseSpecifier spec, List<PlanPhasePriceOverride> overrides, DateTime requestedDateWithMs, CallContext context)
             throws SubscriptionBaseApiException;
 
     public boolean cancel(DefaultSubscriptionBase subscription, CallContext context)
@@ -63,17 +64,17 @@ public interface SubscriptionBaseApiService {
 
     // Return the effective date of the change
     public DateTime changePlan(DefaultSubscriptionBase subscription, String productName, BillingPeriod term,
-                               String priceList, CallContext context)
+                               String priceList, List<PlanPhasePriceOverride> overrides, CallContext context)
             throws SubscriptionBaseApiException;
 
     // Return the effective date of the change
     public DateTime changePlanWithRequestedDate(DefaultSubscriptionBase subscription, String productName, BillingPeriod term,
-                                                String priceList, DateTime requestedDate, CallContext context)
+                                                String priceList, List<PlanPhasePriceOverride> overrides, DateTime requestedDate, CallContext context)
             throws SubscriptionBaseApiException;
 
     // Return the effective date of the change
     public DateTime changePlanWithPolicy(DefaultSubscriptionBase subscription, String productName, BillingPeriod term,
-                                         String priceList, BillingActionPolicy policy, CallContext context)
+                                         String priceList, List<PlanPhasePriceOverride> overrides, BillingActionPolicy policy, CallContext context)
             throws SubscriptionBaseApiException;
 
     public int cancelAddOnsIfRequired(final Product baseProduct, final UUID bundleId, final DateTime effectiveDate, final CallContext context) throws CatalogApiException;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultPlanPhasePriceOverridesWithCallContext.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultPlanPhasePriceOverridesWithCallContext.java
new file mode 100644
index 0000000..cdfb2fd
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultPlanPhasePriceOverridesWithCallContext.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.api.svcs;
+
+import java.util.List;
+
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
+import org.killbill.billing.util.callcontext.CallContext;
+
+public class DefaultPlanPhasePriceOverridesWithCallContext implements PlanPhasePriceOverridesWithCallContext {
+
+    private final List<PlanPhasePriceOverride> overrides;
+    private final CallContext callContext;
+
+    public DefaultPlanPhasePriceOverridesWithCallContext(final List<PlanPhasePriceOverride> overrides, final CallContext callContext) {
+        this.overrides = overrides;
+        this.callContext = callContext;
+    }
+
+    @Override
+    public List<PlanPhasePriceOverride> getOverrides() {
+        return overrides;
+    }
+
+    @Override
+    public CallContext getCallContext() {
+        return callContext;
+    }
+}
\ No newline at end of file
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 49e8b32..8650ea8 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
@@ -38,6 +38,8 @@ import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanChangeResult;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
@@ -101,7 +103,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
     }
 
     @Override
-    public SubscriptionBase createSubscription(final UUID bundleId, final PlanPhaseSpecifier spec, final DateTime requestedDateWithMs, final InternalCallContext context) throws SubscriptionBaseApiException {
+    public SubscriptionBase createSubscription(final UUID bundleId, final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDateWithMs, final InternalCallContext context) throws SubscriptionBaseApiException {
         try {
             final String realPriceList = (spec.getPriceListName() == null) ? PriceListSet.DEFAULT_PRICELIST_NAME : spec.getPriceListName();
             final DateTime now = clock.getUTCNow();
@@ -111,8 +113,11 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
             }
             final DateTime effectiveDate = requestedDate;
 
+            final CallContext callContext = internalCallContextFactory.createCallContext(context);
             final Catalog catalog = catalogService.getFullCatalog(context);
-            final Plan plan = catalog.findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);
+            final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, callContext);
+
+            final Plan plan = catalog.createOrFindPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, overridesWithContext, requestedDate);
             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",
@@ -126,7 +131,6 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
 
             final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, context);
             final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, requestedDate, effectiveDate, context);
-            final CallContext callContext = internalCallContextFactory.createCallContext(context);
             return apiService.createPlan(new SubscriptionBuilder()
                                                  .setId(UUID.randomUUID())
                                                  .setBundleId(bundleId)
@@ -396,8 +400,10 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
             final PlanPhaseSpecifier inputSpec = dryRunArguments.getPlanPhaseSpecifier();
             final String realPriceList = (inputSpec != null && inputSpec.getPriceListName() != null) ? inputSpec.getPriceListName() : PriceListSet.DEFAULT_PRICELIST_NAME;
             final Catalog catalog = catalogService.getFullCatalog(context);
+
+            final PlanPhasePriceOverridesWithCallContext overridesWithContext = null; // TODO not supported to dryRun with custom price
             final Plan plan = (inputSpec != null && inputSpec.getProductName() != null && inputSpec.getBillingPeriod() != null) ?
-                              catalog.findPlan(inputSpec.getProductName(), inputSpec.getBillingPeriod(), realPriceList, utcNow) : null;
+                              catalog.createOrFindPlan(inputSpec.getProductName(), inputSpec.getBillingPeriod(), realPriceList, overridesWithContext, utcNow) : null;
             final TenantContext tenantContext = internalCallContextFactory.createTenantContext(context);
 
             if (dryRunArguments != null) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionDataRepair.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionDataRepair.java
index d9d8c0b..86ea5a5 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionDataRepair.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionDataRepair.java
@@ -20,6 +20,8 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
+import javax.annotation.concurrent.Immutable;
+
 import org.joda.time.DateTime;
 
 import org.killbill.billing.ErrorCode;
@@ -30,6 +32,7 @@ import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
@@ -52,6 +55,7 @@ import org.killbill.clock.Clock;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 
 public class SubscriptionDataRepair extends DefaultSubscriptionBase {
 
@@ -114,11 +118,11 @@ public class SubscriptionDataRepair extends DefaultSubscriptionBase {
             switch (input.getSubscriptionTransitionType()) {
                 case CREATE:
                 case RE_CREATE:
-                    recreate(spec, input.getRequestedDate(), context);
+                    recreate(spec, ImmutableList.<PlanPhasePriceOverride>of(), input.getRequestedDate(), context);
                     checkAddonRights(baseSubscription, internalTenantContext);
                     break;
                 case CHANGE:
-                    changePlanWithDate(spec.getProductName(), spec.getBillingPeriod(), spec.getPriceListName(), input.getRequestedDate(), context);
+                    changePlanWithDate(spec.getProductName(), spec.getBillingPeriod(), spec.getPriceListName(), ImmutableList.<PlanPhasePriceOverride>of(), input.getRequestedDate(), context);
                     checkAddonRights(baseSubscription, internalTenantContext);
                     trickleDownBPEffectForAddon(addonSubscriptions, getLastUserEventEffectiveDate(), context);
                     break;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
index 9688a17..f337463 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
@@ -33,6 +33,7 @@ import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.Product;
@@ -205,9 +206,9 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
         return null;
     }
 
-    public boolean recreate(final PlanPhaseSpecifier spec, final DateTime requestedDate,
+    public boolean recreate(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDate,
                             final CallContext context) throws SubscriptionBaseApiException {
-        return apiService.recreatePlan(this, spec, requestedDate, context);
+        return apiService.recreatePlan(this, spec, overrides, requestedDate, context);
     }
 
     @Override
@@ -233,20 +234,20 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
 
     @Override
     public DateTime changePlan(final String productName, final BillingPeriod term, final String priceList,
-                               final CallContext context) throws SubscriptionBaseApiException {
-        return apiService.changePlan(this, productName, term, priceList, context);
+                               final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
+        return apiService.changePlan(this, productName, term, priceList, overrides, context);
     }
 
     @Override
-    public DateTime changePlanWithDate(final String productName, final BillingPeriod term, final String priceList,
+    public DateTime changePlanWithDate(final String productName, final BillingPeriod term, final String priceList, final List<PlanPhasePriceOverride> overrides,
                                        final DateTime requestedDate, final CallContext context) throws SubscriptionBaseApiException {
-        return apiService.changePlanWithRequestedDate(this, productName, term, priceList, requestedDate, context);
+        return apiService.changePlanWithRequestedDate(this, productName, term, priceList, overrides, requestedDate, context);
     }
 
     @Override
     public DateTime changePlanWithPolicy(final String productName, final BillingPeriod term, final String priceList,
-                                         final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
-        return apiService.changePlanWithPolicy(this, productName, term, priceList, policy, context);
+                                         final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy,  final CallContext context) throws SubscriptionBaseApiException {
+        return apiService.changePlanWithPolicy(this, productName, term, priceList, overrides, policy, context);
     }
 
     @Override
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 c8e6580..9601adf 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
@@ -37,6 +37,8 @@ import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanChangeResult;
 import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.PriceList;
@@ -48,6 +50,7 @@ import org.killbill.billing.subscription.alignment.PlanAligner;
 import org.killbill.billing.subscription.alignment.TimedPhase;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseApiService;
+import org.killbill.billing.subscription.api.svcs.DefaultPlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.subscription.engine.addon.AddonUtils;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
@@ -103,7 +106,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
 
     @Deprecated
     @Override
-    public boolean recreatePlan(final DefaultSubscriptionBase subscription, final PlanPhaseSpecifier spec, final DateTime requestedDateWithMs, final CallContext context)
+    public boolean recreatePlan(final DefaultSubscriptionBase subscription, final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDateWithMs, final CallContext context)
             throws SubscriptionBaseApiException {
         final EntitlementState currentState = subscription.getState();
         if (currentState != null && currentState != EntitlementState.CANCELLED) {
@@ -117,7 +120,8 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         try {
             final String realPriceList = (spec.getPriceListName() == null) ? PriceListSet.DEFAULT_PRICELIST_NAME : spec.getPriceListName();
             final InternalTenantContext internalCallContext = createTenantContextFromBundleId(subscription.getBundleId(), context);
-            final Plan plan = catalogService.getFullCatalog(internalCallContext).findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, effectiveDate);
+            final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, context);
+            final Plan plan = catalogService.getFullCatalog(internalCallContext).createOrFindPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, overridesWithContext, effectiveDate);
             final PlanPhase phase = plan.getAllPhases()[0];
             if (phase == null) {
                 throw new SubscriptionBaseError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog",
@@ -263,7 +267,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
 
     @Override
     public DateTime changePlan(final DefaultSubscriptionBase subscription, final String productName, final BillingPeriod term,
-                               final String priceList, final CallContext context) throws SubscriptionBaseApiException {
+                               final String priceList, final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
         final DateTime now = clock.getUTCNow();
 
         validateEntitlementState(subscription);
@@ -273,7 +277,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         validateEffectiveDate(subscription, effectiveDate);
 
         try {
-            return doChangePlan(subscription, productName, term, planChangeResult.getNewPriceList().getName(), now, effectiveDate, context);
+            return doChangePlan(subscription, productName, term, planChangeResult.getNewPriceList().getName(), overrides, now, effectiveDate, context);
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
@@ -281,7 +285,8 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
 
     @Override
     public DateTime changePlanWithRequestedDate(final DefaultSubscriptionBase subscription, final String productName, final BillingPeriod term,
-                                                final String priceList, final DateTime requestedDateWithMs, final CallContext context) throws SubscriptionBaseApiException {
+                                                final String priceList, final List<PlanPhasePriceOverride> overrides,
+                                                final DateTime requestedDateWithMs, final CallContext context) throws SubscriptionBaseApiException {
         final DateTime now = clock.getUTCNow();
         final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
 
@@ -289,7 +294,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         validateEntitlementState(subscription);
 
         try {
-            return doChangePlan(subscription, productName, term, priceList, now, effectiveDate, context);
+            return doChangePlan(subscription, productName, term, priceList, overrides, now, effectiveDate, context);
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
@@ -297,7 +302,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
 
     @Override
     public DateTime changePlanWithPolicy(final DefaultSubscriptionBase subscription, final String productName, final BillingPeriod term,
-                                         final String priceList, final BillingActionPolicy policy, final CallContext context)
+                                         final String priceList, final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy, final CallContext context)
             throws SubscriptionBaseApiException {
         final DateTime now = clock.getUTCNow();
 
@@ -305,7 +310,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
 
         final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy);
         try {
-            return doChangePlan(subscription, productName, term, priceList, now, effectiveDate, context);
+            return doChangePlan(subscription, productName, term, priceList, overrides, now, effectiveDate, context);
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
@@ -342,11 +347,13 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
                                   final String newProductName,
                                   final BillingPeriod newBillingPeriod,
                                   final String newPriceList,
+                                  final List<PlanPhasePriceOverride> overrides,
                                   final DateTime now,
                                   final DateTime effectiveDate,
                                   final CallContext context) throws SubscriptionBaseApiException, CatalogApiException {
         final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
-        final Plan newPlan = catalogService.getFullCatalog(internalCallContext).findPlan(newProductName, newBillingPeriod, newPriceList, effectiveDate, subscription.getStartDate());
+        final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, context);
+        final Plan newPlan = catalogService.getFullCatalog(internalCallContext).createOrFindPlan(newProductName, newBillingPeriod, newPriceList, overridesWithContext, effectiveDate, subscription.getStartDate());
 
         final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, now, effectiveDate, now, false, internalCallContext);
         dao.changePlan(subscription, changeEvents, internalCallContext);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/migration/TestMigration.java b/subscription/src/test/java/org/killbill/billing/subscription/api/migration/TestMigration.java
index 53a85e1..322b6ca 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/migration/TestMigration.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/migration/TestMigration.java
@@ -281,7 +281,7 @@ public class TestMigration extends SubscriptionTestSuiteWithEmbeddedDB {
 
         // Now make an IMMEDIATE change of plan
         testListener.pushExpectedEvent(NextEvent.CHANGE);
-        subscription.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow(), callContext);
+        subscription.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, clock.getUTCNow(), callContext);
         assertListenerStatus();
 
         final List<SubscriptionBaseTransition> newTransitions = subscription.getAllTransitions();
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairBP.java b/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairBP.java
index a7c6624..49601b6 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairBP.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairBP.java
@@ -581,7 +581,7 @@ public class TestRepairBP extends SubscriptionTestSuiteWithEmbeddedDB {
         subscriptionInternalApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, internalCallContext);
         baseSubscription = subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext);
 
-        baseSubscription.changePlan("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, callContext);
+        baseSubscription.changePlan("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, callContext);
 
         // CHECK CHANGE DID NOT OCCUR YET
         Plan currentPlan = baseSubscription.getCurrentPlan();
@@ -654,7 +654,7 @@ public class TestRepairBP extends SubscriptionTestSuiteWithEmbeddedDB {
 
                 testListener.pushExpectedEvent(NextEvent.CHANGE);
                 final DateTime changeTime = clock.getUTCNow();
-                baseSubscription.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, callContext);
+                baseSubscription.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, changeTime, callContext);
                 assertListenerStatus();
 
                 repairApi.repairBundle(bRepair, true, callContext);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithError.java b/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithError.java
index dbd4f57..d8baee8 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithError.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/timeline/TestRepairWithError.java
@@ -98,7 +98,7 @@ public class TestRepairWithError extends SubscriptionTestSuiteNoDB {
 
                 testListener.pushExpectedEvent(NextEvent.CHANGE);
                 final DateTime changeTime = clock.getUTCNow();
-                baseSubscription.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, callContext);
+                baseSubscription.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, changeTime, callContext);
                 assertListenerStatus();
 
                 // MOVE AFTER TRIAL
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java
index 60bf798..387f53a 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java
@@ -311,7 +311,7 @@ public class TestTransfer extends SubscriptionTestSuiteWithEmbeddedDB {
         final String newBaseProduct1 = "Assault-Rifle";
         final BillingPeriod newBaseTerm1 = BillingPeriod.ANNUAL;
         testListener.pushExpectedEvent(NextEvent.CHANGE);
-        newBaseSubscription.changePlan(newBaseProduct1, newBaseTerm1, basePriceList, callContext);
+        newBaseSubscription.changePlan(newBaseProduct1, newBaseTerm1, basePriceList, null, callContext);
         assertListenerStatus();
 
         newPlan = newBaseSubscription.getCurrentPlan();
@@ -327,7 +327,7 @@ public class TestTransfer extends SubscriptionTestSuiteWithEmbeddedDB {
 
         final String newBaseProduct2 = "Pistol";
         final BillingPeriod newBaseTerm2 = BillingPeriod.ANNUAL;
-        newBaseSubscriptionWithCtd.changePlan(newBaseProduct2, newBaseTerm2, basePriceList, callContext);
+        newBaseSubscriptionWithCtd.changePlan(newBaseProduct2, newBaseTerm2, basePriceList, null, callContext);
 
         newPlan = newBaseSubscriptionWithCtd.getCurrentPlan();
         assertEquals(newPlan.getProduct().getName(), newBaseProduct1);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java
index 54bc97f..0cea1b8 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java
@@ -111,7 +111,7 @@ public class TestSubscriptionHelper {
             throws SubscriptionBaseApiException {
         testListener.pushExpectedEvent(NextEvent.CREATE);
         final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionApi.createSubscription(bundleId,
-                                                                                                                  new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSet, null),
+                                                                                                                  new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSet, null), null,
                                                                                                                   requestedDate == null ? clock.getUTCNow() : requestedDate, internalCallContext);
         assertNotNull(subscription);
 
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java
index 5daba7c..8829276 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java
@@ -311,7 +311,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
 
         testListener.pushExpectedEvent(NextEvent.CHANGE);
         testListener.pushExpectedEvent(NextEvent.CANCEL);
-        baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, callContext);
+        baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, null, callContext);
         assertListenerStatus();
 
         // REFETCH AO SUBSCRIPTION AND CHECK THIS CANCELLED
@@ -367,7 +367,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
         assertEquals(aoStatus.get(0).getPriceList(), aoSubscription.getCurrentPriceList().getName());
         assertEquals(aoStatus.get(0).getReason(), DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN);
 
-        baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, callContext);
+        baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, null, callContext);
 
         // REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
         aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
index 3699306..35f6bed 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
@@ -82,7 +82,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
 
             // CHANGE PLAN
             testListener.pushExpectedEvent(NextEvent.CHANGE);
-            subscription.changePlan(toProd, toTerm, toPlanSet, callContext);
+            subscription.changePlan(toProd, toTerm, toPlanSet, null, callContext);
             assertListenerStatus();
 
             // CHECK CHANGE PLAN
@@ -123,7 +123,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
 
         // RE READ SUBSCRIPTION + CHANGE PLAN
         subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
-        subscription.changePlan(toProd, toTerm, toPlanSet, callContext);
+        subscription.changePlan(toProd, toTerm, toPlanSet, null, callContext);
         assertListenerStatus();
 
         // CHECK CHANGE PLAN
@@ -166,7 +166,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
         clock.addDeltaFromReality(it.toDurationMillis());
 
         // CHANGE PLAN IMM
-        subscription.changePlan(toProd, toTerm, toPlanSet, callContext);
+        subscription.changePlan(toProd, toTerm, toPlanSet, null, callContext);
         checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.TRIAL);
 
         assertListenerStatus();
@@ -220,7 +220,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
 
         // CHANGE PLAN
         currentTime = clock.getUTCNow();
-        subscription.changePlan(toProd, toTerm, toPlanSet, callContext);
+        subscription.changePlan(toProd, toTerm, toPlanSet, null, callContext);
 
         checkChangePlan(subscription, fromProd, ProductCategory.BASE, fromTerm, PhaseType.EVERGREEN);
 
@@ -286,12 +286,12 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
         subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
 
         // CHANGE EOT
-        subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", callContext);
+        subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", null, callContext);
         assertListenerStatus();
 
         // CHANGE
         testListener.pushExpectedEvent(NextEvent.CHANGE);
-        subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", callContext);
+        subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", null, callContext);
         assertListenerStatus();
 
         final Plan currentPlan = subscription.getCurrentPlan();
@@ -328,11 +328,11 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
         subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
 
         // CHANGE EOT
-        subscription.changePlan("Shotgun", BillingPeriod.MONTHLY, "gunclubDiscount", callContext);
+        subscription.changePlan("Shotgun", BillingPeriod.MONTHLY, "gunclubDiscount", null, callContext);
         assertListenerStatus();
 
         // CHANGE EOT
-        subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", callContext);
+        subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", null, callContext);
         assertListenerStatus();
 
         // CHECK NO CHANGE OCCURED YET
@@ -394,7 +394,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
 
         // CHANGE IMMEDIATE TO A 3 PHASES PLAN
         testListener.pushExpectedEvent(NextEvent.CHANGE);
-        subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", callContext);
+        subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", null, callContext);
         assertListenerStatus();
 
         // CHECK EVERYTHING LOOKS CORRECT
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
index 6e284d0..d85336b 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
@@ -57,7 +57,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
 
         testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.PHASE);
         final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
-                                                                                                                          testUtil.getProductSpecifier(productName, planSetName, term, null), requestedDate, internalCallContext);
+                                                                                                                          testUtil.getProductSpecifier(productName, planSetName, term, null), null, requestedDate, internalCallContext);
         assertListenerStatus();
         assertNotNull(subscription);
 
@@ -71,7 +71,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
 
         testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.PHASE);
         final DefaultSubscriptionBase newSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(newBundle.getId(),
-                                                                                                                             testUtil.getProductSpecifier(productName, planSetName, term, null), requestedDate, internalCallContext);
+                                                                                                                             testUtil.getProductSpecifier(productName, planSetName, term, null), null, requestedDate, internalCallContext);
 
         subscriptionInternalApi.updateExternalKey(newBundle.getId(), "myNewSuperKey", internalCallContext);
 
@@ -95,7 +95,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
         testListener.pushExpectedEvent(NextEvent.CREATE);
 
         final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
-                                                                                                                          testUtil.getProductSpecifier(productName, planSetName, term, null), requestedDate, internalCallContext);
+                                                                                                                          testUtil.getProductSpecifier(productName, planSetName, term, null), null, requestedDate, internalCallContext);
         assertNotNull(subscription);
 
         //
@@ -135,7 +135,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
         testListener.pushExpectedEvent(NextEvent.CREATE);
 
         final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
-                                                                                                                          testUtil.getProductSpecifier(productName, planSetName, term, PhaseType.EVERGREEN), clock.getUTCNow(), internalCallContext);
+                                                                                                                          testUtil.getProductSpecifier(productName, planSetName, term, PhaseType.EVERGREEN), null, clock.getUTCNow(), internalCallContext);
         assertNotNull(subscription);
 
         assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
@@ -169,7 +169,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
 
         final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
                                                                                                                           testUtil.getProductSpecifier(productName, planSetName, term, null),
-                                                                                                                          clock.getUTCNow(), internalCallContext);
+                                                                                                                          null, clock.getUTCNow(), internalCallContext);
         assertNotNull(subscription);
 
         assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
@@ -218,7 +218,8 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
 
         // CREATE SUBSCRIPTION
         DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
-                                                                                                                    testUtil.getProductSpecifier(productName, planSetName, term, null), clock.getUTCNow(), internalCallContext);
+                                                                                                                    testUtil.getProductSpecifier(productName, planSetName, term, null),
+                                                                                                                    null, clock.getUTCNow(), internalCallContext);
         assertNotNull(subscription);
 
         PlanPhase currentPhase = subscription.getCurrentPhase();
@@ -258,7 +259,8 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
         testListener.pushExpectedEvent(NextEvent.CREATE);
 
         final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
-                                                                                                                          testUtil.getProductSpecifier(productName, planSetName, term, null), clock.getUTCNow(), internalCallContext);
+                                                                                                                          testUtil.getProductSpecifier(productName, planSetName, term, null),
+                                                                                                                          null, clock.getUTCNow(), internalCallContext);
         assertNotNull(subscription);
 
         assertListenerStatus();
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
index c24b2a4..2166d4c 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
@@ -92,7 +92,7 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
         try {
             subscriptionInternalApi.createSubscription(bundleId,
                                                        testUtil.getProductSpecifier(productName, planSet, term, null),
-                                                       clock.getUTCNow(), internalCallContext);
+                                                       null, clock.getUTCNow(), internalCallContext);
             Assert.fail("Exception expected, error code: " + expected);
         } catch (final SubscriptionBaseApiException e) {
             assertEquals(e.getCode(), expected.getCode());
@@ -106,7 +106,7 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
         testListener.pushExpectedEvent(NextEvent.CANCEL);
         subscription.cancelWithDate(clock.getUTCNow(), callContext);
         try {
-            subscription.changePlanWithDate("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow(), callContext);
+            subscription.changePlanWithDate("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, clock.getUTCNow(), callContext);
         } catch (final SubscriptionBaseApiException e) {
             assertEquals(e.getCode(), ErrorCode.SUB_CHANGE_NON_ACTIVE.getCode());
         }
@@ -117,7 +117,7 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
         final SubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
 
         try {
-            subscription.changePlanWithPolicy("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, BillingActionPolicy.ILLEGAL, callContext);
+            subscription.changePlanWithPolicy("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, BillingActionPolicy.ILLEGAL, callContext);
             Assert.fail();
         } catch (final SubscriptionBaseError error) {
             assertTrue(true);
@@ -125,7 +125,7 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
         }
 
         // Assume the call takes less than a second
-        assertEquals(DefaultClock.truncateMs(subscription.changePlanWithPolicy("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, BillingActionPolicy.IMMEDIATE, callContext)),
+        assertEquals(DefaultClock.truncateMs(subscription.changePlanWithPolicy("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, BillingActionPolicy.IMMEDIATE, callContext)),
                      DefaultClock.truncateMs(clock.getUTCNow()));
         assertEquals(subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext).getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
     }
@@ -152,7 +152,7 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
 
         subscription.cancelWithPolicy(BillingActionPolicy.END_OF_TERM, callContext);
         try {
-            subscription.changePlanWithDate("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow(), callContext);
+            subscription.changePlanWithDate("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, clock.getUTCNow(), callContext);
         } catch (final SubscriptionBaseApiException e) {
             assertEquals(e.getCode(), ErrorCode.SUB_CHANGE_FUTURE_CANCELLED.getCode());
         }
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiRecreate.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiRecreate.java
index b87adb7..95da44a 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiRecreate.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiRecreate.java
@@ -54,7 +54,8 @@ public abstract class TestUserApiRecreate extends SubscriptionTestSuiteWithEmbed
         testListener.pushExpectedEvent(NextEvent.PHASE);
         testListener.pushExpectedEvent(NextEvent.CREATE);
         DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
-                                                                                                                    testUtil.getProductSpecifier(productName, planSetName, term, null), requestedDate, internalCallContext);
+                                                                                                                    testUtil.getProductSpecifier(productName, planSetName, term, null),
+                                                                                                                    null, requestedDate, internalCallContext);
         assertNotNull(subscription);
         assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
         assertEquals(subscription.getBundleId(), bundle.getId());
@@ -70,9 +71,11 @@ public abstract class TestUserApiRecreate extends SubscriptionTestSuiteWithEmbed
         try {
             if (fromUserAPi) {
                 subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
-                                                                                                    testUtil.getProductSpecifier(productName, planSetName, term, null), requestedDate, internalCallContext);
+                                                                                                    testUtil.getProductSpecifier(productName, planSetName, term, null),
+                                                                                                    null, requestedDate, internalCallContext);
             } else {
-                subscription.recreate(testUtil.getProductSpecifier(productName, planSetName, term, null), requestedDate, callContext);
+                subscription.recreate(testUtil.getProductSpecifier(productName, planSetName, term, null),
+                                      null, requestedDate, callContext);
             }
             Assert.fail("Expected Create API to fail since BP already exists");
         } catch (SubscriptionBaseApiException e) {
@@ -95,9 +98,11 @@ public abstract class TestUserApiRecreate extends SubscriptionTestSuiteWithEmbed
 
         if (fromUserAPi) {
             subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
-                                                                                                testUtil.getProductSpecifier(productName, planSetName, term, null), requestedDate, internalCallContext);
+                                                                                                testUtil.getProductSpecifier(productName, planSetName, term, null),
+                                                                                                null, requestedDate, internalCallContext);
         } else {
-            subscription.recreate(testUtil.getProductSpecifier(productName, planSetName, term, null), clock.getUTCNow(), callContext);
+            subscription.recreate(testUtil.getProductSpecifier(productName, planSetName, term, null),
+                                  null, clock.getUTCNow(), callContext);
         }
         assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
         assertEquals(subscription.getBundleId(), bundle.getId());
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
index a3a3130..6a1accf 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
@@ -73,7 +73,7 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
         }
         try {
             log.info("Loading catalog cache for tenant " + internalTenantContext.getTenantRecordId());
-            return callback.loadCatalog(catalogXMLs);
+            return callback.loadCatalog(catalogXMLs, tenantRecordId);
         } catch (final CatalogApiException e) {
             throw new IllegalStateException(String.format("Failed to de-serialize catalog for tenant %s : %s",
                                                           internalTenantContext.getTenantRecordId(), e.getMessage()), e);
@@ -82,6 +82,6 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
 
     public interface LoaderCallback {
 
-        public Object loadCatalog(final List<String> catalogXMLs) throws CatalogApiException;
+        public Object loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException;
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/DBTestingHelper.java b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
index 40a5e4e..670eaa1 100644
--- a/util/src/test/java/org/killbill/billing/DBTestingHelper.java
+++ b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
@@ -160,7 +160,7 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
                                "    PRIMARY KEY (record_id)\n" +
                                ");");
 
-        for (final String pack : new String[]{"account", "analytics", "beatrix", "subscription", "util", "payment", "invoice", "entitlement", "usage", "meter", "tenant"}) {
+        for (final String pack : new String[]{"catalog", "account", "analytics", "beatrix", "subscription", "util", "payment", "invoice", "entitlement", "usage", "meter", "tenant"}) {
             for (final String ddlFile : new String[]{"ddl.sql", "ddl_test.sql"}) {
                 final String ddl;
                 try {
diff --git a/util/src/test/java/org/killbill/billing/mock/MockSubscription.java b/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
index 5817928..8dd38d7 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
@@ -21,6 +21,7 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.mockito.Mockito;
 
 import org.killbill.billing.catalog.api.BillingActionPolicy;
@@ -94,20 +95,20 @@ public class MockSubscription implements SubscriptionBase {
     }
 
     @Override
-    public DateTime changePlan(final String productName, final BillingPeriod term, final String priceList, final CallContext context) throws SubscriptionBaseApiException {
-        return sub.changePlan(productName, term, priceList, context);
+    public DateTime changePlan(final String productName, final BillingPeriod term, final String priceList, final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
+        return sub.changePlan(productName, term, priceList, overrides, context);
     }
 
     @Override
-    public DateTime changePlanWithDate(final String productName, final BillingPeriod term, final String priceList, final DateTime requestedDate,
+    public DateTime changePlanWithDate(final String productName, final BillingPeriod term, final String priceList, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDate,
                                        final CallContext context) throws SubscriptionBaseApiException {
-        return sub.changePlanWithDate(productName, term, priceList, requestedDate, context);
+        return sub.changePlanWithDate(productName, term, priceList, overrides, requestedDate, context);
     }
 
     @Override
     public DateTime changePlanWithPolicy(final String productName, final BillingPeriod term, final String priceList,
-                                         final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
-        return sub.changePlanWithPolicy(productName, term, priceList, policy, context);
+                                         final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
+        return sub.changePlanWithPolicy(productName, term, priceList, overrides, policy, context);
     }
 
     @Override