killbill-memoizeit

Changes

Details

diff --git a/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java b/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java
index ea74974..1073d87 100644
--- a/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java
+++ b/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java
@@ -46,6 +46,8 @@ public interface SubscriptionInternalEvent extends BusInternalEvent {
 
     String getPreviousPhase();
 
+    Integer getPreviousBillCycleDayLocal();
+
     String getNextPlan();
 
     String getNextPhase();
@@ -54,6 +56,8 @@ public interface SubscriptionInternalEvent extends BusInternalEvent {
 
     String getNextPriceList();
 
+    Integer getNextBillCycleDayLocal();
+
     Integer getRemainingEventsForUserOperation();
 
     Long getTotalOrdering();
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 a8ccdf6..a389d59 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
@@ -96,4 +96,7 @@ public interface SubscriptionBaseInternalApi {
     public Iterable<DateTime> getFutureNotificationsForAccount(InternalCallContext context);
 
     public Map<UUID, DateTime> getNextFutureEventForSubscriptions(final SubscriptionBaseTransitionType eventType, final InternalCallContext internalCallContext);
+
+
+    public void updateBCD(final UUID subscriptionId, final int bcd, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException;
 }
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
index 7a7729e..94606d0 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
@@ -45,6 +45,10 @@ public enum SubscriptionBaseTransitionType {
      */
     PHASE,
     /**
+     * Update BCD for a specific subscription
+     */
+    BCD_CHANGE,
+    /**
      * Generated by the system to mark the start of blocked billing overdue state. This is not on disk but computed by junction to create the billing events.
      */
     START_BILLING_DISABLED,
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java b/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java
index dc56c4e..434b964 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java
@@ -73,5 +73,11 @@ public interface SubscriptionBaseTimeline extends Entity {
          * @return the name of the phase
          */
         public String getPlanPhaseName();
+
+        /**
+         *
+         * @return the new billCycleDayLocal
+         */
+        public Integer getBillCycleDayLocal();
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestEventJson.java b/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestEventJson.java
index 77ab028..1703097 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestEventJson.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestEventJson.java
@@ -27,7 +27,7 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.beatrix.BeatrixTestSuiteNoDB;
 import org.killbill.billing.util.jackson.ObjectMapper;
 
-public class TestEventJson extends BeatrixTestSuiteNoDB {
+public class  TestEventJson extends BeatrixTestSuiteNoDB {
 
     private final ObjectMapper mapper = new ObjectMapper();
 
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 bd25206..43d4cff 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
@@ -202,6 +202,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     @Inject
     protected SubscriptionApi subscriptionApi;
 
+
     @Named(BeatrixIntegrationModule.NON_OSGI_PLUGIN_NAME)
     @Inject
     protected MockPaymentProviderPlugin paymentPlugin;
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
new file mode 100644
index 0000000..ff42a6e
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.beatrix.integration;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertNotNull;
+
+public class TestWithBCDUpdate extends TestIntegrationBase {
+
+    @Inject
+    protected SubscriptionBaseInternalApi subscriptionBaseInternalApi;
+
+
+    @Test(groups = "slow")
+    public void testBCDChangeInTrial() throws Exception {
+
+        final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        // BP creation : Will set Account BCD to the first (2016-4-1 + 30 days = 2016-5-1)
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        // 2016-4-4 : (BP still in TRIAL)
+        clock.addDays(3);
+
+        // Set next BCD to be the 15
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 15, internalCallContext);
+        Thread.sleep(1000);
+        assertListenerStatus();
+
+        // 2016-5-15 : Catch BCD_CHANGE event
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.NULL_INVOICE);
+        clock.addDays(11);
+        assertListenerStatus();
+
+        // 2016-5-1 : BP out of TRIAL
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(16);
+        assertListenerStatus();
+
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, new BigDecimal("116.64")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2016-5-15 : NEW BCD
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(14);
+        assertListenerStatus();
+
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 15), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+    }
+
+
+    @Test(groups = "slow")
+    public void testBCDChangeAfterTrialFollowOtherBCDChange() throws Exception {
+
+        final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        // BP creation : Will set Account BCD to the first (2016-4-1 + 30 days = 2016-5-1)
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        // 2016-5-1 : BP out of TRIAL
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        // Set next BCD to be the 15
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 15, internalCallContext);
+        Thread.sleep(1000);
+        assertListenerStatus();
+
+        // 2016-5-15 : Catch BCD_CHANGE event and repair invoice accordingly
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(14);
+        assertListenerStatus();
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 15), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-137.07")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2016-6-01 : Original notification for 2016-6-01 (prior BCD change)
+        busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
+        clock.addDays(17);
+        assertListenerStatus();
+
+
+        // 2016-6-15
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(14);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 15), new LocalDate(2016, 7, 15), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // Set next BCD to be the 10
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 10, internalCallContext);
+        Thread.sleep(1000);
+        assertListenerStatus();
+
+        // 2016-7-10
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(25);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 10), new LocalDate(2016, 8, 10), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 10), new LocalDate(2016, 7, 15), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-41.66")));
+        invoiceChecker.checkInvoice(invoices.get(4).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+    }
+
+
+    @Test(groups = "slow")
+    public void testBCDChangeBeforeChangePlan() throws Exception {
+
+        final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        // BP creation : Will set Account BCD to the first (2016-4-1 + 30 days = 2016-5-1)
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        // 2016-5-1 : BP out of TRIAL
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 10, internalCallContext);
+
+        // 2016-5-5
+        clock.addDays(4);
+        changeEntitlementAndCheckForCompletion(baseEntitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE);
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 5, 10), InvoiceItemType.RECURRING, new BigDecimal("99.99")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-217.70")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 5, 5), InvoiceItemType.CBA_ADJ, new BigDecimal("117.71")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2016-5-10
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(5);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2016, 6, 10), InvoiceItemType.RECURRING, new BigDecimal("599.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2016, 5, 10), InvoiceItemType.CBA_ADJ, new BigDecimal("-117.71")));
+        invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+    }
+
+
+    @Test(groups = "slow")
+    public void testBCDChangeAfterChangePlan() throws Exception {
+
+
+        final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        // BP creation : Will set Account BCD to the first (2016-4-1 + 30 days = 2016-5-1)
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        // 2016-5-1 : BP out of TRIAL
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        // 2016-5-5
+        clock.addDays(4);
+        changeEntitlementAndCheckForCompletion(baseEntitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("522.54")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-217.70")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 10, internalCallContext);
+
+        // 2016-5-10
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(5);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2016, 6, 10), InvoiceItemType.RECURRING, new BigDecimal("599.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-425.77")));
+        invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+    }
+
+    @Test(groups = "slow")
+    public void testBCDChangeForAnnualSubscriptionAndCancellation() throws Exception {
+
+        final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        // BP creation : Will set Account BCD to the first (2016-4-1 + 30 days = 2016-5-1)
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.ANNUAL;
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        // 2016-5-1 : BP out of TRIAL
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 10, internalCallContext);
+
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(9);
+        assertListenerStatus();
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2017, 5, 10), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2017, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2340.77")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2017, 5, 1 (at 13, 42, 0)
+        busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
+        clock.setTime(new DateTime(2017, 5, 1, 0, 13, 42, 0, testTimeZone));
+        assertListenerStatus();
+
+
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        //clock.setDay(new LocalDate(2017, 5, 10));
+        clock.addDays(9);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 10), new LocalDate(2018, 5, 10), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+        invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+    }
+
+
+    @Test(groups = "slow")
+    public void testBCDChangeForAO() throws Exception {
+
+        final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        // BP creation : Will set Account BCD to the first (2016-4-1 + 30 days = 2016-5-1)
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        // 2016-4-4 : (BP still in TRIAL)
+        // Laser-Scope has 1 month DISCOUNT
+        clock.addDays(3);
+        final DefaultEntitlement aoEntitlement = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY,
+                                                                                       NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        // 2016-5-1 : BP out of TRIAL + AO
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(27);
+        assertListenerStatus();
+
+        // 2016-5-4: Laser-Scope out of DISCOUNT
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(3);
+        assertListenerStatus();
+
+        // 2016-6-1 : BP + AO invoice
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(28);
+        assertListenerStatus();
+
+        // 2016-6-4 : Change BCD for AO and
+        clock.addDays(3);
+
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        subscriptionBaseInternalApi.updateBCD(aoEntitlement.getId(), 4, internalCallContext);
+        assertListenerStatus();
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+        List<Invoice> invoices = null;
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 4), new LocalDate(2016, 7, 4), InvoiceItemType.RECURRING, new BigDecimal("1999.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 4), new LocalDate(2016, 7, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-1799.96")));
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        invoiceChecker.checkInvoice(invoices.get(5).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2016-7-1 : BP only
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(27);
+        assertListenerStatus();
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 1), new LocalDate(2016, 8, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        invoiceChecker.checkInvoice(invoices.get(6).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2016-7-4 : AO only
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(3);
+        assertListenerStatus();
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 4), new LocalDate(2016, 8, 4), InvoiceItemType.RECURRING, new BigDecimal("1999.95")));
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        invoiceChecker.checkInvoice(invoices.get(7).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        checkNoMoreInvoiceToGenerate(account);
+    }
+
+    @Test(groups = "slow")
+    public void testBlockPastUnpaidPeriodAndRealignBCD() throws Exception {
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+        List<Invoice> invoices = null;
+
+        final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        // BP creation : Will set Account BCD to the first (2016-4-1 + 30 days = 2016-5-1)
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+
+        paymentPlugin.makeNextPaymentFailWithError();
+
+        // 2016-5-1 : BP out of TRIAL
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        clock.addDays(30);
+        assertListenerStatus();
+
+
+        //
+        // Let's assume 15 days later, the customer comes back and wants to continue using the service (after he updated his payment method)
+        //
+        // The company 'a.b.c' decides to block both the billing and entitlement for the past 15 days and also move his BCD to
+        // the 16 so he gets to pay right away and for a full period (MONTHLY)
+        //
+        // 2016-5-16
+        busHandler.pushExpectedEvents(NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.PAYMENT_ERROR);
+        paymentPlugin.makeNextPaymentFailWithError();
+        clock.addDays(15);
+        assertListenerStatus();
+
+
+        // First BLOCK subscription starting from the 2016-5-1
+        // This will generate the credit for the full period, bringing by account balance to 0
+        busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE);
+        final BlockingState blockingState = new DefaultBlockingState(baseEntitlement.getId(), BlockingStateType.SUBSCRIPTION, "COURTESY_BLOCK", "company.a.b.c", true, true, true, null);
+        subscriptionApi.addBlockingState(blockingState,  new LocalDate(2016, 5, 1),  ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-249.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 16), new LocalDate(2016, 5, 16), InvoiceItemType.CBA_ADJ, new BigDecimal("249.95")));
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // Second, move the BCD to the 16
+        // Because we did not unblock yet, we don't have a new invoice but we see the NULL_INVOICE event
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.NULL_INVOICE);
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 16, internalCallContext);
+        assertListenerStatus();
+
+        // Third, unblock starting at the 16, will generate a full period invoice
+        busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE,  NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        final BlockingState unblockingState = new DefaultBlockingState(baseEntitlement.getId(), BlockingStateType.SUBSCRIPTION, "END_OF_COURTESY_BLOCK", "company.a.b.c", false, false, false, null);
+        subscriptionApi.addBlockingState(unblockingState,  new LocalDate(2016, 5, 16),  ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 16), new LocalDate(2016, 6, 16), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+    }
+
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java b/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java
index f7898b5..2f66d29 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java
@@ -16,9 +16,14 @@
 
 package org.killbill.billing.catalog.api.user;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+
 import javax.inject.Inject;
 
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.StandaloneCatalog;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.CatalogService;
@@ -31,6 +36,7 @@ import org.killbill.billing.tenant.api.TenantUserApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.xmlloader.XMLLoader;
 
 public class DefaultCatalogUserApi implements CatalogUserApi {
 
@@ -65,11 +71,17 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
     @Override
     public void uploadCatalog(final String catalogXML, final CallContext callContext) throws CatalogApiException {
         try {
+            // Validation purpose:  Will throw if bad XML or catalog validation fails
+            final InputStream stream = new ByteArrayInputStream(catalogXML.getBytes());
+            XMLLoader.getObjectFromStream(new URI("dummy"), stream, StandaloneCatalog.class);
+
             final InternalTenantContext internalTenantContext = createInternalTenantContext(callContext);
             catalogCache.clearCatalog(internalTenantContext);
             tenantApi.addTenantKeyValue(TenantKey.CATALOG.toString(), catalogXML, callContext);
         } catch (TenantApiException e) {
             throw new CatalogApiException(e);
+        } catch (final Exception e) {
+            throw new IllegalStateException(e);
         }
     }
 
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
index c4dcbd3..dc0198b 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
@@ -1571,9 +1571,11 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
                                                   null,
                                                   null,
                                                   null,
+                                                  null,
                                                   nextPlan,
                                                   nextPhase,
                                                   nextPriceList,
+                                                  null,
                                                   1L,
                                                   createdDate,
                                                   UUID.randomUUID(),
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountTimelineJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountTimelineJson.java
index 7537ada..0cb8603 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountTimelineJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountTimelineJson.java
@@ -26,6 +26,7 @@ import java.util.Set;
 import java.util.UUID;
 
 import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.entitlement.api.SubscriptionBundle;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItem;
@@ -62,11 +63,11 @@ public class AccountTimelineJson {
                                final List<Payment> payments,
                                final List<InvoicePayment> invoicePayments,
                                final List<SubscriptionBundle> bundles,
-                               final AccountAuditLogs accountAuditLogs) {
+                               final AccountAuditLogs accountAuditLogs) throws CatalogApiException {
         this.account = new AccountJson(account, null, null, accountAuditLogs);
         this.bundles = new LinkedList<BundleJson>();
         for (final SubscriptionBundle bundle : bundles) {
-            final BundleJson jsonWithSubscriptions = new BundleJson(bundle, accountAuditLogs);
+            final BundleJson jsonWithSubscriptions = new BundleJson(bundle, account.getCurrency(), accountAuditLogs);
             this.bundles.add(jsonWithSubscriptions);
         }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BundleJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BundleJson.java
index 22effcd..0dbd4b5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BundleJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BundleJson.java
@@ -23,6 +23,8 @@ import java.util.List;
 
 import javax.annotation.Nullable;
 
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.entitlement.api.Subscription;
 import org.killbill.billing.entitlement.api.SubscriptionBundle;
 import org.killbill.billing.util.audit.AccountAuditLogs;
@@ -56,18 +58,20 @@ public class BundleJson extends JsonBase {
         this.timeline = timeline;
     }
 
-    public BundleJson(final SubscriptionBundle bundle, @Nullable final AccountAuditLogs accountAuditLogs) {
+    public BundleJson(final SubscriptionBundle bundle, @Nullable final Currency currency, @Nullable final AccountAuditLogs accountAuditLogs) throws CatalogApiException {
         super(toAuditLogJson(accountAuditLogs == null ? null : accountAuditLogs.getAuditLogsForBundle(bundle.getId())));
         this.accountId = bundle.getAccountId().toString();
         this.bundleId = bundle.getId().toString();
         this.externalKey = bundle.getExternalKey();
         this.subscriptions = new LinkedList<SubscriptionJson>();
         for (final Subscription subscription : bundle.getSubscriptions()) {
-            this.subscriptions.add(new SubscriptionJson(subscription, accountAuditLogs));
+            this.subscriptions.add(new SubscriptionJson(subscription, currency, accountAuditLogs));
         }
         this.timeline = new BundleTimelineJson(bundle.getTimeline(), accountAuditLogs);
     }
 
+
+
     public List<SubscriptionJson> getSubscriptions() {
         return subscriptions;
     }
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
index 1ce8cbb..6f8745a 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PhasePriceOverrideJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PhasePriceOverrideJson.java
@@ -90,7 +90,8 @@ public class PhasePriceOverrideJson {
 
         final PhasePriceOverrideJson that = (PhasePriceOverrideJson) o;
 
-        if (fixedPrice != null ? !fixedPrice.equals(that.fixedPrice) : that.fixedPrice != null) {
+
+        if (fixedPrice != null ? fixedPrice.compareTo(that.fixedPrice) != 0 : that.fixedPrice != null) {
             return false;
         }
         if (phaseName != null ? !phaseName.equals(that.phaseName) : that.phaseName != null) {
@@ -99,7 +100,7 @@ public class PhasePriceOverrideJson {
         if (phaseType != null ? !phaseType.equals(that.phaseType) : that.phaseType != null) {
             return false;
         }
-        if (recurringPrice != null ? !recurringPrice.equals(that.recurringPrice) : that.recurringPrice != null) {
+        if (recurringPrice != null ? recurringPrice.compareTo(that.recurringPrice) != 0 : that.recurringPrice != null) {
             return false;
         }
         return true;
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 adb80de..c3eba91 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
@@ -18,6 +18,8 @@
 
 package org.killbill.billing.jaxrs.json;
 
+import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -26,6 +28,9 @@ import javax.annotation.Nullable;
 import org.joda.time.LocalDate;
 import org.killbill.billing.ObjectType;
 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.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.Product;
@@ -69,7 +74,6 @@ public class SubscriptionJson extends JsonBase {
     private final List<EventSubscriptionJson> events;
     private final List<PhasePriceOverrideJson> priceOverrides;
 
-
     public static class EventSubscriptionJson extends JsonBase {
 
         private final String eventId;
@@ -314,7 +318,7 @@ public class SubscriptionJson extends JsonBase {
         this.priceOverrides = priceOverrides;
     }
 
-    public SubscriptionJson(final Subscription subscription, @Nullable final AccountAuditLogs accountAuditLogs) {
+    public SubscriptionJson(final Subscription subscription, @Nullable final Currency currency, @Nullable final AccountAuditLogs accountAuditLogs) throws CatalogApiException {
         super(toAuditLogJson(accountAuditLogs == null ? null : accountAuditLogs.getAuditLogsForSubscription(subscription.getId())));
         this.startDate = subscription.getEffectiveStartDate();
 
@@ -360,8 +364,20 @@ public class SubscriptionJson extends JsonBase {
         for (final SubscriptionEvent subscriptionEvent : subscription.getSubscriptionEvents()) {
             this.events.add(new EventSubscriptionJson(subscriptionEvent, accountAuditLogs));
         }
-        // It may be nice to recreate the override that were applied on the plans associated with that subscription
-        this.priceOverrides = new LinkedList<PhasePriceOverrideJson>();
+
+        // We fill the catalog info every time we get the currency from the account (even if this is not overridden Plan)
+        this.priceOverrides = new ArrayList<PhasePriceOverrideJson>();
+        if (currency != null) {
+            final Plan plan = subscription.getLastActivePlan();
+            if (plan != null) {
+                for (final PlanPhase cur : plan.getAllPhases()) {
+                    final BigDecimal fixedPrice = cur.getFixed() != null ? cur.getFixed().getPrice().getPrice(currency) : null;
+                    final BigDecimal recurringPrice = cur.getRecurring() != null ? cur.getRecurring().getRecurringPrice().getPrice(currency) : null;
+                    final PhasePriceOverrideJson phase = new PhasePriceOverrideJson(cur.getName(), cur.getPhaseType().toString(), fixedPrice, recurringPrice);
+                    priceOverrides.add(phase);
+                }
+            }
+        }
     }
 
     public String getAccountId() {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index 290c0dc..dc1fe50 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -58,6 +58,7 @@ import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.account.api.AccountEmail;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.account.api.MutableAccountData;
+import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
@@ -268,7 +269,7 @@ public class AccountResource extends JaxRsResourceBase {
         final TenantContext tenantContext = context.createContext(request);
 
         final UUID uuid = UUID.fromString(accountId);
-        accountUserApi.getAccountById(uuid, tenantContext);
+       final Account account = accountUserApi.getAccountById(uuid, tenantContext);
 
         final List<SubscriptionBundle> bundles = (externalKey != null) ?
                                                  subscriptionApi.getSubscriptionBundlesForAccountIdAndExternalKey(uuid, externalKey, tenantContext) :
@@ -277,7 +278,12 @@ public class AccountResource extends JaxRsResourceBase {
         final Collection<BundleJson> result = Collections2.transform(bundles, new Function<SubscriptionBundle, BundleJson>() {
             @Override
             public BundleJson apply(final SubscriptionBundle input) {
-                return new BundleJson(input, null);
+                try {
+                    return new BundleJson(input, account.getCurrency(), null);
+                } catch (final CatalogApiException e) {
+                    // Not the cleanest thing, but guava Api don't allow throw..
+                    throw new RuntimeException(e);
+                }
             }
         });
         return Response.status(Status.OK).entity(result).build();
@@ -385,7 +391,7 @@ public class AccountResource extends JaxRsResourceBase {
     public Response getAccountTimeline(@PathParam("accountId") final String accountIdString,
                                        @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                        @QueryParam(QUERY_PARALLEL) @DefaultValue("false") final Boolean parallel,
-                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException, SubscriptionApiException, InvoiceApiException {
+                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException, SubscriptionApiException, InvoiceApiException, CatalogApiException {
         final TenantContext tenantContext = context.createContext(request);
 
         final UUID accountId = UUID.fromString(accountIdString);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
index a09e5db..f32c0f3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
@@ -41,9 +41,11 @@ 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.CatalogApiException;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.api.EntitlementApi;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
@@ -114,10 +116,13 @@ public class BundleResource extends JaxRsResourceBase {
     @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid bundle id supplied"),
                            @ApiResponse(code = 404, message = "Bundle not found")})
     public Response getBundle(@PathParam("bundleId") final String bundleId,
-                              @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
+                              @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, AccountApiException, CatalogApiException {
         final UUID id = UUID.fromString(bundleId);
-        final SubscriptionBundle bundle = subscriptionApi.getSubscriptionBundle(id, context.createContext(request));
-        final BundleJson json = new BundleJson(bundle, null);
+
+        final TenantContext tenantContext = this.context.createContext(request);
+        final SubscriptionBundle bundle = subscriptionApi.getSubscriptionBundle(id, tenantContext);
+        final Account account = accountUserApi.getAccountById(bundle.getAccountId(), tenantContext);
+        final BundleJson json = new BundleJson(bundle, account.getCurrency(), null);
         return Response.status(Status.OK).entity(json).build();
     }
 
@@ -127,9 +132,13 @@ public class BundleResource extends JaxRsResourceBase {
     @ApiOperation(value = "Retrieve a bundle by external key", response = BundleJson.class)
     @ApiResponses(value = {@ApiResponse(code = 404, message = "Bundle not found")})
     public Response getBundleByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
-                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
-        final SubscriptionBundle bundle = subscriptionApi.getActiveSubscriptionBundleForExternalKey(externalKey, context.createContext(request));
-        final BundleJson json = new BundleJson(bundle, null);
+                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, AccountApiException, CatalogApiException {
+
+        final TenantContext tenantContext = this.context.createContext(request);
+
+        final SubscriptionBundle bundle = subscriptionApi.getActiveSubscriptionBundleForExternalKey(externalKey, tenantContext);
+        final Account account = accountUserApi.getAccountById(bundle.getAccountId(), tenantContext);
+        final BundleJson json = new BundleJson(bundle, account.getCurrency(), null);
         return Response.status(Status.OK).entity(json).build();
     }
 
@@ -155,7 +164,13 @@ public class BundleResource extends JaxRsResourceBase {
                                                         if (accountsAuditLogs.get().get(bundle.getAccountId()) == null) {
                                                             accountsAuditLogs.get().put(bundle.getAccountId(), auditUserApi.getAccountAuditLogs(bundle.getAccountId(), auditMode.getLevel(), tenantContext));
                                                         }
-                                                        return new BundleJson(bundle, accountsAuditLogs.get().get(bundle.getAccountId()));
+
+                                                        try {
+                                                            return new BundleJson(bundle, null, accountsAuditLogs.get().get(bundle.getAccountId()));
+                                                        } catch (final CatalogApiException unused) {
+                                                            // Does not happen because we pass a null Currency
+                                                            throw new RuntimeException(unused);
+                                                        }
                                                     }
                                                 },
                                                 nextPageUri);
@@ -185,7 +200,12 @@ public class BundleResource extends JaxRsResourceBase {
                                                         if (accountsAuditLogs.get().get(bundle.getAccountId()) == null) {
                                                             accountsAuditLogs.get().put(bundle.getAccountId(), auditUserApi.getAccountAuditLogs(bundle.getAccountId(), auditMode.getLevel(), tenantContext));
                                                         }
-                                                        return new BundleJson(bundle, accountsAuditLogs.get().get(bundle.getAccountId()));
+                                                        try {
+                                                            return new BundleJson(bundle, null, accountsAuditLogs.get().get(bundle.getAccountId()));
+                                                        } catch (final CatalogApiException unused) {
+                                                            // Does not happen because we pass a null Currency
+                                                            throw new RuntimeException(unused);
+                                                        }
                                                     }
                                                 },
                                                 nextPageUri);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
index 04f7f8b..a262d14 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
@@ -16,9 +16,6 @@
 
 package org.killbill.billing.jaxrs.resources;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -38,7 +35,6 @@ import javax.ws.rs.core.UriInfo;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.killbill.billing.account.api.AccountUserApi;
-import org.killbill.billing.catalog.StandaloneCatalog;
 import org.killbill.billing.catalog.VersionedCatalog;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
@@ -57,7 +53,6 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.clock.Clock;
 import org.killbill.commons.metrics.TimedResource;
-import org.killbill.xmlloader.XMLLoader;
 import org.killbill.xmlloader.XMLWriter;
 
 import com.google.inject.Inject;
@@ -76,9 +71,6 @@ public class CatalogResource extends JaxRsResourceBase {
 
     private final CatalogUserApi catalogUserApi;
 
-    // Catalog API don't quite support multiple catalogs per tenant
-    private static final String catalogName = "unused";
-
     @Inject
     public CatalogResource(final JaxrsUriBuilder uriBuilder,
                            final TagUserApi tagUserApi,
@@ -114,10 +106,6 @@ public class CatalogResource extends JaxRsResourceBase {
                                      @HeaderParam(HDR_COMMENT) final String comment,
                                      @javax.ws.rs.core.Context final HttpServletRequest request,
                                      @javax.ws.rs.core.Context final UriInfo uriInfo) throws Exception {
-        // Validation purpose:  Will throw if bad XML or catalog validation fails
-        final InputStream stream = new ByteArrayInputStream(catalogXML.getBytes());
-        XMLLoader.getObjectFromStream(new URI(JaxrsResource.CATALOG_PATH), stream, StandaloneCatalog.class);
-
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         catalogUserApi.uploadCatalog(catalogXML, callContext);
         return uriBuilder.buildResponse(uriInfo, CatalogResource.class, null, null);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index 1ca26ae..18513b1 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -128,6 +128,8 @@ public interface JaxrsResource {
 
     public static final String QUERY_AUDIT = "audit";
 
+    public static final String QUERY_BCD = "bcd";
+
     public static final String QUERY_PARALLEL = "parallel";
 
     public static final String QUERY_NOTIFICATION_CALLBACK = "cb";
@@ -252,4 +254,6 @@ public interface JaxrsResource {
     public static final String COMBO = "combo";
     public static final String MIGRATION = "migration";
 
+    public static final String BCD = "bcd";
+
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 0f4de38..ee5cf1a 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -117,6 +117,10 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
 
     static final Logger log = LoggerFactory.getLogger(JaxRsResourceBase.class);
 
+    // Catalog API don't quite support multiple catalogs per tenant
+    protected static final String catalogName = "unused";
+
+
     protected static final ObjectMapper mapper = new ObjectMapper();
 
     protected final JaxrsUriBuilder uriBuilder;
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 a74d09b..5d7140a 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
@@ -47,6 +47,7 @@ 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.CatalogApiException;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
@@ -141,10 +142,12 @@ public class SubscriptionResource extends JaxRsResourceBase {
     @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid subscription id supplied"),
                            @ApiResponse(code = 404, message = "Subscription not found")})
     public Response getEntitlement(@PathParam("subscriptionId") final String subscriptionId,
-                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
+                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, AccountApiException, CatalogApiException {
         final UUID uuid = UUID.fromString(subscriptionId);
-        final Subscription subscription = subscriptionApi.getSubscriptionForEntitlementId(uuid, context.createContext(request));
-        final SubscriptionJson json = new SubscriptionJson(subscription, null);
+        final TenantContext context = this.context.createContext(request);
+        final Subscription subscription = subscriptionApi.getSubscriptionForEntitlementId(uuid, context);
+        final Account account = accountUserApi.getAccountById(subscription.getAccountId(), context);
+        final SubscriptionJson json = new SubscriptionJson(subscription, account.getCurrency(), null);
         return Response.status(Status.OK).entity(json).build();
     }
 
@@ -436,7 +439,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
             }
 
             @Override
-            public Response doResponseOk(final Response operationResponse) throws SubscriptionApiException {
+            public Response doResponseOk(final Response operationResponse) throws SubscriptionApiException, AccountApiException, CatalogApiException {
                 if (operationResponse.getStatus() != Status.OK.getStatusCode()) {
                     return operationResponse;
                 }
@@ -600,7 +603,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
 
         public boolean isImmOperation();
 
-        public Response doResponseOk(final T operationResponse) throws SubscriptionApiException;
+        public Response doResponseOk(final T operationResponse) throws SubscriptionApiException, AccountApiException, CatalogApiException;
     }
 
     private class EntitlementCallCompletion<T> {
@@ -621,6 +624,8 @@ public class SubscriptionResource extends JaxRsResourceBase {
                 return callback.doResponseOk(operationValue);
             } catch (final InterruptedException e) {
                 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+            } catch (final CatalogApiException e) {
+                throw new EntitlementApiException(e);
             } catch (final TimeoutException e) {
                 return Response.status(408).build();
             } finally {
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
index b387188..7183e3c 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
@@ -81,7 +81,7 @@ public class DefaultBillingEvent implements BillingEvent {
         final String prevPhaseName = transition.getPreviousPhase();
         final PlanPhase prevPlanPhase = (prevPhaseName != null) ? catalog.findPhase(prevPhaseName, transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
 
-        this.fixedPrice = getFixedPrice(nextPlanPhase, currency);
+        this.fixedPrice = transition.getTransitionType() != SubscriptionBaseTransitionType.BCD_CHANGE ? getFixedPrice(nextPlanPhase, currency) : null;
         this.currency = currency;
         this.description = transition.getTransitionType().toString();
         this.billingPeriod = getRecurringBillingPeriod(isActive ? nextPlanPhase : prevPlanPhase);
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index 7ec0b85..cb91c31 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -195,9 +195,18 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
             }
 
 
+            Integer overridenBCD = null;
             for (final EffectiveSubscriptionInternalEvent transition : billingTransitions) {
                 try {
-                    final int bcdLocal = bcdCalculator.calculateBcd(account, currentAccountBCD, bundleId, subscription, transition, context);
+                    //
+                    // A BCD_CHANGE transition defines a new billCycleDayLocal for the subscription and this overrides whatever computation
+                    // occurs below (which is based on billing alignment policy). Also multiple of those BCD_CHANGE transitions could occur,
+                    // to define different intervals with different billing cycle days.
+                    //
+                    overridenBCD = transition.getNextBillCycleDayLocal() != null ? transition.getNextBillCycleDayLocal() : overridenBCD;
+                    final int bcdLocal = overridenBCD != null ?
+                                         overridenBCD :
+                                         bcdCalculator.calculateBcd(account, currentAccountBCD, bundleId, subscription, transition, context);
 
                     if (currentAccountBCD == 0 && !updatedAccountBCD) {
                         accountApi.updateBCD(account.getExternalKey(), bcdLocal, context);
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
index df07cbc..2a23e7a 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
@@ -281,9 +281,9 @@ public class TestBillingApi extends JunctionTestSuiteNoDB {
         final PriceList nextPriceList = catalog.findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
 
         final EffectiveSubscriptionInternalEvent t = new MockEffectiveSubscriptionEvent(
-                eventId, subId, bunId, then, now, null, null, null, null, EntitlementState.ACTIVE,
+                eventId, subId, bunId, then, now, null, null, null, null, null, EntitlementState.ACTIVE,
                 nextPlan.getName(), nextPhase.getName(),
-                nextPriceList.getName(), 1L,
+                nextPriceList.getName(), null, 1L,
                 SubscriptionBaseTransitionType.CREATE, 1, null, 1L, 2L, null);
 
         effectiveSubscriptionTransitions.add(t);
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 080d812..be975a1 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
@@ -31,7 +31,6 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.billing.client.KillBillClientException;
 import org.killbill.billing.client.model.Account;
 import org.killbill.billing.client.model.Bundle;
 import org.killbill.billing.client.model.Invoice;
@@ -66,6 +65,16 @@ public class TestEntitlement extends TestJaxrsBase {
 
         // Retrieves with GET
         Subscription objFromJson = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
+        Assert.assertEquals(objFromJson.getPriceOverrides().size(), 2);
+        Assert.assertEquals(objFromJson.getPriceOverrides().get(0).getFixedPrice(), BigDecimal.ZERO);
+        Assert.assertNull(objFromJson.getPriceOverrides().get(0).getRecurringPrice());
+
+        Assert.assertNull(objFromJson.getPriceOverrides().get(1).getFixedPrice());
+        Assert.assertEquals(objFromJson.getPriceOverrides().get(1).getRecurringPrice(), new BigDecimal("249.95"));
+
+        // Equality in java client is not correctly implemented so manually check PriceOverrides section and then reset before equality
+        objFromJson.setPriceOverrides(null);
+        entitlementJson.setPriceOverrides(null);
         Assert.assertTrue(objFromJson.equals(entitlementJson));
 
         // Change plan IMM
@@ -111,6 +120,10 @@ public class TestEntitlement extends TestJaxrsBase {
 
         // Retrieves with GET
         Subscription objFromJson = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
+        // Equality in java client is not correctly implemented so manually check PriceOverrides section and then reset before equality
+        objFromJson.setPriceOverrides(null);
+        entitlementJson.setPriceOverrides(null);
+
         Assert.assertTrue(objFromJson.equals(entitlementJson));
 
         // MOVE AFTER TRIAL
@@ -167,6 +180,10 @@ public class TestEntitlement extends TestJaxrsBase {
 
         // Retrieves with GET
         Subscription objFromJson = killBillClient.getSubscription(subscriptionJson.getSubscriptionId());
+        // Equality in java client is not correctly implemented so manually check PriceOverrides section and then reset before equality
+        objFromJson.setPriceOverrides(null);
+        subscriptionJson.setPriceOverrides(null);
+
         Assert.assertTrue(objFromJson.equals(subscriptionJson));
         assertEquals(objFromJson.getBillingPeriod(), BillingPeriod.ANNUAL);
 
@@ -279,4 +296,5 @@ public class TestEntitlement extends TestJaxrsBase {
         final Subscription objFromJson = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
         Assert.assertTrue(objFromJson.equals(entitlementJson));
     }
+
 }
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 5f4ef51..065afad 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
@@ -28,6 +28,7 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
@@ -70,7 +71,8 @@ import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseServ
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
-import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
+import org.killbill.billing.subscription.events.bcd.BCDEventData;
 import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
 import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.callcontext.CallContext;
@@ -609,7 +611,15 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         final Iterable<SubscriptionBaseEvent> filteredEvents = Iterables.filter(events, new Predicate<SubscriptionBaseEvent>() {
             @Override
             public boolean apply(final SubscriptionBaseEvent input) {
-                return (eventType == SubscriptionBaseTransitionType.PHASE && input.getType() == EventType.PHASE) || input.getType() != EventType.PHASE;
+                switch (input.getType()) {
+                    case PHASE:
+                        return eventType == SubscriptionBaseTransitionType.PHASE;
+                    case BCD_UPDATE:
+                        return eventType == SubscriptionBaseTransitionType.BCD_CHANGE;
+                    case API_USER:
+                    default:
+                        return true;
+                }
             }
         });
         final Map<UUID, DateTime> result = filteredEvents.iterator().hasNext() ? new HashMap<UUID, DateTime>() : ImmutableMap.<UUID, DateTime>of();
@@ -622,6 +632,47 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         return result;
     }
 
+    @Override
+    public void updateBCD(final UUID subscriptionId, final int bcd, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException {
+        final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) getSubscriptionFromId(subscriptionId, internalCallContext);
+        final DateTime effectiveDate = getEffectiveDateForNewBCD(bcd, internalCallContext);
+        final BCDEvent bcdEvent = BCDEventData.createBCDEvent(subscription, effectiveDate, bcd);
+        dao.createBCDChangeEvent(subscription, bcdEvent, internalCallContext);
+    }
+
+
+    private DateTime getEffectiveDateForNewBCD(final int bcd, final InternalCallContext internalCallContext) {
+        if (internalCallContext.getAccountRecordId() == null) {
+            throw new IllegalStateException("Need to have a valid context with accountRecordId");
+        }
+
+        // Today as seen by this account
+        final LocalDate startDate = internalCallContext.toLocalDate(clock.getUTCNow());
+
+        // We want to compute a LocalDate in account TZ which maps to the provided 'bcd' and then compute an effectiveDate for when that BCD_CHANGE event needs to be triggered
+        //
+        // There is a bit of complexity to make sure the date we chose exists (e.g: a BCD of 31 in a february month would not make sense).
+        final int currentDay = startDate.getDayOfMonth();
+        final int lastDayOfMonth = startDate.dayOfMonth().getMaximumValue();
+
+        final LocalDate requestedDate;
+        if (bcd < currentDay) {
+            final LocalDate startDatePlusOneMonth = startDate.plusMonths(1);
+            final int lastDayOfNextMonth = startDatePlusOneMonth.dayOfMonth().getMaximumValue();
+            final int originalBCDORLastDayOfMonth = bcd <= lastDayOfNextMonth ? bcd : lastDayOfNextMonth;
+            requestedDate = new LocalDate(startDatePlusOneMonth.getYear(), startDatePlusOneMonth.getMonthOfYear(), originalBCDORLastDayOfMonth);
+        } else if (bcd == currentDay) {
+            // will default to immediate event
+            requestedDate = null;
+        } else if (bcd <= lastDayOfMonth) {
+            requestedDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), bcd);
+        } else /* bcd > lastDayOfMonth && bcd > currentDay */{
+            requestedDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), lastDayOfMonth);
+        }
+        return requestedDate == null ? clock.getUTCNow() : internalCallContext.toUTCDateTime(requestedDate);
+    }
+
+
     private DateTime getBundleStartDateWithSanity(final UUID bundleId, @Nullable final DefaultSubscriptionBase baseSubscription, final Plan plan,
                                                   final DateTime effectiveDate, final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
         switch (plan.getProduct().getCategory()) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java
index 436393f..f554eeb 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java
@@ -37,6 +37,7 @@ import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
 import org.killbill.billing.subscription.events.phase.PhaseEvent;
 import org.killbill.billing.subscription.events.user.ApiEvent;
 import org.killbill.billing.subscription.events.user.ApiEventType;
@@ -85,6 +86,7 @@ public class DefaultSubscriptionBaseTimeline implements SubscriptionBaseTimeline
             PhaseType phaseType = null;
             String planName = null;
             String planPhaseName = null;
+            Integer billCycleDayLocal = null;
 
             ApiEventType apiType = null;
             switch (cur.getType()) {
@@ -99,6 +101,11 @@ public class DefaultSubscriptionBaseTimeline implements SubscriptionBaseTimeline
                     priceListName = prevPriceListName;
                     break;
 
+                case BCD_UPDATE:
+                    final BCDEvent bcdEvent = (BCDEvent) cur;
+                    billCycleDayLocal = bcdEvent.getBillCycleDayLocal();
+                    break;
+
                 case API_USER:
                     final ApiEvent userEV = (ApiEvent) cur;
                     apiType = userEV.getApiEventType();
@@ -116,6 +123,7 @@ public class DefaultSubscriptionBaseTimeline implements SubscriptionBaseTimeline
 
             final String planNameWithClosure = planName;
             final String planPhaseNameWithClosure = planPhaseName;
+            final Integer billCycleDayLocalWithClosure = billCycleDayLocal;
             final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
             result.add(new ExistingEvent() {
                 @Override
@@ -147,6 +155,11 @@ public class DefaultSubscriptionBaseTimeline implements SubscriptionBaseTimeline
                 public String getPlanPhaseName() {
                     return planPhaseNameWithClosure;
                 }
+
+                @Override
+                public Integer getBillCycleDayLocal() {
+                    return billCycleDayLocalWithClosure;
+                }
             });
 
             prevPlanName = planName;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java
index aca165c..4e4d7b7 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java
@@ -42,10 +42,12 @@ public class DefaultEffectiveSubscriptionEvent extends DefaultSubscriptionEvent 
                                              @JsonProperty("previousPlan") final String previousPlan,
                                              @JsonProperty("previousPhase") final String previousPhase,
                                              @JsonProperty("previousPriceList") final String previousPriceList,
+                                             @JsonProperty("previousBillCycleDayLocal") final Integer previousBillCycleDayLocal,
                                              @JsonProperty("nextState") final EntitlementState nextState,
                                              @JsonProperty("nextPlan") final String nextPlan,
                                              @JsonProperty("nextPhase") final String nextPhase,
                                              @JsonProperty("nextPriceList") final String nextPriceList,
+                                             @JsonProperty("nextBillCycleDayLocal") final Integer nextBillCycleDayLocal,
                                              @JsonProperty("totalOrdering") final Long totalOrdering,
                                              @JsonProperty("transitionType") final SubscriptionBaseTransitionType transitionType,
                                              @JsonProperty("remainingEventsForUserOperation") final Integer remainingEventsForUserOperation,
@@ -54,7 +56,7 @@ public class DefaultEffectiveSubscriptionEvent extends DefaultSubscriptionEvent 
                                              @JsonProperty("searchKey2") final Long searchKey2,
                                              @JsonProperty("userToken") final UUID userToken) {
         super(eventId, subscriptionId, bundleId, effectiveTransitionTime, effectiveTransitionTime, previousState, previousPlan,
-              previousPhase, previousPriceList, nextState, nextPlan, nextPhase, nextPriceList, totalOrdering,
+              previousPhase, previousPriceList, previousBillCycleDayLocal, nextState, nextPlan, nextPhase, nextPriceList, nextBillCycleDayLocal, totalOrdering,
               transitionType, remainingEventsForUserOperation, startDate, searchKey1, searchKey2, userToken);
     }
 }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java
index 2c78684..1e918ef 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java
@@ -40,10 +40,12 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent 
                                              @JsonProperty("previousPlan") final String previousPlan,
                                              @JsonProperty("previousPhase") final String previousPhase,
                                              @JsonProperty("previousPriceList") final String previousPriceList,
+                                             @JsonProperty("previousBillCycleDayLocal") final Integer previousBillCycleDayLocal,
                                              @JsonProperty("nextState") final EntitlementState nextState,
                                              @JsonProperty("nextPlan") final String nextPlan,
                                              @JsonProperty("nextPhase") final String nextPhase,
                                              @JsonProperty("nextPriceList") final String nextPriceList,
+                                             @JsonProperty("nextBillCycleDayLocal") final Integer nextBillCycleDayLocal,
                                              @JsonProperty("totalOrdering") final Long totalOrdering,
                                              @JsonProperty("transitionType") final SubscriptionBaseTransitionType transitionType,
                                              @JsonProperty("remainingEventsForUserOperation") final Integer remainingEventsForUserOperation,
@@ -52,7 +54,7 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent 
                                              @JsonProperty("searchKey2") final Long searchKey2,
                                              @JsonProperty("userToken") final UUID userToken) {
         super(eventId, subscriptionId, bundleId, requestedTransitionTime, effectiveTransitionTime, previousState, previousPlan,
-              previousPhase, previousPriceList, nextState, nextPlan, nextPhase, nextPriceList, totalOrdering,
+              previousPhase, previousPriceList, previousBillCycleDayLocal, nextState, nextPlan, nextPhase, nextPriceList, nextBillCycleDayLocal, totalOrdering,
               transitionType, remainingEventsForUserOperation, startDate, searchKey1, searchKey2, userToken);
     }
 
@@ -63,6 +65,6 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent 
                                              final Long searchKey2,
                                              final UUID userToken) {
         this(nextEvent.getId(), nextEvent.getSubscriptionId(), subscription.getBundleId(), nextEvent.getEffectiveDate(), nextEvent.getEffectiveDate(),
-             null, null, null, null, null, null, null, null, nextEvent.getTotalOrdering(), transitionType, 0, null, searchKey1, searchKey2, userToken);
+             null, null, null, null, null, null, null, null, null, null, nextEvent.getTotalOrdering(), transitionType, 0, null, searchKey1, searchKey2, userToken);
     }
 }
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 9594dc5..48d01e9 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
@@ -50,6 +50,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData
 import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionDataIterator.Visibility;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
 import org.killbill.billing.subscription.events.phase.PhaseEvent;
 import org.killbill.billing.subscription.events.user.ApiEvent;
 import org.killbill.billing.subscription.events.user.ApiEventType;
@@ -558,6 +559,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
         EntitlementState nextState = null;
         String nextPlanName = null;
         String nextPhaseName = null;
+        Integer nextBillingCycleDayLocal = null;
 
         UUID prevEventId = null;
         DateTime prevCreatedDate = null;
@@ -565,6 +567,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
         PriceList previousPriceList = null;
         Plan previousPlan = null;
         PlanPhase previousPhase = null;
+        Integer previousBillingCycleDayLocal = null;
 
         transitions = new LinkedList<SubscriptionBaseTransition>();
 
@@ -588,6 +591,11 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
                     nextPhaseName = phaseEV.getPhase();
                     break;
 
+                case BCD_UPDATE:
+                    final BCDEvent bcdEvent = (BCDEvent) cur;
+                    nextBillingCycleDayLocal = bcdEvent.getBillCycleDayLocal();
+                    break;
+
                 case API_USER:
                     final ApiEvent userEV = (ApiEvent) cur;
                     apiEventType = userEV.getApiEventType();
@@ -635,15 +643,18 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
             nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, cur.getEffectiveDate(), getAlignStartDate()) : null;
             nextPriceList = (nextPlan != null) ? nextPlan.getPriceList() : null;
 
-            final SubscriptionBaseTransitionData transition = new SubscriptionBaseTransitionData(
+            final SubscriptionBaseTransitionData transition = new SubscriptionBaseTransitionData (
                     cur.getId(), id, bundleId, cur.getType(), apiEventType,
                     cur.getEffectiveDate(),
                     prevEventId, prevCreatedDate,
                     previousState, previousPlan, previousPhase,
                     previousPriceList,
+                    previousBillingCycleDayLocal,
                     nextEventId, nextCreatedDate,
                     nextState, nextPlan, nextPhase,
-                    nextPriceList, cur.getTotalOrdering(),
+                    nextPriceList,
+                    nextBillingCycleDayLocal,
+                    cur.getTotalOrdering(),
                     cur.getCreatedDate(),
                     nextUserToken,
                     isFromDisk);
@@ -656,6 +667,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
             previousPriceList = nextPriceList;
             prevEventId = nextEventId;
             prevCreatedDate = nextCreatedDate;
+            previousBillingCycleDayLocal = nextBillingCycleDayLocal;
 
         }
     }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java
index 04601ac..89f8aa7 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java
@@ -41,14 +41,17 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
     private final String previousPriceList;
     private final String previousPlan;
     private final String previousPhase;
+    private final Integer previousBillCycleDayLocal;
     private final EntitlementState nextState;
     private final String nextPriceList;
     private final String nextPlan;
     private final String nextPhase;
+    private final Integer nextBillCycleDayLocal;
     private final Integer remainingEventsForUserOperation;
     private final SubscriptionBaseTransitionType transitionType;
     private final DateTime startDate;
 
+
     public DefaultSubscriptionEvent(final SubscriptionBaseTransitionData in, final DateTime startDate,
                                     final Long searchKey1,
                                     final Long searchKey2,
@@ -62,10 +65,12 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
              (in.getPreviousPlan() != null) ? in.getPreviousPlan().getName() : null,
              (in.getPreviousPhase() != null) ? in.getPreviousPhase().getName() : null,
              (in.getPreviousPriceList() != null) ? in.getPreviousPriceList().getName() : null,
+             in.getPreviousBillingCycleDayLocal(),
              in.getNextState(),
              (in.getNextPlan() != null) ? in.getNextPlan().getName() : null,
              (in.getNextPhase() != null) ? in.getNextPhase().getName() : null,
              (in.getNextPriceList() != null) ? in.getNextPriceList().getName() : null,
+             in.getNextBillingCycleDayLocal(),
              in.getTotalOrdering(),
              in.getTransitionType(),
              in.getRemainingEventsForUserOperation(),
@@ -85,10 +90,12 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
                                     @JsonProperty("previousPlan") final String previousPlan,
                                     @JsonProperty("previousPhase") final String previousPhase,
                                     @JsonProperty("previousPriceList") final String previousPriceList,
+                                    @JsonProperty("previousBillCycleDayLocal") final Integer previousBillCycleDayLocal,
                                     @JsonProperty("nextState") final EntitlementState nextState,
                                     @JsonProperty("nextPlan") final String nextPlan,
                                     @JsonProperty("nextPhase") final String nextPhase,
                                     @JsonProperty("nextPriceList") final String nextPriceList,
+                                    @JsonProperty("nextBillCycleDayLocal") final Integer nextBillCycleDayLocal,
                                     @JsonProperty("totalOrdering") final Long totalOrdering,
                                     @JsonProperty("transitionType") final SubscriptionBaseTransitionType transitionType,
                                     @JsonProperty("remainingEventsForUserOperation") final Integer remainingEventsForUserOperation,
@@ -104,11 +111,13 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
         this.effectiveTransitionTime = effectiveTransitionTime;
         this.previousState = previousState;
         this.previousPriceList = previousPriceList;
+        this.previousBillCycleDayLocal = previousBillCycleDayLocal;
         this.previousPlan = previousPlan;
         this.previousPhase = previousPhase;
         this.nextState = nextState;
         this.nextPlan = nextPlan;
         this.nextPriceList = nextPriceList;
+        this.nextBillCycleDayLocal = nextBillCycleDayLocal;
         this.nextPhase = nextPhase;
         this.totalOrdering = totalOrdering;
         this.transitionType = transitionType;
@@ -154,6 +163,11 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
     }
 
     @Override
+    public Integer getPreviousBillCycleDayLocal() {
+        return previousBillCycleDayLocal;
+    }
+
+    @Override
     public String getNextPlan() {
         return nextPlan;
     }
@@ -179,6 +193,11 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
     }
 
     @Override
+    public Integer getNextBillCycleDayLocal() {
+        return nextBillCycleDayLocal;
+    }
+
+    @Override
     public Integer getRemainingEventsForUserOperation() {
         return remainingEventsForUserOperation;
     }
@@ -221,10 +240,12 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
         sb.append(", effectiveTransitionTime=").append(effectiveTransitionTime);
         sb.append(", previousState=").append(previousState);
         sb.append(", previousPriceList='").append(previousPriceList).append('\'');
+        sb.append(", previousBillCycleDayLocal='").append(previousBillCycleDayLocal).append('\'');
         sb.append(", previousPlan='").append(previousPlan).append('\'');
         sb.append(", previousPhase='").append(previousPhase).append('\'');
         sb.append(", nextState=").append(nextState);
         sb.append(", nextPriceList='").append(nextPriceList).append('\'');
+        sb.append(", nextBillCycleDayLocal='").append(nextBillCycleDayLocal).append('\'');
         sb.append(", nextPlan='").append(nextPlan).append('\'');
         sb.append(", nextPhase='").append(nextPhase).append('\'');
         sb.append(", remainingEventsForUserOperation=").append(remainingEventsForUserOperation);
@@ -263,6 +284,9 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
         if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
             return false;
         }
+        if (nextBillCycleDayLocal != null ? !nextBillCycleDayLocal.equals(that.nextBillCycleDayLocal) : that.nextBillCycleDayLocal != null) {
+            return false;
+        }
         if (nextState != that.nextState) {
             return false;
         }
@@ -275,6 +299,9 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
         if (previousPriceList != null ? !previousPriceList.equals(that.previousPriceList) : that.previousPriceList != null) {
             return false;
         }
+        if (previousBillCycleDayLocal != null ? !previousBillCycleDayLocal.equals(that.previousBillCycleDayLocal) : that.previousBillCycleDayLocal != null) {
+            return false;
+        }
         if (previousState != that.previousState) {
             return false;
         }
@@ -309,10 +336,12 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
         result = 31 * result + (effectiveTransitionTime != null ? effectiveTransitionTime.hashCode() : 0);
         result = 31 * result + (previousState != null ? previousState.hashCode() : 0);
         result = 31 * result + (previousPriceList != null ? previousPriceList.hashCode() : 0);
+        result = 31 * result + (previousBillCycleDayLocal != null ? previousBillCycleDayLocal.hashCode() : 0);
         result = 31 * result + (previousPlan != null ? previousPlan.hashCode() : 0);
         result = 31 * result + (previousPhase != null ? previousPhase.hashCode() : 0);
         result = 31 * result + (nextState != null ? nextState.hashCode() : 0);
         result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
+        result = 31 * result + (nextBillCycleDayLocal != null ? nextBillCycleDayLocal.hashCode() : 0);
         result = 31 * result + (nextPlan != null ? nextPlan.hashCode() : 0);
         result = 31 * result + (nextPhase != null ? nextPhase.hashCode() : 0);
         result = 31 * result + (remainingEventsForUserOperation != null ? remainingEventsForUserOperation.hashCode() : 0);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java
index df2ccad..34de332 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java
@@ -39,6 +39,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
     private final DateTime effectiveTransitionTime;
     private final EntitlementState previousState;
     private final PriceList previousPriceList;
+    private final Integer previousBillingCycleDayLocal;
     private final UUID previousEventId;
     private final DateTime previousEventCreatedDate;
     private final Plan previousPlan;
@@ -47,6 +48,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
     private final DateTime nextEventCreatedDate;
     private final EntitlementState nextState;
     private final PriceList nextPriceList;
+    private final Integer nextBillingCycleDayLocal;
     private final Plan nextPlan;
     private final PlanPhase nextPhase;
     private final Boolean isFromDisk;
@@ -66,12 +68,14 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
                                           final Plan previousPlan,
                                           final PlanPhase previousPhase,
                                           final PriceList previousPriceList,
+                                          final Integer previousBillingCycleDayLocal,
                                           final UUID nextEventId,
                                           final DateTime nextEventCreatedDate,
                                           final EntitlementState nextState,
                                           final Plan nextPlan,
                                           final PlanPhase nextPhase,
                                           final PriceList nextPriceList,
+                                          final Integer nextBillingCycleDayLocal,
                                           final Long totalOrdering,
                                           final DateTime createdDate,
                                           final UUID userToken,
@@ -84,11 +88,13 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
         this.effectiveTransitionTime = effectiveTransitionTime;
         this.previousState = previousState;
         this.previousPriceList = previousPriceList;
+        this.previousBillingCycleDayLocal = previousBillingCycleDayLocal;
         this.previousPlan = previousPlan;
         this.previousPhase = previousPhase;
         this.nextState = nextState;
         this.nextPlan = nextPlan;
         this.nextPriceList = nextPriceList;
+        this.nextBillingCycleDayLocal = nextBillingCycleDayLocal;
         this.nextPhase = nextPhase;
         this.totalOrdering = totalOrdering;
         this.previousEventId = previousEventId;
@@ -118,6 +124,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
         this.previousEventCreatedDate = input.getPreviousEventCreatedDate();
         this.previousState = input.getPreviousState();
         this.previousPriceList = input.getPreviousPriceList();
+        this.previousBillingCycleDayLocal = input.getPreviousBillingCycleDayLocal();
         this.previousPlan = input.getPreviousPlan();
         this.previousPhase = input.getPreviousPhase();
         this.nextEventId = input.getNextEventId();
@@ -125,6 +132,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
         this.nextState = input.getNextState();
         this.nextPlan = input.getNextPlan();
         this.nextPriceList = input.getNextPriceList();
+        this.nextBillingCycleDayLocal = input.getNextBillingCycleDayLocal();
         this.nextPhase = input.getNextPhase();
         this.totalOrdering = input.getTotalOrdering();
         this.isFromDisk = input.isFromDisk();
@@ -208,6 +216,14 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
         return nextPriceList;
     }
 
+    public Integer getPreviousBillingCycleDayLocal() {
+        return previousBillingCycleDayLocal;
+    }
+
+    public Integer getNextBillingCycleDayLocal() {
+        return nextBillingCycleDayLocal;
+    }
+
     public UUID getUserToken() {
         return userToken;
     }
@@ -227,6 +243,8 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
                 return apiEventType.getSubscriptionTransitionType();
             case PHASE:
                 return SubscriptionBaseTransitionType.PHASE;
+            case BCD_UPDATE:
+                return SubscriptionBaseTransitionType.BCD_CHANGE;
             default:
                 throw new SubscriptionBaseError("Unexpected event type " + eventType);
         }
@@ -270,10 +288,12 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
         sb.append(", effectiveTransitionTime=").append(effectiveTransitionTime);
         sb.append(", previousState=").append(previousState);
         sb.append(", previousPriceList=").append(previousPriceList);
+        sb.append(", previousBillingCycleDayLocal=").append(previousBillingCycleDayLocal);
         sb.append(", previousPlan=").append(previousPlan);
         sb.append(", previousPhase=").append(previousPhase);
         sb.append(", nextState=").append(nextState);
         sb.append(", nextPriceList=").append(nextPriceList);
+        sb.append(", nextBillingCycleDayLocal=").append(nextBillingCycleDayLocal);
         sb.append(", nextPlan=").append(nextPlan);
         sb.append(", nextPhase=").append(nextPhase);
         sb.append(", isFromDisk=").append(isFromDisk);
@@ -321,6 +341,9 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
         if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
             return false;
         }
+        if (nextBillingCycleDayLocal != null ? !nextBillingCycleDayLocal.equals(that.nextBillingCycleDayLocal) : that.nextBillingCycleDayLocal != null) {
+            return false;
+        }
         if (nextState != that.nextState) {
             return false;
         }
@@ -333,6 +356,9 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
         if (previousPriceList != null ? !previousPriceList.equals(that.previousPriceList) : that.previousPriceList != null) {
             return false;
         }
+        if (previousBillingCycleDayLocal != null ? !previousBillingCycleDayLocal.equals(that.previousBillingCycleDayLocal) : that.previousBillingCycleDayLocal != null) {
+            return false;
+        }
         if (previousState != that.previousState) {
             return false;
         }
@@ -348,7 +374,6 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
         if (userToken != null ? !userToken.equals(that.userToken) : that.userToken != null) {
             return false;
         }
-
         return true;
     }
 
@@ -363,10 +388,12 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
         result = 31 * result + (effectiveTransitionTime != null ? effectiveTransitionTime.hashCode() : 0);
         result = 31 * result + (previousState != null ? previousState.hashCode() : 0);
         result = 31 * result + (previousPriceList != null ? previousPriceList.hashCode() : 0);
+        result = 31 * result + (previousBillingCycleDayLocal != null ? previousBillingCycleDayLocal.hashCode() : 0);
         result = 31 * result + (previousPlan != null ? previousPlan.hashCode() : 0);
         result = 31 * result + (previousPhase != null ? previousPhase.hashCode() : 0);
         result = 31 * result + (nextState != null ? nextState.hashCode() : 0);
         result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
+        result = 31 * result + (nextBillingCycleDayLocal != null ? nextBillingCycleDayLocal.hashCode() : 0);
         result = 31 * result + (nextPlan != null ? nextPlan.hashCode() : 0);
         result = 31 * result + (nextPhase != null ? nextPhase.hashCode() : 0);
         result = 31 * result + (isFromDisk != null ? isFromDisk.hashCode() : 0);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
index bf05324..6b157c2 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
@@ -159,6 +159,8 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
             } else if (event.getType() == EventType.API_USER && subscription.getCategory() == ProductCategory.BASE) {
                 final CallContext callContext = internalCallContextFactory.createCallContext(context);
                 eventSent = onBasePlanEvent(subscription, event, callContext);
+            } else if (event.getType() == EventType.BCD_UPDATE) {
+                eventSent = false;
             }
 
             if (!eventSent) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index cead31c..cb42eb6 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -66,6 +66,8 @@ import org.killbill.billing.subscription.engine.dao.model.SubscriptionModelDao;
 import org.killbill.billing.subscription.events.EventBaseBuilder;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
+import org.killbill.billing.subscription.events.bcd.BCDEventBuilder;
 import org.killbill.billing.subscription.events.phase.PhaseEvent;
 import org.killbill.billing.subscription.events.phase.PhaseEventBuilder;
 import org.killbill.billing.subscription.events.user.ApiEvent;
@@ -622,7 +624,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
                 final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
                 final UUID subscriptionId = subscription.getId();
 
-                cancelFutureEventsFromTransaction(subscriptionId, changeEvents.get(0).getEffectiveDate(), entitySqlDaoWrapperFactory, context);
+                cancelFutureEventsFromTransaction(subscriptionId, changeEvents.get(0).getEffectiveDate(), entitySqlDaoWrapperFactory, false, context);
 
                 for (final SubscriptionBaseEvent cur : changeEvents) {
                     transactional.create(new SubscriptionEventModelDao(cur), context);
@@ -673,7 +675,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
     private void cancelSubscriptionFromTransaction(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent cancelEvent, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context, final int seqId)
             throws EntityPersistenceException {
         final UUID subscriptionId = subscription.getId();
-        cancelFutureEventsFromTransaction(subscriptionId, cancelEvent.getEffectiveDate(), entitySqlDaoWrapperFactory, context);
+        cancelFutureEventsFromTransaction(subscriptionId, cancelEvent.getEffectiveDate(), entitySqlDaoWrapperFactory, true, context);
         entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).create(new SubscriptionEventModelDao(cancelEvent), context);
 
         final boolean isBusEvent = cancelEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0;
@@ -687,10 +689,12 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         cancelFutureEventFromTransaction(subscriptionId, entitySqlDaoWrapperFactory, EventType.PHASE, null, context);
     }
 
-    private void cancelFutureEventsFromTransaction(final UUID subscriptionId, final DateTime effectiveDate, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) {
+    private void cancelFutureEventsFromTransaction(final UUID subscriptionId, final DateTime effectiveDate, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final boolean includingBCDChange, final InternalCallContext context) {
         final List<SubscriptionEventModelDao> eventModels = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).getFutureActiveEventForSubscription(subscriptionId.toString(), effectiveDate.toDate(), context);
         for (final SubscriptionEventModelDao cur : eventModels) {
-            unactivateEventFromTransaction(cur, entitySqlDaoWrapperFactory, context);
+            if (includingBCDChange || cur.getEventType() != EventType.BCD_UPDATE) {
+                unactivateEventFromTransaction(cur, entitySqlDaoWrapperFactory, context);
+            }
         }
     }
 
@@ -868,9 +872,20 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
                 // Set total ordering value of the fake dryRun event to make sure billing events are correctly ordered
                 final SubscriptionBaseEvent curAdjustedDryRun;
                 if (!events.isEmpty()) {
-                    final EventBaseBuilder eventBuilder = (curDryRun.getType() == EventType.API_USER) ?
-                                                          new ApiEventBuilder((ApiEvent) curDryRun) :
-                                                          new PhaseEventBuilder((PhaseEvent) curDryRun);
+
+                    final EventBaseBuilder eventBuilder;
+                    switch(curDryRun.getType()) {
+                        case PHASE:
+                            eventBuilder = new PhaseEventBuilder((PhaseEvent) curDryRun);
+                            break;
+                        case BCD_UPDATE:
+                            eventBuilder = new BCDEventBuilder((BCDEvent) curDryRun);
+                            break;
+                        case API_USER:
+                        default:
+                            eventBuilder = new ApiEventBuilder((ApiEvent) curDryRun);
+                            break;
+                    }
                     eventBuilder.setTotalOrdering(events.get(events.size() - 1).getTotalOrdering() + 1);
 
                     curAdjustedDryRun = eventBuilder.build();
@@ -915,6 +930,25 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         });
     }
 
+    @Override
+    public void createBCDChangeEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent bcdEvent, final InternalCallContext context) {
+        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+            @Override
+            public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
+                transactional.create(new SubscriptionEventModelDao(bcdEvent), context);
+
+                // Notify the Bus
+                notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, bcdEvent, SubscriptionBaseTransitionType.BCD_CHANGE, context);
+                final boolean isBusEvent = bcdEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0;
+                recordBusOrFutureNotificationFromTransaction(subscription, bcdEvent, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
+
+                return null;
+            }
+        });
+
+    }
+
     private DefaultSubscriptionBase createSubscriptionForInternalUse(final SubscriptionBase shellSubscription, final List<SubscriptionBaseEvent> events, final InternalTenantContext context) throws CatalogApiException {
         final DefaultSubscriptionBase result = new DefaultSubscriptionBase(new SubscriptionBuilder(((DefaultSubscriptionBase) shellSubscription)), null, clock);
         if (events.size() > 0) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
index 9bcdd69..9063bba 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
@@ -19,10 +19,11 @@ package org.killbill.billing.subscription.engine.dao.model;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-
+import org.killbill.billing.subscription.events.EventBaseBuilder;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
-import org.killbill.billing.subscription.events.EventBaseBuilder;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
+import org.killbill.billing.subscription.events.bcd.BCDEventBuilder;
 import org.killbill.billing.subscription.events.phase.PhaseEvent;
 import org.killbill.billing.subscription.events.phase.PhaseEventBuilder;
 import org.killbill.billing.subscription.events.user.ApiEvent;
@@ -43,6 +44,7 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
     private String planName;
     private String phaseName;
     private String priceListName;
+    private int billingCycleDayLocal;
     private boolean isActive;
 
     public SubscriptionEventModelDao() {
@@ -51,7 +53,7 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
 
     public SubscriptionEventModelDao(final UUID id, final long totalOrdering, final EventType eventType, final ApiEventType userType,
                                      final DateTime effectiveDate, final UUID subscriptionId,
-                                     final String planName, final String phaseName, final String priceListName,
+                                     final String planName, final String phaseName, final String priceListName, final int billingCycleDayLocal,
                                      final boolean active, final DateTime createDate, final DateTime updateDate) {
         super(id, createDate, updateDate);
         this.totalOrdering = totalOrdering;
@@ -62,6 +64,7 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
         this.planName = planName;
         this.phaseName = phaseName;
         this.priceListName = priceListName;
+        this.billingCycleDayLocal = billingCycleDayLocal;
         this.isActive = active;
     }
 
@@ -73,8 +76,15 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
         this.effectiveDate = src.getEffectiveDate();
         this.subscriptionId = src.getSubscriptionId();
         this.planName = eventType == EventType.API_USER ? ((ApiEvent) src).getEventPlan() : null;
-        this.phaseName = eventType == EventType.API_USER ? ((ApiEvent) src).getEventPlanPhase() : ((PhaseEvent) src).getPhase();
+        if (eventType == EventType.API_USER) {
+            this.phaseName = ((ApiEvent) src).getEventPlanPhase();
+        } else if (eventType == EventType.PHASE) {
+            this.phaseName = ((PhaseEvent) src).getPhase();
+        } else {
+            this.phaseName = null;
+        }
         this.priceListName = eventType == EventType.API_USER ? ((ApiEvent) src).getPriceList() : null;
+        this.billingCycleDayLocal = eventType == EventType.BCD_UPDATE ? ((BCDEvent) src).getBillCycleDayLocal() : 0;
         this.isActive = src.isActive();
     }
 
@@ -110,6 +120,10 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
         return priceListName;
     }
 
+    public int getBillingCycleDayLocal() {
+        return billingCycleDayLocal;
+    }
+
     // TODO required for jdbi binder
     public boolean getIsActive() {
         return isActive;
@@ -151,6 +165,11 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
         this.priceListName = priceListName;
     }
 
+
+    public void setBillingCycleDayLocal(final int billingCycleDayLocal) {
+        this.billingCycleDayLocal = billingCycleDayLocal;
+    }
+
     public void setIsActive(final boolean isActive) {
         this.isActive = isActive;
     }
@@ -161,16 +180,21 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
             return null;
         }
 
-        final EventBaseBuilder<?> base = ((src.getEventType() == EventType.PHASE) ?
-                                          new PhaseEventBuilder() :
-                                          new ApiEventBuilder())
-                .setTotalOrdering(src.getTotalOrdering())
-                .setUuid(src.getId())
-                .setSubscriptionId(src.getSubscriptionId())
-                .setCreatedDate(src.getCreatedDate())
-                .setUpdatedDate(src.getUpdatedDate())
-                .setEffectiveDate(src.getEffectiveDate())
-                .setActive(src.isActive());
+        final EventBaseBuilder<?> base;
+        if (src.getEventType() == EventType.PHASE) {
+            base = new PhaseEventBuilder();
+        } else if (src.getEventType() == EventType.BCD_UPDATE) {
+            base = new BCDEventBuilder();
+        } else {
+            base = new ApiEventBuilder();
+        }
+        base.setTotalOrdering(src.getTotalOrdering())
+            .setUuid(src.getId())
+            .setSubscriptionId(src.getSubscriptionId())
+            .setCreatedDate(src.getCreatedDate())
+            .setUpdatedDate(src.getUpdatedDate())
+            .setEffectiveDate(src.getEffectiveDate())
+            .setActive(src.isActive());
 
         SubscriptionBaseEvent result;
         if (src.getEventType() == EventType.PHASE) {
@@ -184,6 +208,8 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
                     .setApiEventType(src.getUserType())
                     .setFromDisk(true);
             result = builder.build();
+        } else if (src.getEventType() == EventType.BCD_UPDATE) {
+            result = (new BCDEventBuilder(base).setBillCycleDayLocal(src.getBillingCycleDayLocal())).build();
         } else {
             throw new SubscriptionBaseError(String.format("Can't figure out event %s", src.getEventType()));
         }
@@ -202,6 +228,7 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
         sb.append(", planName='").append(planName).append('\'');
         sb.append(", phaseName='").append(phaseName).append('\'');
         sb.append(", priceListName='").append(priceListName).append('\'');
+        sb.append(", billingCycleDayLocal=").append(billingCycleDayLocal);
         sb.append(", isActive=").append(isActive);
         sb.append('}');
         return sb.toString();
@@ -248,7 +275,9 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
         if (userType != that.userType) {
             return false;
         }
-
+        if (billingCycleDayLocal != that.billingCycleDayLocal) {
+            return false;
+        }
         return true;
     }
 
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
index 8003503..846be18 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
@@ -95,5 +95,6 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
 
     public void updateBundleExternalKey(UUID bundleId, String externalKey, InternalCallContext context);
 
+    public void createBCDChangeEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent bcdEvent, InternalCallContext context);
 }
 
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
index 33358f6..4ead49c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
@@ -18,7 +18,10 @@ package org.killbill.billing.subscription.engine.dao;
 
 import java.util.Date;
 import java.util.List;
+import java.util.UUID;
 
+import org.joda.time.DateTime;
+import org.killbill.billing.entity.EntityPersistenceException;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
@@ -43,15 +46,13 @@ public interface SubscriptionEventSqlDao extends EntitySqlDao<SubscriptionEventM
 
     @SqlQuery
     public List<SubscriptionEventModelDao> getFutureActiveEventForSubscription(@Bind("subscriptionId") String subscriptionId,
-                                                                              @Bind("now") Date now,
-                                                                              @BindBean final InternalTenantContext context);
+                                                                               @Bind("now") Date now,
+                                                                               @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public List<SubscriptionEventModelDao> getEventsForSubscription(@Bind("subscriptionId") String subscriptionId,
                                                                     @BindBean final InternalTenantContext context);
 
-
     @SqlQuery
     public List<SubscriptionEventModelDao> getFutureActiveEventsForAccount(@Bind("now") Date now, @BindBean final InternalTenantContext context);
-
 }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEvent.java
new file mode 100644
index 0000000..011f54c
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEvent.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.events.bcd;
+
+import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+
+public interface BCDEvent extends SubscriptionBaseEvent {
+
+    public Integer getBillCycleDayLocal();
+}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventBuilder.java
new file mode 100644
index 0000000..7c0926d
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventBuilder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.events.bcd;
+
+import org.killbill.billing.subscription.events.EventBaseBuilder;
+import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+
+public class BCDEventBuilder extends EventBaseBuilder<BCDEventBuilder> {
+
+    private Integer billCycleDayLocal;
+
+    public BCDEventBuilder() {
+        super();
+    }
+
+
+    public BCDEventBuilder(final BCDEvent event) {
+        super(event);
+        this.billCycleDayLocal = event.getBillCycleDayLocal();
+    }
+
+    public BCDEventBuilder(final EventBaseBuilder<?> base) {
+        super(base);
+    }
+
+
+    @Override
+    public SubscriptionBaseEvent build() {
+        return new BCDEventData(this);
+    }
+
+    public Integer getBillCycleDayLocal() {
+        return billCycleDayLocal;
+    }
+
+    public BCDEventBuilder setBillCycleDayLocal(final Integer billCycleDayLocal) {
+        this.billCycleDayLocal = billCycleDayLocal;
+        return this;
+    }
+}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventData.java b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventData.java
new file mode 100644
index 0000000..c935d6c
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventData.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.events.bcd;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.killbill.billing.subscription.events.EventBase;
+
+public class BCDEventData extends EventBase implements BCDEvent {
+
+    private final Integer billCycleDayLocal;
+
+    public BCDEventData(final BCDEventBuilder builder) {
+        super(builder);
+        this.billCycleDayLocal = builder.getBillCycleDayLocal();
+    }
+
+    @Override
+    public EventType getType() {
+        return EventType.BCD_UPDATE;
+    }
+
+    @Override
+    public String toString() {
+        return "BCDEventData {" +
+               "uuid=" + getId() +
+               ", subscriptionId=" + getSubscriptionId() +
+               ", createdDate=" + getCreatedDate() +
+               ", updatedDate=" + getUpdatedDate() +
+               ", effectiveDate=" + getEffectiveDate() +
+               ", totalOrdering=" + getTotalOrdering() +
+               ", isActive=" + isActive() +
+               '}';
+    }
+
+    // Hack until we introduce a proper field for that
+    @Override
+    public Integer getBillCycleDayLocal() {
+        return billCycleDayLocal;
+    }
+
+    public static BCDEvent createBCDEvent(final DefaultSubscriptionBase subscription, final DateTime effectiveDate, final int billCycleDayLocal) {
+        return new BCDEventData(new BCDEventBuilder()
+                                        .setSubscriptionId(subscription.getId())
+                                        .setEffectiveDate(effectiveDate)
+                                        .setActive(true)
+                                        .setBillCycleDayLocal(billCycleDayLocal));
+    }
+
+}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java
index f710d44..36e7f24 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java
@@ -27,7 +27,8 @@ public interface SubscriptionBaseEvent extends Comparable<SubscriptionBaseEvent>
 
     public enum EventType {
         API_USER,
-        PHASE
+        PHASE,
+        BCD_UPDATE
     }
 
     public EventType getType();
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql b/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
index d10805e..9e5e0c6 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
@@ -4,13 +4,14 @@ DROP TABLE IF EXISTS subscription_events;
 CREATE TABLE subscription_events (
     record_id serial unique,
     id varchar(36) NOT NULL,
-    event_type varchar(9) NOT NULL,
+    event_type varchar(15) NOT NULL,
     user_type varchar(25) DEFAULT NULL,
     effective_date datetime NOT NULL,
     subscription_id varchar(36) NOT NULL,
     plan_name varchar(255) DEFAULT NULL,
     phase_name varchar(255) DEFAULT NULL,
     price_list_name varchar(64) DEFAULT NULL,
+    billing_cycle_day_local int DEFAULT NULL,
     is_active boolean default true,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
index a1f8af9..49cca79 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
@@ -21,6 +21,7 @@ tableFields(prefix) ::= <<
 , <prefix> plan_name
 , <prefix> phase_name
 , <prefix> price_list_name
+, <prefix> billing_cycle_day_local
 , <prefix> is_active
 , <prefix> created_by
 , <prefix> created_date
@@ -36,6 +37,7 @@ tableValues() ::= <<
 , :planName
 , :phaseName
 , :priceListName
+, :billingCycleDayLocal
 , :isActive
 , :createdBy
 , :createdDate
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java b/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java
index cee8006..772055c 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java
@@ -37,7 +37,7 @@ public class TestEventJson extends SubscriptionTestSuiteNoDB {
     public void testSubscriptionEvent() throws Exception {
 
         final EffectiveSubscriptionInternalEvent e = new DefaultEffectiveSubscriptionEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), new DateTime(),
-                                                                                           EntitlementState.ACTIVE, "pro", "TRIAL", "DEFAULT", EntitlementState.CANCELLED, null, null, null, 3L,
+                                                                                           EntitlementState.ACTIVE, "pro", "TRIAL", "DEFAULT", null, EntitlementState.CANCELLED, null, null, null, null, 3L,
                                                                                            SubscriptionBaseTransitionType.CANCEL, 0, new DateTime(), 1L, 2L, null);
 
         final String json = mapper.writeValueAsString(e);
@@ -46,4 +46,17 @@ public class TestEventJson extends SubscriptionTestSuiteNoDB {
         final Object obj = mapper.readValue(json, claz);
         Assert.assertTrue(obj.equals(e));
     }
+
+    // Verify deserialization will work when we miss fields (previousBillCycleDayLocal, nextBillCycleDayLocal)
+    @Test(groups = "fast")
+    public void testSubscriptionEventWithNoBillCycleDayLocal() throws Exception {
+
+        final String json = "{\"eventId\":\"9e901bbc-bbcb-4f0a-8511-e58029bbea91\",\"subscriptionId\":\"c373056c-bb0c-4562-ab06-f595176aa4ae\",\"bundleId\":\"f61536b1-fc76-4337-b1e8-e38383894352\",\"effectiveTransitionTime\":\"2016-05-26T23:02:20.322Z\",\"previousState\":\"ACTIVE\",\"previousPlan\":\"pro\",\"previousPhase\":\"TRIAL\",\"previousPriceList\":\"DEFAULT\",\"nextState\":\"CANCELLED\",\"nextPlan\":null,\"nextPhase\":null,\"nextPriceList\":null,\"totalOrdering\":3,\"transitionType\":\"CANCEL\",\"remainingEventsForUserOperation\":0,\"startDate\":\"2016-05-26T23:02:20.322Z\",\"searchKey1\":1,\"searchKey2\":2,\"userToken\":null,\"requestedTransitionTime\":\"2016-05-26T23:02:20.322Z\"}";
+        final Class<?> claz = Class.forName(DefaultEffectiveSubscriptionEvent.class.getName());
+        final DefaultEffectiveSubscriptionEvent obj = (DefaultEffectiveSubscriptionEvent) mapper.readValue(json, claz);
+
+        Assert.assertEquals(obj.getId(), UUID.fromString("9e901bbc-bbcb-4f0a-8511-e58029bbea91"));
+        Assert.assertNull(obj.getPreviousBillCycleDayLocal());
+        Assert.assertNull(obj.getNextBillCycleDayLocal());
+    }
 }
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
index 2a377af..c6704e1 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
@@ -122,6 +122,11 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
             }
 
             @Override
