Details
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
index d3d525a..b791185 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
@@ -59,7 +59,7 @@ public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements
public DefaultFixed(final DefaultFixed in, final PlanPhasePriceOverride override) {
this.type = in.getType();
- this.fixedPrice = new DefaultInternationalPrice(fixedPrice, override, true);
+ this.fixedPrice = in.getPrice() != null ? new DefaultInternationalPrice((DefaultInternationalPrice) in.getPrice(), override, true) : null;
}
@Override
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
index 0cea252..ec6e80f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
@@ -52,7 +52,7 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
public DefaultInternationalPrice() {}
public DefaultInternationalPrice(final DefaultInternationalPrice in, final PlanPhasePriceOverride override, final boolean fixed) {
- this.prices = new DefaultPrice[in.getPrices().length];
+ this.prices = in.getPrices() != null ? new DefaultPrice[in.getPrices().length] : null;
// There is a question on whether we keep the other prices that were not overridden or only have one entry for the overridden price on that currency.
for (int i = 0; i < in.getPrices().length; i++) {
final DefaultPrice curPrice = (DefaultPrice) in.getPrices()[i];
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 7770d74..567ec9f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -87,8 +87,8 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
this.effectiveDateForExistingSubscriptons = in.getEffectiveDateForExistingSubscriptons();
this.product = (DefaultProduct) in.getProduct();
this.initialPhases = new DefaultPlanPhase[in.getInitialPhases().length];
- for (int i = 0; i< overrides.length; i++) {
- final DefaultPlanPhase newPhase = new DefaultPlanPhase(in.getFinalPhase(), overrides[i]);
+ for (int i = 0; i< overrides.length - 1; i++) {
+ final DefaultPlanPhase newPhase = new DefaultPlanPhase(in.getInitialPhases()[i], overrides[i]);
initialPhases[i] = newPhase;
}
this.finalPhase = new DefaultPlanPhase(in.getFinalPhase(), overrides[overrides.length - 1]);
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
index b28f371..02b7840 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
@@ -49,7 +49,7 @@ public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implem
public DefaultRecurring(final DefaultRecurring in, final PlanPhasePriceOverride override) {
this.billingPeriod = in.getBillingPeriod();
- this.recurringPrice = new DefaultInternationalPrice(recurringPrice, override, false);
+ this.recurringPrice = in.getRecurringPrice() != null ? new DefaultInternationalPrice(in.getRecurringPrice(), override, false) : null;
}
@Override
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
index b1b20b4..88b6438 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
@@ -22,11 +22,14 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.DefaultPlan;
+import org.killbill.billing.catalog.DefaultPlanPhase;
import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.CatalogUserApi;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
@@ -40,10 +43,11 @@ import org.killbill.billing.catalog.dao.CatalogOverridePlanDefinitionModelDao;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
+import sun.org.mozilla.javascript.internal.ast.ErrorCollector;
public class DefaultPriceOverride implements PriceOverride {
- final Pattern CUSTOM_PLAN_NAME_PATTERN = Pattern.compile("(.*)-(\\d+)$");
+ public static final Pattern CUSTOM_PLAN_NAME_PATTERN = Pattern.compile("(.*)-(\\d+)$");
private final CatalogOverrideDao overrideDao;
@@ -61,8 +65,8 @@ public class DefaultPriceOverride implements PriceOverride {
final PlanPhasePriceOverride curOverride = Iterables.tryFind(overrides, new Predicate<PlanPhasePriceOverride>() {
@Override
public boolean apply(final PlanPhasePriceOverride input) {
- if (input.getPhaseName() != null && input.getPhaseName().equals(curPhase.getName())) {
- return true;
+ if (input.getPhaseName() != null) {
+ return input.getPhaseName().equals(curPhase.getName());
}
// If the phaseName was not passed, we infer by matching the phaseType. This obvously would not work in a case where
// a plan is defined with multiple phases of the same type.
@@ -78,6 +82,23 @@ public class DefaultPriceOverride implements PriceOverride {
null;
}
+ for (int i = 0; i < resolvedOverride.length; i++) {
+ final PlanPhasePriceOverride curOverride = resolvedOverride[i];
+ if (curOverride != null) {
+ final DefaultPlanPhase curPhase = (DefaultPlanPhase) parentPlan.getAllPhases()[i];
+
+ if (curPhase.getFixed() == null && curOverride.getFixedPrice() != null) {
+ final String error = String.format("There is no existing fixed price for the phase %s", curPhase.getName());
+ throw new CatalogApiException(ErrorCode.CAT_INVALID_INVALID_PRICE_OVERRIDE, parentPlan.getName(), error);
+ }
+
+ if (curPhase.getRecurring() == null && curOverride.getRecurringPrice() != null) {
+ final String error = String.format("There is no existing recurring price for the phase %s", curPhase.getName());
+ throw new CatalogApiException(ErrorCode.CAT_INVALID_INVALID_PRICE_OVERRIDE, parentPlan.getName(), error);
+ }
+ }
+ }
+
final CatalogOverridePlanDefinitionModelDao overriddenPlan = overrideDao.getOrCreateOverridePlanDefinition(parentPlan.getName(), catalogEffectiveDate, resolvedOverride, context);
final String planName = new StringBuffer(parentPlan.getName()).append("-").append(overriddenPlan.getRecordId()).toString();
final DefaultPlan result = new DefaultPlan(planName, (DefaultPlan) parentPlan, resolvedOverride);
@@ -88,6 +109,9 @@ public class DefaultPriceOverride implements PriceOverride {
public DefaultPlan getOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
final Matcher m = CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
+ if (!m.matches()) {
+ throw new CatalogApiException(ErrorCode.CAT_NO_SUCH_PLAN, planName);
+ }
final String parentPlanName = m.group(1);
final Long planDefRecordId = Long.parseLong(m.group(2));
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java b/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java
index 0cbebac..a556b15 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/CatalogTestSuiteWithEmbeddedDB.java
@@ -20,6 +20,7 @@ package org.killbill.billing.catalog;
import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
import org.killbill.billing.catalog.dao.CatalogOverrideDao;
import org.killbill.billing.catalog.glue.TestCatalogModuleWithEmbeddedDB;
+import org.killbill.billing.catalog.override.PriceOverride;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.skife.jdbi.v2.IDBI;
import org.testng.annotations.BeforeClass;
@@ -36,6 +37,9 @@ public class CatalogTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEm
@Inject
protected IDBI dbi;
+ @Inject
+ protected PriceOverride priceOverride;
+
@Override
protected KillbillConfigSource getConfigSource() {
return getConfigSource("/resource.properties");
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverrideDao.java b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverrideDao.java
index ee92a26..29188c3 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverrideDao.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/dao/TestCatalogOverrideDao.java
@@ -28,7 +28,6 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.xmlloader.XMLLoader;
-import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.annotations.Test;
import com.google.common.io.Resources;
@@ -70,7 +69,6 @@ public class TestCatalogOverrideDao extends CatalogTestSuiteWithEmbeddedDB {
assertEquals(phases.size(), 2);
}
-
@Test(groups = "slow")
public void testGetOverriddenPlanPhases() throws Exception {
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java b/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
new file mode 100644
index 0000000..6f8e96b
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.CurrencyValueNull;
+import org.killbill.billing.catalog.api.InternationalPrice;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.Price;
+import org.killbill.billing.catalog.override.DefaultPriceOverride;
+import org.killbill.xmlloader.XMLLoader;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class TestDefaultPriceOverride extends CatalogTestSuiteWithEmbeddedDB {
+
+ @Test(groups = "slow")
+ public void testBasic() throws Exception {
+
+ final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+ final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
+
+ final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+ final PlanPhasePriceOverride phase1 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[0].getName(), Currency.USD, BigDecimal.ONE, null);
+ overrides.add(phase1);
+ final PlanPhasePriceOverride phase3 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[2].getName(), Currency.USD, null, new BigDecimal("142.41"));
+ overrides.add(phase3);
+
+ final DefaultPlan overriddenPlan = priceOverride.getOrCreateOverriddenPlan(plan, new DateTime(catalog.getEffectiveDate()), overrides, internalCallContext);
+
+ final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(overriddenPlan.getName());
+ assertTrue(m.matches());
+ assertEquals(m.group(1), plan.getName());
+ assertEquals(m.group(2), "1"); // first entry in the table
+
+ assertEquals(overriddenPlan.getProduct().getName(), plan.getProduct().getName());
+ assertEquals(overriddenPlan.getRecurringBillingPeriod(), plan.getRecurringBillingPeriod());
+ if (plan.getEffectiveDateForExistingSubscriptons() != null) {
+ assertEquals(overriddenPlan.getEffectiveDateForExistingSubscriptons().compareTo(plan.getEffectiveDateForExistingSubscriptons()), 0);
+ }
+ assertEquals(overriddenPlan.getFinalPhase().getName(), plan.getFinalPhase().getName());
+ assertEquals(overriddenPlan.getPlansAllowedInBundle(), plan.getPlansAllowedInBundle());
+
+ assertEquals(overriddenPlan.getAllPhases().length, overriddenPlan.getAllPhases().length);
+ for (int i = 0; i < overriddenPlan.getAllPhases().length; i++) {
+
+ final DefaultPlanPhase initialPhase = (DefaultPlanPhase) plan.getAllPhases()[i];
+ final DefaultPlanPhase newPhase = (DefaultPlanPhase) overriddenPlan.getAllPhases()[i];
+
+ final PlanPhasePriceOverride override = Iterables.tryFind(overrides, new Predicate<PlanPhasePriceOverride>() {
+ @Override
+ public boolean apply(final PlanPhasePriceOverride input) {
+ return input.getPhaseName().equals(initialPhase.getName());
+ }
+ }).orNull();
+
+ assertEquals(newPhase.getName(), initialPhase.getName());
+ assertEquals(newPhase.getDuration(), initialPhase.getDuration());
+ assertEquals(newPhase.getPhaseType(), initialPhase.getPhaseType());
+ assertEquals(newPhase.getUsages().length, initialPhase.getUsages().length);
+ if (initialPhase.getFixed() != null) {
+ assertEquals(newPhase.getFixed().getType(), initialPhase.getFixed().getType());
+ assertInternationalPrice(newPhase.getFixed().getPrice(), initialPhase.getFixed().getPrice(), override, true);
+ }
+ if (initialPhase.getRecurring() != null) {
+ assertInternationalPrice(newPhase.getRecurring().getRecurringPrice(), initialPhase.getRecurring().getRecurringPrice(), override, false);
+ }
+ }
+ }
+
+ @Test(groups = "slow", expectedExceptions = CatalogApiException.class)
+ public void testWithInvalidPriceOverride() throws Exception {
+
+ final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+ final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
+
+ final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+ final PlanPhasePriceOverride phase1 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[0].getName(), Currency.USD, null, BigDecimal.ONE);
+ overrides.add(phase1);
+
+ priceOverride.getOrCreateOverriddenPlan(plan, new DateTime(catalog.getEffectiveDate()), overrides, internalCallContext);
+
+ }
+
+ @Test(groups = "slow")
+ public void testGetOverriddenPlan() throws Exception {
+
+ final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+ final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
+
+ final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+ final PlanPhasePriceOverride phase1 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[0].getName(), Currency.USD, BigDecimal.ONE, null);
+ overrides.add(phase1);
+ final PlanPhasePriceOverride phase3 = new DefaultPlanPhasePriceOverride(plan.getAllPhases()[2].getName(), Currency.USD, null, new BigDecimal("142.41"));
+ overrides.add(phase3);
+
+ final DefaultPlan overriddenPlanCreated = priceOverride.getOrCreateOverriddenPlan(plan, new DateTime(catalog.getEffectiveDate()), overrides, internalCallContext);
+
+ System.out.println("overriddenPlanCreated = " + overriddenPlanCreated.getName());
+
+ final DefaultPlan overriddenPlan = priceOverride.getOverriddenPlan(overriddenPlanCreated.getName(), catalog, internalCallContext);
+
+ assertEquals(overriddenPlan.getProduct().getName(), plan.getProduct().getName());
+ assertEquals(overriddenPlan.getRecurringBillingPeriod(), plan.getRecurringBillingPeriod());
+ if (plan.getEffectiveDateForExistingSubscriptons() != null) {
+ assertEquals(overriddenPlan.getEffectiveDateForExistingSubscriptons().compareTo(plan.getEffectiveDateForExistingSubscriptons()), 0);
+ }
+ assertEquals(overriddenPlan.getFinalPhase().getName(), plan.getFinalPhase().getName());
+ assertEquals(overriddenPlan.getPlansAllowedInBundle(), plan.getPlansAllowedInBundle());
+
+ assertEquals(overriddenPlan.getAllPhases().length, overriddenPlan.getAllPhases().length);
+ for (int i = 0; i < overriddenPlan.getAllPhases().length; i++) {
+
+ final DefaultPlanPhase initialPhase = (DefaultPlanPhase) plan.getAllPhases()[i];
+ final DefaultPlanPhase newPhase = (DefaultPlanPhase) overriddenPlan.getAllPhases()[i];
+
+ final PlanPhasePriceOverride override = Iterables.tryFind(overrides, new Predicate<PlanPhasePriceOverride>() {
+ @Override
+ public boolean apply(final PlanPhasePriceOverride input) {
+ return input.getPhaseName().equals(initialPhase.getName());
+ }
+ }).orNull();
+
+ assertEquals(newPhase.getName(), initialPhase.getName());
+ assertEquals(newPhase.getDuration(), initialPhase.getDuration());
+ assertEquals(newPhase.getPhaseType(), initialPhase.getPhaseType());
+ assertEquals(newPhase.getUsages().length, initialPhase.getUsages().length);
+ if (initialPhase.getFixed() != null) {
+ assertEquals(newPhase.getFixed().getType(), initialPhase.getFixed().getType());
+ assertInternationalPrice(newPhase.getFixed().getPrice(), initialPhase.getFixed().getPrice(), override, true);
+ }
+ if (initialPhase.getRecurring() != null) {
+ assertInternationalPrice(newPhase.getRecurring().getRecurringPrice(), initialPhase.getRecurring().getRecurringPrice(), override, false);
+ }
+ }
+ }
+
+ private void assertInternationalPrice(final InternationalPrice newInternationalPrice, final InternationalPrice initInternationalPrice, final PlanPhasePriceOverride override, final boolean isFixed) throws CurrencyValueNull {
+ assertEquals(newInternationalPrice.getPrices().length, initInternationalPrice.getPrices().length);
+ for (int i = 0; i < newInternationalPrice.getPrices().length; i++) {
+ final Price initPrice = initInternationalPrice.getPrices()[i];
+ final Price newPrice = newInternationalPrice.getPrices()[i];
+ if (override != null && override.getCurrency() == initPrice.getCurrency() &&
+ ((isFixed && override.getFixedPrice() != null) || (!isFixed && override.getRecurringPrice() != null))) {
+ assertEquals(newPrice.getValue().compareTo(isFixed ? override.getFixedPrice() : override.getRecurringPrice()), 0);
+ } else {
+ if (initPrice != null && initPrice.getValue() != null) {
+ assertEquals(newPrice.getValue().compareTo(initPrice.getValue()), 0);
+ }
+ }
+ }
+ }
+}