use of org.killbill.billing.subscription.api.user.SubscriptionBaseBundle in project killbill by killbill.
the class DefaultEntitlementApi method transferEntitlementsOverrideBillingPolicy.
@Override
public UUID transferEntitlementsOverrideBillingPolicy(final UUID sourceAccountId, final UUID destAccountId, final String externalKey, @Nullable final LocalDate effectiveDate, final BillingActionPolicy billingPolicy, final Iterable<PluginProperty> properties, final CallContext context) throws EntitlementApiException {
logTransferEntitlement(log, sourceAccountId, destAccountId, externalKey, effectiveDate, billingPolicy);
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(null, externalKey, new ArrayList<EntitlementSpecifier>(), effectiveDate, effectiveDate, false);
final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.TRANSFER_BUNDLE, sourceAccountId, destAccountId, baseEntitlementWithAddOnsSpecifierList, billingPolicy, properties, context);
final WithEntitlementPlugin<UUID> transferWithPlugin = new WithEntitlementPlugin<UUID>() {
@Override
public UUID doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
final boolean cancelImm;
switch(billingPolicy) {
case IMMEDIATE:
cancelImm = true;
break;
case END_OF_TERM:
cancelImm = false;
break;
default:
throw new RuntimeException("Unexpected billing policy " + billingPolicy);
}
final InternalCallContext contextWithSourceAccountRecordId = internalCallContextFactory.createInternalCallContext(sourceAccountId, context);
try {
final UUID activeSubscriptionIdForKey = entitlementUtils.getFirstActiveSubscriptionIdForKeyOrNull(externalKey, contextWithSourceAccountRecordId);
final SubscriptionBase baseSubscription = activeSubscriptionIdForKey != null ? subscriptionBaseInternalApi.getSubscriptionFromId(activeSubscriptionIdForKey, contextWithSourceAccountRecordId) : null;
final SubscriptionBaseBundle baseBundle = baseSubscription != null ? subscriptionBaseInternalApi.getBundleFromId(baseSubscription.getBundleId(), contextWithSourceAccountRecordId) : null;
if (baseBundle == null || !baseBundle.getAccountId().equals(sourceAccountId)) {
throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_KEY, externalKey));
}
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = getFirstBaseEntitlementWithAddOnsSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(), contextWithSourceAccountRecordId);
final SubscriptionBaseBundle newBundle = subscriptionBaseTransferApi.transferBundle(sourceAccountId, destAccountId, externalKey, requestedDate, true, cancelImm, context);
final Map<BlockingState, UUID> blockingStates = new HashMap<BlockingState, UUID>();
// Note that there is no un-transfer at the moment, so we effectively add a blocking state on disk for all subscriptions
for (final SubscriptionBase subscriptionBase : subscriptionBaseInternalApi.getSubscriptionsForBundle(baseBundle.getId(), null, contextWithSourceAccountRecordId)) {
final BlockingState blockingState = new DefaultBlockingState(subscriptionBase.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, requestedDate);
blockingStates.put(blockingState, subscriptionBase.getBundleId());
}
entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(blockingStates, contextWithSourceAccountRecordId);
// Add blocking events for transferred subscriptions..
final InternalCallContext contextWithDestAccountRecordId = internalCallContextFactory.createInternalCallContext(destAccountId, context);
blockingStates.clear();
final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), contextWithDestAccountRecordId);
for (final SubscriptionBase subscriptionBase : subscriptionBaseInternalApi.getSubscriptionsForBundle(newBundle.getId(), null, contextWithDestAccountRecordId)) {
final BlockingState newBlockingState = new DefaultBlockingState(subscriptionBase.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
blockingStates.put(newBlockingState, subscriptionBase.getBundleId());
}
entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(blockingStates, contextWithDestAccountRecordId);
return newBundle.getId();
} catch (final SubscriptionBaseTransferApiException e) {
throw new EntitlementApiException(e);
} catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
}
};
return pluginExecution.executeWithPlugin(transferWithPlugin, pluginContext);
}
use of org.killbill.billing.subscription.api.user.SubscriptionBaseBundle in project killbill by killbill.
the class DefaultSubscriptionApi method updateExternalKey.
@Override
public void updateExternalKey(final UUID bundleId, final String newExternalKey, final CallContext callContext) throws EntitlementApiException {
logUpdateExternalKey(log, bundleId, newExternalKey);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContextWithoutAccountRecordId(callContext);
final SubscriptionBaseBundle bundle;
final ImmutableAccountData account;
try {
bundle = subscriptionBaseInternalApi.getBundleFromId(bundleId, internalCallContext);
account = accountApi.getImmutableAccountDataById(bundle.getAccountId(), internalCallContext);
} catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
} catch (AccountApiException e) {
throw new EntitlementApiException(e);
}
final LocalDate effectiveDate = new LocalDate(clock.getUTCNow(), account.getTimeZone());
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(bundleId, newExternalKey, new ArrayList<EntitlementSpecifier>(), effectiveDate, effectiveDate, false);
final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UPDATE_BUNDLE_EXTERNAL_KEY, bundle.getAccountId(), null, baseEntitlementWithAddOnsSpecifierList, null, ImmutableList.<PluginProperty>of(), callContext);
final WithEntitlementPlugin<Void> updateExternalKeyWithPlugin = new WithEntitlementPlugin<Void>() {
final InternalCallContext internalCallContextWithValidAccountId = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
@Override
public Void doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
subscriptionBaseInternalApi.updateExternalKey(bundleId, newExternalKey, internalCallContextWithValidAccountId);
return null;
}
};
pluginExecution.executeWithPlugin(updateExternalKeyWithPlugin, pluginContext);
}
use of org.killbill.billing.subscription.api.user.SubscriptionBaseBundle in project killbill by killbill.
the class EventsStreamBuilder method buildForEntitlement.
public EventsStream buildForEntitlement(final UUID entitlementId, final InternalTenantContext internalTenantContext) throws EntitlementApiException {
final SubscriptionBaseBundle bundle;
final SubscriptionBase subscription;
final List<SubscriptionBase> allSubscriptionsForBundle;
final SubscriptionBase baseSubscription;
try {
subscription = subscriptionInternalApi.getSubscriptionFromId(entitlementId, internalTenantContext);
bundle = subscriptionInternalApi.getBundleFromId(subscription.getBundleId(), internalTenantContext);
allSubscriptionsForBundle = subscriptionInternalApi.getSubscriptionsForBundle(subscription.getBundleId(), null, internalTenantContext);
baseSubscription = findBaseSubscription(allSubscriptionsForBundle);
} catch (SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
final ImmutableAccountData account;
try {
account = accountInternalApi.getImmutableAccountDataById(bundle.getAccountId(), internalTenantContext);
} catch (AccountApiException e) {
throw new EntitlementApiException(e);
}
// Retrieve the blocking states
final List<BlockingState> blockingStatesForAccount = defaultBlockingStateDao.getBlockingAllForAccountRecordId(internalTenantContext);
final Map<UUID, Integer> bcdCache = new HashMap<UUID, Integer>();
return buildForEntitlement(blockingStatesForAccount, account, bundle, baseSubscription, subscription, allSubscriptionsForBundle, bcdCache, internalTenantContext);
}
use of org.killbill.billing.subscription.api.user.SubscriptionBaseBundle 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.SubscriptionBaseBundle in project killbill by killbill.
the class DefaultSubscriptionInternalApi method createBundleForAccount.
@Override
public SubscriptionBaseBundle createBundleForAccount(final UUID accountId, final String bundleKey, final InternalCallContext context) throws SubscriptionBaseApiException {
final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(bundleKey, context);
//
// Because the creation of the SubscriptionBundle is not atomic (with creation of Subscription/SubscriptionEvent), we verify if we were left
// with an empty SubscriptionBaseBundle form a past failing operation (See #684). We only allow reuse if such SubscriptionBaseBundle is fully
// empty (and don't allow use case where all Subscription are cancelled, which is the condition for that key to be re-used)
// Such condition should have been checked upstream (to decide whether that key is valid or not)
//
final SubscriptionBaseBundle existingBundleForAccount = Iterables.tryFind(existingBundles, new Predicate<SubscriptionBaseBundle>() {
@Override
public boolean apply(final SubscriptionBaseBundle input) {
return input.getAccountId().equals(accountId);
}
}).orNull();
// If Bundle already exists, and there is 0 Subscription, we reuse
if (existingBundleForAccount != null) {
try {
final Map<UUID, List<SubscriptionBase>> accountSubscriptions = dao.getSubscriptionsForAccount(context);
final List<SubscriptionBase> subscriptions = accountSubscriptions.get(existingBundleForAccount.getId());
if (subscriptions == null || subscriptions.size() == 0) {
return existingBundleForAccount;
}
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
}
final DateTime now = clock.getUTCNow();
final DateTime originalCreatedDate = !existingBundles.isEmpty() ? existingBundles.get(0).getCreatedDate() : now;
final DefaultSubscriptionBaseBundle bundle = new DefaultSubscriptionBaseBundle(bundleKey, accountId, now, originalCreatedDate, now, now);
if (null != bundleKey && bundleKey.length() > 255) {
throw new SubscriptionBaseApiException(ErrorCode.EXTERNAL_KEY_LIMIT_EXCEEDED);
}
return dao.createSubscriptionBundle(bundle, context);
}
Aggregations