Details
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
index cb8993d..1e61a99 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
@@ -21,6 +21,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
+import com.ning.billing.catalog.api.ActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
@@ -42,6 +43,10 @@ public interface Subscription extends Entity, Blockable {
public boolean changePlan(String productName, BillingPeriod term, String priceList, DateTime requestedDate, CallContext context)
throws EntitlementUserApiException;
+ public boolean changePlanWithPolicy(String productName, BillingPeriod term, String priceList, DateTime requestedDate,
+ ActionPolicy policy, CallContext context)
+ throws EntitlementUserApiException;
+
public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
throws EntitlementUserApiException;
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestEntitlement.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestEntitlement.java
new file mode 100644
index 0000000..57220ce
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestEntitlement.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.beatrix.integration;
+
+import org.joda.time.LocalDate;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.ActionPolicy;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+@Guice(modules = {BeatrixModule.class})
+public class TestEntitlement extends TestIntegrationBase {
+
+ @Test(groups = "slow")
+ public void testForcePolicy() throws Exception {
+ // We take april as it has 30 days (easier to play with BCD)
+ final LocalDate today = new LocalDate(2012, 4, 1);
+ final Account account = createAccountWithPaymentMethod(getAccountData(1));
+
+ // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+ clock.setDeltaFromReality(today.toDateTimeAtCurrentTime().getMillis() - clock.getUTCNow().getMillis());
+ final SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.ANNUAL;
+ final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE);
+ final PlanPhaseSpecifier bpPlanPhaseSpecifier = new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null);
+ final SubscriptionData bpSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ bpPlanPhaseSpecifier,
+ null,
+ context));
+ assertNotNull(bpSubscription);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId()).size(), 1);
+
+ assertEquals(entitlementUserApi.getSubscriptionFromId(bpSubscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+
+ // Move out of trials for interesting invoices adjustments
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDays(40);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+
+ //
+ // FORCE AN IMMEDIATE CHANGE OF THE BILLING PERIOD
+ //
+ assertTrue(bpSubscription.changePlanWithPolicy(productName, BillingPeriod.MONTHLY, planSetName, clock.getUTCNow(), ActionPolicy.IMMEDIATE, context));
+ assertEquals(entitlementUserApi.getSubscriptionFromId(bpSubscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ //
+ // FORCE ANOTHER CHANGE
+ //
+ assertTrue(bpSubscription.changePlanWithPolicy(productName, BillingPeriod.ANNUAL, planSetName, clock.getUTCNow(), ActionPolicy.IMMEDIATE, context));
+ assertEquals(entitlementUserApi.getSubscriptionFromId(bpSubscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java
index 8d216a2..1e35021 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java
@@ -13,10 +13,12 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
+
package com.ning.billing.entitlement.api;
import org.joda.time.DateTime;
+import com.ning.billing.catalog.api.ActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
@@ -45,4 +47,8 @@ public interface SubscriptionApiService {
public boolean changePlan(SubscriptionData subscription, String productName, BillingPeriod term,
String priceList, DateTime requestedDate, CallContext context)
throws EntitlementUserApiException;
+
+ public boolean changePlanWithPolicy(SubscriptionData subscription, String productName, BillingPeriod term,
+ String priceList, DateTime requestedDate, ActionPolicy policy, CallContext context)
+ throws EntitlementUserApiException;
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionApiService.java
index 99e3e58..da61996 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionApiService.java
@@ -21,7 +21,6 @@ import java.util.List;
import org.joda.time.DateTime;
-import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.ActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
@@ -57,7 +56,10 @@ import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
+import com.google.inject.Inject;
+
public class DefaultSubscriptionApiService implements SubscriptionApiService {
+
private final Clock clock;
private final EntitlementDao dao;
private final CatalogService catalogService;
@@ -71,6 +73,7 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
this.dao = dao;
}
+ @Override
public SubscriptionData createPlan(final SubscriptionBuilder builder, final Plan plan, final PhaseType initialPhase,
final String realPriceList, final DateTime requestedDate, final DateTime effectiveDate, final DateTime processedDate,
final CallContext context) throws EntitlementUserApiException {
@@ -80,6 +83,7 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
return subscription;
}
+ @Override
public boolean recreatePlan(final SubscriptionData subscription, final PlanPhaseSpecifier spec, final DateTime requestedDateWithMs, final CallContext context)
throws EntitlementUserApiException {
final SubscriptionState currentState = subscription.getState();
@@ -131,8 +135,8 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
final TimedPhase nextTimedPhase = curAndNextPhases[1];
final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
- PhaseEventData.createNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, processedDate, nextTimedPhase.getStartPhase()) :
- null;
+ PhaseEventData.createNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, processedDate, nextTimedPhase.getStartPhase()) :
+ null;
final List<EntitlementEvent> events = new ArrayList<EntitlementEvent>();
events.add(creationEvent);
if (nextPhaseEvent != null) {
@@ -149,6 +153,7 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
}
}
+ @Override
public boolean cancel(final SubscriptionData subscription, final DateTime requestedDateWithMs, final boolean eot, final CallContext context) throws EntitlementUserApiException {
try {
final SubscriptionState currentState = subscription.getState();
@@ -187,6 +192,7 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
}
}
+ @Override
public boolean uncancel(final SubscriptionData subscription, final CallContext context) throws EntitlementUserApiException {
if (!subscription.isSubscriptionFutureCancelled()) {
throw new EntitlementUserApiException(ErrorCode.ENT_UNCANCEL_BAD_STATE, subscription.getId().toString());
@@ -207,8 +213,8 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
final TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, now, now);
final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
- PhaseEventData.createNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
- null;
+ PhaseEventData.createNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
+ null;
if (nextPhaseEvent != null) {
uncancelEvents.add(nextPhaseEvent);
}
@@ -219,78 +225,108 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
return true;
}
+ @Override
public boolean changePlan(final SubscriptionData subscription, final String productName, final BillingPeriod term,
- final String priceList, final DateTime requestedDateWithMs, final CallContext context) throws EntitlementUserApiException {
- try {
- final DateTime now = clock.getUTCNow();
- final DateTime requestedDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
- validateRequestedDate(subscription, now, requestedDate);
+ final String priceList, final DateTime requestedDateWithMs, final CallContext context)
+ throws EntitlementUserApiException {
+ final DateTime now = clock.getUTCNow();
+ final DateTime requestedDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
- final PriceList currentPriceList = subscription.getCurrentPriceList();
+ validateRequestedDate(subscription, now, requestedDate);
+ validateSubscriptionState(subscription);
- final SubscriptionState currentState = subscription.getState();
- if (currentState != SubscriptionState.ACTIVE) {
- throw new EntitlementUserApiException(ErrorCode.ENT_CHANGE_NON_ACTIVE, subscription.getId(), currentState);
- }
+ final PlanChangeResult planChangeResult = getPlanChangeResult(subscription, productName, term, priceList, requestedDate);
+ final ActionPolicy policy = planChangeResult.getPolicy();
- if (subscription.isSubscriptionFutureCancelled()) {
- throw new EntitlementUserApiException(ErrorCode.ENT_CHANGE_FUTURE_CANCELLED, subscription.getId());
- }
- PlanChangeResult planChangeResult = null;
- try {
-
- final Product destProduct = catalogService.getFullCatalog().findProduct(productName, requestedDate);
- final Plan currentPlan = subscription.getCurrentPlan();
- final PlanPhaseSpecifier fromPlanPhase = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
- currentPlan.getProduct().getCategory(),
- currentPlan.getBillingPeriod(),
- currentPriceList.getName(), subscription.getCurrentPhase().getPhaseType());
- final PlanSpecifier toPlanPhase = new PlanSpecifier(productName,
- destProduct.getCategory(),
- term,
- priceList);
-
- planChangeResult = catalogService.getFullCatalog().planChange(fromPlanPhase, toPlanPhase, requestedDate);
- } catch (CatalogApiException e) {
- throw new EntitlementUserApiException(e);
- }
+ try {
+ return doChangePlan(subscription, planChangeResult, now, requestedDate, productName, term, policy, context);
+ } catch (CatalogApiException e) {
+ throw new EntitlementUserApiException(e);
+ }
+ }
- final ActionPolicy policy = planChangeResult.getPolicy();
- final PriceList newPriceList = planChangeResult.getNewPriceList();
+ @Override
+ public boolean changePlanWithPolicy(final SubscriptionData subscription, final String productName, final BillingPeriod term,
+ final String priceList, final DateTime requestedDateWithMs, final ActionPolicy policy, final CallContext context)
+ throws EntitlementUserApiException {
+ final DateTime now = clock.getUTCNow();
+ final DateTime requestedDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
- final Plan newPlan = catalogService.getFullCatalog().findPlan(productName, term, newPriceList.getName(), requestedDate, subscription.getStartDate());
- final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, requestedDate);
+ validateRequestedDate(subscription, now, requestedDate);
+ validateSubscriptionState(subscription);
- final TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
+ final PlanChangeResult planChangeResult = getPlanChangeResult(subscription, productName, term, priceList, requestedDate);
- final EntitlementEvent changeEvent = new ApiEventChange(new ApiEventBuilder()
- .setSubscriptionId(subscription.getId())
- .setEventPlan(newPlan.getName())
- .setEventPlanPhase(currentTimedPhase.getPhase().getName())
- .setEventPriceList(newPriceList.getName())
- .setActiveVersion(subscription.getActiveVersion())
- .setProcessedDate(now)
- .setEffectiveDate(effectiveDate)
- .setRequestedDate(requestedDate)
- .setUserToken(context.getUserToken())
- .setFromDisk(true));
+ try {
+ return doChangePlan(subscription, planChangeResult, now, requestedDate, productName, term, policy, context);
+ } catch (CatalogApiException e) {
+ throw new EntitlementUserApiException(e);
+ }
+ }
- final TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
- final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
- PhaseEventData.createNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
- null;
- final List<EntitlementEvent> changeEvents = new ArrayList<EntitlementEvent>();
- // Only add the PHASE if it does not coincide with the CHANGE, if not this is 'just' a CHANGE.
- if (nextPhaseEvent != null && !nextPhaseEvent.getEffectiveDate().equals(changeEvent.getEffectiveDate())) {
- changeEvents.add(nextPhaseEvent);
- }
- changeEvents.add(changeEvent);
- dao.changePlan(subscription, changeEvents, context);
- subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
- return (policy == ActionPolicy.IMMEDIATE);
+ private PlanChangeResult getPlanChangeResult(final SubscriptionData subscription, final String productName,
+ final BillingPeriod term, final String priceList, final DateTime requestedDate) throws EntitlementUserApiException {
+ final PlanChangeResult planChangeResult;
+ try {
+ final Product destProduct = catalogService.getFullCatalog().findProduct(productName, requestedDate);
+ final Plan currentPlan = subscription.getCurrentPlan();
+ final PriceList currentPriceList = subscription.getCurrentPriceList();
+ final PlanPhaseSpecifier fromPlanPhase = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
+ currentPlan.getProduct().getCategory(),
+ currentPlan.getBillingPeriod(),
+ currentPriceList.getName(),
+ subscription.getCurrentPhase().getPhaseType());
+ final PlanSpecifier toPlanPhase = new PlanSpecifier(productName,
+ destProduct.getCategory(),
+ term,
+ priceList);
+
+ planChangeResult = catalogService.getFullCatalog().planChange(fromPlanPhase, toPlanPhase, requestedDate);
} catch (CatalogApiException e) {
throw new EntitlementUserApiException(e);
}
+
+ return planChangeResult;
+ }
+
+ private boolean doChangePlan(final SubscriptionData subscription, final PlanChangeResult planChangeResult,
+ final DateTime now, final DateTime requestedDate, final String productName,
+ final BillingPeriod term, final ActionPolicy policy, final CallContext context) throws EntitlementUserApiException, CatalogApiException {
+ final PriceList newPriceList = planChangeResult.getNewPriceList();
+
+ final Plan newPlan = catalogService.getFullCatalog().findPlan(productName, term, newPriceList.getName(), requestedDate, subscription.getStartDate());
+ final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, requestedDate);
+
+ final TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
+
+ final EntitlementEvent changeEvent = new ApiEventChange(new ApiEventBuilder()
+ .setSubscriptionId(subscription.getId())
+ .setEventPlan(newPlan.getName())
+ .setEventPlanPhase(currentTimedPhase.getPhase().getName())
+ .setEventPriceList(newPriceList.getName())
+ .setActiveVersion(subscription.getActiveVersion())
+ .setProcessedDate(now)
+ .setEffectiveDate(effectiveDate)
+ .setRequestedDate(requestedDate)
+ .setUserToken(context.getUserToken())
+ .setFromDisk(true));
+
+ final TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
+ final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
+ PhaseEventData.createNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
+ null;
+
+ final List<EntitlementEvent> changeEvents = new ArrayList<EntitlementEvent>();
+ // Only add the PHASE if it does not coincide with the CHANGE, if not this is 'just' a CHANGE.
+ if (nextPhaseEvent != null && !nextPhaseEvent.getEffectiveDate().equals(changeEvent.getEffectiveDate())) {
+ changeEvents.add(nextPhaseEvent);
+ }
+ changeEvents.add(changeEvent);
+
+ dao.changePlan(subscription, changeEvents, context);
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
+
+ return (policy == ActionPolicy.IMMEDIATE);
}
private void validateRequestedDate(final SubscriptionData subscription, final DateTime now, final DateTime requestedDate)
@@ -306,4 +342,14 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
requestedDate.toString(), previousTransition.getEffectiveTransitionTime());
}
}
+
+ private void validateSubscriptionState(final SubscriptionData subscription) throws EntitlementUserApiException {
+ final SubscriptionState currentState = subscription.getState();
+ if (currentState != SubscriptionState.ACTIVE) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_CHANGE_NON_ACTIVE, subscription.getId(), currentState);
+ }
+ if (subscription.isSubscriptionFutureCancelled()) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_CHANGE_FUTURE_CANCELLED, subscription.getId());
+ }
+ }
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 58fe83c..d7b8a78 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -176,11 +176,15 @@ public class SubscriptionData extends EntityBase implements Subscription {
}
@Override
- public boolean changePlan(final String productName, final BillingPeriod term,
- final String priceList, final DateTime requestedDate, final CallContext context)
- throws EntitlementUserApiException {
- return apiService.changePlan(this, productName, term, priceList,
- requestedDate, context);
+ public boolean changePlan(final String productName, final BillingPeriod term, final String priceList,
+ final DateTime requestedDate, final CallContext context) throws EntitlementUserApiException {
+ return apiService.changePlan(this, productName, term, priceList, requestedDate, context);
+ }
+
+ @Override
+ public boolean changePlanWithPolicy(final String productName, final BillingPeriod term, final String priceList,
+ final DateTime requestedDate, final ActionPolicy policy, final CallContext context) throws EntitlementUserApiException {
+ return apiService.changePlanWithPolicy(this, productName, term, priceList, requestedDate, policy, context);
}
@Override
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
index afa82d0..adcddbc 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
@@ -29,11 +29,13 @@ import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.ErrorCode;
import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.ActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
import com.ning.billing.util.clock.DefaultClock;
@@ -59,7 +61,6 @@ public class TestUserApiError extends TestApiBase {
tCreateSubscriptionInternal(bundle.getId(), "Shotgun", null, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.CAT_PLAN_NOT_FOUND);
// WRONG PLAN SET
tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, "Whatever", ErrorCode.CAT_PRICE_LIST_NOT_FOUND);
-
}
@Test(groups = "fast")
@@ -162,6 +163,22 @@ public class TestUserApiError extends TestApiBase {
}
@Test(groups = "fast")
+ public void testChangeSubscriptionWithPolicy() throws Exception {
+ final Subscription subscription = createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ try {
+ subscription.changePlanWithPolicy("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow(), ActionPolicy.ILLEGAL, context);
+ Assert.fail();
+ } catch (EntitlementError error) {
+ assertTrue(true);
+ assertEquals(entitlementApi.getSubscriptionFromId(subscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+ }
+
+ assertTrue(subscription.changePlanWithPolicy("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow(), ActionPolicy.IMMEDIATE, context));
+ assertEquals(entitlementApi.getSubscriptionFromId(subscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.MONTHLY);
+ }
+
+ @Test(groups = "fast")
public void testChangeSubscriptionFutureCancelled() {
try {
Subscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 984f633..095df0a 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -47,6 +47,7 @@ public interface JaxrsResource {
public static final String QUERY_CALL_TIMEOUT = "call_timeout_sec";
public static final String QUERY_DRY_RUN = "dry_run";
public static final String QUERY_TARGET_DATE = "target_date";
+ public static final String QUERY_POLICY = "policy";
public static final String QUERY_ACCOUNT_ID = "account_id";
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
index 6851692..38027ff 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
@@ -42,6 +42,7 @@ import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.ning.billing.catalog.api.ActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
import com.ning.billing.catalog.api.ProductCategory;
@@ -156,6 +157,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
@QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
@QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+ @QueryParam(QUERY_POLICY) final String policyString,
@HeaderParam(HDR_CREATED_BY) final String createdBy,
@HeaderParam(HDR_REASON) final String reason,
@HeaderParam(HDR_COMMENT) final String comment) throws EntitlementUserApiException {
@@ -169,7 +171,16 @@ public class SubscriptionResource extends JaxRsResourceBase {
final UUID uuid = UUID.fromString(subscriptionId);
final Subscription current = entitlementApi.getSubscriptionFromId(uuid);
final DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
- isImmediateOp = current.changePlan(subscription.getProductName(), BillingPeriod.valueOf(subscription.getBillingPeriod()), subscription.getPriceList(), inputDate, ctx);
+
+ if (policyString == null) {
+ isImmediateOp = current.changePlan(subscription.getProductName(), BillingPeriod.valueOf(subscription.getBillingPeriod()),
+ subscription.getPriceList(), inputDate, ctx);
+ } else {
+ final ActionPolicy policy = ActionPolicy.valueOf(policyString.toUpperCase());
+ isImmediateOp = current.changePlanWithPolicy(subscription.getProductName(), BillingPeriod.valueOf(subscription.getBillingPeriod()),
+ subscription.getPriceList(), inputDate, policy, ctx);
+ }
+
return Response.status(Status.OK).build();
}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
index bfce70b..3a7f932 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
@@ -21,6 +21,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
+import com.ning.billing.catalog.api.ActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
@@ -65,14 +66,25 @@ public class BlockingSubscription implements Subscription {
}
@Override
- public boolean changePlan(final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate,
+ public boolean changePlan(final String productName, final BillingPeriod term, final String priceList, final DateTime requestedDate,
final CallContext context) throws EntitlementUserApiException {
try {
checker.checkBlockedChange(this);
} catch (BlockingApiException e) {
throw new EntitlementUserApiException(e, e.getCode(), e.getMessage());
}
- return subscription.changePlan(productName, term, planSet, requestedDate, context);
+ return subscription.changePlan(productName, term, priceList, requestedDate, context);
+ }
+
+ @Override
+ public boolean changePlanWithPolicy(final String productName, final BillingPeriod term, final String priceList,
+ final DateTime requestedDate, final ActionPolicy policy, final CallContext context) throws EntitlementUserApiException {
+ try {
+ checker.checkBlockedChange(this);
+ } catch (BlockingApiException e) {
+ throw new EntitlementUserApiException(e, e.getCode(), e.getMessage());
+ }
+ return subscription.changePlanWithPolicy(productName, term, priceList, requestedDate, policy, context);
}
@Override
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
index ce1e9f4..da9b666 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
@@ -59,7 +59,7 @@ public class TestSubscription extends TestJaxrsBase {
Assert.assertNotNull(subscriptionJson.getChargedThroughDate());
Assert.assertEquals(subscriptionJson.getChargedThroughDate().toString(), "2012-04-25T00:00:00.000Z");
- String uri = JaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+ String uri = JaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId();
// Retrieves with GET
Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
@@ -93,14 +93,13 @@ public class TestSubscription extends TestJaxrsBase {
crappyWaitForLackOfProperSynchonization();
- //
// Cancel EOT
- uri = JaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+ uri = JaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId();
response = doDelete(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
// Retrieves to check EndDate
- uri = JaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+ uri = JaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId();
response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
@@ -132,4 +131,48 @@ public class TestSubscription extends TestJaxrsBase {
Assert.assertEquals(response.getStatusCode(), Status.NOT_FOUND.getStatusCode());
}
+ @Test(groups = "slow")
+ public void testOverridePolicy() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final AccountJson accountJson = createAccountWithDefaultPaymentMethod("xil", "shdxilhkkl", "xil@yahoo.com");
+ final BundleJsonNoSubscriptions bundleJson = createBundle(accountJson.getAccountId(), "99999");
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.ANNUAL;
+
+ final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), productName, ProductCategory.BASE.toString(), term.toString(), true);
+ Assert.assertNotNull(subscriptionJson.getChargedThroughDate());
+ Assert.assertEquals(subscriptionJson.getChargedThroughDate().toString(), "2012-04-25T00:00:00.000Z");
+
+ final String uri = JaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId();
+
+ // Retrieves with GET
+ Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ String baseJson = response.getResponseBody();
+ SubscriptionJsonNoEvents objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+ Assert.assertTrue(objFromJson.equals(subscriptionJson));
+ assertEquals(objFromJson.getBillingPeriod(), BillingPeriod.ANNUAL.toString());
+
+ // Change billing period immediately
+ final SubscriptionJsonNoEvents newInput = new SubscriptionJsonNoEvents(subscriptionJson.getSubscriptionId(),
+ subscriptionJson.getBundleId(),
+ subscriptionJson.getStartDate(),
+ subscriptionJson.getProductName(),
+ subscriptionJson.getProductCategory(),
+ BillingPeriod.MONTHLY.toString(),
+ subscriptionJson.getPriceList(),
+ subscriptionJson.getChargedThroughDate(),
+ subscriptionJson.getCancelledDate());
+ baseJson = mapper.writeValueAsString(newInput);
+ final Map<String, String> queryParams = getQueryParamsForCallCompletion(CALL_COMPLETION_TIMEOUT_SEC);
+ queryParams.put(JaxrsResource.QUERY_POLICY, "immediate");
+ response = doPut(uri, baseJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ baseJson = response.getResponseBody();
+ objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+ assertEquals(objFromJson.getBillingPeriod(), BillingPeriod.MONTHLY.toString());
+ }
}
diff --git a/util/src/test/java/com/ning/billing/mock/MockSubscription.java b/util/src/test/java/com/ning/billing/mock/MockSubscription.java
index 2a3c024..013c9e0 100644
--- a/util/src/test/java/com/ning/billing/mock/MockSubscription.java
+++ b/util/src/test/java/com/ning/billing/mock/MockSubscription.java
@@ -24,6 +24,8 @@ import org.joda.time.DateTimeZone;
import org.mockito.Mockito;
import com.google.common.collect.ImmutableList;
+
+import com.ning.billing.catalog.api.ActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
@@ -78,9 +80,15 @@ public class MockSubscription implements Subscription {
}
@Override
- public boolean changePlan(final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate,
+ public boolean changePlan(final String productName, final BillingPeriod term, final String priceList, final DateTime requestedDate,
final CallContext context) throws EntitlementUserApiException {
- return sub.changePlan(productName, term, planSet, requestedDate, context);
+ return sub.changePlan(productName, term, priceList, requestedDate, context);
+ }
+
+ @Override
+ public boolean changePlanWithPolicy(final String productName, final BillingPeriod term, final String priceList,
+ final DateTime requestedDate, final ActionPolicy policy, final CallContext context) throws EntitlementUserApiException {
+ return sub.changePlan(productName, term, priceList, requestedDate, context);
}
@Override