use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class TestPlanAligner method testChangeWithTargetPhaseType2.
//
// Scenario : change Plan with CHANGE_OF_PLAN to a new Plan that has {DISCOUNT, EVERGREEN} and specifying a target PhaseType = EVERGREEN
//
@Test(groups = "fast")
public void testChangeWithTargetPhaseType2() throws Exception {
final String productName = "pistol-monthly";
final DateTime now = clock.getUTCNow();
final DateTime bundleStartDate = now;
final DateTime alignStartDate = bundleStartDate;
final Plan plan = currentVersion.findPlan(productName);
final TimedPhase[] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, null, PriceListSet.DEFAULT_PRICELIST_NAME, now, catalog, internalCallContext);
Assert.assertEquals(phases.length, 2);
Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.TRIAL);
Assert.assertEquals(phases[0].getStartPhase(), now);
Assert.assertEquals(phases[1].getPhase().getPhaseType(), PhaseType.EVERGREEN);
Assert.assertEquals(phases[1].getStartPhase(), now.plusDays(30));
final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, PhaseType.TRIAL);
final String newProductName = "assault-rifle-annual-rescue";
final Plan newPlan = currentVersion.findPlan(newProductName);
final DateTime effectiveChangeDate = defaultSubscriptionBase.getStartDate().plusDays(5);
final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.EVERGREEN, catalog, internalCallContext);
// We end up straight on EVERGREEN Phase and because we are CHANGE_OF_PLAN aligned the start is at the effective date of the change
Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate.plusDays(5));
Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
final TimedPhase nextPhase = planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.EVERGREEN, catalog, internalCallContext);
Assert.assertNull(nextPhase);
}
use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class DefaultSubscriptionBaseService method processEventReady.
@Override
public void processEventReady(final SubscriptionBaseEvent event, final int seqId, final InternalCallContext context) {
if (!event.isActive()) {
return;
}
try {
final SubscriptionCatalog catalog = subscriptionCatalogApi.getFullCatalog(context);
final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(event.getSubscriptionId(), catalog, context);
if (subscription == null) {
log.warn("Error retrieving subscriptionId='{}'", event.getSubscriptionId());
return;
}
final SubscriptionBaseTransitionData transition = subscription.getTransitionFromEvent(event, seqId);
if (transition == null) {
log.warn("Skipping event ='{}', no matching transition was built", event.getType());
return;
}
boolean eventSent = false;
if (event.getType() == EventType.PHASE) {
eventSent = onPhaseEvent(subscription, event, catalog, context);
} else if (event.getType() == EventType.API_USER && subscription.getCategory() == ProductCategory.BASE) {
final CallContext callContext = internalCallContextFactory.createCallContext(context);
eventSent = onBasePlanEvent(subscription, event, catalog, callContext);
} else if (event.getType() == EventType.BCD_UPDATE) {
eventSent = false;
}
if (!eventSent) {
// Methods above invoking the DAO will send this event directly from the transaction
final BusEvent busEvent = new DefaultEffectiveSubscriptionEvent(transition, subscription.getAlignStartDate(), context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
eventBus.post(busEvent);
}
} catch (final EventBusException e) {
log.warn("Failed to post event {}", event, e);
} catch (final CatalogApiException e) {
log.warn("Failed to post event {}", event, e);
}
}
use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class DefaultSubscriptionInternalApi method setChargedThroughDate.
@Override
public void setChargedThroughDate(final UUID subscriptionId, final DateTime chargedThruDate, final InternalCallContext context) throws SubscriptionBaseApiException {
try {
final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(subscriptionId, context);
final SubscriptionBuilder builder = new SubscriptionBuilder(subscription).setChargedThroughDate(chargedThruDate);
dao.updateChargedThroughDate(new DefaultSubscriptionBase(builder), context);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
}
use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class DefaultSubscriptionInternalApi method createSubscription.
@Override
public SubscriptionBase createSubscription(final UUID bundleId, final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDateWithMs, final boolean isMigrated, final InternalCallContext context) throws SubscriptionBaseApiException {
try {
final DateTime now = clock.getUTCNow();
final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
/*
if (requestedDate.isAfter(now)) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_INVALID_REQUESTED_DATE, now.toString(), requestedDate.toString());
}
*/
final CallContext callContext = internalCallContextFactory.createCallContext(context);
final Catalog catalog = catalogService.getFullCatalog(true, true, context);
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, 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()));
}
final SubscriptionBaseBundle bundle = dao.getSubscriptionBundleFromId(bundleId, context);
if (bundle == null) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleId);
}
final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, context);
// verify the number of subscriptions (of the same kind) allowed per bundle
if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
if (plan.getPlansAllowedInBundle() != -1 && plan.getPlansAllowedInBundle() > 0 && addonUtils.countExistingAddOnsWithSamePlanName(getSubscriptionsForBundle(bundleId, null, context), plan.getName()) >= 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 DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, effectiveDate, context);
return apiService.createPlan(new SubscriptionBuilder().setId(UUIDs.randomUUID()).setBundleId(bundleId).setBundleExternalKey(bundle.getExternalKey()).setCategory(plan.getProduct().getCategory()).setBundleStartDate(bundleStartDate).setAlignStartDate(effectiveDate).setMigrated(isMigrated), plan, spec.getPhaseType(), plan.getPriceListName(), effectiveDate, now, callContext);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
}
use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class DefaultSubscriptionInternalApi method populateDryRunEvents.
private void populateDryRunEvents(@Nullable final UUID bundleId, @Nullable final DryRunArguments dryRunArguments, final Collection<SubscriptionBaseEvent> outputDryRunEvents, final Collection<SubscriptionBase> outputSubscriptions, final InternalTenantContext context) throws SubscriptionBaseApiException {
if (dryRunArguments == null || dryRunArguments.getAction() == null) {
return;
}
final DateTime utcNow = clock.getUTCNow();
List<SubscriptionBaseEvent> dryRunEvents = null;
try {
final PlanPhaseSpecifier inputSpec = dryRunArguments.getPlanPhaseSpecifier();
final boolean isInputSpecNullOrEmpty = inputSpec == null || (inputSpec.getPlanName() == null && inputSpec.getProductName() == null && inputSpec.getBillingPeriod() == null);
final Catalog catalog = catalogService.getFullCatalog(true, true, context);
// Create an overridesWithContext with a null context to indicate this is dryRun and no price overriden plan should be created.
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(dryRunArguments.getPlanPhasePriceOverrides(), null);
final Plan plan = isInputSpecNullOrEmpty ? null : catalog.createOrFindPlan(inputSpec, overridesWithContext, utcNow);
final TenantContext tenantContext = internalCallContextFactory.createTenantContext(context);
switch(dryRunArguments.getAction()) {
case START_BILLING:
final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, context);
final DateTime startEffectiveDate = dryRunArguments.getEffectiveDate() != null ? context.toUTCDateTime(dryRunArguments.getEffectiveDate()) : utcNow;
final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, startEffectiveDate, context);
final UUID subscriptionId = UUIDs.randomUUID();
dryRunEvents = apiService.getEventsOnCreation(bundleId, subscriptionId, startEffectiveDate, bundleStartDate, plan, inputSpec.getPhaseType(), plan.getPriceListName(), startEffectiveDate, utcNow, 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(), context);
DateTime changeEffectiveDate = dryRunArguments.getEffectiveDate() != null ? context.toUTCDateTime(dryRunArguments.getEffectiveDate()) : null;
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.getPlanChangeEffectiveDate(policy, null, null, -1, context);
}
dryRunEvents = apiService.getEventsOnChangePlan(subscriptionForChange, plan, plan.getPriceListName(), changeEffectiveDate, utcNow, true, context);
break;
case STOP_BILLING:
final DefaultSubscriptionBase subscriptionForCancellation = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), context);
DateTime cancelEffectiveDate = dryRunArguments.getEffectiveDate() != null ? context.toUTCDateTime(dryRunArguments.getEffectiveDate()) : null;
if (dryRunArguments.getEffectiveDate() == null) {
BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
if (policy == null) {
final Plan currentPlan = subscriptionForCancellation.getCurrentPlan();
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(currentPlan.getName(), subscriptionForCancellation.getCurrentPhase().getPhaseType());
policy = catalogService.getFullCatalog(true, true, context).planCancelPolicy(spec, utcNow);
}
// 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.getPlanChangeEffectiveDate(policy, null, null, -1, context);
}
dryRunEvents = apiService.getEventsOnCancelPlan(subscriptionForCancellation, cancelEffectiveDate, utcNow, true, context);
break;
default:
throw new IllegalArgumentException("Unexpected dryRunArguments action " + dryRunArguments.getAction());
}
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
if (dryRunEvents != null && !dryRunEvents.isEmpty()) {
outputDryRunEvents.addAll(dryRunEvents);
}
}
Aggregations