killbill-memoizeit
Changes
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java 45(+38 -7)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java 44(+30 -14)
Details
diff --git a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
index f29f04e..20027f2 100644
--- a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
+++ b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
@@ -62,6 +62,8 @@ public interface EventsStream {
boolean isSubscriptionCancelled();
+ boolean isBlockChange(final DateTime effectiveDate);
+
int getDefaultBillCycleDayLocal();
Collection<BlockingState> getPendingEntitlementCancellationEvents();
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 d8d941c..eb1ff14 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
@@ -44,6 +44,7 @@ import org.killbill.billing.entitlement.EventsStream;
import org.killbill.billing.entitlement.api.EntitlementPluginExecution.WithEntitlementPlugin;
import org.killbill.billing.entitlement.api.svcs.DefaultEntitlementApiBase;
import org.killbill.billing.entitlement.block.BlockingChecker;
+import org.killbill.billing.entitlement.block.BlockingChecker.BlockingAggregator;
import org.killbill.billing.entitlement.dao.BlockingStateDao;
import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
@@ -157,6 +158,10 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
try {
final DateTime now = clock.getUTCNow();
+
+ final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, contextWithValidAccountRecordId);
+ checkForAccountBlockingChange(accountId, entitlementRequestedDate, contextWithValidAccountRecordId);
+
final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, contextWithValidAccountRecordId);
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = getFirstBaseEntitlementWithAddOnsSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
@@ -165,7 +170,6 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(), now, contextWithValidAccountRecordId);
final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundle.getId(), specifier.getPlanPhaseSpecifier(), specifier.getOverrides(), billingRequestedDate, isMigrated, contextWithValidAccountRecordId);
- final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, contextWithValidAccountRecordId);
final BlockingState newBlockingState = new DefaultBlockingState(subscription.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(newBlockingState), subscription.getBundleId(), contextWithValidAccountRecordId);
@@ -221,11 +225,25 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
try {
+ final DateTime now = clock.getUTCNow();
+
+ // Verify if operation is allowed by looking for is_block_change on Account
+ final Iterator<BaseEntitlementWithAddOnsSpecifier> it1 = baseEntitlementWithAddOnsSpecifiers.iterator();
+ DateTime upTo = null;
+ while (it1.hasNext()) {
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = it1.next();
+ final DateTime cur = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, contextWithValidAccountRecordId);
+ if (cur != null) {
+ upTo = upTo == null || upTo.compareTo(cur) < 0 ? cur : upTo;
+ }
+ }
+ // Note that to fully check for block_change we should also look for BlockingState at the BUNDLE/SUBSCRIPTION level in case some of the input contain a BP that already exists.
+ checkForAccountBlockingChange(accountId, upTo, contextWithValidAccountRecordId);
+
final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(accountId, baseEntitlementWithAddOnsSpecifiers, contextWithValidAccountRecordId);
final Map<BlockingState, UUID> blockingStateMap = new HashMap<BlockingState, UUID>();
int i = 0;
- final DateTime now = clock.getUTCNow();
for (final Iterator<BaseEntitlementWithAddOnsSpecifier> it = baseEntitlementWithAddOnsSpecifiers.iterator(); it.hasNext(); i++) {
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = it.next();
for (final SubscriptionBase subscriptionBase : subscriptionsWithAddOns.get(i).getSubscriptionBaseList()) {
@@ -291,6 +309,11 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final WithEntitlementPlugin<Entitlement> addEntitlementWithPlugin = new WithEntitlementPlugin<Entitlement>() {
@Override
public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+
+ final DateTime now = clock.getUTCNow();
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, callContext);
+ final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, context);
+
final EventsStream eventsStreamForBaseSubscription = eventsStreamBuilder.buildForBaseSubscription(bundleId, callContext);
if (eventsStreamForBaseSubscription.isEntitlementCancelled() ||
@@ -301,7 +324,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
}
// Check the base entitlement state is not blocked
- if (eventsStreamForBaseSubscription.isBlockChange()) {
+ if (eventsStreamForBaseSubscription.isBlockChange(entitlementRequestedDate)) {
throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
}
@@ -309,12 +332,9 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = getFirstBaseEntitlementWithAddOnsSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
final EntitlementSpecifier specifier = getFirstEntitlementSpecifier(baseEntitlementWithAddOnsSpecifier);
- final DateTime now = clock.getUTCNow();
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(eventsStreamForBaseSubscription.getAccountId(), callContext);
final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(), now, context);
final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundleId, specifier.getPlanPhaseSpecifier(), specifier.getOverrides(), billingRequestedDate, isMigrated, context);
- final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, context);
final BlockingState newBlockingState = new DefaultBlockingState(subscription.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(newBlockingState), subscription.getBundleId(), context);
@@ -417,7 +437,6 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context);
super.resume(bundleId, localEffectiveDate, properties, contextWithValidAccountRecordId);
-
}
@Override
@@ -515,4 +534,16 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
};
return pluginExecution.executeWithPlugin(transferWithPlugin, pluginContext);
}
+
+ private void checkForAccountBlockingChange(final UUID accountId, @Nullable final DateTime upTo, final InternalCallContext context) throws EntitlementApiException {
+
+ try {
+ final BlockingAggregator blockingAggregator = checker.getBlockedStatus(accountId, BlockingStateType.ACCOUNT, upTo, context);
+ if (blockingAggregator.isBlockChange()) {
+ throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_ACCOUNT, accountId.toString()));
+ }
+ } catch (final BlockingApiException e) {
+ throw new EntitlementApiException(e);
+ }
+ }
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index 27d9591..c991055 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -48,6 +48,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
@@ -72,7 +73,7 @@ public class DefaultEventsStream implements EventsStream {
private final LocalDate utcToday;
private final int defaultBillCycleDayLocal;
- private BlockingAggregator blockingAggregator;
+ private BlockingAggregator currentStateBlockingAggregator;
private List<BlockingState> subscriptionEntitlementStates;
private LocalDate entitlementEffectiveStartDate;
private DateTime entitlementEffectiveStartDateTime;
@@ -167,7 +168,7 @@ public class DefaultEventsStream implements EventsStream {
@Override
public boolean isBlockChange() {
- return blockingAggregator.isBlockChange();
+ return currentStateBlockingAggregator.isBlockChange();
}
public boolean isEntitlementFutureCancelled() {
@@ -199,6 +200,13 @@ public class DefaultEventsStream implements EventsStream {
}
@Override
+ public boolean isBlockChange(final DateTime effectiveDate) {
+ Preconditions.checkState(effectiveDate != null);
+ final BlockingAggregator aggregator = getBlockingAggregator(effectiveDate);
+ return aggregator.isBlockChange();
+ }
+
+ @Override
public int getDefaultBillCycleDayLocal() {
return defaultBillCycleDayLocal;
}
@@ -385,24 +393,32 @@ public class DefaultEventsStream implements EventsStream {
private void setup() {
computeEntitlementBlockingStates();
- computeBlockingAggregator();
+ computeCurrentBlockingAggregator();
computeEntitlementStartEvent();
computeEntitlementCancelEvent();
computeStateForEntitlement();
}
- private void computeBlockingAggregator() {
+ private void computeCurrentBlockingAggregator() {
+ currentStateBlockingAggregator = getBlockingAggregator(null);
+ }
+
+ private BlockingAggregator getBlockingAggregator(final DateTime upTo) {
- final List<BlockingState> currentSubscriptionBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.SUBSCRIPTION, subscription.getId());
- final List<BlockingState> currentBundleBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.SUBSCRIPTION_BUNDLE, subscription.getBundleId());
- final List<BlockingState> currentAccountBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.ACCOUNT, account.getId());
- blockingAggregator = blockingChecker.getBlockedStatus(currentAccountBlockingStatesForServices,
- currentBundleBlockingStatesForServices,
- currentSubscriptionBlockingStatesForServices,
- internalTenantContext);
+ final List<BlockingState> currentSubscriptionBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.SUBSCRIPTION, subscription.getId(), upTo);
+ final List<BlockingState> currentBundleBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.SUBSCRIPTION_BUNDLE, subscription.getBundleId(), upTo);
+ final List<BlockingState> currentAccountBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.ACCOUNT, account.getId(), upTo);
+ return blockingChecker.getBlockedStatus(currentAccountBlockingStatesForServices,
+ currentBundleBlockingStatesForServices,
+ currentSubscriptionBlockingStatesForServices, internalTenantContext);
}
- private List<BlockingState> filterCurrentBlockableStatePerService(final BlockingStateType type, final UUID blockableId) {
+
+
+ private List<BlockingState> filterCurrentBlockableStatePerService(final BlockingStateType type, final UUID blockableId, @Nullable final DateTime upTo) {
+
+ final DateTime resolvedUpTo = upTo != null ? upTo : utcNow;
+
final Map<String, BlockingState> currentBlockingStatePerService = new HashMap<String, BlockingState>();
for (final BlockingState blockingState : blockingStates) {
if (!blockingState.getBlockedId().equals(blockableId)) {
@@ -411,7 +427,7 @@ public class DefaultEventsStream implements EventsStream {
if (blockingState.getType() != type) {
continue;
}
- if (blockingState.getEffectiveDate().isAfter(utcNow)) {
+ if (blockingState.getEffectiveDate().isAfter(resolvedUpTo)) {
continue;
}
@@ -462,7 +478,7 @@ public class DefaultEventsStream implements EventsStream {
entitlementState = EntitlementState.PENDING;
} else {
// Gather states across all services and check if one of them is set to 'blockEntitlement'
- entitlementState = (blockingAggregator != null && blockingAggregator.isBlockEntitlement() ? EntitlementState.BLOCKED : EntitlementState.ACTIVE);
+ entitlementState = (currentStateBlockingAggregator != null && currentStateBlockingAggregator.isBlockEntitlement() ? EntitlementState.BLOCKED : EntitlementState.ACTIVE);
}
}
}
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 718b61e..4965c31 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
@@ -355,7 +355,9 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
clock.addDays(5);
testListener.pushExpectedEvents(NextEvent.BLOCK);
- entitlementApi.pause(baseEntitlement.getBundleId(), new LocalDate(clock.getUTCNow()), ImmutableList.<PluginProperty>of(), callContext);
+ final LocalDate blockingStateDate = new LocalDate(clock.getUTCNow());
+ entitlementApi.pause(baseEntitlement.getBundleId(), blockingStateDate, ImmutableList
+ .<PluginProperty>of(), callContext);
assertListenerStatus();
// Verify blocking state
@@ -374,7 +376,8 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Try to add an ADD_ON, it should fail because BASE is blocked
try {
final PlanPhaseSpecifier spec3 = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec3, null, effectiveDateSpec1, effectiveDateSpec1, false, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec3, null, blockingStateDate, effectiveDateSpec1, false, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should not be able to create ADD-ON because BP is paused");
} catch (EntitlementApiException e) {
assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
}
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java
index d96a38c..d3283b8 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java
@@ -21,18 +21,22 @@ package org.killbill.billing.entitlement.block;
import java.util.List;
import java.util.UUID;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.api.TestApiListener.NextEvent;
-import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
-import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
+import org.killbill.billing.entitlement.api.EntitlementApiException;
+import org.killbill.billing.entitlement.api.SubscriptionApiException;
import org.killbill.billing.junction.DefaultBlockingState;
import org.killbill.billing.payment.api.PluginProperty;
import org.testng.Assert;
@@ -44,6 +48,7 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
@@ -150,7 +155,6 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
subscriptionApi.addBlockingState(state2, null, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
-
baseEntitlement = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
assertEquals(baseEntitlement.getState(), EntitlementState.BLOCKED);
@@ -164,7 +168,6 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
baseEntitlement = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
assertEquals(baseEntitlement.getState(), EntitlementState.BLOCKED);
-
// Remove blocking at bundle level.
clock.addDays(1);
testListener.pushExpectedEvent(NextEvent.BLOCK);
@@ -183,7 +186,119 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
return input.getService().equals(service);
}
}));
-
Assert.assertEquals(history.size(), 4);
}
+
+ @Test(groups = "slow")
+ public void testCreateBaseSubscriptionOnBlockedChangeAcount() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2017, 5, 1);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(1));
+
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
+ final BlockingState blockChangeAccount = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "State1", "Service1", true, false, false, clock.getUTCNow());
+ subscriptionApi.addBlockingState(blockChangeAccount, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Try create subscription right now
+ try {
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should fail to create entitlement when ACCOUNT has been 'change' blocked");
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Try create subscription in the future
+ try {
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, initialDate.plusDays(3), null, false, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should fail to create entitlement when ACCOUNT has been 'change' blocked");
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Try create subscription in the past
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, initialDate.minusDays(3), null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testCreateAOSubscriptionOnBlockedChangeAcount() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2017, 5, 1);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(1));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, initialDate.minusDays(3), null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
+ final BlockingState blockChangeAccount = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "State1", "Service1", true, false, false, clock.getUTCNow());
+ subscriptionApi.addBlockingState(blockChangeAccount, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Try create subscription right now
+ try {
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should fail to create ADD_ON");
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Try create subscription in the future
+ try {
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, initialDate.plusDays(2), null, false, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should fail to create ADD_ON");
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Try create subscription in the past
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, initialDate.minusDays(2), null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testCreateAOSubscriptionOnFutureBlockedChangeAcount() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2017, 5, 1);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(1));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, initialDate.minusDays(3), null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Create future BlockingState
+ final LocalDate blockingChange = initialDate.plusDays(3);
+ final BlockingState blockChangeAccount = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "State1", "Service1", true, false, false, null);
+ subscriptionApi.addBlockingState(blockChangeAccount, blockingChange, ImmutableList.<PluginProperty>of(), callContext);
+
+ // Create ADD_ON in the future as well
+ try {
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, blockingChange, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Create ADD_ON now (prior future BlockingState)
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ }
+
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
index cb7dfcb..047f073 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
@@ -151,6 +151,11 @@ public class InvoiceApiHelper {
final Map<UUID, BigDecimal> output = dao.computeItemAdjustments(invoiceToBeAdjusted.getId().toString(), input, context);
+ // Nothing to adjust
+ if (output.get(invoiceItemId) == null) {
+ return null;
+ }
+
// If we pass that stage, it means the validation succeeded so we just need to extract resulting amount and negate the result.
final BigDecimal amountToAdjust = output.get(invoiceItemId).negate();
// Finally, create the adjustment
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index c9fbef9..777333a 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -432,7 +432,9 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
effectiveDate,
description,
internalCallContextFactory.createInternalCallContext(accountId, context));
- invoice.addInvoiceItem(adjustmentItem);
+ if (adjustmentItem != null) {
+ invoice.addInvoiceItem(adjustmentItem);
+ }
return ImmutableList.<Invoice>of(invoice);
}
@@ -445,9 +447,9 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
return InvoiceItemType.ITEM_ADJ.equals(invoiceItem.getInvoiceItemType());
}
});
- Preconditions.checkState(adjustmentInvoiceItems.size() == 1, "Should have created a single adjustment item: " + adjustmentInvoiceItems);
+ Preconditions.checkState(adjustmentInvoiceItems.size() <= 1, "Should have created a single adjustment item: " + adjustmentInvoiceItems);
- return adjustmentInvoiceItems.iterator().next();
+ return adjustmentInvoiceItems.iterator().hasNext() ? adjustmentInvoiceItems.iterator().next() : null;
}
@Override
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index 170933f..f83ab5f 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -262,6 +262,9 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
// Verify the adjusted account balance
final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId, callContext);
Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+
+ // Verify idempotency
+ Assert.assertNull(invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(), clock.getUTCToday(), null, callContext));
}
@Test(groups = "slow")
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index e7276f2..d169cf9 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -59,16 +59,22 @@ import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.account.api.AccountEmail;
import org.killbill.billing.account.api.AccountUserApi;
import org.killbill.billing.account.api.MutableAccountData;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
import org.killbill.billing.entitlement.api.EntitlementApiException;
+import org.killbill.billing.entitlement.api.Subscription;
import org.killbill.billing.entitlement.api.SubscriptionApi;
import org.killbill.billing.entitlement.api.SubscriptionApiException;
import org.killbill.billing.entitlement.api.SubscriptionBundle;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoicePayment;
import org.killbill.billing.invoice.api.InvoicePaymentApi;
import org.killbill.billing.invoice.api.InvoiceUserApi;
@@ -360,6 +366,7 @@ public class AccountResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", account.getId(), request);
}
+
@TimedResource
@PUT
@Consumes(APPLICATION_JSON)
@@ -386,25 +393,71 @@ public class AccountResource extends JaxRsResourceBase {
return getAccount(accountIdStr, false, false, new AuditMode(AuditLevel.NONE.toString()), request);
}
- // Not supported
+
@TimedResource
@DELETE
@Path("/{accountId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
- @ApiOperation(value = "Delete account", hidden = true)
+ @ApiOperation(value = "Close account")
@ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id supplied")})
- public Response cancelAccount(@PathParam("accountId") final String accountId,
- @javax.ws.rs.core.Context final HttpServletRequest request) {
- /*
- try {
- accountUserApi.cancelAccount(accountId);
- return Response.status(Status.NO_CONTENT).build();
- } catch (AccountApiException e) {
- log.info(String.format("Failed to cancel account %s", accountId), e);
- return Response.status(Status.BAD_REQUEST).build();
+ public Response closeAccount(@PathParam(QUERY_ACCOUNT_ID) final String accountIdStr,
+ @QueryParam(QUERY_CANCEL_ALL_SUBSCRIPTIONS) @DefaultValue("false") final Boolean cancelAllSubscriptions,
+ @QueryParam(QUERY_WRITE_OFF_UNPAID_INVOICES) @DefaultValue("false") final Boolean writeOffUnpaidInvoices,
+ @QueryParam(QUERY_ITEM_ADJUST_UNPAID_INVOICES) @DefaultValue("false") final Boolean itemAdjustUnpaidInvoices,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, AccountApiException, EntitlementApiException, InvoiceApiException, TagApiException {
+ final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+ final UUID accountId = UUID.fromString(accountIdStr);
+
+ if (cancelAllSubscriptions) {
+ final List<SubscriptionBundle> bundles = subscriptionApi.getSubscriptionBundlesForAccountId(accountId, callContext);
+ final Iterable<Subscription> subscriptions = Iterables.concat(Iterables.transform(bundles, new Function<SubscriptionBundle, List<Subscription>>() {
+ @Override
+ public List<Subscription> apply(final SubscriptionBundle input) {
+ return input.getSubscriptions();
+ }
+ }));
+
+ final Iterable<Subscription> toBeCancelled = Iterables.filter(subscriptions, new Predicate<Subscription>() {
+ @Override
+ public boolean apply(final Subscription input) {
+ return input.getLastActiveProductCategory() != ProductCategory.ADD_ON && input.getBillingEndDate() == null;
+ }
+ });
+ for (final Subscription cur : toBeCancelled) {
+ cur.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.END_OF_TERM, ImmutableList.<PluginProperty>of(), callContext);
+ }
+ }
+
+ final Collection<Invoice> unpaidInvoices = writeOffUnpaidInvoices || itemAdjustUnpaidInvoices ? invoiceApi.getUnpaidInvoicesByAccountId(accountId, null, callContext) : ImmutableList.<Invoice>of();
+ if (writeOffUnpaidInvoices) {
+ for (final Invoice cur : unpaidInvoices) {
+ invoiceApi.tagInvoiceAsWrittenOff(cur.getId(), callContext);
+ }
+ } else if (itemAdjustUnpaidInvoices) {
+
+ final List<InvoiceItemType> ADJUSTABLE_TYPES = ImmutableList.<InvoiceItemType>of(InvoiceItemType.EXTERNAL_CHARGE,
+ InvoiceItemType.FIXED,
+ InvoiceItemType.RECURRING,
+ InvoiceItemType.TAX,
+ InvoiceItemType.USAGE,
+ InvoiceItemType.PARENT_SUMMARY);
+ final String description = comment != null ? comment : "Close Account";
+ for (final Invoice invoice : unpaidInvoices) {
+ for (final InvoiceItem item : invoice.getInvoiceItems()) {
+ if (ADJUSTABLE_TYPES.contains(item.getInvoiceItemType())) {
+ invoiceApi.insertInvoiceItemAdjustment(accountId, invoice.getId(), item.getId(), clock.getUTCToday(), description, callContext);
+ }
+ }
+ }
}
- */
- return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+
+ final BlockingStateJson blockingState = new BlockingStateJson(accountIdStr, "CLOSE_ACCOUNT", "account-service", true, false, false, null, BlockingStateType.ACCOUNT, null);
+ addBlockingState(blockingState, accountIdStr, BlockingStateType.ACCOUNT, null, ImmutableList.<String>of(), createdBy, reason, comment, request);
+
+ return Response.status(Status.OK).build();
}
@TimedResource
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 cf30e0c..2e815f3 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
@@ -479,7 +479,11 @@ public class InvoiceResource extends JaxRsResourceBase {
callContext);
}
- return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId(), request);
+ if (adjustmentItem == null) {
+ return Response.status(Status.NOT_FOUND).build();
+ } else {
+ return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId(), request);
+ }
}
@TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index dd706db..b7d6a38 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -18,6 +18,9 @@
package org.killbill.billing.jaxrs.resources;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.QueryParam;
+
public interface JaxrsResource {
public static final String API_PREFIX = "";
@@ -93,6 +96,10 @@ public interface JaxrsResource {
public static final String QUERY_ACCOUNT_ID = "accountId";
+ public static final String QUERY_CANCEL_ALL_SUBSCRIPTIONS = "cancelAllSubscriptions";
+ public static final String QUERY_WRITE_OFF_UNPAID_INVOICES = "writeOffUnpaidInvoices";
+ public static final String QUERY_ITEM_ADJUST_UNPAID_INVOICES = "itemAdjustUnpaidInvoices";
+
public static final String QUERY_BLOCKING_STATE_TYPES = "blockingStateTypes";
public static final String QUERY_BLOCKING_STATE_SVCS = "blockingStateSvcs";
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 4427ea6..c01e757 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -41,15 +41,14 @@ import org.killbill.billing.client.model.Invoice;
import org.killbill.billing.client.model.PhasePriceOverride;
import org.killbill.billing.client.model.Subscription;
import org.killbill.billing.client.model.Tags;
-import org.killbill.billing.entitlement.EntitlementTransitionType;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.util.api.AuditLevel;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
@@ -323,11 +322,11 @@ public class TestEntitlement extends TestJaxrsBase {
}
@Test(groups = "slow", description = "Create a base entitlement and also addOns entitlements under the same bundle")
- public void testEntitlementWithAddOns() throws Exception {
+ public void testEntitlementWithAddOnsWithWRITTEN_OFF() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
- final Account accountJson = createAccountWithDefaultPaymentMethod();
+ final Account accountJson = createAccount();
final Subscription base = new Subscription();
base.setAccountId(accountJson.getAccountId());
@@ -357,23 +356,43 @@ public class TestEntitlement extends TestJaxrsBase {
subscriptions.add(base);
subscriptions.add(addOn1);
subscriptions.add(addOn2);
+
final Bundle bundle = killBillClient.createSubscriptionWithAddOns(subscriptions, null, 10, requestOptions);
assertNotNull(bundle);
assertEquals(bundle.getExternalKey(), "base");
assertEquals(bundle.getSubscriptions().size(), 3);
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, AuditLevel.FULL, requestOptions);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, requestOptions);
assertEquals(invoices.size(), 1);
+ assertEquals(invoices.get(0).getBalance().compareTo(BigDecimal.ZERO), 1);
+ assertEquals(killBillClient.getInvoiceTags(invoices.get(0).getInvoiceId(), requestOptions).size(), 0);
+
+ final Bundles accountBundles = killBillClient.getAccountBundles(accountJson.getAccountId(), requestOptions);
+ assertEquals(accountBundles.size(), 1);
+ for (final Subscription subscription : accountBundles.get(0).getSubscriptions()) {
+ assertEquals(subscription.getState(), EntitlementState.ACTIVE);
+ }
+
+ killBillClient.closeAccount(accountJson.getAccountId(), true, true, false, requestOptions);
+
+ final Bundles accountBundlesAfterClose = killBillClient.getAccountBundles(accountJson.getAccountId(), requestOptions);
+ assertEquals(accountBundlesAfterClose.size(), 1);
+ for (final Subscription subscription : accountBundlesAfterClose.get(0).getSubscriptions()) {
+ assertEquals(subscription.getState(), EntitlementState.CANCELLED);
+ }
+
+ final List<Invoice> invoicesAfterClose = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, requestOptions);
+ assertEquals(invoicesAfterClose.size(), 1);
+ assertEquals(invoicesAfterClose.get(0).getBalance().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(killBillClient.getInvoiceTags(invoicesAfterClose.get(0).getInvoiceId(), requestOptions).size(), 1);
}
-
-
@Test(groups = "slow", description = "Create a bulk of base entitlement and addOns under the same transaction")
- public void testCreateEntitlementsWithAddOns() throws Exception {
+ public void testCreateEntitlementsWithAddOnsThenCloseAccountWithItemAdjustment() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
- final Account accountJson = createAccountWithDefaultPaymentMethod();
+ final Account accountJson = createAccount();
final Subscription base = new Subscription();
base.setAccountId(accountJson.getAccountId());
@@ -406,13 +425,37 @@ public class TestEntitlement extends TestJaxrsBase {
bulkList.add(new BulkBaseSubscriptionAndAddOns(subscriptions));
final Bundles bundles = killBillClient.createSubscriptionsWithAddOns(bulkList, null, 10, requestOptions);
-
assertNotNull(bundles);
assertEquals(bundles.size(), 2);
assertFalse(bundles.get(0).getExternalKey().equals(bundles.get(1).getExternalKey()));
final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, requestOptions);
assertEquals(invoices.size(), 1);
+ assertEquals(invoices.get(0).getBalance().compareTo(BigDecimal.ZERO), 1);
+ assertEquals(killBillClient.getInvoiceTags(invoices.get(0).getInvoiceId(), requestOptions).size(), 0);
+
+ final Bundles accountBundles = killBillClient.getAccountBundles(accountJson.getAccountId(), requestOptions);
+ assertEquals(accountBundles.size(), 2);
+ for (final Bundle bundle : accountBundles) {
+ for (final Subscription subscription : bundle.getSubscriptions()) {
+ assertEquals(subscription.getState(), EntitlementState.ACTIVE);
+ }
+ }
+
+ killBillClient.closeAccount(accountJson.getAccountId(), true, false, true, requestOptions);
+
+ final Bundles accountBundlesAfterClose = killBillClient.getAccountBundles(accountJson.getAccountId(), requestOptions);
+ assertEquals(accountBundlesAfterClose.size(), 2);
+ for (final Bundle bundle : accountBundlesAfterClose) {
+ for (final Subscription subscription : bundle.getSubscriptions()) {
+ assertEquals(subscription.getState(), EntitlementState.CANCELLED);
+ }
+ }
+
+ final List<Invoice> invoicesAfterClose = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, requestOptions);
+ assertEquals(invoicesAfterClose.size(), 1);
+ assertEquals(invoicesAfterClose.get(0).getBalance().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(killBillClient.getInvoiceTags(invoicesAfterClose.get(0).getInvoiceId(), requestOptions).size(), 0);
}
@Test(groups = "slow", description = "Create a bulk of base entitlements and addOns under the same transaction",
@@ -497,7 +540,6 @@ public class TestEntitlement extends TestJaxrsBase {
assertEquals(invoices.size(), 2);
}
-
@Test(groups = "slow", description = "Can create an entitlement in the future")
public void testCreateEntitlementInTheFuture() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
@@ -594,8 +636,6 @@ public class TestEntitlement extends TestJaxrsBase {
Assert.assertEquals(result2.getBillCycleDayLocal(), new Integer(9));
}
-
-
@Test(groups = "slow", description = "Can create subscription and change plan using planName")
public void testEntitlementUsingPlanName() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);