killbill-memoizeit
Changes
api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java 4(+4 -0)
entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java 10(+10 -0)
pom.xml 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java 2(+2 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java 25(+19 -6)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 49(+46 -3)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 40(+27 -13)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java 4(+3 -1)
subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java 2(+2 -0)
subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventUndoChange.java 25(+25 -0)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java 16(+8 -8)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java 40(+40 -0)
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 b018613..29b10da 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
@@ -58,6 +58,9 @@ public interface SubscriptionBase extends Entity, Blockable {
public DateTime changePlan(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final CallContext context)
throws SubscriptionBaseApiException;
+ public boolean undoChangePlan(final CallContext context)
+ throws SubscriptionBaseApiException;
+
// Return the effective date of the change
public DateTime changePlanWithDate(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDate, final CallContext context)
throws SubscriptionBaseApiException;
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
index 94606d0..2dcf5bd 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
@@ -41,6 +41,10 @@ public enum SubscriptionBaseTransitionType {
*/
UNCANCEL,
/**
+ * Occurs when a user undo a pending change before it reached its effective date
+ */
+ UNDO_CHANGE,
+ /**
* Generated by the system to mark a change of phase
*/
PHASE,
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 345da81..5491cae 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
@@ -52,6 +52,7 @@ import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKey;
import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
+import org.killbill.billing.entitlement.logging.EntitlementLoggingHelper;
import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
import org.killbill.billing.entitlement.plugin.api.OperationType;
import org.killbill.billing.entity.EntityBase;
@@ -80,6 +81,7 @@ import com.google.common.collect.ImmutableList;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCancelEntitlement;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logChangePlan;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logUncancelEntitlement;
+import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logUndoChangePlan;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logUpdateBCD;
public class DefaultEntitlement extends EntityBase implements Entitlement {
@@ -393,7 +395,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
false);
final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
- final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UNCANCEL_SUBSCRIPTION,
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UNDO_PENDING_SUBSCRIPTION_OPERATION,
getAccountId(),
null,
baseEntitlementWithAddOnsSpecifierList,
@@ -612,6 +614,51 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
}
@Override
+ public void undoChangePlan(final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+
+ logUndoChangePlan(log, this);
+
+ checkForPermissions(Permission.ENTITLEMENT_CAN_CHANGE_PLAN, callContext);
+
+ // Get the latest state from disk
+ refresh(callContext);
+
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+ getBundleId(),
+ getExternalKey(),
+ null,
+ null,
+ null,
+ false);
+ final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+ baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UNDO_PENDING_SUBSCRIPTION_OPERATION,
+ getAccountId(),
+ null,
+ baseEntitlementWithAddOnsSpecifierList,
+ null,
+ properties,
+ callContext);
+
+ final WithEntitlementPlugin<Void> undoChangePlanEntitlementWithPlugin = new WithEntitlementPlugin<Void>() {
+
+ @Override
+ public Void doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+
+ try {
+ getSubscriptionBase().undoChangePlan(callContext);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+ return null;
+ }
+ };
+
+ pluginExecution.executeWithPlugin(undoChangePlanEntitlementWithPlugin, pluginContext);
+
+ }
+
+ @Override
public Entitlement changePlanWithDate(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, @Nullable final LocalDate effectiveDate, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
logChangePlan(log, this, spec, overrides, effectiveDate, null);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java b/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
index 0793d6f..a0b158c 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
@@ -214,6 +214,16 @@ public abstract class EntitlementLoggingHelper {
}
}
+ public static void logUndoChangePlan(final Logger log, final Entitlement entitlement) {
+ if (log.isInfoEnabled()) {
+ final StringBuilder logLine = new StringBuilder("Undo Entitlement Change Plan: ")
+ .append(" id = '")
+ .append(entitlement.getId())
+ .append("'");
+ log.info(logLine.toString());
+ }
+ }
+
public static void logChangePlan(final Logger log, final Entitlement entitlement, final PlanSpecifier spec,
final List<PlanPhasePriceOverride> overrides, final LocalDate entitlementEffectiveDate, final BillingActionPolicy actionPolicy) {
if (log.isInfoEnabled()) {
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 e7e6c9b..7923488 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
@@ -260,6 +260,10 @@ public interface JaxrsResource {
public static final String CBA_REBALANCING = "cbaRebalancing";
+
+ public static final String UNDO_CHANGE_PLAN = "undoChangePlan";
+ public static final String UNDO_CANCEL = "uncancel";
+
public static final String PAUSE = "pause";
public static final String RESUME = "resume";
public static final String BLOCK = "block";
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 d54e936..7a9430c 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
@@ -499,7 +499,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@TimedResource
@PUT
- @Path("/{subscriptionId:" + UUID_PATTERN + "}/uncancel")
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}/" + UNDO_CANCEL)
@Produces(APPLICATION_JSON)
@ApiOperation(value = "Un-cancel an entitlement")
@ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid subscription id supplied"),
@@ -519,6 +519,26 @@ public class SubscriptionResource extends JaxRsResourceBase {
@TimedResource
@PUT
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}/" + UNDO_CHANGE_PLAN)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Undo a pending change plan on an entitlement")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid subscription id supplied"),
+ @ApiResponse(code = 404, message = "Entitlement not found")})
+ public Response undoChangeEntitlementPlan(@PathParam("subscriptionId") final String subscriptionId,
+ @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @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 EntitlementApiException {
+ final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+ final UUID uuid = UUID.fromString(subscriptionId);
+ final Entitlement current = entitlementApi.getEntitlementForId(uuid, context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ current.undoChangePlan(pluginProperties, context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ return Response.status(Status.OK).build();
+ }
+
+ @TimedResource
+ @PUT
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@Path("/{subscriptionId:" + UUID_PATTERN + "}")
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index c5359c3..bb4dedc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.141.5</version>
+ <version>0.141.6-SNAPSHOT</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.19.0-SNAPSHOT</version>
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 3eb79f2..94150fa 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
@@ -669,8 +669,61 @@ public class TestEntitlement extends TestJaxrsBase {
Assert.assertEquals(newEntitlementJson.getBillingPeriod(), BillingPeriod.MONTHLY);
Assert.assertEquals(newEntitlementJson.getPriceList(), DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
Assert.assertEquals(newEntitlementJson.getPlanName(), "pistol-monthly");
+ }
+
+ @Test(groups = "slow", description = "Can changePlan and undo changePlan on a subscription")
+ public void testEntitlementUndoChangePlan() 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 String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+
+ final Subscription entitlementJson = createEntitlement(accountJson.getAccountId(), "99999", productName,
+ ProductCategory.BASE, term, true);
+
+
+
+ // Change plan in the future
+ 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());
+
+ Subscription refreshedSubscription = killBillClient.updateSubscription(newInput, new LocalDate(2012, 4, 28), null, CALL_COMPLETION_TIMEOUT_SEC, requestOptions);
+ Assert.assertNotNull(refreshedSubscription);
+
+
+ final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ killBillClient.undoChangePlan(refreshedSubscription.getSubscriptionId(), requestOptions);
+
+ // MOVE AFTER TRIAL
+ final Interval it2 = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+ clock.addDeltaFromReality(it2.toDurationMillis());
+
+ crappyWaitForLackOfProperSynchonization();
+
+ // Retrieves to check EndDate
+ refreshedSubscription = killBillClient.getSubscription(entitlementJson.getSubscriptionId(), requestOptions);
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().size(), 2);
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().get(0).getPhaseName(), "shotgun-monthly-trial");
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().get(0).getFixedPrice(), BigDecimal.ZERO);
+ Assert.assertNull(refreshedSubscription.getPriceOverrides().get(0).getRecurringPrice());
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().get(1).getPhaseName(), "shotgun-monthly-evergreen");
+ Assert.assertNull(refreshedSubscription.getPriceOverrides().get(1).getFixedPrice());
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().get(1).getRecurringPrice(), new BigDecimal("249.95"));
}
+
}
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 7bcba98..2740f09 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
@@ -117,4 +117,6 @@ public interface SubscriptionBaseApiService {
final boolean addCancellationAddOnForEventsIfRequired,
final Catalog fullCatalog,
final InternalTenantContext internalTenantContext) throws CatalogApiException;
+
+ boolean undoChangePlan(DefaultSubscriptionBase defaultSubscriptionBase, CallContext context) throws SubscriptionBaseApiException;
}
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 1543d9b..30d18ba 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
@@ -41,7 +41,6 @@ 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.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -280,6 +279,11 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
}
@Override
+ public boolean undoChangePlan(final CallContext context) throws SubscriptionBaseApiException {
+ return apiService.undoChangePlan(this, context);
+ }
+
+ @Override
public DateTime changePlanWithDate(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides,
final DateTime requestedDate, final CallContext context) throws SubscriptionBaseApiException {
return apiService.changePlanWithRequestedDate(this, spec, overrides, requestedDate, context);
@@ -501,10 +505,10 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
prev = curData;
}
}
- // Since UNCANCEL are not part of the transitions, we compute a new 'UNCANCEL' transition based on the event right before that UNCANCEL
- // This is used to be able to send a bus event for uncancellation
- if (prev != null && event.getType() == EventType.API_USER && ((ApiEvent) event).getApiEventType() == ApiEventType.UNCANCEL) {
- final SubscriptionBaseTransitionData withSeq = new SubscriptionBaseTransitionData((SubscriptionBaseTransitionData) prev, EventType.API_USER, ApiEventType.UNCANCEL, seqId);
+ // Since UNCANCEL/UNDO_CHANGE are not part of the transitions, we compute a new transition based on the event right before
+ // This is used to be able to send a bus event for uncancellation/undo_change
+ if (prev != null && event.getType() == EventType.API_USER && (((ApiEvent) event).getApiEventType() == ApiEventType.UNCANCEL || ((ApiEvent) event).getApiEventType() == ApiEventType.UNDO_CHANGE)) {
+ final SubscriptionBaseTransitionData withSeq = new SubscriptionBaseTransitionData(prev, EventType.API_USER, ((ApiEvent) event).getApiEventType(), seqId);
return withSeq;
}
return null;
@@ -569,10 +573,18 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
throw new SubscriptionBaseError(String.format("Failed to find InitialTransitionForCurrentPlan id = %s", getId()));
}
- public boolean isSubscriptionFutureCancelled() {
+ public boolean isFutureCancelled() {
return getFutureEndDate() != null;
}
+
+ public boolean isPendingChangePlan() {
+ final SubscriptionBaseTransition pendingTransition = getPendingTransition();
+ return pendingTransition != null && pendingTransition.getTransitionType() == SubscriptionBaseTransitionType.CHANGE;
+ }
+
+
+
public DateTime getPlanChangeEffectiveDate(final BillingActionPolicy policy, @Nullable final BillingAlignment alignment, @Nullable final Integer accountBillCycleDayLocal, final InternalTenantContext context) {
final DateTime candidateResult;
@@ -733,6 +745,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
nextPhaseName = null;
break;
case UNCANCEL:
+ case UNDO_CHANGE:
default:
throw new SubscriptionBaseError(String.format(
"Unexpected UserEvent type = %s", userEV
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 60d5928..8f439a9 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
@@ -68,6 +68,7 @@ import org.killbill.billing.subscription.events.user.ApiEventChange;
import org.killbill.billing.subscription.events.user.ApiEventCreate;
import org.killbill.billing.subscription.events.user.ApiEventType;
import org.killbill.billing.subscription.events.user.ApiEventUncancel;
+import org.killbill.billing.subscription.events.user.ApiEventUndoChange;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
@@ -281,7 +282,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
@Override
public boolean uncancel(final DefaultSubscriptionBase subscription, final CallContext context) throws SubscriptionBaseApiException {
- if (!subscription.isSubscriptionFutureCancelled()) {
+ if (!subscription.isFutureCancelled()) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_UNCANCEL_BAD_STATE, subscription.getId().toString());
}
try {
@@ -313,7 +314,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
uncancelEvents.add(nextPhaseEvent);
}
- dao.uncancelSubscription(subscription, uncancelEvents, fullCatalog, internalCallContext);
+ dao.uncancelSubscription(subscription, uncancelEvents, internalCallContext);
subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
return true;
} catch (final CatalogApiException e) {
@@ -552,6 +553,48 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
@Override
+ public boolean undoChangePlan(final DefaultSubscriptionBase subscription, final CallContext context) throws SubscriptionBaseApiException {
+ if (!subscription.isPendingChangePlan()) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_UNDO_CHANGE_BAD_STATE, subscription.getId().toString());
+ }
+ try {
+
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
+ final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
+
+ final DateTime now = clock.getUTCNow();
+ final SubscriptionBaseEvent undoChangePlanEvent = new ApiEventUndoChange(new ApiEventBuilder()
+ .setSubscriptionId(subscription.getId())
+ .setEffectiveDate(now)
+ .setFromDisk(true));
+
+ final List<SubscriptionBaseEvent> undoChangePlanEvents = new ArrayList<SubscriptionBaseEvent>();
+ undoChangePlanEvents.add(undoChangePlanEvent);
+
+ //
+ // Used to compute effective for next phase (which was set unactive during cancellation).
+ // In case of a pending subscription we don't want to pass an effective date prior the CREATE event as we would end up with the wrong
+ // transition in PlanAligner (next transition would be CREATE instead of potential next PHASE)
+ //
+ final DateTime planAlignerEffectiveDate = subscription.getState() == EntitlementState.PENDING ? subscription.getStartDate() : now;
+
+ final TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, planAlignerEffectiveDate, fullCatalog, internalCallContext);
+ final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
+ PhaseEventData.createNextPhaseEvent(subscription.getId(), nextTimedPhase.getPhase().getName(), nextTimedPhase.getStartPhase()) :
+ null;
+ if (nextPhaseEvent != null) {
+ undoChangePlanEvents.add(nextPhaseEvent);
+ }
+
+ dao.undoChangePlan(subscription, undoChangePlanEvents, internalCallContext);
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
+ return true;
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+ }
+
+ @Override
public int handleBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final Catalog catalog, final CallContext context) throws CatalogApiException {
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
@@ -628,7 +671,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
if (effectiveDate != null && effectiveDate.compareTo(subscription.getStartDate()) < 0) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_NON_ACTIVE, subscription.getId(), currentState);
}
- if (subscription.isSubscriptionFutureCancelled()) {
+ if (subscription.isFutureCancelled()) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_FUTURE_CANCELLED, subscription.getId());
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 3c799ea..322f804 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -24,10 +24,12 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -671,7 +673,17 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void uncancelSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> uncancelEvents, final Catalog catalog, final InternalCallContext context) {
+ public void uncancelSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> uncancelEvents, final InternalCallContext context) {
+ undoOperation(subscription, uncancelEvents, ApiEventType.CANCEL, SubscriptionBaseTransitionType.UNCANCEL, context);
+ }
+
+ @Override
+ public void undoChangePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> undoChangePlanEvents, final InternalCallContext context) {
+ undoOperation(subscription, undoChangePlanEvents, ApiEventType.CHANGE, SubscriptionBaseTransitionType.UNDO_CHANGE, context);
+ }
+
+
+ private void undoOperation(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> inputEvents, final ApiEventType targetOperation, final SubscriptionBaseTransitionType transitionType, final InternalCallContext context) {
final InternalCallContext contextWithUpdatedDate = contextWithUpdatedDate(context);
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@@ -680,23 +692,24 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
final UUID subscriptionId = subscription.getId();
- SubscriptionEventModelDao cancelledEvent = null;
+
+ Set<SubscriptionEventModelDao> targetEvents = new HashSet<SubscriptionEventModelDao>();
final Date now = clock.getUTCNow().toDate();
final List<SubscriptionEventModelDao> eventModels = transactional.getFutureActiveEventForSubscription(subscriptionId.toString(), now, contextWithUpdatedDate);
for (final SubscriptionEventModelDao cur : eventModels) {
- if (cur.getUserType() == ApiEventType.CANCEL) {
- if (cancelledEvent != null) {
- throw new SubscriptionBaseError(String.format("Found multiple cancelWithRequestedDate active events for subscriptions %s", subscriptionId.toString()));
- }
- cancelledEvent = cur;
+ if (cur.getEventType() == EventType.API_USER && cur.getUserType() == targetOperation) {
+ targetEvents.add(cur);
+ } else if (cur.getEventType() == EventType.PHASE) {
+ targetEvents.add(cur);
}
}
- if (cancelledEvent != null) {
- final String cancelledEventId = cancelledEvent.getId().toString();
- transactional.unactiveEvent(cancelledEventId, contextWithUpdatedDate);
- for (final SubscriptionBaseEvent cur : uncancelEvents) {
+ if (!targetEvents.isEmpty()) {
+ for (SubscriptionEventModelDao target : targetEvents) {
+ transactional.unactiveEvent(target.getId().toString(), contextWithUpdatedDate);
+ }
+ for (final SubscriptionBaseEvent cur : inputEvents) {
transactional.create(new SubscriptionEventModelDao(cur), contextWithUpdatedDate);
recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory,
cur.getEffectiveDate(),
@@ -705,7 +718,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
// Notify the Bus of the latest requested change
- notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, uncancelEvents.get(uncancelEvents.size() - 1), SubscriptionBaseTransitionType.UNCANCEL, contextWithUpdatedDate);
+ notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, inputEvents.get(inputEvents.size() - 1), transitionType, contextWithUpdatedDate);
}
return null;
@@ -783,11 +796,12 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
});
}
+
private List<SubscriptionBaseEvent> filterSubscriptionBaseEvents(final Collection<SubscriptionEventModelDao> models) {
final Collection<SubscriptionEventModelDao> filteredModels = Collections2.filter(models, new Predicate<SubscriptionEventModelDao>() {
@Override
public boolean apply(@Nullable final SubscriptionEventModelDao input) {
- return input.getUserType() != ApiEventType.UNCANCEL;
+ return input.getUserType() != ApiEventType.UNCANCEL && input.getUserType() != ApiEventType.UNDO_CHANGE ;
}
});
return new ArrayList<SubscriptionBaseEvent>(Collections2.transform(filteredModels, new Function<SubscriptionEventModelDao, SubscriptionBaseEvent>() {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
index c31b372..4393e40 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
@@ -92,10 +92,12 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public void cancelSubscriptions(List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, InternalCallContext context);
- public void uncancelSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> uncancelEvents, final Catalog catalog, InternalCallContext context);
+ public void uncancelSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> uncancelEvents, InternalCallContext context);
public void changePlan(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> changeEvents, List<DefaultSubscriptionBase> subscriptionsToBeCancelled, List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, InternalCallContext context);
+ public void undoChangePlan(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> undoChangePlanEvents, InternalCallContext context);
+
public void transfer(UUID srcAccountId, UUID destAccountId, BundleTransferData data, List<TransferCancelData> transferCancelData, final Catalog catalog, InternalCallContext fromContext, InternalCallContext toContext);
public void updateBundleExternalKey(UUID bundleId, String externalKey, InternalCallContext context);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java
index 7e3ce7a..e09d14a 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java
@@ -100,6 +100,8 @@ public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
result = new ApiEventCancel(this);
} else if (apiEventType == ApiEventType.UNCANCEL) {
result = new ApiEventUncancel(this);
+ } else if (apiEventType == ApiEventType.UNDO_CHANGE) {
+ result = new ApiEventUndoChange(this);
} else {
throw new IllegalStateException("Unknown ApiEventType " + apiEventType);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventType.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventType.java
index 1646243..88e4e7c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventType.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventType.java
@@ -44,6 +44,12 @@ public enum ApiEventType {
return SubscriptionBaseTransitionType.CANCEL;
}
},
+ UNDO_CHANGE {
+ @Override
+ public SubscriptionBaseTransitionType getSubscriptionTransitionType() {
+ return SubscriptionBaseTransitionType.UNDO_CHANGE;
+ }
+ },
UNCANCEL {
@Override
public SubscriptionBaseTransitionType getSubscriptionTransitionType() {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventUndoChange.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventUndoChange.java
new file mode 100644
index 0000000..add830d
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventUndoChange.java
@@ -0,0 +1,25 @@
+/*
+ * 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
+ * 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.events.user;
+
+public class ApiEventUndoChange extends ApiEventBase {
+
+ public ApiEventUndoChange(final ApiEventBuilder builder) {
+ super(builder.setApiEventType(ApiEventType.UNDO_CHANGE));
+ }
+}
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 28b6a80..4503630 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
@@ -115,13 +115,13 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
aoSubscription.cancel(callContext);
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
// CANCEL BASE NOW
baseSubscription.cancel(callContext);
baseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext);
assertEquals(baseSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(baseSubscription.isSubscriptionFutureCancelled());
+ assertTrue(baseSubscription.isFutureCancelled());
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
List<SubscriptionBaseTransition> aoTransitions = aoSubscription.getAllTransitions();
@@ -183,7 +183,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
// MOVE AFTER CANCELLATION
testListener.pushExpectedEvent(NextEvent.CANCEL);
@@ -237,7 +237,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
testListener.pushExpectedEvent(NextEvent.UNCANCEL);
baseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext);
@@ -246,7 +246,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertFalse(aoSubscription.isSubscriptionFutureCancelled());
+ assertFalse(aoSubscription.isFutureCancelled());
// CANCEL AGAIN
it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(1));
@@ -256,11 +256,11 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
baseSubscription.cancel(callContext);
baseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext);
assertEquals(baseSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(baseSubscription.isSubscriptionFutureCancelled());
+ assertTrue(baseSubscription.isFutureCancelled());
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
assertListenerStatus();
}
@@ -372,7 +372,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
// MOVE AFTER CHANGE
testListener.pushExpectedEvent(NextEvent.CHANGE);
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 d7d20b5..e3539fb 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
@@ -592,4 +592,44 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.CHANGE);
assertEquals(refreshedSubscription.getAllTransitions().get(2).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
}
+
+ @Test(groups = "slow")
+ public void testUndoChangePlan() throws SubscriptionBaseApiException {
+
+ final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ clock.setTime(clock.getUTCNow().plusSeconds(1));
+
+ // Change plan in the future
+ final DateTime targetDate = clock.getUTCNow().plusDays(3);
+ subscription.changePlanWithDate(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, targetDate, callContext);assertListenerStatus();
+
+ DefaultSubscriptionBase refreshedSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(refreshedSubscription.getAllTransitions().size(), 3);
+ assertEquals(refreshedSubscription.getAllTransitions().get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.CHANGE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(2).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
+
+ clock.addDays(1);
+
+ testListener.pushExpectedEvent(NextEvent.UNDO_CHANGE);
+ subscription.undoChangePlan(callContext);
+ assertListenerStatus();
+
+ // No CHANGE_PLAN
+ clock.addDays(3);
+ assertListenerStatus();
+
+ // Verify PHASE event for Pistol is actif
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDays(26);
+ assertListenerStatus();
+
+
+ refreshedSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(refreshedSubscription.getAllTransitions().size(), 2);
+ assertEquals(refreshedSubscription.getAllTransitions().get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
+
+ }
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index ba31884..8bf6081 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -377,6 +377,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, catalog, context);
}
+
private void insertEvent(final SubscriptionBaseEvent event, final InternalCallContext context) {
synchronized (events) {
events.add(event);
@@ -432,10 +433,20 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
@Override
public void uncancelSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> uncancelEvents,
- final Catalog catalog, final InternalCallContext context) {
+ final InternalCallContext context) {
+ undoPendingOperation(subscription, uncancelEvents, ApiEventType.CANCEL, context);
+ }
+ @Override
+ public void undoChangePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> undoChangePlanEvents, final InternalCallContext context) {
+ undoPendingOperation(subscription, undoChangePlanEvents, ApiEventType.CHANGE, context);
+ }
+
+
+ private void undoPendingOperation(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> inputEvents,
+ final ApiEventType targetType, final InternalCallContext context) {
synchronized (events) {
- boolean foundCancel = false;
+ boolean foundEvent = false;
final Iterator<SubscriptionBaseEvent> it = events.descendingIterator();
while (it.hasNext()) {
final SubscriptionBaseEvent cur = it.next();
@@ -443,14 +454,14 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
continue;
}
if (cur.getType() == EventType.API_USER &&
- ((ApiEvent) cur).getApiEventType() == ApiEventType.CANCEL) {
+ ((ApiEvent) cur).getApiEventType() == targetType) {
it.remove();
- foundCancel = true;
+ foundEvent = true;
break;
}
}
- if (foundCancel) {
- for (final SubscriptionBaseEvent cur : uncancelEvents) {
+ if (foundEvent) {
+ for (final SubscriptionBaseEvent cur : inputEvents) {
insertEvent(cur, context);
}
}
diff --git a/util/src/test/java/org/killbill/billing/api/TestApiListener.java b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
index fd0bc8c..d730885 100644
--- a/util/src/test/java/org/killbill/billing/api/TestApiListener.java
+++ b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
@@ -106,6 +106,7 @@ public class TestApiListener {
CREATE,
TRANSFER,
CHANGE,
+ UNDO_CHANGE,
CANCEL,
UNCANCEL,
PAUSE,
@@ -164,6 +165,10 @@ public class TestApiListener {
assertEqualsNicely(NextEvent.CHANGE);
notifyIfStackEmpty();
break;
+ case UNDO_CHANGE:
+ assertEqualsNicely(NextEvent.UNDO_CHANGE);
+ notifyIfStackEmpty();
+ break;
case UNCANCEL:
assertEqualsNicely(NextEvent.UNCANCEL);
notifyIfStackEmpty();
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 146a130..7a72c14 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
@@ -89,6 +89,11 @@ public class MockSubscription implements SubscriptionBase {
}
@Override
+ public boolean undoChangePlan(final CallContext context) throws SubscriptionBaseApiException {
+ return sub.undoChangePlan(context);
+ }
+
+ @Override
public DateTime changePlanWithDate(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDate,
final CallContext context) throws SubscriptionBaseApiException {
return sub.changePlanWithDate(spec, overrides, requestedDate, context);