killbill-aplcache

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
index f893c7e..33c8f44 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
@@ -24,11 +24,13 @@ import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.payment.api.PluginProperty;
@@ -101,6 +103,260 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
     }
 
     @Test(groups = "slow")
+    public void testRetirePlanWithUncancel() throws Exception {
+        // Catalog v1 starts in 2011-01-01
+        // Catalog v2 starts in 2015-12-01
+        final LocalDate today = new LocalDate(2015, 10, 5);
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(today);
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        final String productName = "Pistol";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+
+        Entitlement bpEntitlement =
+                createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName,
+                                                           ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        assertNotNull(bpEntitlement);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+        assertEquals(bpEntitlement.getState(), EntitlementState.ACTIVE);
+
+        // Move out a month.
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        // Cancel entitlement
+        bpEntitlement = bpEntitlement.cancelEntitlementWithDate(new LocalDate("2016-05-01"), true, ImmutableList.<PluginProperty>of(), callContext);
+        assertEquals(bpEntitlement.getState(), EntitlementState.ACTIVE);
+        assertListenerStatus();
+
+        // Move out a month.
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        // Catalog v2 should start now.
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, term, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+        try {
+            entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey2", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+            fail(); // force to fail is there is not an exception
+        } catch (final EntitlementApiException e) {
+            assertEquals(e.getCode(), ErrorCode.CAT_PLAN_NOT_FOUND.getCode());
+        }
+
+        // Uncancel entitlement
+        busHandler.pushExpectedEvents(NextEvent.UNCANCEL);
+        bpEntitlement.uncancelEntitlement(ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        // Move out a month and verify 'Pistol' plan continue working as expected.
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        assertEquals(invoices.size(), 4);
+        for (final Invoice invoice : invoices) {
+            assertEquals(invoice.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testRetirePlanAfterChange() throws Exception {
+        // Catalog v1 starts in 2011-01-01
+        // Catalog v3 starts in 2016-01-01
+        final LocalDate today = new LocalDate(2015, 7, 5);
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(today);
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+        final String productName = "Pistol";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+        final PlanPhaseSpecifier spec1 = new PlanPhaseSpecifier(productName, term, "DEFAULT", null);
+
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        Entitlement bpEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec1, "externalKey", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        assertNotNull(bpEntitlement);
+        assertEquals(bpEntitlement.getLastActivePhase().getPhaseType(), PhaseType.TRIAL);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+
+        // Move out after trial (2015-08-04)
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+
+        // 2015-08-05
+        clock.addDays(1);
+        assertListenerStatus();
+
+        // Change to discount phase in SpecialDiscount pricelist (CBA generated, no payment)
+        // Note that we need to trigger a CHANGE outside a TRIAL phase to generate a CHANGE event (otherwise, a CREATE is generated)
+        final PlanPhaseSpecifier spec2 = new PlanPhaseSpecifier(productName, term, "SpecialDiscount", null);
+        busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
+        bpEntitlement = bpEntitlement.changePlanWithDate(spec2, null, clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.DISCOUNT);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+
+        // Move out after discount phase (happens on 2015-11-04)
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        // 2015-09-05
+        clock.addMonths(1);
+        assertListenerStatus();
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        // 2015-10-05
+        clock.addMonths(1);
+        assertListenerStatus();
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        // 2015-11-05
+        clock.addMonths(1);
+        // This verifies the PlanAligner.getNextTimedPhase codepath with a CHANGE transition type
+        assertListenerStatus();
+
+        assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
+
+        // Move out a month (2015-12-05)
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        // Move out a month (2016-01-01)
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        // Catalog v3 should start now.
+
+        try {
+            entitlementApi.createBaseEntitlement(account.getId(), spec2, "externalKey2", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+            fail(); // force to fail is there is not an exception
+        } catch (final EntitlementApiException e) {
+            assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PRODUCT.getCode());
+        }
+
+        // Move out a month and verify 'Pistol' discounted plan continues working as expected.
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        assertEquals(invoices.size(), 9);
+    }
+
+    @Test(groups = "slow")
+    public void testRetirePlanWithUncancelAfterChange() throws Exception {
+        // Catalog v1 starts in 2011-01-01
+        // Catalog v3 starts in 2016-01-01
+        final LocalDate today = new LocalDate(2015, 7, 5);
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(today);
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+        final String productName = "Pistol";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+        final PlanPhaseSpecifier spec1 = new PlanPhaseSpecifier(productName, term, "DEFAULT", null);
+
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        Entitlement bpEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec1, "externalKey", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        assertNotNull(bpEntitlement);
+        assertEquals(bpEntitlement.getLastActivePhase().getPhaseType(), PhaseType.TRIAL);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+
+        // Move out after trial (2015-08-04)
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+
+        // 2015-08-05
+        clock.addDays(1);
+        assertListenerStatus();
+
+        // Change to discount phase in SpecialDiscount pricelist (CBA generated, no payment)
+        // Note that we need to trigger a CHANGE outside a TRIAL phase to generate a CHANGE event (otherwise, a CREATE is generated)
+        final PlanPhaseSpecifier spec2 = new PlanPhaseSpecifier(productName, term, "SpecialDiscount", null);
+        busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
+        bpEntitlement = bpEntitlement.changePlanWithDate(spec2, null, clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.DISCOUNT);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+
+        // Cancel entitlement
+        bpEntitlement = bpEntitlement.cancelEntitlementWithDate(new LocalDate("2016-05-01"), true, ImmutableList.<PluginProperty>of(), callContext);
+        assertEquals(bpEntitlement.getState(), EntitlementState.ACTIVE);
+        assertListenerStatus();
+
+        // Move out after discount phase (happens on 2015-11-04)
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        // 2015-09-05
+        clock.addMonths(1);
+        assertListenerStatus();
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        // 2015-10-05
+        clock.addMonths(1);
+        assertListenerStatus();
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        // 2015-11-05
+        clock.addMonths(1);
+        // This verifies the PlanAligner.getNextTimedPhase codepath with a CHANGE transition type
+        assertListenerStatus();
+
+        assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
+
+        // Move out a month (2015-12-05)
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        // Uncancel entitlement
+        busHandler.pushExpectedEvents(NextEvent.UNCANCEL);
+        bpEntitlement.uncancelEntitlement(ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        // Move out a month (2016-01-01)
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        // Catalog v3 should start now.
+
+        try {
+            entitlementApi.createBaseEntitlement(account.getId(), spec2, "externalKey2", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+            fail(); // force to fail is there is not an exception
+        } catch (final EntitlementApiException e) {
+            assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PRODUCT.getCode());
+        }
+
+        // Move out a month and verify 'Pistol' discounted plan continues working as expected.
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+        assertEquals(invoices.size(), 9);
+    }
+
+    @Test(groups = "slow")
     public void testRetireProduct() throws Exception {
         // Catalog v1 starts in 2011-01-01
         // Catalog v3 starts in 2016-01-01
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
index 1f1028b..f215100 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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
@@ -30,6 +30,7 @@ import org.killbill.billing.ObjectType;
 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.DefaultPlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
@@ -38,7 +39,6 @@ import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.entitlement.api.Entitlement;
-import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.entitlement.api.SubscriptionEventType;
 import org.killbill.billing.invoice.api.DryRunArguments;
 import org.killbill.billing.invoice.api.DryRunType;
@@ -50,14 +50,12 @@ import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
-import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
 
 import static com.tc.util.Assert.fail;
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
 public class TestIntegrationInvoice extends TestIntegrationBase {
@@ -506,4 +504,65 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
 
     }
 
+    @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/783")
+    public void testIntegrationWithRecurringFreePlan() throws Exception {
+        final DateTime initialCreationDate = new DateTime(2017, 1, 1, 0, 0, 0, 0, testTimeZone);
+        // set clock to the initial start date
+        clock.setTime(initialCreationDate);
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+
+        // Price override of $0
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        overrides.add(new DefaultPlanPhasePriceOverride("blowdart-monthly-notrial-evergreen", account.getCurrency(), null, BigDecimal.ZERO));
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", overrides, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2017, 1, 1), new LocalDate(2017, 2, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+        // 2017-02-01
+        busHandler.pushExpectedEvents(NextEvent.INVOICE);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 1), new LocalDate(2017, 3, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+        // Do the change mid-month so the repair triggers the bug in https://github.com/killbill/killbill/issues/783
+        entitlement.changePlanWithDate(spec, ImmutableList.<PlanPhasePriceOverride>of(), new LocalDate("2017-02-15"), ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        // 2017-02-15
+        busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addDays(15);
+        assertListenerStatus();
+
+        // Note: no repair
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 1), new LocalDate(2017, 3, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 1), new LocalDate(2017, 2, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 15), new LocalDate(2017, 3, 1), InvoiceItemType.RECURRING, new BigDecimal("14.98")));
+
+        // 2017-03-01
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addDays(15);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2017, 3, 1), new LocalDate(2017, 4, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+        // 2017-04-01
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 5, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2017, 4, 1), new LocalDate(2017, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+    }
 }
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
index 8ba0800..e25d782 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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
@@ -28,8 +28,12 @@ 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.DefaultPlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
@@ -538,4 +542,197 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
         invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
         expectedInvoices.clear();
     }
+
+    @Test(groups = "slow")
+    public void testBCDChangeFromFreePlanToPayingPlanNoTrial() throws Exception {
+        final PlanPhaseSpecifier specNoTrial = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+        testBCDChangeFromFreePlanToPayingPlan(specNoTrial);
+    }
+
+    @Test(groups = "slow")
+    public void testBCDChangeFromFreePlanToPayingPlanWithTrial() throws Exception {
+        // Change to the paying plan (alignment is START_OF_SUBSCRIPTION, but because we are already in an EVERGREEN phase, we will re-align with the EVERGREEN phase of the new 3-phases paying plan)
+        final PlanPhaseSpecifier specWithTrial = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "DEFAULT", null);
+        testBCDChangeFromFreePlanToPayingPlan(specWithTrial);
+    }
+
+    @Test(groups = "slow")
+    public void testBCDChangeFromFreePlanToPayingPlanWithTrialAndCHANGE_OF_PLANPolicy30DaysMonth() throws Exception {
+        final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setTime(initialDate);
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+
+        // Price override of $0
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        overrides.add(new DefaultPlanPhasePriceOverride("blowdart-monthly-notrial-evergreen", account.getCurrency(), null, BigDecimal.ZERO));
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        // BP creation : Will set Account BCD to the first (DateOfFirstRecurringNonZeroCharge is the subscription start date in this case)
+        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", overrides, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+        // 2016-4-15
+        clock.addDays(14);
+
+        // Set next BCD to be the 15
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE);
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 15, null, internalCallContext);
+        assertListenerStatus();
+
+        // Re-alignment invoice
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 4, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 15), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+        // Change to the paying plan (alignment is CHANGE_OF_PLAN: we end up in TRIAL)
+        final PlanPhaseSpecifier specWithTrial = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "trial", null);
+        busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
+        baseEntitlement.changePlanOverrideBillingPolicy(specWithTrial, ImmutableList.<PlanPhasePriceOverride>of(), clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        // Trial invoice
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 15), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+
+        // Verify next month (extra null invoice because of the original notification set on the 1st)
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        // First paying invoice
+        invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+        // Verify next month
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 5, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 15), new LocalDate(2016, 7, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+    }
+
+    @Test(groups = "slow")
+    public void testBCDChangeFromFreePlanToPayingPlanWithTrialAndCHANGE_OF_PLANPolicy31DaysMonth() throws Exception {
+        final DateTime initialDate = new DateTime(2016, 5, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setTime(initialDate);
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+
+        // Price override of $0
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        overrides.add(new DefaultPlanPhasePriceOverride("blowdart-monthly-notrial-evergreen", account.getCurrency(), null, BigDecimal.ZERO));
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        // BP creation : Will set Account BCD to the first (DateOfFirstRecurringNonZeroCharge is the subscription start date in this case)
+        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", overrides, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 6, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+        // 2016-5-15
+        clock.addDays(14);
+
+        // Set next BCD to be the 14
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 14, null, internalCallContext);
+        // No bus event, no invoice expected
+        assertListenerStatus();
+
+        // Change to the paying plan (alignment is CHANGE_OF_PLAN: we end up in TRIAL)
+        // Extra NULL_INVOICE event because invoice computes a future notification effective right away
+        final PlanPhaseSpecifier specWithTrial = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "trial", null);
+        busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.NULL_INVOICE, NextEvent.INVOICE);
+        baseEntitlement.changePlanOverrideBillingPolicy(specWithTrial, ImmutableList.<PlanPhasePriceOverride>of(), clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        // Trial invoice (with re-alignment invoice item from free plan)
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+
+        // Verify next month (extra null invoice because of the original notification set on the 1st)
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        // First paying invoice
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 14), new LocalDate(2016, 7, 14), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+        // Verify next month
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 14), new LocalDate(2016, 8, 14), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+    }
+
+    private void testBCDChangeFromFreePlanToPayingPlan(final PlanSpecifier toSpec) throws Exception {
+        final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+        clock.setTime(initialDate);
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+        assertNotNull(account);
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+
+        // Price override of $0
+        final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+        overrides.add(new DefaultPlanPhasePriceOverride("blowdart-monthly-notrial-evergreen", account.getCurrency(), null, BigDecimal.ZERO));
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        // BP creation : Will set Account BCD to the first (DateOfFirstRecurringNonZeroCharge is the subscription start date in this case)
+        final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", overrides, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+        // 2016-4-15
+        clock.addDays(14);
+
+        // Set next BCD to be the 15
+        busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE);
+        subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 15, null, internalCallContext);
+        assertListenerStatus();
+
+        // Re-alignment invoice
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 4, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 15), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+        // Change to the paying plan
+        busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        baseEntitlement.changePlanOverrideBillingPolicy(toSpec, ImmutableList.<PlanPhasePriceOverride>of(), clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        // First paying invoice
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 15), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+        // Verify next month (null invoice because of the original notification set on the 1st)
+        busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+        // Verify next month
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addMonths(1);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 5, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 15), new LocalDate(2016, 7, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index cf7c9a3..5afa415 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -223,7 +223,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
 
         for (final DefaultPlanPhase cur : initialPhases) {
             cur.validate(catalog, errors);
-            if (cur.getPhaseType() == PhaseType.EVERGREEN || cur.getPhaseType() == PhaseType.FIXEDTERM) {
+            if (cur.getPhaseType() == PhaseType.EVERGREEN) {
                 errors.add(new ValidationError(String.format("Initial Phase %s of plan %s cannot be of type %s",
                                                              cur.getName(), name, cur.getPhaseType()),
                                                catalog.getCatalogURI(), DefaultPlan.class, ""));
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
index f0707ed..e66e48d 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
@@ -39,6 +39,7 @@ import org.killbill.billing.catalog.api.TimeUnit;
 import org.killbill.billing.catalog.api.user.DefaultSimplePlanDescriptor;
 import org.killbill.xmlloader.XMLLoader;
 import org.killbill.xmlloader.XMLWriter;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -258,6 +259,59 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
 
 
     @Test(groups = "fast")
+    public void testPlanWithNonFinalFixedTermPhase() throws Exception {
+
+
+        final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarBasic.xml").toExternalForm(), StandaloneCatalog.class);
+
+        final MutableStaticCatalog mutableCatalog = new DefaultMutableStaticCatalog(catalog);
+
+        final DefaultProduct newProduct = new DefaultProduct();
+        newProduct.setName("Something");
+        newProduct.setCatagory(ProductCategory.BASE);
+        newProduct.initialize((StandaloneCatalog) mutableCatalog, null);
+        mutableCatalog.addProduct(newProduct);
+
+
+        final DefaultPlanPhase trialPhase = new DefaultPlanPhase();
+        trialPhase.setPhaseType(PhaseType.TRIAL);
+        trialPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.DAYS).setNumber(14));
+        trialPhase.setFixed(new DefaultFixed().setFixedPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.ZERO)})));
+
+        // Add a Plan with a FIXEDTERM phase
+        final DefaultPlanPhase fixedTermPhase = new DefaultPlanPhase();
+        fixedTermPhase.setPhaseType(PhaseType.FIXEDTERM);
+        fixedTermPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(3));
+        fixedTermPhase.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
+
+
+        final DefaultPlanPhase evergreenPhase = new DefaultPlanPhase();
+        evergreenPhase.setPhaseType(PhaseType.EVERGREEN);
+        evergreenPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(1));
+        evergreenPhase.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
+
+
+        final DefaultPlan newPlan = new DefaultPlan();
+        newPlan.setName("something-with-fixed-term");
+        newPlan.setPriceListName(DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
+        newPlan.setProduct(newProduct);
+        newPlan.setInitialPhases(new DefaultPlanPhase[] {trialPhase, fixedTermPhase});
+        newPlan.setFinalPhase(fixedTermPhase);
+        mutableCatalog.addPlan(newPlan);
+        newPlan.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
+
+        final String newCatalogStr = XMLWriter.writeXML((StandaloneCatalog) mutableCatalog, StandaloneCatalog.class);
+        final StandaloneCatalog newCatalog =  XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
+
+        final DefaultPlan targetPlan = newCatalog.findCurrentPlan("something-with-fixed-term");
+        Assert.assertEquals(targetPlan.getInitialPhases().length, 2);
+        Assert.assertEquals(targetPlan.getInitialPhases()[1].getPhaseType(), PhaseType.FIXEDTERM);
+
+    }
+
+
+
+    @Test(groups = "fast")
     public void testVerifyXML() throws Exception {
 
         final StandaloneCatalog originalCatalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarBasic.xml").toExternalForm(), StandaloneCatalog.class);
diff --git a/catalog/src/test/resources/catalogTest.xml b/catalog/src/test/resources/catalogTest.xml
index d15d55f..f8d9f93 100644
--- a/catalog/src/test/resources/catalogTest.xml
+++ b/catalog/src/test/resources/catalogTest.xml
@@ -143,6 +143,11 @@
                 <alignment>CHANGE_OF_PRICELIST</alignment>
             </changeAlignmentCase>
             <changeAlignmentCase>
+                <fromPriceList>notrial</fromPriceList>
+                <toPriceList>trial</toPriceList>
+                <alignment>CHANGE_OF_PLAN</alignment>
+            </changeAlignmentCase>
+            <changeAlignmentCase>
                 <alignment>START_OF_SUBSCRIPTION</alignment>
             </changeAlignmentCase>
         </changeAlignment>
@@ -297,6 +302,43 @@
                 </recurring>
             </finalPhase>
         </plan>
+        <plan name="blowdart-monthly-trial">
+            <product>Blowdart</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>29.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
         <plan name="pistol-weekly">
             <product>Pistol</product>
             <initialPhases>
@@ -1333,7 +1375,11 @@
             <plans>
                 <plan>blowdart-monthly-notrial</plan>
                 <plan>pistol-monthly-notrial</plan>
-
+            </plans>
+        </childPriceList>
+        <childPriceList name="trial">
+            <plans>
+                <plan>blowdart-monthly-trial</plan>
             </plans>
         </childPriceList>
     </priceLists>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index e4217f3..0e7fa4d 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -306,6 +306,9 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
                     } else {
                         if (existingInvoicesForExternalCharges.get(invoiceIdForExternalCharge) == null) {
                             final Invoice existingInvoiceForExternalCharge = getInvoice(invoiceIdForExternalCharge, context);
+                            if (InvoiceStatus.COMMITTED.equals(existingInvoiceForExternalCharge.getStatus())) {
+                                throw new InvoiceApiException(ErrorCode.INVOICE_ALREADY_COMMITTED, existingInvoiceForExternalCharge.getId());
+                            }
                             existingInvoicesForExternalCharges.put(invoiceIdForExternalCharge, existingInvoiceForExternalCharge);
                         }
                         invoiceForExternalCharge = existingInvoicesForExternalCharges.get(invoiceIdForExternalCharge);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
index b9eda76..0e6ea00 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
@@ -206,6 +206,9 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
 
         final InvoiceItemBase that = (InvoiceItemBase) o;
 
+        if (getInvoiceItemType() != null ? !getInvoiceItemType().equals(that.getInvoiceItemType()) : that.getInvoiceItemType() != null) {
+            return false;
+        }
         if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
         }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
