Search in sources :

Example 1 with Plan

use of org.killbill.billing.catalog.api.Plan in project killbill by killbill.

the class DefaultSubscriptionInternalApi method verifyAndBuildSubscriptionSpecifiers.

private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final UUID bundleId, final String externalKey, final Iterable<EntitlementSpecifier> entitlements, final boolean isMigrated, final InternalCallContext context, final DateTime now, final DateTime effectiveDate, final Catalog catalog, final CallContext callContext) throws SubscriptionBaseApiException, CatalogApiException {
    final List<SubscriptionSpecifier> subscriptions = new ArrayList<SubscriptionSpecifier>();
    boolean first = true;
    final List<SubscriptionBase> subscriptionsForBundle = getSubscriptionsForBundle(bundleId, null, context);
    for (final EntitlementSpecifier entitlement : entitlements) {
        final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
        final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(entitlement.getOverrides(), callContext);
        final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, effectiveDate);
        final PlanPhase phase = plan.getAllPhases()[0];
        if (phase == null) {
            throw new SubscriptionBaseError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog", spec.getProductName(), spec.getBillingPeriod().toString(), plan.getPriceListName()));
        }
        if (first) {
            first = false;
            if (plan.getProduct().getCategory() != ProductCategory.BASE) {
                throw new SubscriptionBaseApiException(new IllegalArgumentException(), ErrorCode.SUB_CREATE_NO_BP.getCode(), "Missing Base Subscription.");
            }
        }
        // verify the number of subscriptions (of the same kind) allowed per bundle and the existing ones
        if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
            if (plan.getPlansAllowedInBundle() != -1 && plan.getPlansAllowedInBundle() > 0) {
                final int existingAddOnsWithSamePlanName = addonUtils.countExistingAddOnsWithSamePlanName(subscriptionsForBundle, plan.getName());
                final int currentAddOnsWithSamePlanName = countCurrentAddOnsWithSamePlanName(entitlements, catalog, plan.getName(), effectiveDate, callContext);
                if ((existingAddOnsWithSamePlanName + currentAddOnsWithSamePlanName) > plan.getPlansAllowedInBundle()) {
                    // a new ADD_ON subscription of the same plan can't be added because it has reached its limit by bundle
                    throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE, plan.getName());
                }
            }
        }
        final SubscriptionSpecifier subscription = new SubscriptionSpecifier();
        subscription.setRealPriceList(plan.getPriceListName());
        subscription.setEffectiveDate(effectiveDate);
        subscription.setProcessedDate(now);
        subscription.setPlan(plan);
        subscription.setInitialPhase(spec.getPhaseType());
        subscription.setBuilder(new SubscriptionBuilder().setId(UUIDs.randomUUID()).setBundleId(bundleId).setBundleExternalKey(externalKey).setCategory(plan.getProduct().getCategory()).setBundleStartDate(effectiveDate).setAlignStartDate(effectiveDate).setMigrated(isMigrated));
        subscriptions.add(subscription);
    }
    return subscriptions;
}
Also used : SubscriptionSpecifier(org.killbill.billing.subscription.api.user.SubscriptionSpecifier) PlanPhaseSpecifier(org.killbill.billing.catalog.api.PlanPhaseSpecifier) ArrayList(java.util.ArrayList) SubscriptionBuilder(org.killbill.billing.subscription.api.user.SubscriptionBuilder) Plan(org.killbill.billing.catalog.api.Plan) SubscriptionBase(org.killbill.billing.subscription.api.SubscriptionBase) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) EntitlementSpecifier(org.killbill.billing.entitlement.api.EntitlementSpecifier) PlanPhasePriceOverridesWithCallContext(org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext) SubscriptionBaseError(org.killbill.billing.subscription.exceptions.SubscriptionBaseError) PlanPhase(org.killbill.billing.catalog.api.PlanPhase) SubscriptionBaseApiException(org.killbill.billing.subscription.api.user.SubscriptionBaseApiException)

Example 2 with Plan

use of org.killbill.billing.catalog.api.Plan in project killbill by killbill.

the class DefaultSubscriptionInternalApi method getDryRunChangePlanEffectiveDate.

