killbill-memoizeit

catalog: ability to change to a plan defined in a subsequent

5/16/2018 6:42:57 AM

Details

diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
index 96869e4..aefc695 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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:
  *
@@ -179,9 +181,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return this.priceLists;
     }
 
-    /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.ICatalog#getPlan(java.lang.String, java.lang.String)
-      */
     @Override
     public Plan createOrFindCurrentPlan(final PlanSpecifier spec, final PlanPhasePriceOverridesWithCallContext unused) throws CatalogApiException {
         final Plan result;
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index f0fa2d5..b9b1115 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
@@ -160,20 +160,22 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
 
     private CatalogPlanEntry findCatalogPlanEntry(final PlanRequestWrapper wrapper,
                                                   final DateTime requestedDate,
-                                                  final DateTime subscriptionStartDate)
-            throws CatalogApiException {
+                                                  final DateTime subscriptionStartDate) throws CatalogApiException {
         final List<StandaloneCatalog> catalogs = versionsBeforeDate(requestedDate.toDate());
         if (catalogs.isEmpty()) {
             throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, requestedDate.toDate().toString());
         }
 
+        CatalogPlanEntry candidateInSubsequentCatalog = null;
         for (int i = catalogs.size() - 1; i >= 0; i--) { // Working backwards to find the latest applicable plan
             final StandaloneCatalog c = catalogs.get(i);
+
             final Plan plan;
             try {
                 plan = wrapper.findPlan(c);
             } catch (final CatalogApiException e) {
-                if (e.getCode() != ErrorCode.CAT_NO_SUCH_PLAN.getCode()) {
+                if (e.getCode() != ErrorCode.CAT_NO_SUCH_PLAN.getCode() &&
+                    e.getCode() != ErrorCode.CAT_PLAN_NOT_FOUND.getCode()) {
                     throw e;
                 } else {
                     // If we can't find an entry it probably means the plan has been retired so we keep looking...
@@ -182,21 +184,29 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
             }
 
 
-            final boolean initialVersion = (i == 0);
+            final boolean oldestCatalog = (i == 0);
             final DateTime catalogEffectiveDate = CatalogDateHelper.toUTCDateTime(c.getEffectiveDate());
-            if (initialVersion || // Prevent issue with time granularity -- see #760
-                !subscriptionStartDate.isBefore(catalogEffectiveDate)) { // It's a new subscription this plan always applies
+            final boolean catalogOlderThanSubscriptionStartDate = !subscriptionStartDate.isBefore(catalogEffectiveDate);
+            if (oldestCatalog || // Prevent issue with time granularity -- see #760
+                catalogOlderThanSubscriptionStartDate) { // It's a new subscription, this plan always applies
                 return new CatalogPlanEntry(c, plan);
-            } else { //Its an existing subscription
-                if (plan.getEffectiveDateForExistingSubscriptions() != null) { //if it is null any change to this does not apply to existing subscriptions
+            } else { // It's an existing subscription
+                if (plan.getEffectiveDateForExistingSubscriptions() != null) { // If it is null, any change to this catalog does not apply to existing subscriptions
                     final DateTime existingSubscriptionDate = CatalogDateHelper.toUTCDateTime(plan.getEffectiveDateForExistingSubscriptions());
-                    if (requestedDate.isAfter(existingSubscriptionDate)) { // this plan is now applicable to existing subs
+                    if (requestedDate.isAfter(existingSubscriptionDate)) { // This plan is now applicable to existing subs
                         return new CatalogPlanEntry(c, plan);
                     }
+                } else if (candidateInSubsequentCatalog == null) {
+                    // Keep the most recent one
+                    candidateInSubsequentCatalog = new CatalogPlanEntry(c, plan);
                 }
             }
         }
 
+        if (candidateInSubsequentCatalog != null) {
+            return candidateInSubsequentCatalog;
+        }
+
         final PlanSpecifier spec = wrapper.getSpec();
         throw new CatalogApiException(ErrorCode.CAT_PLAN_NOT_FOUND,
                                       spec.getPlanName() != null ? spec.getPlanName() : "undefined",
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java
index f5513d6..b3f5bd0 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -27,19 +27,13 @@ import java.net.URL;
 import java.util.Iterator;
 import java.util.List;
 
-import javax.xml.bind.JAXBException;
-import javax.xml.transform.TransformerException;
-
 import org.joda.time.DateTime;
 import org.killbill.billing.catalog.CatalogTestSuiteNoDB;
 import org.killbill.billing.catalog.StandaloneCatalog;
-import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
 import org.killbill.billing.catalog.VersionedCatalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
-import org.killbill.billing.catalog.api.InvalidConfigException;
 import org.testng.Assert;
 import org.testng.annotations.Test;
-import org.xml.sax.SAXException;
 
 import com.google.common.io.Files;
 import com.google.common.io.Resources;
@@ -118,14 +112,16 @@ public class TestVersionedCatalogLoader extends CatalogTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testLoad() throws IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, CatalogApiException {
+    public void testLoad() throws CatalogApiException {
         final VersionedCatalog c = loader.loadDefaultCatalog(Resources.getResource("versionedCatalog").toString());
-        Assert.assertEquals(c.size(), 3);
+        Assert.assertEquals(c.size(), 4);
         final Iterator<StandaloneCatalog> it = c.iterator();
         DateTime dt = new DateTime("2011-01-01T00:00:00+00:00");
         Assert.assertEquals(it.next().getEffectiveDate(), dt.toDate());
         dt = new DateTime("2011-02-02T00:00:00+00:00");
         Assert.assertEquals(it.next().getEffectiveDate(), dt.toDate());
+        dt = new DateTime("2011-02-03T00:00:00+00:00");
+        Assert.assertEquals(it.next().getEffectiveDate(), dt.toDate());
         dt = new DateTime("2011-03-03T00:00:00+00:00");
         Assert.assertEquals(it.next().getEffectiveDate(), dt.toDate());
     }
@@ -145,7 +141,7 @@ public class TestVersionedCatalogLoader extends CatalogTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testLoadCatalogFromInsideResourceFolder() throws CatalogApiException, URISyntaxException, IOException {
+    public void testLoadCatalogFromInsideResourceFolder() throws CatalogApiException {
         final VersionedCatalog c = loader.loadDefaultCatalog("com/acme/SpyCarCustom.xml");
         Assert.assertEquals(c.size(), 1);
         final DateTime dt = new DateTime("2015-10-04T00:00:00+00:00");
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
index b70e54f..ed7ea43 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -22,15 +22,29 @@ import java.math.BigDecimal;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
+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.PlanSpecifier;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 public class TestVersionedCatalog extends CatalogTestSuiteNoDB {
 
+    final DateTime dt0 = new DateTime("2010-01-01T00:00:00+00:00");
+    // WeaponsHireSmall-1.xml
+    final DateTime dt1 = new DateTime("2011-01-01T00:01:00+00:00");
+    // WeaponsHireSmall-2.xml
+    final DateTime dt2 = new DateTime("2011-02-02T00:01:00+00:00");
+    // WeaponsHireSmall-2a.xml
+    final DateTime dt2a = new DateTime("2011-02-03T00:01:00+00:00");
+    // effectiveDateForExistingSubscriptions from the catalogs 2 and 2a
+    final DateTime dt214 = new DateTime("2011-02-14T00:01:00+00:00");
+    // WeaponsHireSmall-3.xml
+    final DateTime dt3 = new DateTime("2011-03-03T00:01:00+00:00");
+
     private VersionedCatalog vc;
 
     @BeforeClass(groups = "fast")
@@ -39,16 +53,9 @@ public class TestVersionedCatalog extends CatalogTestSuiteNoDB {
         vc = loader.loadDefaultCatalog("versionedCatalog");
     }
 
-
     @Test(groups = "fast")
     public void testFindPlanWithDates() throws Exception {
-        final DateTime dt0 = new DateTime("2010-01-01T00:00:00+00:00");
-        final DateTime dt1 = new DateTime("2011-01-01T00:01:00+00:00");
-        final DateTime dt2 = new DateTime("2011-02-02T00:01:00+00:00");
-        final DateTime dt214 = new DateTime("2011-02-14T00:01:00+00:00");
-        final DateTime dt3 = new DateTime("2011-03-03T00:01:00+00:00");
-
-        // We find it although the date provided is too early because we default to first catalog version
+        // We find it although the date provided is too early because we default to first catalog version (see also testErrorOnDateTooEarly below)
         final Plan newSubPlan0 = vc.findPlan("pistol-monthly", dt0, dt0);
 
         final Plan newSubPlan1 = vc.findPlan("pistol-monthly", dt1, dt1);
@@ -66,17 +73,66 @@ public class TestVersionedCatalog extends CatalogTestSuiteNoDB {
         final Plan exSubPlan2 = vc.findPlan("pistol-monthly", dt2, dt1);
         final Plan exSubPlan214 = vc.findPlan("pistol-monthly", dt214, dt1);
         final Plan exSubPlan3 = vc.findPlan("pistol-monthly", dt3, dt1);
+        // Plan added in subsequent catalog (at dt2)
+        final Plan exSubPlan4 = vc.findPlan("shotgun-quarterly", dt2, dt1);
+        final Plan exSubPlan5 = vc.findPlan("shotgun-quarterly", dt2a, dt1);
+        final Plan exSubPlan6 = vc.findPlan("shotgun-quarterly", dt214, dt1);
+        final Plan exSubPlan7 = vc.findPlan("shotgun-quarterly", dt214, dt214);
 
         Assert.assertEquals(exSubPlan2.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("29.95"));
         Assert.assertEquals(exSubPlan214.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("39.95"));
         Assert.assertEquals(exSubPlan3.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("39.95"));
+        Assert.assertEquals(exSubPlan4.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("249.95"));
+        // Old price
+        Assert.assertEquals(exSubPlan5.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("249.95"));
+        // New price
+        Assert.assertEquals(exSubPlan6.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("259.95"));
+        Assert.assertEquals(exSubPlan7.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("259.95"));
+    }
+
+    // Similar to testFindPlanWithDates, but use the API with PlanSpecifier
+    @Test(groups = "fast")
+    public void testFindPlanWithDatesAndPlanSpecifier() throws Exception {
+        final PlanSpecifier pistolMonthly = new PlanSpecifier("Pistol", BillingPeriod.MONTHLY, "DEFAULT");
+        final PlanSpecifier shotgunQuarterly = new PlanSpecifier("Shotgun", BillingPeriod.QUARTERLY, "DEFAULT");
+
+        // We find it although the date provided is too early because we default to first catalog version (see also testErrorOnDateTooEarly below)
+        final Plan newSubPlan0 = vc.createOrFindPlan(pistolMonthly, null, dt0, dt0);
+
+        final Plan newSubPlan1 = vc.createOrFindPlan(pistolMonthly, null, dt1, dt1);
+        final Plan newSubPlan2 = vc.createOrFindPlan(pistolMonthly, null, dt2, dt2);
+        final Plan newSubPlan214 = vc.createOrFindPlan(pistolMonthly, null, dt214, dt214);
+        final Plan newSubPlan3 = vc.createOrFindPlan(pistolMonthly, null, dt3, dt3);
 
+        Assert.assertEquals(newSubPlan1.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("29.95"));
+        Assert.assertEquals(newSubPlan2.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("39.95"));
+        Assert.assertEquals(newSubPlan214.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("39.95"));
+        Assert.assertEquals(newSubPlan3.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("49.95"));
+
+        // Existing subscription
+
+        final Plan exSubPlan2 = vc.createOrFindPlan(pistolMonthly, null, dt2, dt1);
+        final Plan exSubPlan214 = vc.createOrFindPlan(pistolMonthly, null, dt214, dt1);
+        final Plan exSubPlan3 = vc.createOrFindPlan(pistolMonthly, null, dt3, dt1);
+        // Plan added in subsequent catalog (at dt2)
+        final Plan exSubPlan4 = vc.createOrFindPlan(shotgunQuarterly, null, dt2, dt1);
+        final Plan exSubPlan5 = vc.createOrFindPlan(shotgunQuarterly, null, dt2a, dt1);
+        final Plan exSubPlan6 = vc.createOrFindPlan(shotgunQuarterly, null, dt214, dt1);
+        final Plan exSubPlan7 = vc.createOrFindPlan(shotgunQuarterly, null, dt214, dt214);
+
+        Assert.assertEquals(exSubPlan2.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("29.95"));
+        Assert.assertEquals(exSubPlan214.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("39.95"));
+        Assert.assertEquals(exSubPlan3.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("39.95"));
+        Assert.assertEquals(exSubPlan4.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("249.95"));
+        // Old price
+        Assert.assertEquals(exSubPlan5.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("249.95"));
+        // New price
+        Assert.assertEquals(exSubPlan6.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("259.95"));
+        Assert.assertEquals(exSubPlan7.getAllPhases()[1].getRecurring().getRecurringPrice().getPrice(Currency.USD), new BigDecimal("259.95"));
     }
 
     @Test(groups = "fast")
     public void testErrorOnDateTooEarly() throws CatalogApiException {
-        final DateTime dt0 = new DateTime("1977-01-01T00:00:00+00:00");
-
         // We find it although the date provided is too early because we default to first catalog version
         vc.findPlan("shotgun-monthly", dt0);
 
@@ -84,25 +140,26 @@ public class TestVersionedCatalog extends CatalogTestSuiteNoDB {
             // We **don't find it** because date is too early and not part of first catalog version
             vc.findPlan("shotgun-quarterly", dt0);
             Assert.fail("Date is too early an exception should have been thrown");
-        } catch (CatalogApiException e) {
+        } catch (final CatalogApiException e) {
             Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PLAN.getCode());
         }
     }
 
-
     @Test(groups = "fast")
     public void testWithDeletedPlan() throws CatalogApiException {
-
         // We find it because this is version 2 whose effectiveDate is "2011-02-02T00:00:00+00:00"
-        vc.findPlan("shotgun-quarterly", new DateTime("2011-02-02T00:01:00+00:00"));
+        vc.findPlan("shotgun-quarterly", dt2);
 
         try {
             // We **don't find it** because date provided matches version 3 where plan was removed
-            vc.findPlan("shotgun-quarterly", new DateTime("2011-03-03T00:01:00+00:00"));
+            vc.findPlan("shotgun-quarterly", dt3);
             Assert.fail("Plan has been removed");
-        } catch (CatalogApiException e) {
+        } catch (final CatalogApiException e) {
             Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PLAN.getCode());
         }
 
+        // Similar test but for existing subscription: we want to find the plan in the original catalog in this case.
+        // This would be called for instance when computing billing events (dt3 could be a future PHASE event for instance)
+        vc.findPlan("shotgun-quarterly", dt3, dt1);
     }
 }
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
index f333572..5f1a214 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!--
   ~ Copyright 2010-2013 Ning, Inc.
+  ~ Copyright 2014-2018 Groupon, Inc
+  ~ Copyright 2014-2018 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,7 +18,7 @@
   -->
 
 <catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+         xsi:noNamespaceSchemaLocation="http://docs.killbill.io/latest/catalog.xsd">
 
     <effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
     <catalogName>WeaponsHireSmall</catalogName>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
index 3fee3ea..0e11d5b 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!--
   ~ Copyright 2010-2013 Ning, Inc.
+  ~ Copyright 2014-2018 Groupon, Inc
+  ~ Copyright 2014-2018 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:
   ~
@@ -15,8 +17,16 @@
   ~ under the License.
   -->
 
+<!---
+
+Changes compared to WeaponsHireSmall-1.xml:
+  * default change policy IMMEDIATE
+  * pistol-monthly price change
+  * new shotgun-quarterly plan
+
+-->
 <catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+         xsi:noNamespaceSchemaLocation="http://docs.killbill.io/latest/catalog.xsd">
 
     <effectiveDate>2011-02-02T00:00:00+00:00</effectiveDate>
     <catalogName>WeaponsHireSmall</catalogName>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2a.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2a.xml
new file mode 100644
index 0000000..c2ecfd6
--- /dev/null
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2a.xml
@@ -0,0 +1,343 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+  ~ Copyright 2010-2013 Ning, Inc.
+  ~ Copyright 2014-2018 Groupon, Inc
+  ~ Copyright 2014-2018 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.
+  -->
+
+<!---
+
+Changes compared to WeaponsHireSmall-2.xml:
+  * shotgun-quarterly price change
+
+-->
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://docs.killbill.io/latest/catalog.xsd">
+
+    <effectiveDate>2011-02-03T00:00:00+00:00</effectiveDate>
+    <catalogName>WeaponsHireSmall</catalogName>
+
+    <recurringBillingMode>IN_ADVANCE</recurringBillingMode>
+
+    <currencies>
+        <currency>USD</currency>
+        <currency>EUR</currency>
+        <currency>GBP</currency>
+    </currencies>
+
+    <units>
+        <unit name="targets"/>
+        <unit name="misfires"/>
+        <unit name="shells"/>
+    </units>
+
+    <products>
+        <product name="Pistol">
+            <category>BASE</category>
+        </product>
+        <product name="Shotgun">
+            <category>BASE</category>
+            <limits>
+                <limit>
+                    <unit>shells</unit>
+                    <max>300</max>
+                </limit>
+            </limits>
+        </product>
+        <product name="Laser-Scope">
+            <category>ADD_ON</category>
+        </product>
+        <product name="Extra-Ammo">
+            <category>ADD_ON</category>
+        </product>
+    </products>
+
+    <rules>
+        <changePolicy>
+            <changePolicyCase>
+                <fromBillingPeriod>MONTHLY</fromBillingPeriod>
+                <toProduct>Shotgun</toProduct>
+                <toBillingPeriod>MONTHLY</toBillingPeriod>
+                <policy>END_OF_TERM</policy>
+            </changePolicyCase>
+            <changePolicyCase>
+                <phaseType>TRIAL</phaseType>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
+            <changePolicyCase>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
+        </changePolicy>
+        <changeAlignment>
+            <changeAlignmentCase>
+                <alignment>START_OF_SUBSCRIPTION</alignment>
+            </changeAlignmentCase>
+        </changeAlignment>
+        <cancelPolicy>
+            <cancelPolicyCase>
+                <policy>IMMEDIATE</policy>
+            </cancelPolicyCase>
+        </cancelPolicy>
+        <createAlignment>
+            <createAlignmentCase>
+                <product>Laser-Scope</product>
+                <alignment>START_OF_SUBSCRIPTION</alignment>
+            </createAlignmentCase>
+            <createAlignmentCase>
+                <alignment>START_OF_BUNDLE</alignment>
+            </createAlignmentCase>
+        </createAlignment>
+    </rules>
+
+    <plans>
+        <plan name="pistol-monthly">
+            <effectiveDateForExistingSubscriptions>2011-02-14T00:00:00+00:00</effectiveDateForExistingSubscriptions>
+
+            <product>Pistol</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice> <!-- empty price implies $0 -->
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>39.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>39.95</value>
+                        </price>
+                        <price>
+                            <currency>USD</currency>
+                            <value>39.95</value>
+                        </price>
+                    </recurringPrice>
+                    <!--
+                    <limits>
+                        <limit>
+                            <unit>targets</unit>
+                            <min>3</min>
+                        </limit>
+                        <limit>
+                            <unit>misfires</unit>
+                            <max>20</max>
+                        </limit>
+                    </limits>
+                    -->
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="shotgun-monthly">
+            <product>Shotgun</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice> <!-- empty price implies $0 -->
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                    <number>-1</number>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>249.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>149.95</value>
+                        </price>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>169.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="shotgun-quarterly">
+            <effectiveDateForExistingSubscriptions>2011-02-14T00:00:00+00:00</effectiveDateForExistingSubscriptions>
+
+            <product>Shotgun</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice> <!-- empty price implies $0 -->
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                    <number>-1</number>
+                </duration>
+                <recurring>
+                    <billingPeriod>QUARTERLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>259.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>159.95</value>
+                        </price>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>179.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="shotgun-annual">
+            <product>Shotgun</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice> <!-- empty price implies $0 -->
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>ANNUAL</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>2399.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>1499.95</value>
+                        </price>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>1699.95</value>
+                        </price>
+                    </recurringPrice>
+                    <!--
+                    <limits>
+                        <limit>
+                            <unit>shells</unit>
+                            <max>200</max>
+                        </limit>
+                    </limits>
+                    -->
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="laser-scope-monthly">
+            <product>Laser-Scope</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>1999.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>1499.95</value>
+                        </price>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>1999.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="extra-ammo-monthly">
+            <product>Extra-Ammo</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>1999.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>1499.95</value>
+                        </price>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>1999.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+    </plans>
+    <priceLists>
+        <defaultPriceList name="DEFAULT">
+            <plans>
+                <plan>pistol-monthly</plan>
+                <plan>shotgun-monthly</plan>
+                <plan>shotgun-quarterly</plan>
+                <plan>shotgun-annual</plan>
+                <plan>laser-scope-monthly</plan>
+                <plan>extra-ammo-monthly</plan>
+            </plans>
+        </defaultPriceList>
+    </priceLists>
+</catalog>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
index eb21229..779a440 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!--
   ~ Copyright 2010-2013 Ning, Inc.
+  ~ Copyright 2014-2018 Groupon, Inc
+  ~ Copyright 2014-2018 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:
   ~
@@ -15,8 +17,15 @@
   ~ under the License.
   -->
 
+<!---
+
+Changes compared to WeaponsHireSmall-2a.xml:
+  * pistol-monthly price change
+  * shotgun-quarterly plan retired
+
+-->
 <catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+         xsi:noNamespaceSchemaLocation="http://docs.killbill.io/latest/catalog.xsd">
 
     <effectiveDate>2011-03-03T00:00:00+00:00</effectiveDate>
     <catalogName>WeaponsHireSmall</catalogName>
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java b/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
index 0793d6f..57b2475 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
@@ -243,7 +243,7 @@ public abstract class EntitlementLoggingHelper {
             }
             if (spec.getPriceListName() != null) {
                 logLine.append(", priceList='")
-                       .append(spec.getBillingPeriod())
+                       .append(spec.getPriceListName())
                        .append("'");
             }
             logPlanPhasePriceOverrides(logLine, overrides);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestDefaultSubscriptionBase.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestDefaultSubscriptionBase.java
index 9dff853..fab5df1 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestDefaultSubscriptionBase.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestDefaultSubscriptionBase.java
@@ -41,7 +41,8 @@ public class TestDefaultSubscriptionBase extends SubscriptionTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testCancelSOT() throws Exception {
-        final DefaultSubscriptionBase subscriptionBase = new DefaultSubscriptionBase(new SubscriptionBuilder());
+        final DateTime startDate = new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC);
+        final DefaultSubscriptionBase subscriptionBase = new DefaultSubscriptionBase(new SubscriptionBuilder().setAlignStartDate(startDate));
 
         final UUID subscriptionId = UUID.randomUUID();
         final List<SubscriptionBaseEvent> inputEvents = new LinkedList<SubscriptionBaseEvent>();
@@ -52,9 +53,9 @@ public class TestDefaultSubscriptionBase extends SubscriptionTestSuiteNoDB {
                                                                 .setFromDisk(true)
                                                                 .setUuid(UUID.randomUUID())
                                                                 .setSubscriptionId(subscriptionId)
-                                                                .setCreatedDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
-                                                                .setUpdatedDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
-                                                                .setEffectiveDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
+                                                                .setCreatedDate(startDate)
+                                                                .setUpdatedDate(startDate)
+                                                                .setEffectiveDate(startDate)
                                                                 .setTotalOrdering(3)
                                                                 .setActive(true)));
         inputEvents.add(new ApiEventCancel(new ApiEventBuilder().setApiEventType(ApiEventType.CANCEL)
@@ -64,9 +65,9 @@ public class TestDefaultSubscriptionBase extends SubscriptionTestSuiteNoDB {
                                                                 .setFromDisk(false)
                                                                 .setUuid(UUID.randomUUID())
                                                                 .setSubscriptionId(subscriptionId)
-                                                                .setCreatedDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
+                                                                .setCreatedDate(startDate)
                                                                 .setUpdatedDate(null)
-                                                                .setEffectiveDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
+                                                                .setEffectiveDate(startDate)
                                                                 .setTotalOrdering(0) // In-memory event
                                                                 .setActive(true)));
         subscriptionBase.rebuildTransitions(inputEvents, catalog);
@@ -74,15 +75,16 @@ public class TestDefaultSubscriptionBase extends SubscriptionTestSuiteNoDB {
         Assert.assertEquals(subscriptionBase.getAllTransitions().size(), 2);
         Assert.assertNull(subscriptionBase.getAllTransitions().get(0).getPreviousState());
         Assert.assertEquals(subscriptionBase.getAllTransitions().get(0).getNextState(), EntitlementState.ACTIVE);
-        Assert.assertEquals(subscriptionBase.getAllTransitions().get(0).getEffectiveTransitionTime(), new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC));
+        Assert.assertEquals(subscriptionBase.getAllTransitions().get(0).getEffectiveTransitionTime(), startDate);
         Assert.assertEquals(subscriptionBase.getAllTransitions().get(1).getPreviousState(), EntitlementState.ACTIVE);
         Assert.assertEquals(subscriptionBase.getAllTransitions().get(1).getNextState(), EntitlementState.CANCELLED);
-        Assert.assertEquals(subscriptionBase.getAllTransitions().get(1).getEffectiveTransitionTime(), new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC));
+        Assert.assertEquals(subscriptionBase.getAllTransitions().get(1).getEffectiveTransitionTime(), startDate);
     }
 
     @Test(groups = "fast", description = "https://github.com/killbill/killbill/issues/897")
     public void testFutureCancelBeforePhase() throws Exception {
-        final DefaultSubscriptionBase subscriptionBase = new DefaultSubscriptionBase(new SubscriptionBuilder());
+        final DateTime startDate = new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC);
+        final DefaultSubscriptionBase subscriptionBase = new DefaultSubscriptionBase(new SubscriptionBuilder().setAlignStartDate(startDate));
 
         final UUID subscriptionId = UUID.randomUUID();
         final List<SubscriptionBaseEvent> inputEvents = new LinkedList<SubscriptionBaseEvent>();
@@ -93,16 +95,16 @@ public class TestDefaultSubscriptionBase extends SubscriptionTestSuiteNoDB {
                                                                 .setFromDisk(true)
                                                                 .setUuid(UUID.randomUUID())
                                                                 .setSubscriptionId(subscriptionId)
-                                                                .setCreatedDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
-                                                                .setUpdatedDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
-                                                                .setEffectiveDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
+                                                                .setCreatedDate(startDate)
+                                                                .setUpdatedDate(startDate)
+                                                                .setEffectiveDate(startDate)
                                                                 .setTotalOrdering(3)
                                                                 .setActive(true)));
         inputEvents.add(new PhaseEventData(new PhaseEventBuilder().setPhaseName("laser-scope-monthly-evergreen")
                                                                   .setUuid(UUID.randomUUID())
                                                                   .setSubscriptionId(subscriptionId)
-                                                                  .setCreatedDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
-                                                                  .setUpdatedDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
+                                                                  .setCreatedDate(startDate)
+                                                                  .setUpdatedDate(startDate)
                                                                   .setEffectiveDate(new DateTime(2012, 6, 1, 0, 0, DateTimeZone.UTC))
                                                                   .setTotalOrdering(4)
                                                                   .setActive(true)));
@@ -113,7 +115,7 @@ public class TestDefaultSubscriptionBase extends SubscriptionTestSuiteNoDB {
                                                                 .setFromDisk(false)
                                                                 .setUuid(UUID.randomUUID())
                                                                 .setSubscriptionId(subscriptionId)
-                                                                .setCreatedDate(new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC))
+                                                                .setCreatedDate(startDate)
                                                                 .setUpdatedDate(null)
                                                                 .setEffectiveDate(new DateTime(2012, 6, 1, 0, 0, DateTimeZone.UTC))
                                                                 .setTotalOrdering(0) // In-memory event
@@ -123,7 +125,7 @@ public class TestDefaultSubscriptionBase extends SubscriptionTestSuiteNoDB {
         Assert.assertEquals(subscriptionBase.getAllTransitions().size(), 2);
         Assert.assertNull(subscriptionBase.getAllTransitions().get(0).getPreviousState());
         Assert.assertEquals(subscriptionBase.getAllTransitions().get(0).getNextState(), EntitlementState.ACTIVE);
-        Assert.assertEquals(subscriptionBase.getAllTransitions().get(0).getEffectiveTransitionTime(), new DateTime(2012, 5, 1, 0, 0, DateTimeZone.UTC));
+        Assert.assertEquals(subscriptionBase.getAllTransitions().get(0).getEffectiveTransitionTime(), startDate);
         Assert.assertEquals(subscriptionBase.getAllTransitions().get(1).getPreviousState(), EntitlementState.ACTIVE);
         Assert.assertEquals(subscriptionBase.getAllTransitions().get(1).getNextState(), EntitlementState.CANCELLED);
         Assert.assertEquals(subscriptionBase.getAllTransitions().get(1).getEffectiveTransitionTime(), new DateTime(2012, 6, 1, 0, 0, DateTimeZone.UTC));