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;
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
Aggregations