use of org.killbill.billing.subscription.events.phase.PhaseEvent in project killbill by killbill.
the class DefaultSubscriptionBaseTimeline method toExistingEvents.
private List<ExistingEvent> toExistingEvents(final Catalog catalog, final ProductCategory category, final List<SubscriptionBaseEvent> events) throws CatalogApiException {
final List<ExistingEvent> result = new LinkedList<SubscriptionBaseTimeline.ExistingEvent>();
String prevPlanName = null;
String prevProductName = null;
BillingPeriod prevBillingPeriod = null;
String prevPriceListName = null;
PhaseType prevPhaseType = null;
DateTime startDate = null;
for (final SubscriptionBaseEvent cur : events) {
if (!cur.isActive()) {
continue;
}
startDate = (startDate == null) ? cur.getEffectiveDate() : startDate;
String productName = null;
BillingPeriod billingPeriod = null;
String priceListName = null;
PhaseType phaseType = null;
String planName = null;
String planPhaseName = null;
Integer billCycleDayLocal = null;
ApiEventType apiType = null;
switch(cur.getType()) {
case PHASE:
final PhaseEvent phaseEV = (PhaseEvent) cur;
planPhaseName = phaseEV.getPhase();
phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType();
// A PHASE event always occurs within the same plan (and is never the first event)
planName = prevPlanName;
productName = prevProductName;
billingPeriod = getBillingPeriod(catalog, phaseEV.getPhase(), cur.getEffectiveDate(), startDate);
priceListName = prevPriceListName;
break;
case BCD_UPDATE:
final BCDEvent bcdEvent = (BCDEvent) cur;
billCycleDayLocal = bcdEvent.getBillCycleDayLocal();
break;
case API_USER:
final ApiEvent userEV = (ApiEvent) cur;
apiType = userEV.getApiEventType();
planName = userEV.getEventPlan();
planPhaseName = userEV.getEventPlanPhase();
final Plan plan = (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getEffectiveDate(), startDate) : null;
phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType;
productName = (plan != null) ? plan.getProduct().getName() : prevProductName;
billingPeriod = (userEV.getEventPlanPhase() != null) ? getBillingPeriod(catalog, userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate) : prevBillingPeriod;
priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName;
break;
}
final SubscriptionBaseTransitionType transitionType = SubscriptionBaseTransitionData.toSubscriptionTransitionType(cur.getType(), apiType);
final String planNameWithClosure = planName;
final String planPhaseNameWithClosure = planPhaseName;
final Integer billCycleDayLocalWithClosure = billCycleDayLocal;
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(planName, phaseType);
result.add(new ExistingEvent() {
@Override
public SubscriptionBaseTransitionType getSubscriptionTransitionType() {
return transitionType;
}
@Override
public ProductCategory getProductCategory() {
return category;
}
@Override
public PlanPhaseSpecifier getPlanPhaseSpecifier() {
return spec;
}
@Override
public UUID getEventId() {
return cur.getId();
}
@Override
public DateTime getEffectiveDate() {
return cur.getEffectiveDate();
}
@Override
public String getPlanName() {
return planNameWithClosure;
}
@Override
public String getPlanPhaseName() {
return planPhaseNameWithClosure;
}
@Override
public Integer getBillCycleDayLocal() {
return billCycleDayLocalWithClosure;
}
});
prevPlanName = planName;
prevProductName = productName;
prevBillingPeriod = billingPeriod;
prevPriceListName = priceListName;
prevPhaseType = phaseType;
}
sortExistingEvent(result);
return result;
}
use of org.killbill.billing.subscription.events.phase.PhaseEvent in project killbill by killbill.
the class TestUserApiCreate method testSimpleCreateSubscription.
@Test(groups = "slow")
public void testSimpleCreateSubscription() throws SubscriptionBaseApiException {
final DateTime init = clock.getUTCNow();
final String productName = "Shotgun";
final BillingPeriod term = BillingPeriod.MONTHLY;
final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, productName, term, planSetName);
assertNotNull(subscription);
assertEquals(subscription.getBundleExternalKey(), bundle.getExternalKey());
testUtil.assertDateWithin(subscription.getStartDate(), init, clock.getUTCNow());
testUtil.assertDateWithin(subscription.getBundleStartDate(), init, clock.getUTCNow());
final Plan currentPlan = subscription.getCurrentPlan();
assertNotNull(currentPlan);
assertEquals(currentPlan.getProduct().getName(), productName);
assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
assertEquals(currentPlan.getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
final PlanPhase currentPhase = subscription.getCurrentPhase();
assertNotNull(currentPhase);
assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
assertListenerStatus();
final List<SubscriptionBaseEvent> events = dao.getPendingEventsForSubscription(subscription.getId(), internalCallContext);
assertNotNull(events);
testUtil.printEvents(events);
assertTrue(events.size() == 1);
assertTrue(events.get(0) instanceof PhaseEvent);
final DateTime nextPhaseChange = ((PhaseEvent) events.get(0)).getEffectiveDate();
final DateTime nextExpectedPhaseChange = TestSubscriptionHelper.addDuration(subscription.getStartDate(), currentPhase.getDuration());
assertEquals(nextPhaseChange, nextExpectedPhaseChange);
testListener.pushExpectedEvent(NextEvent.PHASE);
final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
clock.addDeltaFromReality(it.toDurationMillis());
final DateTime futureNow = clock.getUTCNow();
assertTrue(futureNow.isAfter(nextPhaseChange));
assertListenerStatus();
}
use of org.killbill.billing.subscription.events.phase.PhaseEvent in project killbill by killbill.
the class DefaultSubscriptionDao method mergeDryRunEvents.
private void mergeDryRunEvents(final UUID subscriptionId, final List<SubscriptionBaseEvent> events, @Nullable final Collection<SubscriptionBaseEvent> dryRunEvents) {
if (dryRunEvents == null || dryRunEvents.isEmpty()) {
return;
}
for (final SubscriptionBaseEvent curDryRun : dryRunEvents) {
boolean swapChangeEventWithCreate = false;
if (curDryRun.getSubscriptionId() != null && curDryRun.getSubscriptionId().equals(subscriptionId)) {
final boolean isApiChange = curDryRun.getType() == EventType.API_USER && ((ApiEvent) curDryRun).getApiEventType() == ApiEventType.CHANGE;
final Iterator<SubscriptionBaseEvent> it = events.iterator();
while (it.hasNext()) {
final SubscriptionBaseEvent event = it.next();
if (event.getEffectiveDate().isAfter(curDryRun.getEffectiveDate())) {
it.remove();
} else if (event.getEffectiveDate().compareTo(curDryRun.getEffectiveDate()) == 0 && isApiChange && (event.getType() == EventType.API_USER && (((ApiEvent) event).getApiEventType() == ApiEventType.CREATE) || ((ApiEvent) event).getApiEventType() == ApiEventType.TRANSFER)) {
it.remove();
swapChangeEventWithCreate = true;
}
}
// Set total ordering value of the fake dryRun event to make sure billing events are correctly ordered
// and also transform CHANGE event into CREATE in case of perfect effectiveDate match
final EventBaseBuilder eventBuilder;
switch(curDryRun.getType()) {
case PHASE:
eventBuilder = new PhaseEventBuilder((PhaseEvent) curDryRun);
break;
case BCD_UPDATE:
eventBuilder = new BCDEventBuilder((BCDEvent) curDryRun);
break;
case API_USER:
default:
eventBuilder = new ApiEventBuilder((ApiEvent) curDryRun);
if (swapChangeEventWithCreate) {
((ApiEventBuilder) eventBuilder).setApiEventType(ApiEventType.CREATE);
}
break;
}
if (!events.isEmpty()) {
eventBuilder.setTotalOrdering(events.get(events.size() - 1).getTotalOrdering() + 1);
}
events.add(eventBuilder.build());
}
}
}
use of org.killbill.billing.subscription.events.phase.PhaseEvent in project killbill by killbill.
the class DefaultSubscriptionBase method rebuildTransitions.
public void rebuildTransitions(final List<SubscriptionBaseEvent> inputEvents, final SubscriptionCatalog catalog) throws CatalogApiException {
if (inputEvents == null) {
return;
}
this.events = inputEvents;
Collections.sort(inputEvents, new Comparator<SubscriptionBaseEvent>() {
@Override
public int compare(final SubscriptionBaseEvent o1, final SubscriptionBaseEvent o2) {
final int res = o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
if (res != 0) {
return res;
}
// In-memory events have a total order of 0, make sure they are after on disk event
if (o1.getTotalOrdering() == 0 && o2.getTotalOrdering() > 0) {
return 1;
} else if (o1.getTotalOrdering() > 0 && o2.getTotalOrdering() == 0) {
return -1;
} else if (o1.getTotalOrdering() == o2.getTotalOrdering()) {
return 0;
} else {
return o1.getTotalOrdering() < (o2.getTotalOrdering()) ? -1 : 1;
}
}
});
removeEverythingPastCancelEvent(events);
final UUID nextUserToken = null;
UUID nextEventId;
DateTime nextCreatedDate;
EntitlementState nextState = null;
String nextPlanName = null;
String nextPhaseName = null;
Integer nextBillingCycleDayLocal = null;
UUID prevEventId = null;
DateTime prevCreatedDate = null;
EntitlementState previousState = null;
PriceList previousPriceList = null;
Plan previousPlan = null;
PlanPhase previousPhase = null;
Integer previousBillingCycleDayLocal = null;
transitions = new LinkedList<SubscriptionBaseTransition>();
// Track each time we change Plan to fetch the Plan from the right catalog version
DateTime lastPlanChangeTime = null;
for (final SubscriptionBaseEvent cur : inputEvents) {
if (!cur.isActive()) {
continue;
}
ApiEventType apiEventType = null;
boolean isFromDisk = true;
nextEventId = cur.getId();
nextCreatedDate = cur.getCreatedDate();
switch(cur.getType()) {
case PHASE:
final PhaseEvent phaseEV = (PhaseEvent) cur;
nextPhaseName = phaseEV.getPhase();
break;
case BCD_UPDATE:
final BCDEvent bcdEvent = (BCDEvent) cur;
nextBillingCycleDayLocal = bcdEvent.getBillCycleDayLocal();
break;
case API_USER:
final ApiEvent userEV = (ApiEvent) cur;
apiEventType = userEV.getApiEventType();
isFromDisk = userEV.isFromDisk();
switch(apiEventType) {
case TRANSFER:
case CREATE:
prevEventId = null;
prevCreatedDate = null;
previousState = null;
previousPlan = null;
previousPhase = null;
previousPriceList = null;
nextState = EntitlementState.ACTIVE;
nextPlanName = userEV.getEventPlan();
nextPhaseName = userEV.getEventPlanPhase();
lastPlanChangeTime = cur.getEffectiveDate();
break;
case CHANGE:
nextPlanName = userEV.getEventPlan();
nextPhaseName = userEV.getEventPlanPhase();
lastPlanChangeTime = cur.getEffectiveDate();
break;
case CANCEL:
nextState = EntitlementState.CANCELLED;
nextPlanName = null;
nextPhaseName = null;
break;
case UNCANCEL:
case UNDO_CHANGE:
default:
throw new SubscriptionBaseError(String.format("Unexpected UserEvent type = %s", userEV.getApiEventType().toString()));
}
break;
default:
throw new SubscriptionBaseError(String.format("Unexpected Event type = %s", cur.getType()));
}
final Plan nextPlan = (nextPlanName != null) ? catalog.findPlan(nextPlanName, cur.getEffectiveDate(), lastPlanChangeTime) : null;
final PlanPhase nextPhase = (nextPlan != null && nextPhaseName != null) ? nextPlan.findPhase(nextPhaseName) : null;
final PriceList nextPriceList = (nextPlan != null) ? nextPlan.getPriceList() : null;
final SubscriptionBaseTransitionData transition = new SubscriptionBaseTransitionData(cur.getId(), id, bundleId, bundleExternalKey, cur.getType(), apiEventType, cur.getEffectiveDate(), prevEventId, prevCreatedDate, previousState, previousPlan, previousPhase, previousPriceList, previousBillingCycleDayLocal, nextEventId, nextCreatedDate, nextState, nextPlan, nextPhase, nextPriceList, nextBillingCycleDayLocal, cur.getTotalOrdering(), cur.getCreatedDate(), nextUserToken, isFromDisk);
transitions.add(transition);
previousState = nextState;
previousPlan = nextPlan;
previousPhase = nextPhase;
previousPriceList = nextPriceList;
prevEventId = nextEventId;
prevCreatedDate = nextCreatedDate;
previousBillingCycleDayLocal = nextBillingCycleDayLocal;
}
}
use of org.killbill.billing.subscription.events.phase.PhaseEvent in project killbill by killbill.
the class DefaultSubscriptionBase method rebuildTransitions.
public void rebuildTransitions(final List<SubscriptionBaseEvent> inputEvents, final Catalog catalog) throws CatalogApiException {
if (inputEvents == null) {
return;
}
this.events = inputEvents;
filterOutDuplicateCancelEvents(events);
UUID nextUserToken = null;
UUID nextEventId = null;
DateTime nextCreatedDate = null;
EntitlementState nextState = null;
String nextPlanName = null;
String nextPhaseName = null;
Integer nextBillingCycleDayLocal = null;
UUID prevEventId = null;
DateTime prevCreatedDate = null;
EntitlementState previousState = null;
PriceList previousPriceList = null;
Plan previousPlan = null;
PlanPhase previousPhase = null;
Integer previousBillingCycleDayLocal = null;
transitions = new LinkedList<SubscriptionBaseTransition>();
for (final SubscriptionBaseEvent cur : inputEvents) {
if (!cur.isActive()) {
continue;
}
ApiEventType apiEventType = null;
boolean isFromDisk = true;
nextEventId = cur.getId();
nextCreatedDate = cur.getCreatedDate();
switch(cur.getType()) {
case PHASE:
final PhaseEvent phaseEV = (PhaseEvent) cur;
nextPhaseName = phaseEV.getPhase();
break;
case BCD_UPDATE:
final BCDEvent bcdEvent = (BCDEvent) cur;
nextBillingCycleDayLocal = bcdEvent.getBillCycleDayLocal();
break;
case API_USER:
final ApiEvent userEV = (ApiEvent) cur;
apiEventType = userEV.getApiEventType();
isFromDisk = userEV.isFromDisk();
switch(apiEventType) {
case TRANSFER:
case CREATE:
prevEventId = null;
prevCreatedDate = null;
previousState = null;
previousPlan = null;
previousPhase = null;
previousPriceList = null;
nextState = EntitlementState.ACTIVE;
nextPlanName = userEV.getEventPlan();
nextPhaseName = userEV.getEventPlanPhase();
break;
case CHANGE:
nextPlanName = userEV.getEventPlan();
nextPhaseName = userEV.getEventPlanPhase();
break;
case CANCEL:
nextState = EntitlementState.CANCELLED;
nextPlanName = null;
nextPhaseName = null;
break;
case UNCANCEL:
default:
throw new SubscriptionBaseError(String.format("Unexpected UserEvent type = %s", userEV.getApiEventType().toString()));
}
break;
default:
throw new SubscriptionBaseError(String.format("Unexpected Event type = %s", cur.getType()));
}
Plan nextPlan = null;
PlanPhase nextPhase = null;
PriceList nextPriceList = null;
nextPlan = (nextPlanName != null) ? catalog.findPlan(nextPlanName, cur.getEffectiveDate(), getAlignStartDate()) : null;
nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, cur.getEffectiveDate(), getAlignStartDate()) : null;
nextPriceList = (nextPlan != null) ? catalog.findPriceListForPlan(nextPlanName, cur.getEffectiveDate(), getAlignStartDate()) : null;
final SubscriptionBaseTransitionData transition = new SubscriptionBaseTransitionData(cur.getId(), id, bundleId, bundleExternalKey, cur.getType(), apiEventType, cur.getEffectiveDate(), prevEventId, prevCreatedDate, previousState, previousPlan, previousPhase, previousPriceList, previousBillingCycleDayLocal, nextEventId, nextCreatedDate, nextState, nextPlan, nextPhase, nextPriceList, nextBillingCycleDayLocal, cur.getTotalOrdering(), cur.getCreatedDate(), nextUserToken, isFromDisk);
transitions.add(transition);
previousState = nextState;
previousPlan = nextPlan;
previousPhase = nextPhase;
previousPriceList = nextPriceList;
prevEventId = nextEventId;
prevCreatedDate = nextCreatedDate;
previousBillingCycleDayLocal = nextBillingCycleDayLocal;
}
}
Aggregations