index df593b8..d194140 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,11 +18,10 @@
 
 package org.killbill.billing.invoice.tree;
 
+import java.math.BigDecimal;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -44,8 +45,8 @@ public class SubscriptionItemTree {
 
     private final List<Item> items = new LinkedList<Item>();
     private final List<Item> existingFullyAdjustedItems = new LinkedList<Item>();
-    private final List<InvoiceItem> existingFixedItems = new LinkedList<InvoiceItem>();
-    private final Map<LocalDate, InvoiceItem> remainingFixedItems = new HashMap<LocalDate, InvoiceItem>();
+    private final List<InvoiceItem> existingIgnoredItems = new LinkedList<InvoiceItem>();
+    private final List<InvoiceItem> remainingIgnoredItems = new LinkedList<InvoiceItem>();
     private final List<InvoiceItem> pendingItemAdj = new LinkedList<InvoiceItem>();
 
     private final UUID targetInvoiceId;
@@ -91,7 +92,12 @@ public class SubscriptionItemTree {
 
         switch (invoiceItem.getInvoiceItemType()) {
             case RECURRING:
-                root.addExistingItem(new ItemsNodeInterval(root, new Item(invoiceItem, targetInvoiceId, ItemAction.ADD)));
+                if (invoiceItem.getAmount().compareTo(BigDecimal.ZERO) == 0) {
+                    // Nothing to repair -- https://github.com/killbill/killbill/issues/783
+                    existingIgnoredItems.add(invoiceItem);
+                } else {
+                    root.addExistingItem(new ItemsNodeInterval(root, new Item(invoiceItem, targetInvoiceId, ItemAction.ADD)));
+                }
                 break;
 
             case REPAIR_ADJ:
@@ -99,7 +105,7 @@ public class SubscriptionItemTree {
                 break;
 
             case FIXED:
-                existingFixedItems.add(invoiceItem);
+                existingIgnoredItems.add(invoiceItem);
                 break;
 
             case ITEM_ADJ:
@@ -158,6 +164,17 @@ public class SubscriptionItemTree {
     public void mergeProposedItem(final InvoiceItem invoiceItem) {
         Preconditions.checkState(!isBuilt, "Tree already built, unable to add new invoiceItem=%s", invoiceItem);
 
+        // Check if it was an existing item ignored for tree purposes (e.g. FIXED or $0 RECURRING, both of which aren't repaired)
+        final InvoiceItem existingItem = Iterables.tryFind(existingIgnoredItems, new Predicate<InvoiceItem>() {
+            @Override
+            public boolean apply(final InvoiceItem input) {
+                return input.matches(invoiceItem);
+            }
+        }).orNull();
+        if (existingItem != null) {
+            return;
+        }
+
         switch (invoiceItem.getInvoiceItemType()) {
             case RECURRING:
                 // merged means we've either matched the proposed to an existing, or triggered a repair
@@ -168,15 +185,7 @@ public class SubscriptionItemTree {
                 break;
 
             case FIXED:
-                final InvoiceItem existingItem = Iterables.tryFind(existingFixedItems, new Predicate<InvoiceItem>() {
-                    @Override
-                    public boolean apply(final InvoiceItem input) {
-                        return input.matches(invoiceItem);
-                    }
-                }).orNull();
-                if (existingItem == null) {
-                    remainingFixedItems.put(invoiceItem.getStartDate(), invoiceItem);
-                }
+                remainingIgnoredItems.add(invoiceItem);
                 break;
 
             default:
@@ -204,7 +213,7 @@ public class SubscriptionItemTree {
     public List<InvoiceItem> getView() {
 
         final List<InvoiceItem> tmp = new LinkedList<InvoiceItem>();
-        tmp.addAll(remainingFixedItems.values());
+        tmp.addAll(remainingIgnoredItems);
         tmp.addAll(Collections2.filter(Collections2.transform(items, new Function<Item, InvoiceItem>() {
             @Override
             public InvoiceItem apply(final Item input) {
@@ -278,8 +287,8 @@ public class SubscriptionItemTree {
         sb.append(", isMerged=").append(isMerged);
         sb.append(", items=").append(items);
         sb.append(", existingFullyAdjustedItems=").append(existingFullyAdjustedItems);
-        sb.append(", existingFixedItems=").append(existingFixedItems);
-        sb.append(", remainingFixedItems=").append(remainingFixedItems);
+        sb.append(", existingIgnoredItems=").append(existingIgnoredItems);
+        sb.append(", remainingIgnoredItems=").append(remainingIgnoredItems);
         sb.append(", pendingItemAdj=").append(pendingItemAdj);
         sb.append('}');
         return sb.toString();
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index be8c47a..d3e6ee4 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -27,6 +27,7 @@ import javax.annotation.Nullable;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.callcontext.DefaultCallContext;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
@@ -35,6 +36,7 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoiceStatus;
+import org.killbill.billing.invoice.api.InvoiceUserApi;
 import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
 import org.killbill.billing.util.api.TagApiException;
 import org.killbill.billing.util.callcontext.CallContext;
@@ -373,5 +375,20 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
         creditInvoice = invoiceUserApi.getInvoice(invoiceId, callContext);
         Assert.assertEquals(creditInvoice.getStatus(), InvoiceStatus.COMMITTED);
 
+        try {
+            final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, null, "Initial external charge", clock.getUTCToday(), new BigDecimal("12.33"), accountCurrency);
+            invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.of(externalCharge), true, callContext);
+            Assert.fail("Should fail to add external charge on already committed invoice");
+        } catch (final InvoiceApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_ALREADY_COMMITTED.getCode());
+        }
+
+        try {
+            invoiceUserApi.insertCreditForInvoice(accountId, invoiceId, creditAmount,
+                                                  clock.getUTCToday(), accountCurrency, null, callContext);
+            Assert.fail("Should fail to add credit on already committed invoice");
+        } catch (final InvoiceApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_ALREADY_COMMITTED.getCode());
+        }
     }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
index 26f4170..49f3ce2 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -1493,6 +1493,32 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
         Assert.assertEquals(previousExistingSize, 3);
     }
 
+    @Test(groups = "fast")
+    public void testWithFreeRecurring() {
+        final LocalDate startDate = new LocalDate(2012, 8, 1);
+        final LocalDate endDate = new LocalDate(2012, 9, 1);
+
+        final BigDecimal monthlyRate1 = new BigDecimal("12.00");
+        final BigDecimal monthlyRate2 = new BigDecimal("24.00");
+
+        final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
+        final InvoiceItem freeMonthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.ZERO, BigDecimal.ZERO, currency);
+        tree.addItem(freeMonthly);
+        final InvoiceItem payingMonthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyRate1, monthlyRate1, currency);
+        tree.addItem(payingMonthly1);
+        tree.flatten(true);
+
+        final InvoiceItem proposedPayingMonthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyRate2, monthlyRate2, currency);
+        tree.mergeProposedItem(proposedPayingMonthly2);
+        tree.buildForMerge();
+
+        final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+        expectedResult.add(proposedPayingMonthly2);
+        final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, endDate, monthlyRate1.negate(), currency, payingMonthly1.getId());
+        expectedResult.add(repair);
+        verifyResult(tree.getView(), expectedResult);
+    }
+
     private void printTreeJSON(final SubscriptionItemTree tree) throws IOException {
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         tree.getRoot().jsonSerializeTree(OBJECT_MAPPER, outputStream);
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 79a3744..1e139a7 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
@@ -350,12 +350,11 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
     }
 
     protected void validatePaymentMethodForAccount(final UUID accountId, final UUID paymentMethodId, final CallContext callContext) throws PaymentApiException {
-        if (paymentMethodId == null) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, accountId);
-        }
-        final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(paymentMethodId, false, false, ImmutableList.<PluginProperty>of(), callContext);
-        if (!paymentMethod.getAccountId().equals(accountId)) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
+        if (paymentMethodId != null) {
+            final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(paymentMethodId, false, false, ImmutableList.<PluginProperty>of(), callContext);
+            if (!paymentMethod.getAccountId().equals(accountId)) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
+            }
         }
     }
 

NEWS 3(+3 -0)

diff --git a/NEWS b/NEWS
index 94d650c..e3a0cb7 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+0.18.12
+    See https://github.com/killbill/killbill/releases/tag/killbill-0.18.12
+
 0.18.11
     See https://github.com/killbill/killbill/releases/tag/killbill-0.18.11
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
index 34a3561..e114d2e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
@@ -116,7 +116,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
     }
 
     @Override
-    public Payment createAuthorizationWithPaymentControl(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+    public Payment createAuthorizationWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
                                                          @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
                                                          final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
         final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
@@ -325,17 +325,12 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
         checkExternalKeyLength(paymentTransactionExternalKey);
 
-        if (paymentMethodId == null && !paymentOptions.isExternalPayment()) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, "paymentMethodId", "should not be null");
-        }
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
 
-        // TODO validate if the code is located properly here
-        // The code should understand that the external payment method needs to be created
-        // if it doesn't exist yet and trigger a CREDIT using the (built-in) external payment plugin.
-        final UUID nonNullPaymentMethodId = (paymentMethodId != null) ?
-                                           paymentMethodId :
-                                           paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+
+        final UUID resolvedPaymentMethodId = (paymentMethodId == null && paymentOptions.isExternalPayment()) ?
+                                             paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext) :
+                                             paymentMethodId;
 
         final String transactionType = TransactionType.PURCHASE.name();
         Payment payment = null;