@Override
public DateTime getDryRunChangePlanEffectiveDate(final SubscriptionBase subscription, final PlanSpecifier spec, final DateTime requestedDateWithMs, final BillingActionPolicy requestedPolicy, final List<PlanPhasePriceOverride> overrides, final InternalCallContext context) throws SubscriptionBaseApiException, CatalogApiException {
    final TenantContext tenantContext = internalCallContextFactory.createTenantContext(context);
    final CallContext callContext = internalCallContextFactory.createCallContext(context);
    // verify the number of subscriptions (of the same kind) allowed per bundle
    final Catalog catalog = catalogService.getFullCatalog(true, true, context);
    final DateTime now = clock.getUTCNow();
    final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
    final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, callContext);
    final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, effectiveDate);
    if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
        if (plan.getPlansAllowedInBundle() != -1 && plan.getPlansAllowedInBundle() > 0 && addonUtils.countExistingAddOnsWithSamePlanName(getSubscriptionsForBundle(subscription.getBundleId(), null, context), plan.getName()) >= plan.getPlansAllowedInBundle()) {
            // the plan can be changed to the new value, because it has reached its limit by bundle
            throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE, plan.getName());
        }
    }
    return apiService.dryRunChangePlan((DefaultSubscriptionBase) subscription, spec, requestedDateWithMs, requestedPolicy, tenantContext);
}
Also used : PlanPhasePriceOverridesWithCallContext(org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext) InternalTenantContext(org.killbill.billing.callcontext.InternalTenantContext) TenantContext(org.killbill.billing.util.callcontext.TenantContext) Plan(org.killbill.billing.catalog.api.Plan) PlanPhasePriceOverridesWithCallContext(org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext) CallContext(org.killbill.billing.util.callcontext.CallContext) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) Catalog(org.killbill.billing.catalog.api.Catalog) DateTime(org.joda.time.DateTime) SubscriptionBaseApiException(org.killbill.billing.subscription.api.user.SubscriptionBaseApiException) PlanPhasePriceOverride(org.killbill.billing.catalog.api.PlanPhasePriceOverride)

Example 3 with Plan

use of org.killbill.billing.catalog.api.Plan in project killbill by killbill.

the class DefaultSubscriptionDao method buildBundleSubscriptions.

private List<SubscriptionBase> buildBundleSubscriptions(final List<SubscriptionBase> input, @Nullable final Multimap<UUID, SubscriptionBaseEvent> eventsForSubscription, @Nullable final Collection<SubscriptionBaseEvent> dryRunEvents, final InternalTenantContext context) throws CatalogApiException {
    if (input == null || input.isEmpty()) {
        return Collections.emptyList();
    }
    // Make sure BasePlan -- if exists-- is first
    Collections.sort(input, DefaultSubscriptionInternalApi.SUBSCRIPTIONS_COMPARATOR);
    final Collection<ApiEventChange> baseChangeEvents = new LinkedList<ApiEventChange>();
    ApiEventCancel baseCancellationEvent = null;
    final List<SubscriptionBase> result = new ArrayList<SubscriptionBase>(input.size());
    for (final SubscriptionBase cur : input) {
        final List<SubscriptionBaseEvent> events = eventsForSubscription != null ? (List<SubscriptionBaseEvent>) eventsForSubscription.get(cur.getId()) : getEventsForSubscription(cur.getId(), context);
        mergeDryRunEvents(cur.getId(), events, dryRunEvents);
        SubscriptionBase reloaded = createSubscriptionForInternalUse(cur, events, context);
        switch(cur.getCategory()) {
            case BASE:
                for (final SubscriptionBaseEvent event : events) {
                    if (!event.isActive()) {
                        continue;
                    } else if (event instanceof ApiEventCancel) {
                        baseCancellationEvent = (ApiEventCancel) event;
                        break;
                    } else if (event instanceof ApiEventChange) {
                        // Need to track all changes, see https://github.com/killbill/killbill/issues/268
                        baseChangeEvents.add((ApiEventChange) event);
                    }
                }
                break;
            case ADD_ON:
                final Plan targetAddOnPlan = reloaded.getCurrentPlan();
                if (targetAddOnPlan == null || reloaded.getFutureEndDate() != null) {
                    // triggers another cancellation before?
                    break;
                }
                SubscriptionBaseEvent baseTriggerEventForAddOnCancellation = baseCancellationEvent;
                for (final ApiEventChange baseChangeEvent : baseChangeEvents) {
                    final String baseProductName = baseChangeEvent.getEventPlan();
                    if ((!addonUtils.isAddonAvailableFromPlanName(baseProductName, targetAddOnPlan, baseChangeEvent.getEffectiveDate(), context)) || (addonUtils.isAddonIncludedFromPlanName(baseProductName, targetAddOnPlan, baseChangeEvent.getEffectiveDate(), context))) {
                        if (baseTriggerEventForAddOnCancellation != null) {
                            if (baseTriggerEventForAddOnCancellation.getEffectiveDate().isAfter(baseChangeEvent.getEffectiveDate())) {
                                baseTriggerEventForAddOnCancellation = baseChangeEvent;
                            }
                        } else {
                            baseTriggerEventForAddOnCancellation = baseChangeEvent;
                        }
                    }
                }
                if (baseTriggerEventForAddOnCancellation != null) {
                    final DateTime now = clock.getUTCNow();
                    final SubscriptionBaseEvent addOnCancelEvent = new ApiEventCancel(new ApiEventBuilder().setSubscriptionId(reloaded.getId()).setEffectiveDate(baseTriggerEventForAddOnCancellation.getEffectiveDate()).setCreatedDate(baseTriggerEventForAddOnCancellation.getCreatedDate()).setFromDisk(false));
                    events.add(addOnCancelEvent);
                    // Finally reload subscription with full set of events
                    reloaded = createSubscriptionForInternalUse(cur, events, context);
                }
                break;
            default:
                break;
        }
        result.add(reloaded);
    }
    return result;
}
Also used : ArrayList(java.util.ArrayList) ApiEventCancel(org.killbill.billing.subscription.events.user.ApiEventCancel) Plan(org.killbill.billing.catalog.api.Plan) LinkedList(java.util.LinkedList) DateTime(org.joda.time.DateTime) SubscriptionBase(org.killbill.billing.subscription.api.SubscriptionBase) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) ApiEventBuilder(org.killbill.billing.subscription.events.user.ApiEventBuilder) SubscriptionBaseEvent(org.killbill.billing.subscription.events.SubscriptionBaseEvent) ApiEventChange(org.killbill.billing.subscription.events.user.ApiEventChange)

