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