Search in sources :

Example 6 with SubscriptionCatalog

use of org.killbill.billing.subscription.catalog.SubscriptionCatalog in project killbill by killbill.

the class DefaultSubscriptionBaseApiService method getPlanChangeResult.

@Override
public PlanChangeResult getPlanChangeResult(final DefaultSubscriptionBase subscription, final PlanSpecifier toPlanPhase, final DateTime effectiveDate, final TenantContext context) throws SubscriptionBaseApiException {
    final PlanChangeResult planChangeResult;
    try {
        final InternalTenantContext internalCallContext = createTenantContextFromBundleId(subscription.getBundleId(), context);
        final Plan currentPlan = subscription.getCurrentOrPendingPlan();
        final PlanPhaseSpecifier fromPlanPhase = new PlanPhaseSpecifier(currentPlan.getName(), subscription.getCurrentOrPendingPhase().getPhaseType());
        final SubscriptionCatalog catalog = subscriptionCatalogApi.getFullCatalog(internalCallContext);
        planChangeResult = catalog.getPlanChangeResult(fromPlanPhase, toPlanPhase, effectiveDate);
    } catch (final CatalogApiException e) {
        throw new SubscriptionBaseApiException(e);
    }
    return planChangeResult;
}
Also used : PlanPhaseSpecifier(org.killbill.billing.catalog.api.PlanPhaseSpecifier) InternalTenantContext(org.killbill.billing.callcontext.InternalTenantContext) CatalogApiException(org.killbill.billing.catalog.api.CatalogApiException) PlanChangeResult(org.killbill.billing.catalog.api.PlanChangeResult) Plan(org.killbill.billing.catalog.api.Plan) SubscriptionCatalog(org.killbill.billing.subscription.catalog.SubscriptionCatalog)

Example 7 with SubscriptionCatalog

use of org.killbill.billing.subscription.catalog.SubscriptionCatalog in project killbill by killbill.

the class DefaultSubscriptionBaseApiService method cancel.

@Override
public boolean cancel(final DefaultSubscriptionBase subscription, final CallContext context) throws SubscriptionBaseApiException {
    final Plan currentPlan = subscription.getCurrentOrPendingPlan();
    final PlanPhaseSpecifier planPhase = new PlanPhaseSpecifier(currentPlan.getName(), null);
    try {
        final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
        final SubscriptionCatalog catalog = subscriptionCatalogApi.getFullCatalog(internalCallContext);
        final BillingActionPolicy policy = catalog.planCancelPolicy(planPhase, clock.getUTCNow(), subscription.getStartDate());
        Preconditions.checkState(policy != BillingActionPolicy.START_OF_TERM, "A default START_OF_TERM policy is not availaible");
        final DateTime effectiveDate = subscription.getEffectiveDateForPolicy(policy, null, -1, null);
        return doCancelPlan(ImmutableMap.<DefaultSubscriptionBase, DateTime>of(subscription, effectiveDate), catalog, internalCallContext);
    } catch (final CatalogApiException e) {
        throw new SubscriptionBaseApiException(e);
    }
}
Also used : PlanPhaseSpecifier(org.killbill.billing.catalog.api.PlanPhaseSpecifier) BillingActionPolicy(org.killbill.billing.catalog.api.BillingActionPolicy) CatalogApiException(org.killbill.billing.catalog.api.CatalogApiException) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) Plan(org.killbill.billing.catalog.api.Plan) SubscriptionCatalog(org.killbill.billing.subscription.catalog.SubscriptionCatalog) DateTime(org.joda.time.DateTime)

Example 8 with SubscriptionCatalog

use of org.killbill.billing.subscription.catalog.SubscriptionCatalog in project killbill by killbill.

the class DefaultSubscriptionBaseTransferApi method transferBundle.