Example 4 with Plan

use of org.killbill.billing.catalog.api.Plan in project killbill by killbill.

the class TestTransfer method testTransferWithAO.

@Test(groups = "slow")
public void testTransferWithAO() throws Exception {
    final String baseProduct = "Shotgun";
    final BillingPeriod baseTerm = BillingPeriod.MONTHLY;
    final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
    // CREATE BP
    final SubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList);
    // MOVE 3 DAYS AND CREATE AO1
    clock.addDays(3);
    final String aoProduct1 = "Telescopic-Scope";
    final BillingPeriod aoTerm1 = BillingPeriod.MONTHLY;
    final DefaultSubscriptionBase aoSubscription1 = testUtil.createSubscription(bundle, aoProduct1, aoTerm1, basePriceList);
    assertEquals(aoSubscription1.getState(), EntitlementState.ACTIVE);
    // MOVE ANOTHER 25 DAYS AND CREATE AO2 [ BP STILL IN TRIAL]
    // LASER-SCOPE IS SUBSCRIPTION ALIGN SO EVERGREN WILL ONLY START IN A MONTH
    clock.addDays(25);
    final String aoProduct2 = "Laser-Scope";
    final BillingPeriod aoTerm2 = BillingPeriod.MONTHLY;
    final DefaultSubscriptionBase aoSubscription2 = testUtil.createSubscription(bundle, aoProduct2, aoTerm2, basePriceList);
    assertEquals(aoSubscription2.getState(), EntitlementState.ACTIVE);
    // MOVE AFTER TRIAL AND AO DISCOUNT PHASE [LASER SCOPE STILL IN DISCOUNT]
    testListener.pushExpectedEvent(NextEvent.PHASE);
    testListener.pushExpectedEvent(NextEvent.PHASE);
    clock.addDays(5);
    assertListenerStatus();
    // SET CTD TO TRIGGER CANCELLATION EOT
    final DateTime ctd = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
    subscriptionInternalApi.setChargedThroughDate(baseSubscription.getId(), ctd, internalCallContext);
    final DateTime transferRequestedDate = clock.getUTCNow();
    testListener.pushExpectedEvent(NextEvent.TRANSFER);
    testListener.pushExpectedEvent(NextEvent.TRANSFER);
    testListener.pushExpectedEvent(NextEvent.TRANSFER);
    transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getExternalKey(), transferRequestedDate, true, false, callContext);
    assertListenerStatus();
    // RETRIEVE NEW BUNDLE AND CHECK SUBSCRIPTIONS
    final List<SubscriptionBaseBundle> bundlesForAccountAndKey = subscriptionInternalApi.getBundlesForAccountAndKey(newAccountId, bundle.getExternalKey(), internalCallContext);
    assertEquals(bundlesForAccountAndKey.size(), 1);
    final SubscriptionBaseBundle newBundle = bundlesForAccountAndKey.get(0);
    final List<SubscriptionBase> subscriptions = subscriptionInternalApi.getSubscriptionsForBundle(newBundle.getId(), null, internalCallContext);
    assertEquals(subscriptions.size(), 3);
    boolean foundBP = false;
    boolean foundAO1 = false;
    boolean foundAO2 = false;
    for (final SubscriptionBase cur : subscriptions) {
        final Plan curPlan = cur.getCurrentPlan();
        final Product curProduct = curPlan.getProduct();
        if (curProduct.getName().equals(baseProduct)) {
            foundBP = true;
            assertTrue(((DefaultSubscriptionBase) cur).getAlignStartDate().compareTo(((DefaultSubscriptionBase) baseSubscription).getAlignStartDate()) == 0);
            assertNull(cur.getPendingTransition());
        } else if (curProduct.getName().equals(aoProduct1)) {
            foundAO1 = true;
            assertTrue(((DefaultSubscriptionBase) cur).getAlignStartDate().compareTo((aoSubscription1).getAlignStartDate()) == 0);
            assertNull(cur.getPendingTransition());
        } else if (curProduct.getName().equals(aoProduct2)) {
            foundAO2 = true;
            assertTrue(((DefaultSubscriptionBase) cur).getAlignStartDate().compareTo((aoSubscription2).getAlignStartDate()) == 0);
            assertNotNull(cur.getPendingTransition());
        } else {
            Assert.fail("Unexpected product " + curProduct.getName());
        }
    }
    assertTrue(foundBP);
    assertTrue(foundAO1);
    assertTrue(foundAO2);
    // MOVE AFTER CANCEL DATE TO TRIGGER OLD SUBSCRIPTIONS CANCELLATION + LASER_SCOPE PHASE EVENT
    testListener.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE);
    testListener.pushExpectedEvent(NextEvent.CANCEL);
    testListener.pushExpectedEvent(NextEvent.CANCEL);
    testListener.pushExpectedEvent(NextEvent.CANCEL);
    clock.addMonths(1);
    assertListenerStatus();
    // ISSUE ANOTHER TRANSFER TO CHECK THAT WE CAN TRANSFER AGAIN-- NOTE WILL NOT WORK ON PREVIOUS ACCOUNT (LIMITATION)
    final DateTime newTransferRequestedDate = clock.getUTCNow();
    testListener.pushExpectedEvent(NextEvent.CANCEL);
    testListener.pushExpectedEvent(NextEvent.TRANSFER);
    testListener.pushExpectedEvent(NextEvent.TRANSFER);
    testListener.pushExpectedEvent(NextEvent.TRANSFER);
    transferApi.transferBundle(newBundle.getAccountId(), finalNewAccountId, newBundle.getExternalKey(), newTransferRequestedDate, true, false, callContext);
    assertListenerStatus();
}
Also used : SubscriptionBase(org.killbill.billing.subscription.api.SubscriptionBase) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) BillingPeriod(org.killbill.billing.catalog.api.BillingPeriod) SubscriptionBaseBundle(org.killbill.billing.subscription.api.user.SubscriptionBaseBundle) Product(org.killbill.billing.catalog.api.Product) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) Plan(org.killbill.billing.catalog.api.Plan) DateTime(org.joda.time.DateTime) Test(org.testng.annotations.Test)

