use of org.killbill.billing.catalog.api.PlanSpecifier in project killbill by killbill.
the class SubscriptionResource method changeEntitlementPlan.
@TimedResource
@PUT
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@Path("/{subscriptionId:" + UUID_PATTERN + "}")
@ApiOperation(value = "Change entitlement plan")
@ApiResponses(value = { @ApiResponse(code = 400, message = "Invalid subscription id supplied"), @ApiResponse(code = 404, message = "Entitlement not found") })
public Response changeEntitlementPlan(final SubscriptionJson entitlement, @PathParam("subscriptionId") final String subscriptionId, @QueryParam(QUERY_REQUESTED_DT) final String requestedDate, @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion, @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec, @QueryParam(QUERY_BILLING_POLICY) final String policyString, @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString, @HeaderParam(HDR_CREATED_BY) final String createdBy, @HeaderParam(HDR_REASON) final String reason, @HeaderParam(HDR_COMMENT) final String comment, @javax.ws.rs.core.Context final HttpServletRequest request) throws EntitlementApiException, AccountApiException, SubscriptionApiException {
verifyNonNullOrEmpty(entitlement, "SubscriptionJson body should be specified");
if (entitlement.getPlanName() == null) {
verifyNonNullOrEmpty(entitlement.getProductName(), "SubscriptionJson productName needs to be set", entitlement.getBillingPeriod(), "SubscriptionJson billingPeriod needs to be set", entitlement.getPriceList(), "SubscriptionJson priceList needs to be set");
}
final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
final EntitlementCallCompletionCallback<Response> callback = new EntitlementCallCompletionCallback<Response>() {
private boolean isImmediateOp = true;
@Override
public Response doOperation(final CallContext ctx) throws EntitlementApiException, InterruptedException, TimeoutException, AccountApiException {
final UUID uuid = UUID.fromString(subscriptionId);
final Entitlement current = entitlementApi.getEntitlementForId(uuid, callContext);
final LocalDate inputLocalDate = toLocalDate(requestedDate);
final Entitlement newEntitlement;
final Account account = accountUserApi.getAccountById(current.getAccountId(), callContext);
final PlanSpecifier planSpec = entitlement.getPlanName() != null ? new PlanSpecifier(entitlement.getPlanName()) : new PlanSpecifier(entitlement.getProductName(), BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList());
final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(entitlement.getPriceOverrides(), planSpec, account.getCurrency());
if (requestedDate == null && policyString == null) {
newEntitlement = current.changePlan(planSpec, overrides, pluginProperties, ctx);
} else if (policyString == null) {
newEntitlement = current.changePlanWithDate(planSpec, overrides, inputLocalDate, pluginProperties, ctx);
} else {
final BillingActionPolicy policy = BillingActionPolicy.valueOf(policyString.toUpperCase());
newEntitlement = current.changePlanOverrideBillingPolicy(planSpec, overrides, inputLocalDate, policy, pluginProperties, ctx);
}
isImmediateOp = newEntitlement.getLastActiveProduct().getName().equals(entitlement.getProductName()) && newEntitlement.getLastActivePlan().getRecurringBillingPeriod() == BillingPeriod.valueOf(entitlement.getBillingPeriod()) && newEntitlement.getLastActivePriceList().getName().equals(entitlement.getPriceList());
return Response.status(Status.OK).build();
}
@Override
public boolean isImmOperation() {
return isImmediateOp;
}
@Override
public Response doResponseOk(final Response operationResponse) throws SubscriptionApiException, AccountApiException, CatalogApiException {
if (operationResponse.getStatus() != Status.OK.getStatusCode()) {
return operationResponse;
}
return getEntitlement(subscriptionId, new AuditMode(AuditLevel.NONE.toString()), request);
}
};
final EntitlementCallCompletion<Response> callCompletionCreation = new EntitlementCallCompletion<Response>();
return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
}
use of org.killbill.billing.catalog.api.PlanSpecifier in project killbill by killbill.
the class TestEntitlementUtils method testChangePlanEOT.
@Test(groups = "slow", description = "Verify add-ons blocking states are added for EOT change plans")
public void testChangePlanEOT() throws Exception {
// Change plan EOT to Assault-Rifle (Telescopic-Scope is included)
final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlanWithDate(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, new LocalDate(2013, 10, 7), ImmutableList.<PluginProperty>of(), callContext);
// No blocking event (EOT)
assertListenerStatus();
// Verify we compute the right blocking states for the "read" path...
checkFutureBlockingStatesToCancel(addOnEntitlement, null, null);
checkFutureBlockingStatesToCancel(changedBaseEntitlement, addOnEntitlement, baseEffectiveEOTCancellationOrChangeDateTime);
// ...and for the "write" path (which will be exercised when the future notification kicks in).
checkActualBlockingStatesToCancel(changedBaseEntitlement, addOnEntitlement, baseEffectiveEOTCancellationOrChangeDateTime, false);
// Verify also the blocking states DAO adds events not on disk
checkBlockingStatesDAO(changedBaseEntitlement, addOnEntitlement, baseEffectiveCancellationOrChangeDate, false);
// Verify the notification kicks in
testListener.pushExpectedEvents(NextEvent.CHANGE, NextEvent.CANCEL, NextEvent.BLOCK);
clock.addDays(30);
assertListenerStatus();
// Refresh the state
final DefaultEntitlement cancelledAddOnEntitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(addOnEntitlement.getId(), callContext);
// Verify we compute the right blocking states for the "read" path...
checkFutureBlockingStatesToCancel(changedBaseEntitlement, null, null);
checkFutureBlockingStatesToCancel(cancelledAddOnEntitlement, null, null);
checkFutureBlockingStatesToCancel(changedBaseEntitlement, cancelledAddOnEntitlement, null);
// ...and for the "write" path (which has been exercised when the notification kicked in).
checkActualBlockingStatesToCancel(changedBaseEntitlement, cancelledAddOnEntitlement, baseEffectiveEOTCancellationOrChangeDateTime, false);
// Verify also the blocking states API doesn't add too many events (now on disk)
checkBlockingStatesDAO(changedBaseEntitlement, cancelledAddOnEntitlement, baseEffectiveCancellationOrChangeDate, false);
}
use of org.killbill.billing.catalog.api.PlanSpecifier in project killbill by killbill.
the class TestEntitlementUtils method testChangePlanIMM.
@Test(groups = "slow", description = "Verify add-ons blocking states are added for IMM change plans")
public void testChangePlanIMM() throws Exception {
// Approximate check, as the blocking state check (checkBlockingStatesDAO) could be a bit off
final DateTime changeDateTime = clock.getUTCNow();
final LocalDate changeDate = clock.getUTCToday();
// Change plan IMM (upgrade) to Assault-Rifle (Telescopic-Scope is included)
testListener.pushExpectedEvents(NextEvent.CHANGE, NextEvent.CANCEL, NextEvent.BLOCK);
final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlan(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// We need to add a 1s delay before invoking the eventsStreamBuilder in the checks below, because
// the ClockMock truncates milliseconds. Otherwise, utcNow is equal to the changeDateTime, and
// the change is considered as pending (see DefaultEventsStream#getPendingSubscriptionEvents)
clock.addDeltaFromReality(1000);
// Refresh the add-on state
final DefaultEntitlement cancelledAddOnEntitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(addOnEntitlement.getId(), callContext);
// Verify we compute the right blocking states for the "read" path...
checkFutureBlockingStatesToCancel(changedBaseEntitlement, null, null);
checkFutureBlockingStatesToCancel(cancelledAddOnEntitlement, null, null);
checkFutureBlockingStatesToCancel(changedBaseEntitlement, cancelledAddOnEntitlement, null);
// ...and for the "write" path (which has been exercised in the change call above).
checkActualBlockingStatesToCancel(changedBaseEntitlement, cancelledAddOnEntitlement, changeDateTime, true);
// Verify also the blocking states DAO doesn't add too many events (all on disk)
checkBlockingStatesDAO(changedBaseEntitlement, cancelledAddOnEntitlement, changeDate, false);
clock.addDays(30);
// No new event
assertListenerStatus();
checkFutureBlockingStatesToCancel(changedBaseEntitlement, null, null);
checkFutureBlockingStatesToCancel(cancelledAddOnEntitlement, null, null);
checkFutureBlockingStatesToCancel(changedBaseEntitlement, cancelledAddOnEntitlement, null);
checkActualBlockingStatesToCancel(changedBaseEntitlement, cancelledAddOnEntitlement, changeDateTime, true);
checkBlockingStatesDAO(changedBaseEntitlement, cancelledAddOnEntitlement, changeDate, false);
}
use of org.killbill.billing.catalog.api.PlanSpecifier in project killbill by killbill.
the class TestWithPriceOverride method testChangePlanWithRecurringPriceOverrideAndSamePlan.
// See issue #596
@Test(groups = "slow")
public void testChangePlanWithRecurringPriceOverrideAndSamePlan() throws Exception {
// We take april as it has 30 days (easier to play with BCD)
// Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
clock.setDay(new LocalDate(2012, 4, 1));
final AccountData accountData = getAccountData(1);
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
accountChecker.checkAccount(account.getId(), accountData, callContext);
final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
// Check bundle after BP got created otherwise we get an error from auditApi.
subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
// Create the add-on
final DefaultEntitlement aoEntitlement = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
// Trigger change plan for AO with price override
clock.addDays(4);
assertListenerStatus();
final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
overrides.add(new DefaultPlanPhasePriceOverride("telescopic-scope-monthly-evergreen", account.getCurrency(), null, new BigDecimal("1200.00")));
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
aoEntitlement.changePlanOverrideBillingPolicy(new PlanSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), overrides, new LocalDate(2012, 5, 5), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
}
use of org.killbill.billing.catalog.api.PlanSpecifier in project killbill by killbill.
the class VersionedCatalog method findCatalogPlanEntry.
private CatalogPlanEntry findCatalogPlanEntry(final PlanRequestWrapper wrapper, final DateTime requestedDate, final DateTime subscriptionStartDate) throws CatalogApiException {
final List<StandaloneCatalog> catalogs = versionsBeforeDate(requestedDate.toDate());
if (catalogs.isEmpty()) {
throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, requestedDate.toDate().toString());
}
for (int i = catalogs.size() - 1; i >= 0; i--) {
// Working backwards to find the latest applicable plan
final StandaloneCatalog c = catalogs.get(i);
final Plan plan;
try {
plan = wrapper.findPlan(c);
} catch (final CatalogApiException e) {
if (e.getCode() != ErrorCode.CAT_NO_SUCH_PLAN.getCode()) {
throw e;
} else {
// If we can't find an entry it probably means the plan has been retired so we keep looking...
continue;
}
}
final DateTime catalogEffectiveDate = CatalogDateHelper.toUTCDateTime(c.getEffectiveDate());
if (!subscriptionStartDate.isBefore(catalogEffectiveDate)) {
// Its a new subscription this plan always applies
return new CatalogPlanEntry(c, plan);
} else {
//Its an existing subscription
if (plan.getEffectiveDateForExistingSubscriptions() != null) {
//if it is null any change to this does not apply to existing subscriptions
final DateTime existingSubscriptionDate = CatalogDateHelper.toUTCDateTime(plan.getEffectiveDateForExistingSubscriptions());
if (requestedDate.isAfter(existingSubscriptionDate)) {
// this plan is now applicable to existing subs
return new CatalogPlanEntry(c, plan);
}
}
}
}
final PlanSpecifier spec = wrapper.getSpec();
throw new CatalogApiException(ErrorCode.CAT_PLAN_NOT_FOUND, spec.getPlanName() != null ? spec.getPlanName() : "undefined", spec.getProductName() != null ? spec.getProductName() : "undefined", spec.getBillingPeriod() != null ? spec.getBillingPeriod() : "undefined", spec.getPriceListName() != null ? spec.getPriceListName() : "undefined");
}
Aggregations