+            public Integer getBillCycleDayLocal() {
+                return null;
+            }
+
+            @Override
             public UUID getEventId() {
                 return UUID.randomUUID();
             }
@@ -139,5 +144,4 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
             }
         };
     }
-
 }
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index 0dfe039..38500c3 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -501,4 +501,10 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
     @Override
     public void updateBundleExternalKey(final UUID bundleId, final String externalKey, final InternalCallContext context) {
     }
+
+    @Override
+    public void createBCDChangeEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent bcdEvent, final InternalCallContext context) {
+
+    }
+
 }
diff --git a/util/src/test/java/org/killbill/billing/api/TestApiListener.java b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
index 61c1252..be41d8d 100644
--- a/util/src/test/java/org/killbill/billing/api/TestApiListener.java
+++ b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
@@ -124,6 +124,7 @@ public class TestApiListener {
         TAG,
         TAG_DEFINITION,
         CUSTOM_FIELD,
+        BCD_CHANGE
     }
 
     @Subscribe
@@ -168,6 +169,10 @@ public class TestApiListener {
                 assertEqualsNicely(NextEvent.PHASE);
                 notifyIfStackEmpty();
                 break;
+            case BCD_CHANGE:
+                assertEqualsNicely(NextEvent.BCD_CHANGE);
+                notifyIfStackEmpty();
+                break;
             default:
                 throw new RuntimeException("Unexpected event type " + eventEffective.getRequestedTransitionTime());
         }
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestWithEmbeddedDBModule.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestWithEmbeddedDBModule.java
index 67915ce..e4a5c36 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestWithEmbeddedDBModule.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestWithEmbeddedDBModule.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -19,6 +19,7 @@
 package org.killbill.billing;
 
 import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.platform.test.PlatformDBTestingHelper;
 import org.killbill.billing.platform.test.config.TestKillbillConfigSource;
 import org.killbill.billing.platform.test.glue.TestPlatformModuleWithEmbeddedDB;
 