Example 5 with Plan

use of org.killbill.billing.catalog.api.Plan in project killbill by killbill.

the class TestTransfer method testTransferBPNoTrialWithNoCTD.

@Test(groups = "slow")
public void testTransferBPNoTrialWithNoCTD() throws Exception {
    final String baseProduct = "Shotgun";
    final BillingPeriod baseTerm = BillingPeriod.MONTHLY;
    final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
    // CREATE BP
    final SubscriptionBase baseSubscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList);
    // MOVE AFTER TRIAL
    testListener.pushExpectedEvent(NextEvent.PHASE);
    clock.addDays(40);
    assertListenerStatus();
    final DateTime beforeTransferDate = clock.getUTCNow();
    final DateTime transferRequestedDate = clock.getUTCNow();
    testListener.pushExpectedEvent(NextEvent.TRANSFER);
    testListener.pushExpectedEvent(NextEvent.CANCEL);
    transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getExternalKey(), transferRequestedDate, false, false, callContext);
    assertListenerStatus();
    final DateTime afterTransferDate = clock.getUTCNow();
    // CHECK OLD BASE IS CANCEL AT THE TRANSFER DATE
    final SubscriptionBase oldBaseSubscription = subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext);
    assertNotNull(oldBaseSubscription.getEndDate());
    testUtil.assertDateWithin(oldBaseSubscription.getEndDate(), beforeTransferDate, afterTransferDate);
    assertTrue(oldBaseSubscription.getEndDate().compareTo(transferRequestedDate) == 0);
    // CHECK NEW BUNDLE EXIST, WITH ONE SUBSCRIPTION STARTING ON TRANSFER_DATE
    final List<SubscriptionBaseBundle> bundlesForAccountAndKey = subscriptionInternalApi.getBundlesForAccountAndKey(newAccountId, bundle.getExternalKey(), internalCallContext);
    assertEquals(bundlesForAccountAndKey.size(), 1);
    final SubscriptionBaseBundle newBundle = bundlesForAccountAndKey.get(0);
    final List<SubscriptionBase> subscriptions = subscriptionInternalApi.getSubscriptionsForBundle(newBundle.getId(), null, internalCallContext);
    assertEquals(subscriptions.size(), 1);
    final SubscriptionBase newBaseSubscription = subscriptions.get(0);
    assertTrue(((DefaultSubscriptionBase) newBaseSubscription).getAlignStartDate().compareTo(((DefaultSubscriptionBase) baseSubscription).getAlignStartDate()) == 0);
    // CHECK ONLY ONE PHASE EXISTS
    assertEquals(subscriptionInternalApi.getAllTransitions(newBaseSubscription, internalCallContext).size(), 1);
    final Plan newPlan = newBaseSubscription.getCurrentPlan();
    assertEquals(newPlan.getProduct().getName(), baseProduct);
    assertEquals(newBaseSubscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
}
Also used : SubscriptionBase(org.killbill.billing.subscription.api.SubscriptionBase) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) BillingPeriod(org.killbill.billing.catalog.api.BillingPeriod) SubscriptionBaseBundle(org.killbill.billing.subscription.api.user.SubscriptionBaseBundle) Plan(org.killbill.billing.catalog.api.Plan) DateTime(org.joda.time.DateTime) Test(org.testng.annotations.Test)