@@ -344,7 +339,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         try {
             logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
-            payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+            payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, resolvedPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
                                                                                  properties, paymentControlPluginNames, callContext, internalCallContext);
 
             paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -607,7 +602,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
     }
 
     @Override
-    public Payment createCreditWithPaymentControl(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+    public Payment createCreditWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
                                                   @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
                                                   final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
         final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
@@ -632,14 +627,11 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
 
             final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
 
-            // TODO validate if the code is located properly here
-            // The code should understand that the external payment method needs to be created
-            // if it doesn't exist yet and trigger a CREDIT using the (built-in) external payment plugin.
-            final UUID nonNullPaymentMethodId = (paymentMethodId != null) ?
-                                               paymentMethodId :
-                                               paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+            final UUID resolvedPaymentMethodId = (paymentMethodId == null && paymentOptions.isExternalPayment()) ?
+                                                                                              paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext) :
+                                                                                              paymentMethodId;
 
-            payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+            payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, resolvedPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
                                                                                properties, paymentControlPluginNames, callContext, internalCallContext);
 
             paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
index ff1ee48..c30581d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
@@ -230,7 +230,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
                 final PaymentPluginApi pluginApi = getPaymentPluginApi(paymentMethodModelDao.getPluginName());
                 paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), properties, tenantContext);
             } catch (final PaymentPluginApiException e) {
-                throw new PaymentApiException(ErrorCode.PAYMENT_GET_PAYMENT_METHODS, paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId());
+                throw new PaymentApiException(e, ErrorCode.PAYMENT_GET_PAYMENT_METHODS, paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId());
             }
         } else {
             paymentMethodPlugin = null;
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
index 4205e8a..a0f6ad4 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
@@ -107,7 +107,7 @@ public class TestCatalog extends TestJaxrsBase {
         Assert.assertEquals(catalogsJson.get(0).getEffectiveDate(), Date.valueOf("2011-01-01"));
         Assert.assertEquals(catalogsJson.get(0).getCurrencies().size(), 3);
         Assert.assertEquals(catalogsJson.get(0).getProducts().size(), 11);
-        Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(), 6);
+        Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(), 7);
 
         for (final Product productJson : catalogsJson.get(0).getProducts()) {
             if (!"BASE".equals(productJson.getType())) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
index 1ce9758..278ea3c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
@@ -63,6 +63,7 @@ public class PlanAligner extends BaseAligner {
         NEXT
     }
 
+
     /**
      * Returns the current and next phase for the subscription in creation
      *
@@ -88,8 +89,8 @@ public class PlanAligner extends BaseAligner {
                                                                    bundleStartDate,
                                                                    plan,
                                                                    initialPhase,
-                                                                   effectiveDate,
                                                                    catalog,
+                                                                   effectiveDate,
                                                                    context);
         final TimedPhase[] result = new TimedPhase[2];
         result[0] = getTimedPhase(timedPhases, effectiveDate, WhichPhase.CURRENT);
@@ -164,8 +165,12 @@ public class PlanAligner extends BaseAligner {
                                                                                subscription.getBundleStartDate(),
                                                                                pendingOrLastPlanTransition.getNextPlan(),
                                                                                pendingOrLastPlanTransition.getNextPhase().getPhaseType(),
-                                                                               effectiveDate,
                                                                                catalog,
+                                                                               // Use the catalog version at subscription creation time: this allows
+                                                                               // for PHASE events and uncancel operations for plans/products/pricelists already retired
+                                                                               // This is not 100% correct in a scenario where the catalog was updated and the alignment rules changed since
+                                                                               // See https://github.com/killbill/killbill/issues/784
+                                                                               subscription.getAlignStartDate(),
                                                                                context);
                     return getTimedPhase(timedPhases, effectiveDate, WhichPhase.NEXT);
                 case CHANGE:
@@ -175,6 +180,8 @@ public class PlanAligner extends BaseAligner {
                                                  pendingOrLastPlanTransition.getPreviousPlan(),
                                                  pendingOrLastPlanTransition.getNextPlan(),
                                                  effectiveDate,
+                                                 // Same remark as above
+                                                 subscription.getAlignStartDate(),
                                                  pendingOrLastPlanTransition.getEffectiveTransitionTime(),
                                                  subscription.getAllTransitions().get(0).getNextPhase().getPhaseType(),
                                                  null,
@@ -194,14 +201,14 @@ public class PlanAligner extends BaseAligner {
                                                    final DateTime bundleStartDate,
                                                    final Plan plan,
                                                    @Nullable final PhaseType initialPhase,
-                                                   final DateTime effectiveDate,
                                                    final Catalog catalog,
+                                                   final DateTime catalogEffectiveDate,
                                                    final InternalTenantContext context)
             throws CatalogApiException, SubscriptionBaseApiException {
         final PlanSpecifier planSpecifier = new PlanSpecifier(plan.getName());
 
         final DateTime planStartDate;
-        final PlanAlignmentCreate alignment = catalog.planCreateAlignment(planSpecifier, effectiveDate);
+        final PlanAlignmentCreate alignment = catalog.planCreateAlignment(planSpecifier, catalogEffectiveDate);
         switch (alignment) {
             case START_OF_SUBSCRIPTION:
                 planStartDate = subscriptionStartDate;
@@ -235,6 +242,7 @@ public class PlanAligner extends BaseAligner {
                                      pendingOrLastPlanTransition.getNextPlan(),
                                      nextPlan,
                                      effectiveDate,
+                                     effectiveDate,
                                      // This method is only called while doing the change, hence we want to pass the change effective date
                                      effectiveDate,
                                      subscription.getAllTransitions().get(0).getNextPhase().getPhaseType(),
@@ -250,6 +258,7 @@ public class PlanAligner extends BaseAligner {
                                              final Plan currentPlan,
                                              final Plan nextPlan,
                                              final DateTime effectiveDate,
+                                             final DateTime catalogEffectiveDate,
                                              final DateTime lastOrCurrentChangeEffectiveDate,
                                              final PhaseType originalInitialPhase,
                                              @Nullable final PhaseType newPlanInitialPhaseType,
@@ -262,7 +271,7 @@ public class PlanAligner extends BaseAligner {
         final PlanSpecifier toPlanSpecifier = new PlanSpecifier(nextPlan.getName());
         final PhaseType initialPhase;
         final DateTime planStartDate;
-        final PlanAlignmentChange alignment = catalog.planChangeAlignment(fromPlanPhaseSpecifier, toPlanSpecifier, effectiveDate);
+        final PlanAlignmentChange alignment = catalog.planChangeAlignment(fromPlanPhaseSpecifier, toPlanSpecifier, catalogEffectiveDate);
         switch (alignment) {
             case START_OF_SUBSCRIPTION:
                 planStartDate = subscriptionStartDate;