@@ -52,8 +53,7 @@ public class GuicyKillbillTestWithEmbeddedDBModule extends GuicyKillbillTestModu
             final DBTestingHelper dbTestingHelper = DBTestingHelper.get();
             configureEmbeddedDB(dbTestingHelper);
         }
-        protected void configureKillbillNodesApi() {
 
-        }
+        protected void configureKillbillNodesApi() {}
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java b/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java
index 793f2f0..60f76fa 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java
@@ -39,10 +39,12 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
     private final DateTime effectiveTransitionTime;
     private final EntitlementState previousState;
     private final String previousPriceList;
+    private final Integer previousBillCycleDayLocal;
     private final String previousPlan;
     private final String previousPhase;
     private final EntitlementState nextState;
     private final String nextPriceList;
+    private final Integer nextBillCycleDayLocal;
     private final String nextPlan;
     private final String nextPhase;
     private final Integer remainingEventsForUserOperation;
@@ -61,10 +63,12 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
                                           @JsonProperty("previousPlan") final String previousPlan,
                                           @JsonProperty("previousPhase") final String previousPhase,
                                           @JsonProperty("previousPriceList") final String previousPriceList,
+                                          @JsonProperty("previousBillCycleDayLocal") final Integer previousBillCycleDayLocal,
                                           @JsonProperty("nextState") final EntitlementState nextState,
                                           @JsonProperty("nextPlan") final String nextPlan,
                                           @JsonProperty("nextPhase") final String nextPhase,
                                           @JsonProperty("nextPriceList") final String nextPriceList,
