use of org.killbill.billing.subscription.api.user.SubscriptionBaseApiException in project killbill by killbill.
the class DefaultSubscriptionBaseCreateApi method createPlansIfNeededAndReorderBPOrStandaloneSpecFirstWithSanity.
private boolean createPlansIfNeededAndReorderBPOrStandaloneSpecFirstWithSanity(final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier, final SubscriptionCatalog catalog, final DateTime effectiveDate, final Collection<EntitlementSpecifier> outputEntitlementSpecifier, final Collection<Plan> outputEntitlementPlans, final CallContext callContext) throws SubscriptionBaseApiException, CatalogApiException {
EntitlementSpecifier basePlanSpecifier = null;
Plan basePlan = null;
final Collection<EntitlementSpecifier> addOnSpecifiers = new ArrayList<EntitlementSpecifier>();
final Collection<EntitlementSpecifier> standaloneSpecifiers = new ArrayList<EntitlementSpecifier>();
final Collection<Plan> addOnsPlans = new ArrayList<Plan>();
final Collection<Plan> standalonePlans = new ArrayList<Plan>();
for (final EntitlementSpecifier cur : subscriptionBaseWithAddOnsSpecifier.getEntitlementSpecifiers()) {
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(cur.getOverrides(), callContext);
// Called by createBaseSubscriptionsWithAddOns only -- no need for subscription start date
final StaticCatalog catalogVersion = catalog.versionForDate(effectiveDate);
final Plan plan = catalogVersion.createOrFindPlan(cur.getPlanPhaseSpecifier(), overridesWithContext);
final boolean isBase = isBaseSpecifier(plan);
final boolean isStandalone = isStandaloneSpecifier(plan);
if (isStandalone) {
standaloneSpecifiers.add(cur);
standalonePlans.add(plan);
} else if (isBase) {
if (basePlanSpecifier == null) {
basePlanSpecifier = cur;
basePlan = plan;
} else {
log.warn("createPlansIfNeededAndReorderBPOrStandaloneSpecFirstWithSanity: multiple basePlanSpecifier");
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER, "multiple base plan specifiers");
}
} else {
addOnSpecifiers.add(cur);
addOnsPlans.add(plan);
}
}
if (basePlanSpecifier != null) {
outputEntitlementSpecifier.add(basePlanSpecifier);
outputEntitlementPlans.add(basePlan);
}
outputEntitlementSpecifier.addAll(addOnSpecifiers);
outputEntitlementPlans.addAll(addOnsPlans);
if (!outputEntitlementSpecifier.isEmpty() && !standaloneSpecifiers.isEmpty()) {
log.warn("createPlansIfNeededAndReorderBPOrStandaloneSpecFirstWithSanity: both outputEntitlementSpecifier and standaloneSpecifiers specified");
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER, "mix of base/add-on specifier(s) and standalone specifier(s)");
}
if (standaloneSpecifiers.isEmpty()) {
return basePlanSpecifier != null;
} else {
outputEntitlementSpecifier.addAll(standaloneSpecifiers);
outputEntitlementPlans.addAll(standalonePlans);
return true;
}
}
use of org.killbill.billing.subscription.api.user.SubscriptionBaseApiException in project killbill by killbill.
the class DefaultSubscriptionBaseCreateApi method prepareSubscriptionAndAddOnsSpecifier.
private void prepareSubscriptionAndAddOnsSpecifier(final Collection<SubscriptionAndAddOnsSpecifier> subscriptionAndAddOns, final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier, final boolean renameCancelledBundleIfExist, final SubscriptionCatalog catalog, final AddonUtils addonUtils, final CacheController<UUID, UUID> accountIdCacheController, final CallContext callContext, final InternalCallContext context) throws SubscriptionBaseApiException, CatalogApiException {
SubscriptionBaseBundle bundle = getBundleWithSanity(subscriptionBaseWithAddOnsSpecifier, catalog, callContext, context);
final DateTime billingRequestedDateRaw = (subscriptionBaseWithAddOnsSpecifier.getBillingEffectiveDate() != null) ? context.toUTCDateTime(subscriptionBaseWithAddOnsSpecifier.getBillingEffectiveDate()) : context.getCreatedDate();
final SubscriptionBase baseSubscription;
final DateTime billingRequestedDate;
if (bundle != null) {
baseSubscription = dao.getBaseSubscription(bundle.getId(), catalog, context);
billingRequestedDate = computeActualBillingRequestedDate(bundle, billingRequestedDateRaw, baseSubscription, catalog, context);
} else {
baseSubscription = null;
billingRequestedDate = billingRequestedDateRaw;
}
final List<EntitlementSpecifier> reorderedSpecifiers = new ArrayList<EntitlementSpecifier>();
final List<Plan> createdOrRetrievedPlans = new ArrayList<Plan>();
final boolean hasBaseOrStandalonePlanSpecifier = createPlansIfNeededAndReorderBPOrStandaloneSpecFirstWithSanity(subscriptionBaseWithAddOnsSpecifier, catalog, billingRequestedDate, reorderedSpecifiers, createdOrRetrievedPlans, callContext);
if (hasBaseOrStandalonePlanSpecifier && baseSubscription != null) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundle.getExternalKey());
}
if (bundle == null && hasBaseOrStandalonePlanSpecifier) {
bundle = createBundleForAccount(callContext.getAccountId(), subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(), renameCancelledBundleIfExist, catalog, accountIdCacheController, context);
} else if (bundle == null) {
log.warn("Invalid specifier: {}", subscriptionBaseWithAddOnsSpecifier);
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey());
}
final List<SubscriptionSpecifier> subscriptionSpecifiers = verifyAndBuildSubscriptionSpecifiers(bundle, hasBaseOrStandalonePlanSpecifier, reorderedSpecifiers, createdOrRetrievedPlans, subscriptionBaseWithAddOnsSpecifier.isMigrated(), billingRequestedDate, catalog, addonUtils, callContext, context);
final SubscriptionAndAddOnsSpecifier subscriptionAndAddOnsSpecifier = new SubscriptionAndAddOnsSpecifier(bundle, billingRequestedDate, subscriptionSpecifiers);
subscriptionAndAddOns.add(subscriptionAndAddOnsSpecifier);
}
use of org.killbill.billing.subscription.api.user.SubscriptionBaseApiException in project killbill by killbill.
the class TestInvoiceDispatcher method testWithParking.
@Test(groups = "slow")
public void testWithParking() throws InvoiceApiException, AccountApiException, CatalogApiException, SubscriptionBaseApiException, TagDefinitionApiException {
final UUID accountId = account.getId();
final BillingEventSet events = new MockBillingEventSet();
final Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
final PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
final DateTime effectiveDate = clock.getUTCNow().minusDays(1);
final Currency currency = Currency.USD;
final BigDecimal fixedPrice = null;
events.add(invoiceUtil.createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase, fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1, BillingMode.IN_ADVANCE, "", 1L, SubscriptionBaseTransitionType.CREATE));
Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(Mockito.<UUID>any(), Mockito.<DryRunArguments>any(), Mockito.<LocalDate>any(), Mockito.<InternalCallContext>any())).thenReturn(events);
final LocalDate target = internalCallContext.toLocalDate(effectiveDate);
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao, internalCallContextFactory, invoicePluginDispatcher, locker, bus, notificationQueueService, invoiceConfig, clock, invoiceOptimizer, parkedAccountsManager);
// Verify initial tags state for account
Assert.assertTrue(tagUserApi.getTagsForAccount(accountId, true, callContext).isEmpty());
// Create chaos on disk
final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(accountId, target, target, currency, false);
final InvoiceItemModelDao invoiceItemModelDao1 = new InvoiceItemModelDao(clock.getUTCNow(), InvoiceItemType.RECURRING, invoiceModelDao.getId(), accountId, subscription.getBundleId(), subscription.getId(), "Bad data", null, plan.getName(), planPhase.getName(), null, null, effectiveDate.toLocalDate(), effectiveDate.plusMonths(1).toLocalDate(), BigDecimal.TEN, BigDecimal.ONE, currency, null);
final InvoiceItemModelDao invoiceItemModelDao2 = new InvoiceItemModelDao(clock.getUTCNow(), InvoiceItemType.RECURRING, invoiceModelDao.getId(), accountId, subscription.getBundleId(), subscription.getId(), "Bad data", null, plan.getName(), planPhase.getName(), null, null, effectiveDate.plusDays(1).toLocalDate(), effectiveDate.plusMonths(1).toLocalDate(), BigDecimal.TEN, BigDecimal.ONE, currency, null);
invoiceModelDao.addInvoiceItem(invoiceItemModelDao1);
invoiceModelDao.addInvoiceItem(invoiceItemModelDao2);
invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), events, ImmutableSet.of(), context);
try {
dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), false, context);
Assert.fail();
} catch (final InvoiceApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
Assert.assertTrue(e.getCause().getMessage().startsWith("Double billing detected"));
}
// Dry-run: no side effect on disk
Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 1);
Assert.assertTrue(tagUserApi.getTagsForAccount(accountId, true, callContext).isEmpty());
try {
dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, null, false, context);
Assert.fail();
} catch (final InvoiceApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
Assert.assertTrue(e.getCause().getMessage().startsWith("Double billing detected"));
}
Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 1);
// No dry-run: account is parked
final List<Tag> tags = tagUserApi.getTagsForAccount(accountId, false, callContext);
Assert.assertEquals(tags.size(), 1);
Assert.assertEquals(tags.get(0).getTagDefinitionId(), SystemTags.PARK_TAG_DEFINITION_ID);
// isApiCall=false
final Invoice nullInvoice1 = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, null, false, context);
Assert.assertNull(nullInvoice1);
// No dry-run and isApiCall=true
try {
dispatcher.processAccount(true, accountId, target, null, false, context);
Assert.fail();
} catch (final InvoiceApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
Assert.assertTrue(e.getCause().getMessage().startsWith("Double billing detected"));
}
// Idempotency
Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 1);
Assert.assertEquals(tagUserApi.getTagsForAccount(accountId, false, callContext), tags);
// Fix state
dbi.withHandle(new HandleCallback<Void>() {
@Override
public Void withHandle(final Handle handle) throws Exception {
handle.execute("delete from invoices");
handle.execute("delete from invoice_items");
return null;
}
});
// Dry-run and isApiCall=false: still parked
final Invoice nullInvoice2 = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), false, context);
Assert.assertNull(nullInvoice2);
// Dry-run and isApiCall=true: call goes through
final Invoice invoice1 = dispatcher.processAccount(true, accountId, target, new DryRunFutureDateArguments(), false, context);
Assert.assertNotNull(invoice1);
Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 0);
// Dry-run: still parked
Assert.assertEquals(tagUserApi.getTagsForAccount(accountId, false, callContext).size(), 1);
// No dry-run and isApiCall=true: call goes through
final Invoice invoice2 = dispatcher.processAccount(true, accountId, target, null, false, context);
Assert.assertNotNull(invoice2);
Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 1);
// No dry-run: now unparked
Assert.assertEquals(tagUserApi.getTagsForAccount(accountId, false, callContext).size(), 0);
Assert.assertEquals(tagUserApi.getTagsForAccount(accountId, true, callContext).size(), 1);
}
use of org.killbill.billing.subscription.api.user.SubscriptionBaseApiException in project killbill by killbill.
the class AddonUtils method checkAddonCreationRights.
public void checkAddonCreationRights(final SubscriptionBase baseSubscription, final Plan targetAddOnPlan, final DateTime requestedDate, final InternalTenantContext context) throws SubscriptionBaseApiException {
if (baseSubscription.getState() == EntitlementState.CANCELLED || (baseSubscription.getState() == EntitlementState.PENDING && context.toLocalDate(baseSubscription.getStartDate()).compareTo(context.toLocalDate(requestedDate)) < 0)) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
}
final Plan currentOrPendingPlan = baseSubscription.getCurrentOrPendingPlan();
final Product baseProduct = currentOrPendingPlan.getProduct();
if (isAddonIncluded(baseProduct, targetAddOnPlan)) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_ALREADY_INCLUDED, targetAddOnPlan.getName(), currentOrPendingPlan.getProduct().getName());
}
if (!isAddonAvailable(baseProduct, targetAddOnPlan)) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_NOT_AVAILABLE, targetAddOnPlan.getName(), currentOrPendingPlan.getProduct().getName());
}
}
use of org.killbill.billing.subscription.api.user.SubscriptionBaseApiException 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);
}
}
Aggregations