@Override
public SubscriptionBaseBundle transferBundle(final UUID sourceAccountId, final UUID destAccountId, final String bundleKey, final DateTime transferDate, final boolean transferAddOn, final boolean cancelImmediately, final CallContext context) throws SubscriptionBaseTransferApiException {
    final InternalCallContext fromInternalCallContext = internalCallContextFactory.createInternalCallContext(sourceAccountId, context);
    final InternalCallContext toInternalCallContext = internalCallContextFactory.createInternalCallContext(destAccountId, context);
    try {
        final SubscriptionCatalog catalog = subscriptionCatalogApi.getFullCatalog(fromInternalCallContext);
        final DateTime effectiveTransferDate = transferDate == null ? context.getCreatedDate() : transferDate;
        if (effectiveTransferDate.isAfter(context.getCreatedDate())) {
            // (subscription always expects the first event to be in the past)
            throw new SubscriptionBaseTransferApiException(ErrorCode.SUB_TRANSFER_INVALID_EFF_DATE, effectiveTransferDate);
        }
        final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getActiveBundleForKey(catalog.getCatalog(), bundleKey, fromInternalCallContext);
        if (bundle == null) {
            throw new SubscriptionBaseTransferApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleKey);
        }
        // Get the bundle timeline for the old account
        final BundleBaseTimeline bundleBaseTimeline = timelineApi.getBundleTimeline(bundle, context);
        final DefaultSubscriptionBaseBundle subscriptionBundleData = new DefaultSubscriptionBaseBundle(bundleKey, destAccountId, effectiveTransferDate, bundle.getOriginalCreatedDate(), context.getCreatedDate(), context.getCreatedDate());
        final List<SubscriptionTransferData> subscriptionTransferDataList = new LinkedList<SubscriptionTransferData>();
        final List<TransferCancelData> transferCancelDataList = new LinkedList<TransferCancelData>();
        DateTime bundleStartdate = null;
        for (final SubscriptionBaseTimeline cur : bundleBaseTimeline.getSubscriptions()) {
            final DefaultSubscriptionBase oldSubscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(cur.getId(), catalog, fromInternalCallContext);
            // Skip already cancelled subscriptions
            if (oldSubscription.getState() == EntitlementState.CANCELLED) {
                continue;
            }
            final List<ExistingEvent> existingEvents = cur.getExistingEvents();
            final ProductCategory productCategory = existingEvents.get(0).getProductCategory();
            // on base plan cancellations, even though we don't support un-transfer today)
            if (productCategory != ProductCategory.ADD_ON || cancelImmediately) {
                // Create the cancelWithRequestedDate event on effectiveCancelDate
                final DateTime effectiveCancelDate = !cancelImmediately && oldSubscription.getChargedThroughDate() != null && effectiveTransferDate.isBefore(oldSubscription.getChargedThroughDate()) ? oldSubscription.getChargedThroughDate() : effectiveTransferDate;
                final SubscriptionBaseEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder().setSubscriptionId(cur.getId()).setEffectiveDate(effectiveCancelDate).setFromDisk(true));
                final TransferCancelData cancelData = new TransferCancelData(oldSubscription, cancelEvent);
                transferCancelDataList.add(cancelData);
            }
            if (productCategory == ProductCategory.ADD_ON && !transferAddOn) {
                continue;
            }
            // We Align with the original subscription
            final DateTime subscriptionAlignStartDate = oldSubscription.getAlignStartDate();
            if (bundleStartdate == null) {
                bundleStartdate = oldSubscription.getStartDate();
            }
            // Create the new subscription for the new bundle on the new account
            final DefaultSubscriptionBase defaultSubscriptionBase = createSubscriptionForApiUse(new SubscriptionBuilder().setId(UUIDs.randomUUID()).setBundleId(subscriptionBundleData.getId()).setBundleExternalKey(subscriptionBundleData.getExternalKey()).setCategory(productCategory).setBundleStartDate(effectiveTransferDate).setAlignStartDate(subscriptionAlignStartDate), ImmutableList.<SubscriptionBaseEvent>of(), catalog, fromInternalCallContext);
            final List<SubscriptionBaseEvent> events = toEvents(existingEvents, defaultSubscriptionBase, effectiveTransferDate, fromInternalCallContext);
            final SubscriptionTransferData curData = new SubscriptionTransferData(defaultSubscriptionBase, events, null);
            subscriptionTransferDataList.add(curData);
        }
        final BundleTransferData bundleTransferData = new BundleTransferData(subscriptionBundleData, subscriptionTransferDataList);
        // Atomically cancelWithRequestedDate all subscription on old account and create new bundle, subscriptions, events for new account
        dao.transfer(sourceAccountId, destAccountId, bundleTransferData, transferCancelDataList, catalog, fromInternalCallContext, toInternalCallContext);
        return bundleTransferData.getData();
    } catch (SubscriptionBaseRepairException e) {
        throw new SubscriptionBaseTransferApiException(e);
    } catch (CatalogApiException e) {
        throw new SubscriptionBaseTransferApiException(e);
    }
}
Also used : InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) SubscriptionBaseTimeline(org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline) SubscriptionCatalog(org.killbill.billing.subscription.catalog.SubscriptionCatalog) DateTime(org.joda.time.DateTime) ApiEventBuilder(org.killbill.billing.subscription.events.user.ApiEventBuilder) SubscriptionBaseBundle(org.killbill.billing.subscription.api.user.SubscriptionBaseBundle) DefaultSubscriptionBaseBundle(org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) ExistingEvent(org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimeline.ExistingEvent) BundleBaseTimeline(org.killbill.billing.subscription.api.timeline.BundleBaseTimeline) SubscriptionBaseRepairException(org.killbill.billing.subscription.api.timeline.SubscriptionBaseRepairException) DefaultSubscriptionBaseBundle(org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle) ApiEventCancel(org.killbill.billing.subscription.events.user.ApiEventCancel) SubscriptionBuilder(org.killbill.billing.subscription.api.user.SubscriptionBuilder) LinkedList(java.util.LinkedList) CatalogApiException(org.killbill.billing.catalog.api.CatalogApiException) ProductCategory(org.killbill.billing.catalog.api.ProductCategory) SubscriptionBaseEvent(org.killbill.billing.subscription.events.SubscriptionBaseEvent)