+                                          @JsonProperty("nextBillCycleDayLocal") final Integer nextBillCycleDayLocal,
                                           @JsonProperty("totalOrdering") final Long totalOrdering,
                                           @JsonProperty("transitionType") final SubscriptionBaseTransitionType transitionType,
                                           @JsonProperty("remainingEventsForUserOperation") final Integer remainingEventsForUserOperation,
@@ -80,11 +84,13 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
         this.effectiveTransitionTime = effectiveTransitionTime;
         this.previousState = previousState;
         this.previousPriceList = previousPriceList;
+        this.previousBillCycleDayLocal = previousBillCycleDayLocal;
         this.previousPlan = previousPlan;
         this.previousPhase = previousPhase;
         this.nextState = nextState;
         this.nextPlan = nextPlan;
         this.nextPriceList = nextPriceList;
+        this.nextBillCycleDayLocal = nextBillCycleDayLocal;
         this.nextPhase = nextPhase;
         this.totalOrdering = totalOrdering;
         this.userToken = userToken;
@@ -132,6 +138,11 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
     }
 
     @Override
+    public Integer getPreviousBillCycleDayLocal() {
+        return previousBillCycleDayLocal;
+    }
+
+    @Override
     public String getNextPlan() {
         return nextPlan;
     }
@@ -158,6 +169,11 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
     }
 
     @Override
+    public Integer getNextBillCycleDayLocal() {
+        return nextBillCycleDayLocal;
+    }
+
+    @Override
     public Integer getRemainingEventsForUserOperation() {
         return remainingEventsForUserOperation;
     }