Search in sources :

Example 6 with SubscriptionBaseEvent

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();
                }
            }
        }
    }
}
Also used : ApiEvent(org.killbill.billing.subscription.events.user.ApiEvent) SubscriptionBaseEvent(org.killbill.billing.subscription.events.SubscriptionBaseEvent) PlanPhasePriceOverride(org.killbill.billing.catalog.api.PlanPhasePriceOverride)

Example 7 with SubscriptionBaseEvent

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);
}
Also used : SubscriptionNotificationKey(org.killbill.billing.subscription.engine.core.SubscriptionNotificationKey) SubscriptionBaseEvent(org.killbill.billing.subscription.events.SubscriptionBaseEvent)

Example 8 with SubscriptionBaseEvent

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);
            }
        }
    }
}
Also used : ApiEvent(org.killbill.billing.subscription.events.user.ApiEvent) SubscriptionBaseEvent(org.killbill.billing.subscription.events.SubscriptionBaseEvent)

Example 9 with SubscriptionBaseEvent

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);
}
Also used : SubscriptionNotificationKey(org.killbill.billing.subscription.engine.core.SubscriptionNotificationKey) SubscriptionBase(org.killbill.billing.subscription.api.SubscriptionBase) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) SubscriptionBaseEvent(org.killbill.billing.subscription.events.SubscriptionBaseEvent)

Example 10 with SubscriptionBaseEvent

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);
    }
}
Also used : PlanPhaseSpecifier(org.killbill.billing.catalog.api.PlanPhaseSpecifier) BillingActionPolicy(org.killbill.billing.catalog.api.BillingActionPolicy) PlanChangeResult(org.killbill.billing.catalog.api.PlanChangeResult) SubscriptionBuilder(org.killbill.billing.subscription.api.user.SubscriptionBuilder) Plan(org.killbill.billing.catalog.api.Plan) LocalDate(org.joda.time.LocalDate) StaticCatalog(org.killbill.billing.catalog.api.StaticCatalog) DateTime(org.joda.time.DateTime) DefaultPlanPhasePriceOverridesWithCallContext(org.killbill.billing.subscription.api.svcs.DefaultPlanPhasePriceOverridesWithCallContext) EntitlementSpecifier(org.killbill.billing.entitlement.api.EntitlementSpecifier) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) PlanPhasePriceOverridesWithCallContext(org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext) DefaultPlanPhasePriceOverridesWithCallContext(org.killbill.billing.subscription.api.svcs.DefaultPlanPhasePriceOverridesWithCallContext) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) UUID(java.util.UUID) SubscriptionBaseEvent(org.killbill.billing.subscription.events.SubscriptionBaseEvent)

Aggregations

SubscriptionBaseEvent (org.killbill.billing.subscription.events.SubscriptionBaseEvent)75 DateTime (org.joda.time.DateTime)31 ApiEventBuilder (org.killbill.billing.subscription.events.user.ApiEventBuilder)26 LinkedList (java.util.LinkedList)25 ArrayList (java.util.ArrayList)23 UUID (java.util.UUID)22 DefaultSubscriptionBase (org.killbill.billing.subscription.api.user.DefaultSubscriptionBase)21 InternalCallContext (org.killbill.billing.callcontext.InternalCallContext)16 CatalogApiException (org.killbill.billing.catalog.api.CatalogApiException)14 Test (org.testng.annotations.Test)14 Plan (org.killbill.billing.catalog.api.Plan)13 PhaseEvent (org.killbill.billing.subscription.events.phase.PhaseEvent)13 ApiEvent (org.killbill.billing.subscription.events.user.ApiEvent)13 ApiEventCancel (org.killbill.billing.subscription.events.user.ApiEventCancel)12 ApiEventCreate (org.killbill.billing.subscription.events.user.ApiEventCreate)11 SubscriptionBase (org.killbill.billing.subscription.api.SubscriptionBase)10 SubscriptionBuilder (org.killbill.billing.subscription.api.user.SubscriptionBuilder)9 SubscriptionNotificationKey (org.killbill.billing.subscription.engine.core.SubscriptionNotificationKey)8 ApiEventChange (org.killbill.billing.subscription.events.user.ApiEventChange)8 ImmutableList (com.google.common.collect.ImmutableList)7