Example 9 with SubscriptionCatalog

use of org.killbill.billing.subscription.catalog.SubscriptionCatalog in project killbill by killbill.

the class TestUserApiError method setChargedThroughDate.

private void setChargedThroughDate(UUID subscriptionId, DateTime chargedThruDate, InternalCallContext context) throws SubscriptionBaseApiException {
    try {
        final SubscriptionCatalog catalog = subscriptionCatalogApi.getFullCatalog(context);
        final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(subscriptionId, catalog, context);
        final SubscriptionBuilder builder = new SubscriptionBuilder(subscription).setChargedThroughDate(chargedThruDate);
        ((MockSubscriptionDaoMemory) dao).updateChargedThroughDate(new DefaultSubscriptionBase(builder), context);
    } catch (final CatalogApiException e) {
        throw new SubscriptionBaseApiException(e);
    }
}
Also used : CatalogApiException(org.killbill.billing.catalog.api.CatalogApiException) SubscriptionCatalog(org.killbill.billing.subscription.catalog.SubscriptionCatalog) MockSubscriptionDaoMemory(org.killbill.billing.subscription.engine.dao.MockSubscriptionDaoMemory)

Example 10 with SubscriptionCatalog

use of org.killbill.billing.subscription.catalog.SubscriptionCatalog in project killbill by killbill.

the class DefaultSubscriptionBase method getSubscriptionBillingEvents.