Aggregations

Plan (org.killbill.billing.catalog.api.Plan)131 Test (org.testng.annotations.Test)88 PlanPhase (org.killbill.billing.catalog.api.PlanPhase)77 DateTime (org.joda.time.DateTime)57 MockPlan (org.killbill.billing.catalog.MockPlan)51 BigDecimal (java.math.BigDecimal)50 MockPlanPhase (org.killbill.billing.catalog.MockPlanPhase)50 LocalDate (org.joda.time.LocalDate)47 BillingEventSet (org.killbill.billing.junction.BillingEventSet)44 BillingEvent (org.killbill.billing.junction.BillingEvent)42 MockBillingEventSet (org.killbill.billing.invoice.MockBillingEventSet)41 Invoice (org.killbill.billing.invoice.api.Invoice)41 UUID (java.util.UUID)38 DefaultInvoice (org.killbill.billing.invoice.model.DefaultInvoice)37 SubscriptionBase (org.killbill.billing.subscription.api.SubscriptionBase)31 ArrayList (java.util.ArrayList)27 DefaultPrice (org.killbill.billing.catalog.DefaultPrice)24 MockInternationalPrice (org.killbill.billing.catalog.MockInternationalPrice)23 RecurringInvoiceItem (org.killbill.billing.invoice.model.RecurringInvoiceItem)23 InvoiceItem (org.killbill.billing.invoice.api.InvoiceItem)22