use of org.killbill.billing.subscription.events.SubscriptionBaseEvent in project killbill by killbill.
the class DefaultSubscriptionBase method filterOutDuplicateCancelEvents.
//
// Hardening against data integrity issues where we have multiple active CANCEL (See #619):
// We skip any cancel events after the first one (subscription cannot be cancelled multiple times).
// The code should prevent such cases from happening but because of #619, some invalid data could be there so to be safe we added this code
//
// Also we remove !onDisk cancel events when there is an onDisk cancel event (can happen during the path where we process the base plan cancel notification, and are
// in the process of adding the new cancel events for the AO)
//
private void filterOutDuplicateCancelEvents(final List<SubscriptionBaseEvent> inputEvents) {
Collections.sort(inputEvents, new Comparator<SubscriptionBaseEvent>() {
@Override
public int compare(final SubscriptionBaseEvent o1, final SubscriptionBaseEvent o2) {
int res = o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
if (res == 0) {
res = o1.getTotalOrdering() < (o2.getTotalOrdering()) ? -1 : 1;
}
return res;
}
});
final boolean isCancelled = Iterables.any(inputEvents, new Predicate<SubscriptionBaseEvent>() {
@Override
public boolean apply(final SubscriptionBaseEvent input) {
if (input.isActive() && input.getType() == EventType.API_USER) {
final ApiEvent userEV = (ApiEvent) input;
if (userEV.getApiEventType() == ApiEventType.CANCEL && userEV.isFromDisk()) {
return true;
}
}
return false;
}
});
if (!isCancelled) {
return;
}
boolean foundFirstOnDiskCancel = false;
final Iterator<SubscriptionBaseEvent> it = inputEvents.iterator();
while (it.hasNext()) {
final SubscriptionBaseEvent input = it.next();
if (!input.isActive()) {
continue;
}
if (input.getType() == EventType.API_USER) {
final ApiEvent userEV = (ApiEvent) input;
if (userEV.getApiEventType() == ApiEventType.CANCEL) {
if (userEV.isFromDisk()) {
if (!foundFirstOnDiskCancel) {
foundFirstOnDiskCancel = true;
} else {
it.remove();
}
} else {
it.remove();
}
}
}
}
}
use of org.killbill.billing.subscription.events.SubscriptionBaseEvent in project killbill by killbill.
the class MockSubscriptionDaoMemory method changePlan.
@Override
public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> changeEvents, final List<DefaultSubscriptionBase> subscriptionsToBeCancelled, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
synchronized (events) {
cancelNextChangeEvent(subscription.getId());
cancelNextPhaseEvent(subscription.getId(), context);
events.addAll(changeEvents);
for (final SubscriptionBaseEvent cur : changeEvents) {
recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new SubscriptionNotificationKey(cur.getId()), context);
}
}
cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, context);
}
use of org.killbill.billing.subscription.events.SubscriptionBaseEvent in project killbill by killbill.
the class MockSubscriptionDaoMemory method uncancelSubscription.
@Override
public void uncancelSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> uncancelEvents, final InternalCallContext context) {
synchronized (events) {
boolean foundCancel = false;
final Iterator<SubscriptionBaseEvent> it = events.descendingIterator();
while (it.hasNext()) {
final SubscriptionBaseEvent cur = it.next();
if (cur.getSubscriptionId() != subscription.getId()) {
continue;
}
if (cur.getType() == EventType.API_USER && ((ApiEvent) cur).getApiEventType() == ApiEventType.CANCEL) {
it.remove();
foundCancel = true;
break;
}
}
if (foundCancel) {
for (final SubscriptionBaseEvent cur : uncancelEvents) {
insertEvent(cur, context);
}
}
}
}
use of org.killbill.billing.subscription.events.SubscriptionBaseEvent in project killbill by killbill.
the class MockSubscriptionDaoMemory method createSubscription.
/*
@Override
public List<SubscriptionBase> getSubscriptionsForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext callcontext) {
for (final SubscriptionBaseBundle cur : bundles) {
if (cur.getExternalKey().equals(bundleKey) && cur.getAccountId().equals(bundleKey)) {
return getSubscriptions(cur.getId(), callcontext);
}
}
return Collections.emptyList();
}
*/
@Override
public void createSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> initialEvents, final InternalCallContext context) {
synchronized (events) {
events.addAll(initialEvents);
for (final SubscriptionBaseEvent cur : initialEvents) {
recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new SubscriptionNotificationKey(cur.getId()), context);
}
}
final SubscriptionBase updatedSubscription = buildSubscription(subscription, context);
subscriptions.add(updatedSubscription);
mockNonEntityDao.addTenantRecordIdMapping(updatedSubscription.getId(), context);
}
use of org.killbill.billing.subscription.events.SubscriptionBaseEvent in project killbill by killbill.
the class SubscriptionApiBase method populateDryRunEvents.
private void populateDryRunEvents(@Nullable final UUID bundleId, @Nullable final DryRunArguments dryRunArguments, final Collection<SubscriptionBaseEvent> outputDryRunEvents, final Collection<DefaultSubscriptionBase> outputSubscriptions, final SubscriptionCatalog catalog, final AddonUtils addonUtils, final TenantContext tenantContext, final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
if (dryRunArguments == null || dryRunArguments.getAction() == null) {
return;
}
final DateTime utcNow = clock.getUTCNow();
List<SubscriptionBaseEvent> dryRunEvents = null;
final EntitlementSpecifier entitlementSpecifier = dryRunArguments.getEntitlementSpecifier();
final PlanPhaseSpecifier inputSpec = entitlementSpecifier.getPlanPhaseSpecifier();
final boolean isInputSpecNullOrEmpty = inputSpec == null || (inputSpec.getPlanName() == null && inputSpec.getProductName() == null && inputSpec.getBillingPeriod() == null);
// Create an overridesWithContext with a null context to indicate this is dryRun and no price overridden plan should be created.
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(entitlementSpecifier.getOverrides(), null);
final LocalDate dryRunEffDt = dryRunArguments.getEffectiveDate();
// EffectiveDate for the event is LocalDate but Catalog effectiveDate is a DateTime, so to maximize the chance of finding 'a' version
// on that date we take the time closer to midnight - if 2 versions where uploaded on the same LocalDate this is returns the latest one.
final DateTime catalogTargetDt = dryRunEffDt != null ? dryRunEffDt.toDateTimeAtStartOfDay(DateTimeZone.UTC).plusDays(1).minusSeconds(1) : utcNow;
final StaticCatalog staticCatalog = catalog.versionForDate(catalogTargetDt);
final Plan plan = isInputSpecNullOrEmpty ? null : staticCatalog.createOrFindPlan(inputSpec, overridesWithContext);
switch(dryRunArguments.getAction()) {
case START_BILLING:
final SubscriptionBase baseSubscription = dao.getBaseSubscription(bundleId, catalog, context);
final DateTime startEffectiveDate = dryRunEffDt != null ? context.toUTCDateTime(dryRunEffDt) : utcNow;
final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, startEffectiveDate, addonUtils, context);
final UUID subscriptionId = UUIDs.randomUUID();
dryRunEvents = apiService.getEventsOnCreation(subscriptionId, startEffectiveDate, bundleStartDate, plan, inputSpec.getPhaseType(), plan.getPriceList().getName(), startEffectiveDate, entitlementSpecifier.getBillCycleDay(), catalog, context);
final SubscriptionBuilder builder = new SubscriptionBuilder().setId(subscriptionId).setBundleId(bundleId).setBundleExternalKey(null).setCategory(plan.getProduct().getCategory()).setBundleStartDate(bundleStartDate).setAlignStartDate(startEffectiveDate);
final DefaultSubscriptionBase newSubscription = new DefaultSubscriptionBase(builder, apiService, clock);
newSubscription.rebuildTransitions(dryRunEvents, catalog);
outputSubscriptions.add(newSubscription);
break;
case CHANGE:
final DefaultSubscriptionBase subscriptionForChange = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), catalog, context);
DateTime changeEffectiveDate = getDryRunEffectiveDate(dryRunEffDt, subscriptionForChange, context);
if (changeEffectiveDate == null) {
BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
if (policy == null) {
final PlanChangeResult planChangeResult = apiService.getPlanChangeResult(subscriptionForChange, inputSpec, utcNow, tenantContext);
policy = planChangeResult.getPolicy();
}
// We pass null for billingAlignment, accountTimezone, account BCD because this is not available which means that dryRun with START_OF_TERM BillingPolicy will fail
changeEffectiveDate = subscriptionForChange.getEffectiveDateForPolicy(policy, null, -1, context);
}
dryRunEvents = apiService.getEventsOnChangePlan(subscriptionForChange, plan, plan.getPriceList().getName(), changeEffectiveDate, true, entitlementSpecifier.getBillCycleDay(), catalog, context);
break;
case STOP_BILLING:
final DefaultSubscriptionBase subscriptionForCancellation = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), catalog, context);
DateTime cancelEffectiveDate = getDryRunEffectiveDate(dryRunEffDt, subscriptionForCancellation, context);
if (dryRunEffDt == null) {
BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
if (policy == null) {
final Plan currentPlan = subscriptionForCancellation.getCurrentPlan();
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(currentPlan.getName(), subscriptionForCancellation.getCurrentPhase().getPhaseType());
policy = catalog.planCancelPolicy(spec, clock.getUTCNow(), subscriptionForCancellation.getStartDate());
}
// We pass null for billingAlignment, accountTimezone, account BCD because this is not available which means that dryRun with START_OF_TERM BillingPolicy will fail
cancelEffectiveDate = subscriptionForCancellation.getEffectiveDateForPolicy(policy, null, -1, context);
}
dryRunEvents = apiService.getEventsOnCancelPlan(subscriptionForCancellation, cancelEffectiveDate, true, catalog, context);
break;
default:
throw new IllegalArgumentException("Unexpected dryRunArguments action " + dryRunArguments.getAction());
}
if (dryRunEvents != null && !dryRunEvents.isEmpty()) {
outputDryRunEvents.addAll(dryRunEvents);
}
}
Aggregations