public List<SubscriptionBillingEvent> getSubscriptionBillingEvents(final VersionedCatalog publicCatalog) throws SubscriptionBaseApiException {
    if (transitions == null) {
        return Collections.emptyList();
    }
    final SubscriptionCatalog catalog = DefaultSubscriptionCatalogApi.wrapCatalog(publicCatalog, clock);
    try {
        final List<SubscriptionBillingEvent> result = new ArrayList<SubscriptionBillingEvent>();
        final SubscriptionBaseTransitionDataIterator it = new SubscriptionBaseTransitionDataIterator(clock, transitions, Order.ASC_FROM_PAST, Visibility.ALL, TimeLimit.ALL);
        // Recomputed for each event from the active Plan -- if Plan is null (cancellation this is not set)
        StaticCatalog lastActiveCatalog = null;
        final PriorityQueue<SubscriptionBillingEvent> candidatesCatalogChangeEvents = new PriorityQueue<SubscriptionBillingEvent>();
        boolean foundInitialEvent = false;
        SubscriptionBaseTransitionData lastPhaseTransition = null;
        while (it.hasNext()) {
            final SubscriptionBaseTransitionData cur = (SubscriptionBaseTransitionData) it.next();
            final boolean isCreateOrTransfer = cur.getTransitionType() == SubscriptionBaseTransitionType.CREATE || cur.getTransitionType() == SubscriptionBaseTransitionType.TRANSFER;
            final boolean isChangeEvent = cur.getTransitionType() == SubscriptionBaseTransitionType.CHANGE;
            final boolean isPhaseEvent = cur.getTransitionType() == SubscriptionBaseTransitionType.PHASE;
            final boolean isCancelEvent = cur.getTransitionType() == SubscriptionBaseTransitionType.CANCEL;
            if (!foundInitialEvent) {
                foundInitialEvent = isCreateOrTransfer;
            }
            // Remove anything prior to first CREATE
            if (foundInitialEvent) {
                // Track the last transition that may modify the phase -- either from a new Plan or new PlanPhase of the same Plan
                if (isCreateOrTransfer || isChangeEvent || isPhaseEvent) {
                    lastPhaseTransition = cur;
                }
                // Look for any catalog change transition whose date is less the cur event
                SubscriptionBillingEvent prevCandidateForCatalogChangeEvents = candidatesCatalogChangeEvents.poll();
                while (prevCandidateForCatalogChangeEvents != null && prevCandidateForCatalogChangeEvents.getEffectiveDate().compareTo(cur.getEffectiveTransitionTime()) < 0) {
                    result.add(prevCandidateForCatalogChangeEvents);
                    prevCandidateForCatalogChangeEvents = candidatesCatalogChangeEvents.poll();
                }
                // If we see a change or a cancellation and we still have catalog change transitions, we discard them
                if (isChangeEvent || isCancelEvent) {
                    candidatesCatalogChangeEvents.clear();
                } else if (prevCandidateForCatalogChangeEvents != null) {
                    candidatesCatalogChangeEvents.add(prevCandidateForCatalogChangeEvents);
                }
                final Plan plan = cur.getNextPlan();
                final PlanPhase planPhase = cur.getNextPhase();
                if (plan != null) {
                    lastActiveCatalog = plan.getCatalog();
                }
                // Computed from lastActiveCatalog
                final DateTime catalogEffectiveDate = CatalogDateHelper.toUTCDateTime(lastActiveCatalog.getEffectiveDate());
                final SubscriptionBillingEvent billingTransition = new DefaultSubscriptionBillingEvent(cur.getTransitionType(), plan, planPhase, cur.getEffectiveTransitionTime(), cur.getTotalOrdering(), cur.getNextBillingCycleDayLocal(), catalogEffectiveDate);
                result.add(billingTransition);
                if (isCreateOrTransfer || isChangeEvent) {
                    // We are moving to a new Plan, we use the latest active catalog version at the time this operation took place.
                    final StaticCatalog catalogVersion = catalog.versionForDate(billingTransition.getEffectiveDate());
                    final Plan currentPlan = catalogVersion.findPlan(billingTransition.getPlan().getName());
                    // Iterate through all more recent version of the catalog to find possible effectiveDateForExistingSubscriptions transition for this Plan
                    Plan nextPlan = catalog.getNextPlanVersion(currentPlan);
                    while (nextPlan != null) {
                        if (nextPlan.getEffectiveDateForExistingSubscriptions() != null) {
                            final DateTime nextEffectiveDate = new DateTime(nextPlan.getEffectiveDateForExistingSubscriptions()).toDateTime(DateTimeZone.UTC);
                            final PlanPhase nextPlanPhase = nextPlan.findPhase(planPhase.getName());
                            // Computed from the nextPlan
                            final DateTime catalogEffectiveDateForNextPlan = CatalogDateHelper.toUTCDateTime(nextPlan.getCatalog().getEffectiveDate());
                            final SubscriptionBillingEvent newBillingTransition = new DefaultSubscriptionBillingEvent(SubscriptionBaseTransitionType.CHANGE, nextPlan, nextPlanPhase, nextEffectiveDate, cur.getTotalOrdering(), cur.getNextBillingCycleDayLocal(), catalogEffectiveDateForNextPlan);
                            candidatesCatalogChangeEvents.add(newBillingTransition);
                        }
                        nextPlan = catalog.getNextPlanVersion(nextPlan);
                    }
                }
            }
        }
        // If we end up with a PlanPhase which is not evergreen, it means it should STOP at the end of the duration
        // Ideally, we should have a special transition type (e.g 'EXPIRED') and not compute this transition dynamically
        // See https://github.com/killbill/killbill/issues/824
        SubscriptionBillingEvent expiredTransition = null;
        if (lastPhaseTransition != null && lastPhaseTransition.getNextPhase() != null && lastPhaseTransition.getNextPhase().getPhaseType() != PhaseType.EVERGREEN) {
            final DateTime effectiveDate = lastPhaseTransition.getNextPhase().getDuration().addToDateTime(lastPhaseTransition.getEffectiveTransitionTime());
            // Insert the expiredTransition at the right place depending on whether or not we still have pending catalog version transitions
            // (there is a sorting of all billing event in junction, so this is not strictly necessary but cleaner)
            expiredTransition = new DefaultSubscriptionBillingEvent(SubscriptionBaseTransitionType.CANCEL, null, null, effectiveDate, lastPhaseTransition.getTotalOrdering(), lastPhaseTransition.getNextBillingCycleDayLocal(), CatalogDateHelper.toUTCDateTime(lastPhaseTransition.getNextPlan().getCatalog().getEffectiveDate()));
        }
        SubscriptionBillingEvent prevCandidateForCatalogChangeEvents = candidatesCatalogChangeEvents.poll();
        while (prevCandidateForCatalogChangeEvents != null) {
            if (expiredTransition != null && expiredTransition.getEffectiveDate().compareTo(prevCandidateForCatalogChangeEvents.getEffectiveDate()) <= 0) {
                result.add(expiredTransition);
                expiredTransition = null;
            }
            result.add(prevCandidateForCatalogChangeEvents);
            prevCandidateForCatalogChangeEvents = candidatesCatalogChangeEvents.poll();
        }
        if (expiredTransition != null) {
            result.add(expiredTransition);
        }
        return result;
    } catch (final CatalogApiException e) {
        throw new SubscriptionBaseApiException(e);
    }
}
Also used : ArrayList(java.util.ArrayList) PriorityQueue(java.util.PriorityQueue) Plan(org.killbill.billing.catalog.api.Plan) SubscriptionCatalog(org.killbill.billing.subscription.catalog.SubscriptionCatalog) StaticCatalog(org.killbill.billing.catalog.api.StaticCatalog) DateTime(org.joda.time.DateTime) CatalogApiException(org.killbill.billing.catalog.api.CatalogApiException) PlanPhase(org.killbill.billing.catalog.api.PlanPhase)

