use of org.killbill.billing.entitlement.api.SubscriptionEvent in project killbill by killbill.
the class CatalogResource method getProductForSubscriptionAndDate.
@TimedResource
@GET
@Path("/product")
@Produces(APPLICATION_JSON)
@ApiOperation(value = "Retrieve product for a given subscription and date", response = ProductJson.class)
@ApiResponses(value = {})
public Response getProductForSubscriptionAndDate(@QueryParam("subscriptionId") final UUID subscriptionId, @QueryParam("requestedDate") final String requestedDateString, @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
verifyNonNullOrEmpty(subscriptionId, "Subscription id needs to be specified");
final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionId, requestedDateString, request);
if (lastEventBeforeRequestedDate == null) {
return Response.status(Status.BAD_REQUEST).entity(String.format("%s is before the subscription start date", requestedDateString)).type("text/plain").build();
}
final Product product = lastEventBeforeRequestedDate.getNextProduct();
if (product == null) {
// Subscription was cancelled at that point
return Response.status(Status.BAD_REQUEST).entity(String.format("%s is after the subscription cancel date", requestedDateString)).type("text/plain").build();
}
final ProductJson productJson = new ProductJson(product);
return Response.status(Status.OK).entity(productJson).build();
}
use of org.killbill.billing.entitlement.api.SubscriptionEvent in project killbill by killbill.
the class CatalogResource method getLastEventBeforeDate.
private SubscriptionEvent getLastEventBeforeDate(final UUID subscriptionId, final String requestedDateString, final HttpServletRequest request) throws SubscriptionApiException {
final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
final DateTime requestedDateTime = requestedDateString != null ? DATE_TIME_FORMATTER.parseDateTime(requestedDateString).toDateTime(DateTimeZone.UTC) : clock.getUTCNow();
final LocalDate requestedDate = requestedDateTime.toLocalDate();
final Subscription subscription = subscriptionApi.getSubscriptionForEntitlementId(subscriptionId, tenantContext);
SubscriptionEvent lastEventBeforeRequestedDate = null;
for (final SubscriptionEvent subscriptionEvent : subscription.getSubscriptionEvents()) {
if (lastEventBeforeRequestedDate == null) {
if (subscriptionEvent.getEffectiveDate().compareTo(requestedDate) > 0) {
// requestedDate too far in the past, before subscription start date
return null;
}
lastEventBeforeRequestedDate = subscriptionEvent;
}
if (subscriptionEvent.getEffectiveDate().compareTo(requestedDate) > 0) {
break;
} else {
lastEventBeforeRequestedDate = subscriptionEvent;
}
}
return lastEventBeforeRequestedDate;
}
use of org.killbill.billing.entitlement.api.SubscriptionEvent in project killbill by killbill.
the class CatalogResource method getPhaseForSubscriptionAndDate.
@TimedResource
@GET
@Path("/phase")
@Produces(APPLICATION_JSON)
@ApiOperation(value = "Retrieve phase for a given subscription and date", response = PhaseJson.class)
@ApiResponses(value = {})
public Response getPhaseForSubscriptionAndDate(@QueryParam("subscriptionId") final UUID subscriptionId, @QueryParam("requestedDate") final String requestedDateString, @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, CurrencyValueNull {
verifyNonNullOrEmpty(subscriptionId, "Subscription id needs to be specified");
final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionId, requestedDateString, request);
if (lastEventBeforeRequestedDate == null) {
return Response.status(Status.BAD_REQUEST).entity(String.format("%s is before the subscription start date", requestedDateString)).type("text/plain").build();
}
final PlanPhase phase = lastEventBeforeRequestedDate.getNextPhase();
if (phase == null) {
// Subscription was cancelled at that point
return Response.status(Status.BAD_REQUEST).entity(String.format("%s is after the subscription cancel date", requestedDateString)).type("text/plain").build();
}
final PhaseJson phaseJson = new PhaseJson(phase);
return Response.status(Status.OK).entity(phaseJson).build();
}
use of org.killbill.billing.entitlement.api.SubscriptionEvent in project killbill by killbill.
the class TestCatalogWithEvents method testChangeWithUsagePlan.
@Test(groups = "slow")
public void testChangeWithUsagePlan() throws Exception {
final LocalDate today = new LocalDate(2020, 1, 1);
clock.setDay(today);
final VersionedCatalog catalog = catalogUserApi.getCatalog("foo", callContext);
final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("water-monthly", null);
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
final UUID subscriptionId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec, null, null, null), UUID.randomUUID().toString(), clock.getUTCToday(), clock.getUTCToday(), false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
recordUsageData(subscriptionId, "t1", "liter", new LocalDate(2020, 1, 1), 10L, callContext);
recordUsageData(subscriptionId, "t2", "liter", new LocalDate(2020, 1, 23), 10L, callContext);
// 2020-2-1
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addMonths(1);
assertListenerStatus();
final Invoice invoice1 = invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2020, 1, 1), new LocalDate(2020, 2, 1), InvoiceItemType.USAGE, new BigDecimal("30.00")));
invoiceChecker.checkTrackingIds(invoice1, ImmutableSet.of("t1", "t2"), internalCallContext);
Assert.assertTrue(invoice1.getInvoiceItems().get(0).getCatalogEffectiveDate().toDate().compareTo(catalog.getVersions().get(0).getEffectiveDate()) == 0);
final Subscription subscription1 = subscriptionApi.getSubscriptionForEntitlementId(subscriptionId, callContext);
final List<SubscriptionEvent> events1 = subscription1.getSubscriptionEvents();
Assert.assertEquals(events1.size(), 2);
Assert.assertTrue(events1.get(0).getNextPlan().getCatalog().getEffectiveDate().compareTo(catalog.getVersions().get(0).getEffectiveDate()) == 0);
// 2020-2-16 (V2 effDt = 2020-2-15)
clock.addDays(15);
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
subscription1.changePlanWithDate(new DefaultEntitlementSpecifier(spec), clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
final Subscription subscription2 = subscriptionApi.getSubscriptionForEntitlementId(subscriptionId, callContext);
final List<SubscriptionEvent> events2 = subscription2.getSubscriptionEvents();
Assert.assertEquals(events2.size(), 3);
Assert.assertTrue(events2.get(0).getNextPlan().getCatalog().getEffectiveDate().compareTo(catalog.getVersions().get(0).getEffectiveDate()) == 0);
// 2020-03-01
busHandler.pushExpectedEvents(NextEvent.INVOICE);
clock.addDays(14);
assertListenerStatus();
// 2020-3-16 (V3 effDt = 2020-3-15)
clock.addDays(15);
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
subscription1.changePlanWithDate(new DefaultEntitlementSpecifier(spec), clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
final Subscription subscription3 = subscriptionApi.getSubscriptionForEntitlementId(subscriptionId, callContext);
final List<SubscriptionEvent> events3 = subscription3.getSubscriptionEvents();
Assert.assertEquals(events3.size(), 4);
Assert.assertTrue(events3.get(0).getNextPlan().getCatalog().getEffectiveDate().compareTo(catalog.getVersions().get(0).getEffectiveDate()) == 0);
Assert.assertTrue(events3.get(1).getNextPlan().getCatalog().getEffectiveDate().compareTo(catalog.getVersions().get(0).getEffectiveDate()) == 0);
// Change catalog V2
Assert.assertTrue(events3.get(2).getNextPlan().getCatalog().getEffectiveDate().compareTo(catalog.getVersions().get(1).getEffectiveDate()) == 0);
// Change catalog V3
Assert.assertTrue(events3.get(3).getNextPlan().getCatalog().getEffectiveDate().compareTo(catalog.getVersions().get(2).getEffectiveDate()) == 0);
}
use of org.killbill.billing.entitlement.api.SubscriptionEvent in project killbill by killbill.
the class TestCatalogRetireElements method testChangePlanTwiceWithNewPlan.
@Test(groups = "slow", description = "See https://github.com/killbill/killbill/issues/1110")
public void testChangePlanTwiceWithNewPlan() throws Exception {
// Catalog v1 starts in 2011-01-01
// Catalog v2 starts in 2015-12-01
// -> Start on catalog V1
final LocalDate today = new LocalDate(2015, 11, 5);
// Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
clock.setDay(today);
final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
final String productName = "Shotgun";
final BillingPeriod term = BillingPeriod.MONTHLY;
final PlanPhaseSpecifier spec1 = new PlanPhaseSpecifier(productName, term, "DEFAULT", null);
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
final UUID bpEntitlementId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec1), "externalKey", null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
Entitlement bpEntitlement = entitlementApi.getEntitlementForId(bpEntitlementId, callContext);
// Move out a month. Date > catalog V2
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
// Current date is > catalog V2
// Change to a plan that exists in V2 but not in V1
final PlanPhaseSpecifier spec2 = new PlanPhaseSpecifier("bazooka-monthly", null);
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
bpEntitlement = bpEntitlement.changePlanWithDate(new DefaultEntitlementSpecifier(spec2), clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Change back to original plan: The code (subscription) chooses the latest version of the catalog when making the change and therefore the call succeeds
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
bpEntitlement.changePlanWithDate(new DefaultEntitlementSpecifier(spec1), clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
//
// The code normally goes through the grandfathering logic to find the version but specifies the transitionTime of the latest CHANGE (and not the subscriptionStartDate)
// and therefore correctly find the latest catalog version, invoicing at the new price 295.95
//
Invoice curInvoice = invoiceChecker.checkInvoice(account.getId(), 4, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2015, 12, 5), new LocalDate(2016, 1, 5), InvoiceItemType.RECURRING, new BigDecimal("295.95")), new ExpectedInvoiceItemCheck(new LocalDate(2015, 12, 5), new LocalDate(2016, 1, 5), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-500.00")), new ExpectedInvoiceItemCheck(new LocalDate(2015, 12, 5), new LocalDate(2015, 12, 5), InvoiceItemType.CBA_ADJ, new BigDecimal("204.05")));
final VersionedCatalog catalog = catalogUserApi.getCatalog("foo", callContext);
// RECURRING should be set against V2
Assert.assertEquals(curInvoice.getInvoiceItems().get(0).getCatalogEffectiveDate().toDate().compareTo(catalog.getVersions().get(1).getEffectiveDate()), 0);
Assert.assertNull(curInvoice.getInvoiceItems().get(1).getCatalogEffectiveDate());
Assert.assertNull(curInvoice.getInvoiceItems().get(2).getCatalogEffectiveDate());
final Subscription bpSubscription = subscriptionApi.getSubscriptionForEntitlementId(bpEntitlementId, callContext);
final List<SubscriptionEvent> events = bpSubscription.getSubscriptionEvents();
// We are seeing START_ENTITLEMENT, START_BILLING, and the **last CHANGE**
// Note that the PHASE and intermediate CHANGE are not being returned (is_active = '0') because all coincided on the same date. This is debatable
// whether this is a good semantics. See #1030
assertEquals(events.size(), 3);
// Verify what we return is the price from the correct catalog version. See #1120
assertEquals(events.get(2).getNextPhase().getRecurring().getRecurringPrice().getPrice(account.getCurrency()).compareTo(new BigDecimal("295.95")), 0);
}
Aggregations