Aggregations

SubscriptionCatalog (org.killbill.billing.subscription.catalog.SubscriptionCatalog)22 CatalogApiException (org.killbill.billing.catalog.api.CatalogApiException)20 DefaultSubscriptionBase (org.killbill.billing.subscription.api.user.DefaultSubscriptionBase)10 SubscriptionBaseApiException (org.killbill.billing.subscription.api.user.SubscriptionBaseApiException)10 DateTime (org.joda.time.DateTime)9 InternalCallContext (org.killbill.billing.callcontext.InternalCallContext)9 ArrayList (java.util.ArrayList)6 Plan (org.killbill.billing.catalog.api.Plan)5 SubscriptionBase (org.killbill.billing.subscription.api.SubscriptionBase)5 InternalTenantContext (org.killbill.billing.callcontext.InternalTenantContext)4 StaticCatalog (org.killbill.billing.catalog.api.StaticCatalog)4 SubscriptionBaseEvent (org.killbill.billing.subscription.events.SubscriptionBaseEvent)4 LinkedList (java.util.LinkedList)3 PlanPhasePriceOverridesWithCallContext (org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext)3 PlanPhaseSpecifier (org.killbill.billing.catalog.api.PlanPhaseSpecifier)3 ApiEventBuilder (org.killbill.billing.subscription.events.user.ApiEventBuilder)3 CallContext (org.killbill.billing.util.callcontext.CallContext)3 TimedPhase (org.killbill.billing.subscription.alignment.TimedPhase)2 DefaultSubscriptionBaseBundle (org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle)2 PhaseEvent (org.killbill.billing.subscription.events.phase.PhaseEvent)2