killbill-memoizeit
Changes
account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java 11(+7 -4)
api/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseApiException.java 7(+4 -3)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java 6(+3 -3)
catalog/src/test/resources/catalogTest.xml 73(+66 -7)
entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java 21(+3 -18)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java 7(+3 -4)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java 22(+13 -9)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java 14(+12 -2)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java 92(+64 -28)
junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java 237(+236 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java 6(+2 -4)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 17(+1 -16)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java 5(+2 -3)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 23(+17 -6)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java 6(+3 -3)
Details
diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
index ba32fd5..44e8b4d 100644
--- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -49,6 +49,7 @@ import org.killbill.billing.util.cache.CacheLoaderArgument;
import org.killbill.billing.util.dao.NonEntityDao;
import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
@@ -104,9 +105,11 @@ public class DefaultAccountInternalApi extends DefaultAccountApiBase implements
}
@Override
- public int getBCD(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
+ public int getBCD(final InternalTenantContext context) throws AccountApiException {
final CacheLoaderArgument arg = createBCDCacheLoaderArgument(context);
- final Integer result = bcdCacheController.get(accountId, arg);
+ Preconditions.checkNotNull(context.getAccountRecordId(), "Context missing accountRecordId");
+ final ImmutableAccountData account = immutableAccountInternalApi.getImmutableAccountDataByRecordId(context.getAccountRecordId(), context);
+ final Integer result = bcdCacheController.get(account.getId(), arg);
return result != null ? result : DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL;
}
diff --git a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java
index f935ae7..6907d07 100644
--- a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -34,7 +34,7 @@ public interface AccountInternalApi extends ImmutableAccountInternalApi {
void updateBCD(String key, int bcd, InternalCallContext context) throws AccountApiException;
- int getBCD(UUID accountId, InternalTenantContext context) throws AccountApiException;
+ int getBCD(InternalTenantContext context) throws AccountApiException;
List<AccountEmail> getEmails(UUID accountId, InternalTenantContext context);
diff --git a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
index a62fc85..5305ac8 100644
--- a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
+++ b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
@@ -66,7 +66,7 @@ public interface EventsStream {
boolean isBlockEntitlement(final DateTime effectiveDate);
- int getDefaultBillCycleDayLocal();
+ Integer getDefaultBillCycleDayLocal();
Collection<BlockingState> getPendingEntitlementCancellationEvents();
diff --git a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java
index 7af38bf..9588349 100644
--- a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java
+++ b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.util.List;
import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.BillingAlignment;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
@@ -41,6 +42,11 @@ public interface BillingEvent extends Comparable<BillingEvent> {
int getBillCycleDayLocal();
/**
+ * @return the BillingAlignment for this transition
+ */
+ BillingAlignment getBillingAlignment();
+
+ /**
* @return the subscription
*/
SubscriptionBase getSubscription();
@@ -105,4 +111,4 @@ public interface BillingEvent extends Comparable<BillingEvent> {
* @return the catalog version (effective date) associated with this billing event.
*/
public DateTime getCatalogEffectiveDate();
-}
\ No newline at end of file
+}
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java
index a9f6125..a94da73 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java
@@ -46,7 +46,7 @@ public interface SubscriptionBase extends Entity, Blockable {
public boolean cancelWithDate(final DateTime requestedDate, final CallContext context)
throws SubscriptionBaseApiException;
- public boolean cancelWithPolicy(final BillingActionPolicy policy, int accountBillCycleDayLocal, final CallContext context)
+ public boolean cancelWithPolicy(final BillingActionPolicy policy, final CallContext context)
throws SubscriptionBaseApiException;
public boolean uncancel(final CallContext context)
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index 4d8b6b9..562aa85 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -47,7 +47,7 @@ public interface SubscriptionBaseInternalApi {
boolean renameCancelledBundleIfExist,
InternalCallContext contextWithValidAccountRecordId) throws SubscriptionBaseApiException;
- public void cancelBaseSubscriptions(Iterable<SubscriptionBase> subscriptions, BillingActionPolicy policy, int accountBillCycleDayLocal, InternalCallContext context) throws SubscriptionBaseApiException;
+ public void cancelBaseSubscriptions(Iterable<SubscriptionBase> subscriptions, BillingActionPolicy policy, InternalCallContext context) throws SubscriptionBaseApiException;
//@VisibleForTesting
SubscriptionBaseBundle createBundleForAccount(UUID accountId, String bundleName, boolean renameCancelledBundleIfExist, InternalCallContext context)
@@ -94,8 +94,6 @@ public interface SubscriptionBaseInternalApi {
public void updateBCD(final UUID subscriptionId, final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException;
- public int getDefaultBillCycleDayLocal(final Map<UUID, Integer> bcdCache, final SubscriptionBase subscription, final SubscriptionBase baseSubscription, final PlanPhaseSpecifier planPhaseSpecifier, final int accountBillCycleDayLocal, final Catalog catalog, final InternalTenantContext context) throws SubscriptionBaseApiException;
-
public UUID getAccountIdFromBundleId(UUID bundleId, InternalTenantContext context) throws SubscriptionBaseApiException;
public UUID getBundleIdFromSubscriptionId(UUID entitlementId, InternalTenantContext context) throws SubscriptionBaseApiException;
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseApiException.java b/api/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseApiException.java
index 99e98b9..3f2126e 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseApiException.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseApiException.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -18,13 +20,12 @@ package org.killbill.billing.subscription.api.user;
import org.killbill.billing.BillingExceptionBase;
import org.killbill.billing.ErrorCode;
-import org.killbill.billing.catalog.api.CatalogApiException;
public class SubscriptionBaseApiException extends BillingExceptionBase {
private static final long serialVersionUID = 19083233L;
- public SubscriptionBaseApiException(final CatalogApiException e) {
+ public SubscriptionBaseApiException(final BillingExceptionBase e) {
super(e, e.getCode(), e.getMessage());
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
index 622d8f5..de36945 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
@@ -234,7 +234,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
final LocalDate initialDate = new LocalDate(2017, 4, 1);
clock.setDay(initialDate);
- // Create account with non BCD to force junction BCD logic to activate
+ // Create account with no BCD
final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
@@ -291,9 +291,9 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
assertEquals(realInvoice.getInvoiceItems().get(0).getStartDate(), futureDate);
assertEquals(realInvoice.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
- // Check BCD is now set
+ // Check BCD is still not set (SUBSCRIPTION alignment)
final Account refreshedAccount2 = accountUserApi.getAccountById(account.getId(), callContext);
- assertEquals(refreshedAccount2.getBillCycleDayLocal(), new Integer(31));
+ assertEquals(refreshedAccount2.getBillCycleDayLocal(), new Integer(0));
// Move clock past startDate to check nothing happens
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
index a9bc6a6..af12b98 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
@@ -562,9 +562,9 @@ public class TestSubscription extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
+ // SUBSCRIPTION alignment: no account BCD
account = accountUserApi.getAccountById(account.getId(), callContext);
- Assert.assertEquals(account.getBillCycleDayLocal().intValue(), 31);
-
+ Assert.assertEquals(account.getBillCycleDayLocal().intValue(), 0);
// Move clock a bit to make sure START_OF_TERM brings us back to last Phase date : 2015-9-5
clock.addDays(5);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
index bbbd2be..d418097 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
@@ -634,6 +634,8 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
// BP creation : Will set Account BCD to the first (DateOfFirstRecurringNonZeroCharge is the subscription start date in this case)
final UUID baseEntitlementId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec, null, overrides), "bundleExternalKey", null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
+ final Account accountBCD = accountUserApi.getAccountById(account.getId(), callContext);
+ assertEquals(accountBCD.getBillCycleDayLocal(), (Integer) 1);
final Entitlement baseEntitlement = entitlementApi.getEntitlementForId(baseEntitlementId, callContext);
invoiceChecker.checkInvoice(account.getId(), 1, callContext,
@@ -844,9 +846,14 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
recordUsageData(aoSubscription.getId(), "tracking-1", "bullets", new LocalDate(2012, 5, 5), 100L, callContext);
recordUsageData(aoSubscription.getId(), "tracking-1", "bullets", new LocalDate(2012, 6, 4), 100L, callContext);
+ // 2012-06-01
+ busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
+ clock.addDays(27);
+ assertListenerStatus();
+
// 2012-06-05
- busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- clock.addMonths(1);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(4);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
catalog/src/test/resources/catalogTest.xml 73(+66 -7)
diff --git a/catalog/src/test/resources/catalogTest.xml b/catalog/src/test/resources/catalogTest.xml
index 77a6048..051639f 100644
--- a/catalog/src/test/resources/catalogTest.xml
+++ b/catalog/src/test/resources/catalogTest.xml
@@ -17,13 +17,13 @@
~ under the License.
-->
-<!-- Use cases covered so far: Tiered Product (Pistol/Shotgun/Assault-Rifle)
- Multiple changeEvent plan policies Multiple PlanAlignment (see below, trial
- add-on alignments and rescue discount package) Product transition rules Add
- on (Scopes, Hoster) Multi-pack addon (Extra-Ammo) Addon Trial aligned to
- base plan (holster-monthly-regular) Addon Trial aligned to creation (holster-monthly-special)
- Rescue discount package (assault-rifle-annual-rescue) Plan phase with a reccurring
- and a one off (refurbish-maintenance) Phan with more than 2 phase (gunclub
+<!-- Use cases covered so far: Tiered Product (Pistol/Shotgun/Assault-Rifle)
+ Multiple changeEvent plan policies Multiple PlanAlignment (see below, trial
+ add-on alignments and rescue discount package) Product transition rules Add
+ on (Scopes, Hoster) Multi-pack addon (Extra-Ammo) Addon Trial aligned to
+ base plan (holster-monthly-regular) Addon Trial aligned to creation (holster-monthly-special)
+ Rescue discount package (assault-rifle-annual-rescue) Plan phase with a reccurring
+ and a one off (refurbish-maintenance) Phan with more than 2 phase (gunclub
discount plans) Use Cases to do: Tiered Add On Riskfree period -->
<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
@@ -69,6 +69,7 @@
<addonProduct>Laser-Scope</addonProduct>
<addonProduct>Holster</addonProduct>
<addonProduct>Bullets</addonProduct>
+ <addonProduct>Cabinet</addonProduct>
</available>
</product>
<product name="Assault-Rifle">
@@ -85,6 +86,9 @@
<product name="Trebuchet">
<category>BASE</category>
</product>
+ <product name="Cannon">
+ <category>BASE</category>
+ </product>
<product name="Cleaning">
<category>ADD_ON</category>
</product>
@@ -106,6 +110,9 @@
<product name="Bullets">
<category>ADD_ON</category>
</product>
+ <product name="Cabinet">
+ <category>ADD_ON</category>
+ </product>
</products>
<rules>
@@ -180,6 +187,11 @@
</createAlignment>
<billingAlignment>
<billingAlignmentCase>
+ <!-- For cabinet-triannual (add-on ACCOUNT aligned) -->
+ <billingPeriod>TRIANNUAL</billingPeriod>
+ <alignment>ACCOUNT</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
<productCategory>ADD_ON</productCategory>
<alignment>BUNDLE</alignment>
</billingAlignmentCase>
@@ -1358,6 +1370,33 @@
<plansAllowedInBundle>-1</plansAllowedInBundle>
<!-- arbitrary number of these (multipack) -->
</plan>
+ <plan name="cabinet-triannual">
+ <product>Cabinet</product>
+ <initialPhases>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>TRIANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
<plan name="trebuchet-usage-in-arrear" prettyName="Trebuchet Monthly Plan">
<product>Trebuchet</product>
<finalPhase type="EVERGREEN">
@@ -1401,6 +1440,24 @@
</usages>
</finalPhase>
</plan>
+ <plan name="cannon-monthly-in-arrear">
+ <product>Cannon</product>
+ <recurringBillingMode>IN_ARREAR</recurringBillingMode>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>500.00</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
</plans>
<priceLists>
<defaultPriceList name="DEFAULT">
@@ -1416,8 +1473,10 @@
<plan>shotgun-annual</plan>
<plan>assault-rifle-annual</plan>
<plan>trebuchet-usage-in-arrear</plan>
+ <plan>cannon-monthly-in-arrear</plan>
<plan>laser-scope-monthly</plan>
<plan>telescopic-scope-monthly</plan>
+ <plan>cabinet-triannual</plan>
<plan>cleaning-monthly</plan>
<plan>extra-ammo-monthly</plan>
<plan>holster-monthly-regular</plan>
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
index 172c7cf..4e3200b 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -36,14 +36,10 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
-import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.DefaultEntitlementService;
-import org.killbill.billing.entitlement.EntitlementService;
import org.killbill.billing.entitlement.EventsStream;
import org.killbill.billing.entitlement.api.EntitlementPluginExecution.WithEntitlementPlugin;
import org.killbill.billing.entitlement.block.BlockingChecker;
@@ -52,7 +48,6 @@ import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKey;
import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
-import org.killbill.billing.entitlement.logging.EntitlementLoggingHelper;
import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
import org.killbill.billing.entitlement.plugin.api.OperationType;
import org.killbill.billing.entity.EntityBase;
@@ -498,7 +493,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
try {
// Cancel subscription base first, to correctly compute the add-ons entitlements we need to cancel (see below)
- getSubscriptionBase().cancelWithPolicy(billingPolicy, eventsStream.getDefaultBillCycleDayLocal(), callContext);
+ getSubscriptionBase().cancelWithPolicy(billingPolicy, callContext);
} catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
index cf0128a..d5c9e39 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -33,13 +33,11 @@ import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.entitlement.DefaultEntitlementService;
import org.killbill.billing.entitlement.EntitlementInternalApi;
-import org.killbill.billing.entitlement.EntitlementService;
import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
@@ -76,7 +74,6 @@ import org.killbill.notificationq.api.NotificationQueueService;
import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase implements EntitlementInternalApi {
@@ -102,14 +99,6 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
return;
}
- int bcd;
- try {
- bcd = accountApi.getBCD(entitlements.iterator().next().getAccountId(), internalCallContext);
- } catch (final AccountApiException e) {
- throw new EntitlementApiException(e);
- }
- Preconditions.checkState(bcd > 0, "Unexpected condition where account info could not be retrieved");
-
final CallContext callContext = internalCallContextFactory.createCallContext(internalCallContext);
final ImmutableMap.Builder<BlockingState, Optional<UUID>> blockingStates = new ImmutableMap.Builder<BlockingState, Optional<UUID>>();
@@ -155,7 +144,6 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
final Callable<Void> preCallbacksCallback = new BulkSubscriptionBaseCancellation(subscriptions,
billingPolicy,
- bcd,
internalCallContext);
pluginExecution.executeWithPlugin(preCallbacksCallback, callbacks, pluginContexts);
@@ -187,23 +175,20 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
private final Iterable<SubscriptionBase> subscriptions;
private final BillingActionPolicy billingPolicy;
- private final int accountBillCycleDayLocal;
private final InternalCallContext callContext;
public BulkSubscriptionBaseCancellation(final Iterable<SubscriptionBase> subscriptions,
final BillingActionPolicy billingPolicy,
- final int accountBillCycleDayLocal,
final InternalCallContext callContext) {
this.subscriptions = subscriptions;
this.billingPolicy = billingPolicy;
- this.accountBillCycleDayLocal = accountBillCycleDayLocal;
this.callContext = callContext;
}
@Override
public Void call() throws Exception {
try {
- subscriptionInternalApi.cancelBaseSubscriptions(subscriptions, billingPolicy, accountBillCycleDayLocal, callContext);
+ subscriptionInternalApi.cancelBaseSubscriptions(subscriptions, billingPolicy, callContext);
} catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index daf7ff6..1caed00 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -33,7 +33,6 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.billing.entitlement.EntitlementService;
import org.killbill.billing.entitlement.EventsStream;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
@@ -72,7 +71,7 @@ public class DefaultEventsStream implements EventsStream {
private final InternalTenantContext internalTenantContext;
private final DateTime utcNow;
private final LocalDate utcToday;
- private final int defaultBillCycleDayLocal;
+ private final Integer defaultBillCycleDayLocal;
private BlockingAggregator currentStateBlockingAggregator;
private List<BlockingState> subscriptionEntitlementStates;
@@ -93,7 +92,7 @@ public class DefaultEventsStream implements EventsStream {
@Nullable final SubscriptionBase baseSubscription,
final SubscriptionBase subscription,
final Collection<SubscriptionBase> allSubscriptionsForBundle,
- final int defaultBillCycleDayLocal,
+ @Nullable final Integer defaultBillCycleDayLocal,
final InternalTenantContext contextWithValidAccountRecordId, final DateTime utcNow) {
sanityChecks(account, bundle, baseSubscription, subscription);
this.account = account;
@@ -234,7 +233,7 @@ public class DefaultEventsStream implements EventsStream {
}
@Override
- public int getDefaultBillCycleDayLocal() {
+ public Integer getDefaultBillCycleDayLocal() {
return defaultBillCycleDayLocal;
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
index 6b1991b..9f72b61 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
@@ -36,6 +36,7 @@ import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.BillingAlignment;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogInternalApi;
@@ -58,6 +59,7 @@ import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
+import org.killbill.billing.util.bcd.BillCycleDayCalculator;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
@@ -149,7 +151,7 @@ public class EventsStreamBuilder {
final int accountBCD;
try {
account = accountInternalApi.getImmutableAccountDataByRecordId(internalTenantContext.getAccountRecordId(), internalTenantContext);
- accountBCD = accountInternalApi.getBCD(account.getId(), internalTenantContext);
+ accountBCD = accountInternalApi.getBCD(internalTenantContext);
} catch (final AccountApiException e) {
throw new EntitlementApiException(e);
}
@@ -276,7 +278,7 @@ public class EventsStreamBuilder {
final InternalTenantContext internalTenantContext) throws EntitlementApiException {
final int accountBCD;
try {
- accountBCD = accountInternalApi.getBCD(bundle.getAccountId(), internalTenantContext);
+ accountBCD = accountInternalApi.getBCD(internalTenantContext);
} catch (final AccountApiException e) {
throw new EntitlementApiException(e);
}
@@ -397,13 +399,15 @@ public class EventsStreamBuilder {
final InternalTenantContext internalTenantContext) throws EntitlementApiException {
try {
- final int defaultAlignmentDay = subscriptionInternalApi.getDefaultBillCycleDayLocal(bcdCache,
- subscription,
- baseSubscription,
- createPlanPhaseSpecifier(subscription),
- accountBCD,
- catalog,
- internalTenantContext);
+ Integer defaultAlignmentDay = null;
+ try {
+ final BillingAlignment alignment = catalog.billingAlignment(createPlanPhaseSpecifier(subscription), clock.getUTCNow(), subscription.getStartDate());
+ if (alignment != BillingAlignment.ACCOUNT || accountBCD != 0) {
+ defaultAlignmentDay = BillCycleDayCalculator.calculateBcdForAlignment(bcdCache, subscription, baseSubscription, alignment, internalTenantContext, accountBCD);
+ }
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
return new DefaultEventsStream(account,
bundle,
blockingStates,
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index 2892e26..e61343d 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -43,6 +43,7 @@ import org.killbill.billing.callcontext.MutableInternalCallContext;
import org.killbill.billing.catalog.MockPlan;
import org.killbill.billing.catalog.MockPlanPhase;
import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingAlignment;
import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Currency;
@@ -362,6 +363,11 @@ public class TestInvoiceHelper {
}
@Override
+ public BillingAlignment getBillingAlignment() {
+ return null;
+ }
+
+ @Override
public SubscriptionBase getSubscription() {
return subscription;
}
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
index ebc9f21..43da1ab 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -24,6 +24,7 @@ import java.util.List;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.BillingAlignment;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -42,6 +43,7 @@ import com.google.common.collect.Lists;
public class DefaultBillingEvent implements BillingEvent {
private final int billCycleDayLocal;
+ private final BillingAlignment billingAlignment;
private final SubscriptionBase subscription;
private final DateTime effectiveDate;
private final PlanPhase planPhase;
@@ -64,6 +66,7 @@ public class DefaultBillingEvent implements BillingEvent {
public DefaultBillingEvent(final SubscriptionInternalEvent transition,
final SubscriptionBase subscription,
final int billCycleDayLocal,
+ final BillingAlignment billingAlignment,
final Currency currency,
final Catalog catalog) throws CatalogApiException {
final boolean isActive = transition.getTransitionType() != SubscriptionBaseTransitionType.CANCEL;
@@ -93,6 +96,7 @@ public class DefaultBillingEvent implements BillingEvent {
this.catalogEffectiveDate = plan == null ? null : new DateTime(plan.getCatalog().getEffectiveDate());
this.billCycleDayLocal = billCycleDayLocal;
+ this.billingAlignment = billingAlignment;
this.catalog = catalog;
this.currency = currency;
this.description = transition.getTransitionType().toString();
@@ -128,6 +132,7 @@ public class DefaultBillingEvent implements BillingEvent {
this.isDisableEvent = isDisableEvent;
this.nextPlanPhase = isDisableEvent ? null : planPhase;
this.catalogEffectiveDate = plan != null ? new DateTime(plan.getCatalog().getEffectiveDate()) : null;
+ this.billingAlignment = null;
}
@Override
@@ -182,6 +187,11 @@ public class DefaultBillingEvent implements BillingEvent {
}
@Override
+ public BillingAlignment getBillingAlignment() {
+ return billingAlignment;
+ }
+
+ @Override
public SubscriptionBase getSubscription() {
return subscription;
}
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index e033ad6..50c2af3 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,7 @@
package org.killbill.billing.junction.plumbing.billing;
+import java.math.BigDecimal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -62,6 +63,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
@@ -139,9 +141,10 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
private void addBillingEventsForBundles(final List<SubscriptionBaseBundle> bundles, final ImmutableAccountData account, final DryRunArguments dryRunArguments, final InternalCallContext context,
final DefaultBillingEventSet result, final Set<UUID> skipSubscriptionsSet, final Catalog catalog, final List<Tag> tagsForAccount) throws AccountApiException, CatalogApiException, SubscriptionBaseApiException {
-
final boolean dryRunMode = dryRunArguments != null;
+ final int currentAccountBCD = accountApi.getBCD(context);
+
// In dryRun mode, when we care about invoice generated for new BASE subscription, no such bundle exists yet; we still
// want to tap into subscriptionBase logic, so we make up a bundleId
if (dryRunArguments != null &&
@@ -150,8 +153,7 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
final UUID fakeBundleId = UUIDs.randomUUID();
final List<SubscriptionBase> subscriptions = subscriptionApi.getSubscriptionsForBundle(fakeBundleId, dryRunArguments, context);
- addBillingEventsForSubscription(account, subscriptions, null, dryRunMode, context, result, skipSubscriptionsSet, catalog);
-
+ addBillingEventsForSubscription(account, subscriptions, null, currentAccountBCD, context, result, skipSubscriptionsSet, catalog);
}
final Map<UUID, List<SubscriptionBase>> subscriptionsForAccount = subscriptionApi.getSubscriptionsForAccount(catalog, context);
@@ -160,11 +162,11 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
final DryRunArguments dryRunArgumentsForBundle = (dryRunArguments != null &&
dryRunArguments.getBundleId() != null &&
dryRunArguments.getBundleId().equals(bundle.getId())) ?
- dryRunArguments : null;
+ dryRunArguments : null;
final List<SubscriptionBase> subscriptions;
// In dryRun mode, optimization is intentionally left as is, since is not a common path.
if (dryRunArgumentsForBundle == null || dryRunArgumentsForBundle.getAction() == null) {
- subscriptions = getSubscriptionsForAccountByBundleId(subscriptionsForAccount,bundle.getId());
+ subscriptions = getSubscriptionsForAccountByBundleId(subscriptionsForAccount, bundle.getId());
} else {
subscriptions = subscriptionApi.getSubscriptionsForBundle(bundle.getId(), dryRunArgumentsForBundle, context);
}
@@ -178,27 +180,63 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
}
} else { // billing is not off
final SubscriptionBase baseSubscription = subscriptions != null && !subscriptions.isEmpty() ? subscriptions.get(0) : null;
- addBillingEventsForSubscription(account, subscriptions, baseSubscription, dryRunMode, context, result, skipSubscriptionsSet, catalog);
+ addBillingEventsForSubscription(account, subscriptions, baseSubscription, currentAccountBCD, context, result, skipSubscriptionsSet, catalog);
}
}
+
+ // If dryRun is specified, we don't want to to update the account BCD value, so we initialize the flag updatedAccountBCD to true
+ if (currentAccountBCD == 0 && !dryRunMode) {
+ BillingEvent oldestAccountAlignedBillingEvent = null;
+
+ for (final BillingEvent event : result) {
+ if (event.getBillingAlignment() != BillingAlignment.ACCOUNT) {
+ continue;
+ }
+
+ final BigDecimal recurringPrice = event.getRecurringPrice(event.getEffectiveDate());
+ final boolean hasRecurringPrice = recurringPrice != null; // Note: could be zero (BCD would still be set, by convention)
+ final boolean hasUsage = event.getUsages() != null && !event.getUsages().isEmpty();
+ if (!hasRecurringPrice &&
+ !hasUsage) {
+ // Nothing to bill, ignored for the purpose of BCD calculation
+ continue;
+ }
+
+ if (oldestAccountAlignedBillingEvent == null ||
+ event.getEffectiveDate().compareTo(oldestAccountAlignedBillingEvent.getEffectiveDate()) < 0 ||
+ (event.getEffectiveDate().compareTo(oldestAccountAlignedBillingEvent.getEffectiveDate()) == 0 && event.getTotalOrdering().compareTo(oldestAccountAlignedBillingEvent.getTotalOrdering()) < 0)) {
+ oldestAccountAlignedBillingEvent = event;
+ }
+ }
+
+ if (oldestAccountAlignedBillingEvent == null) {
+ return;
+ }
+
+ // BCD in the account timezone
+ final int accountBCDCandidate = oldestAccountAlignedBillingEvent.getBillCycleDayLocal();
+ Preconditions.checkState(accountBCDCandidate > 0, "Wrong Account BCD calculation for event: " + oldestAccountAlignedBillingEvent);
+
+ log.info("Setting account BCD='{}', accountId='{}'", accountBCDCandidate, account.getId());
+ accountApi.updateBCD(account.getExternalKey(), accountBCDCandidate, context);
+
+ // Because we now have computed the real BCD, we need to re-compute the BillingEvents BCD for ACCOUNT alignments (see BillCycleDayCalculator#calculateBcdForAlignment).
+ // The code could maybe be optimized (no need to re-run the full function?), but since it's run once per account, it's probably not worth it.
+ result.clear();
+ addBillingEventsForBundles(bundles, account, dryRunArguments, context, result, skipSubscriptionsSet, catalog, tagsForAccount);
+ }
}
private void addBillingEventsForSubscription(final ImmutableAccountData account,
final List<SubscriptionBase> subscriptions,
final SubscriptionBase baseSubscription,
- final boolean dryRunMode,
+ final int currentAccountBCD,
final InternalCallContext context,
final DefaultBillingEventSet result,
final Set<UUID> skipSubscriptionsSet,
- final Catalog catalog) throws AccountApiException, CatalogApiException, SubscriptionBaseApiException {
-
- // If dryRun is specified, we don't want to to update the account BCD value, so we initialize the flag updatedAccountBCD to true
- boolean updatedAccountBCD = dryRunMode;
-
+ final Catalog catalog) throws CatalogApiException {
final Map<UUID, Integer> bcdCache = new HashMap<UUID, Integer>();
- int currentAccountBCD = accountApi.getBCD(account.getId(), context);
-
for (final SubscriptionBase subscription : subscriptions) {
final List<EffectiveSubscriptionInternalEvent> billingTransitions = subscriptionApi.getBillingTransitions(subscription, context);
@@ -212,6 +250,8 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
Integer overridenBCD = null;
for (final EffectiveSubscriptionInternalEvent transition : billingTransitions) {
+ final BillingAlignment alignment = catalog.billingAlignment(getPlanPhaseSpecifierFromTransition(catalog, transition), transition.getEffectiveTransitionTime(), subscription.getStartDate());
+
//
// A BCD_CHANGE transition defines a new billCycleDayLocal for the subscription and this overrides whatever computation
// occurs below (which is based on billing alignment policy). Also multiple of those BCD_CHANGE transitions could occur,
@@ -220,23 +260,19 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
overridenBCD = transition.getNextBillCycleDayLocal() != null ? transition.getNextBillCycleDayLocal() : overridenBCD;
final int bcdLocal = overridenBCD != null ?
overridenBCD :
- calculateBcdForTransition(catalog, bcdCache, baseSubscription, subscription, currentAccountBCD, transition, context);
+ calculateBcdForTransition(alignment, bcdCache, baseSubscription, subscription, currentAccountBCD, context);
- if (currentAccountBCD == 0 && !updatedAccountBCD) {
- log.info("Setting account BCD='{}', accountId='{}'", bcdLocal, account.getId());
- accountApi.updateBCD(account.getExternalKey(), bcdLocal, context);
- updatedAccountBCD = true;
- }
-
- final BillingEvent event = new DefaultBillingEvent(transition, subscription, bcdLocal, account.getCurrency(), catalog);
+ final BillingEvent event = new DefaultBillingEvent(transition, subscription, bcdLocal, alignment, account.getCurrency(), catalog);
result.add(event);
}
}
}
- private int calculateBcdForTransition(final Catalog catalog, final Map<UUID, Integer> bcdCache, final SubscriptionBase baseSubscription, final SubscriptionBase subscription, final int accountBillCycleDayLocal, final EffectiveSubscriptionInternalEvent transition, final InternalTenantContext internalTenantContext)
- throws CatalogApiException, AccountApiException, SubscriptionBaseApiException {
- final BillingAlignment alignment = catalog.billingAlignment(getPlanPhaseSpecifierFromTransition(catalog, transition), transition.getEffectiveTransitionTime(), subscription.getStartDate());
+ private int calculateBcdForTransition(final BillingAlignment realBillingAlignment, final Map<UUID, Integer> bcdCache, final SubscriptionBase baseSubscription, final SubscriptionBase subscription, final int accountBillCycleDayLocal, final InternalTenantContext internalTenantContext) {
+ BillingAlignment alignment = realBillingAlignment;
+ if (alignment == BillingAlignment.ACCOUNT && accountBillCycleDayLocal == 0) {
+ alignment = BillingAlignment.SUBSCRIPTION;
+ }
return BillCycleDayCalculator.calculateBcdForAlignment(bcdCache, subscription, baseSubscription, alignment, internalTenantContext, accountBillCycleDayLocal);
}
@@ -281,7 +317,7 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
});
}
- private List<Tag> getTagsForObjectType(final ObjectType objectType, final List<Tag> tags, final @Nullable UUID objectId) {
+ private List<Tag> getTagsForObjectType(final ObjectType objectType, final List<Tag> tags, @Nullable final UUID objectId) {
return ImmutableList.<Tag>copyOf(Iterables.<Tag>filter(tags,
new Predicate<Tag>() {
@Override
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
index f65d823..e6e6daf 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
@@ -40,7 +40,6 @@ import org.killbill.billing.catalog.api.InternationalPrice;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PriceList;
-import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
@@ -285,7 +284,7 @@ public class TestBillingApi extends JunctionTestSuiteNoDB {
Mockito.when(account.getTimeZone()).thenReturn(DateTimeZone.UTC);
Mockito.when(accountInternalApi.getAccountById(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(account);
Mockito.when(accountInternalApi.getImmutableAccountDataById(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(account);
- Mockito.when(accountInternalApi.getBCD(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(billCycleDay);
+ Mockito.when(accountInternalApi.getBCD(Mockito.<InternalTenantContext>any())).thenReturn(billCycleDay);
return account;
}
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
index a0e95c5..ae11ca8 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
@@ -31,11 +31,13 @@ import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
-import org.killbill.billing.entitlement.EntitlementService;
+import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.DefaultBaseEntitlementWithAddOnsSpecifier;
import org.killbill.billing.entitlement.api.DefaultEntitlementApi;
import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.DefaultBlockingState;
import org.killbill.billing.junction.JunctionTestSuiteWithEmbeddedDB;
@@ -50,6 +52,239 @@ import com.google.common.collect.ImmutableList;
public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbeddedDB {
+ @Test(groups = "slow")
+ public void testRecurringInArrear() throws Exception {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ // Account with no BCD
+ final Account account = createAccount(getAccountData(0));
+ Assert.assertEquals(account.getBillCycleDayLocal(), (Integer) 0);
+
+ // Create base entitlement
+ final String bundleKey = UUID.randomUUID().toString();
+ final EntitlementSpecifier entitlementSpecifierBase = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Cannon", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null));
+ final BaseEntitlementWithAddOnsSpecifier specifier = new DefaultBaseEntitlementWithAddOnsSpecifier(null, bundleKey, ImmutableList.of(entitlementSpecifierBase), null, null, false);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.createBaseEntitlementsWithAddOns(account.getId(),
+ ImmutableList.of(specifier),
+ false,
+ ImmutableList.<PluginProperty>of(),
+ callContext);
+ assertListenerStatus();
+
+ final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ Assert.assertEquals(entitlements.size(), 1);
+ Assert.assertEquals(entitlements.get(0).getLastActiveProduct().getName(), "Cannon");
+ Assert.assertNull(entitlements.get(0).getBillCycleDayLocal());
+
+ // Account still has no BCD
+ final Account accountNoBCD = accountApi.getAccountById(account.getId(), callContext);
+ Assert.assertEquals(accountNoBCD.getBillCycleDayLocal(), (Integer) 0);
+
+ List<BillingEvent> events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext));
+ Assert.assertEquals(events.size(), 1);
+ Assert.assertEquals(events.get(0).getBillCycleDayLocal(), 7);
+
+ // Verify BCD
+ final Account accountWithBCD = accountApi.getAccountById(account.getId(), callContext);
+ Assert.assertEquals(accountWithBCD.getBillCycleDayLocal(), (Integer) 7);
+
+ // Verify GET
+ final List<Entitlement> entitlementsUpdated = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ Assert.assertEquals(entitlementsUpdated.size(), 1);
+ Assert.assertEquals(entitlementsUpdated.get(0).getLastActiveProduct().getName(), "Cannon");
+ Assert.assertEquals(entitlementsUpdated.get(0).getBillCycleDayLocal(), (Integer) 7);
+
+ events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext));
+ Assert.assertEquals(events.size(), 1);
+ Assert.assertEquals(events.get(0).getBillCycleDayLocal(), 7);
+ }
+
+ @Test(groups = "slow")
+ public void testUsageInArrear() throws Exception {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ // Account with no BCD
+ final Account account = createAccount(getAccountData(0));
+ Assert.assertEquals(account.getBillCycleDayLocal(), (Integer) 0);
+
+ // Create base entitlement
+ final String bundleKey = UUID.randomUUID().toString();
+ final EntitlementSpecifier entitlementSpecifierBase = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Trebuchet", BillingPeriod.NO_BILLING_PERIOD, PriceListSet.DEFAULT_PRICELIST_NAME, null));
+ final BaseEntitlementWithAddOnsSpecifier specifier = new DefaultBaseEntitlementWithAddOnsSpecifier(null, bundleKey, ImmutableList.of(entitlementSpecifierBase), null, null, false);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.createBaseEntitlementsWithAddOns(account.getId(),
+ ImmutableList.of(specifier),
+ false,
+ ImmutableList.<PluginProperty>of(),
+ callContext);
+ assertListenerStatus();
+
+ final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ Assert.assertEquals(entitlements.size(), 1);
+ Assert.assertEquals(entitlements.get(0).getLastActiveProduct().getName(), "Trebuchet");
+ Assert.assertNull(entitlements.get(0).getBillCycleDayLocal());
+
+ // Account still has no BCD
+ final Account accountNoBCD = accountApi.getAccountById(account.getId(), callContext);
+ Assert.assertEquals(accountNoBCD.getBillCycleDayLocal(), (Integer) 0);
+
+ List<BillingEvent> events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext));
+ Assert.assertEquals(events.size(), 1);
+ Assert.assertEquals(events.get(0).getBillCycleDayLocal(), 7);
+
+ // Verify BCD
+ final Account accountWithBCD = accountApi.getAccountById(account.getId(), callContext);
+ Assert.assertEquals(accountWithBCD.getBillCycleDayLocal(), (Integer) 7);
+
+ // Verify GET
+ final List<Entitlement> entitlementsUpdated = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ Assert.assertEquals(entitlementsUpdated.size(), 1);
+ Assert.assertEquals(entitlementsUpdated.get(0).getLastActiveProduct().getName(), "Trebuchet");
+ Assert.assertEquals(entitlementsUpdated.get(0).getBillCycleDayLocal(), (Integer) 7);
+
+ events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext));
+ Assert.assertEquals(events.size(), 1);
+ Assert.assertEquals(events.get(0).getBillCycleDayLocal(), 7);
+ }
+
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/865")
+ public void testBCDUpdateMultipleSubscriptionsAccountAligned() throws Exception {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ // Account with no BCD
+ final Account account = createAccount(getAccountData(0));
+ Assert.assertEquals(account.getBillCycleDayLocal(), (Integer) 0);
+
+ // Create 2 entitlements, one base with one add-on. All entitlements are ACCOUNT aligned
+ final String bundleKey1 = UUID.randomUUID().toString();
+ final EntitlementSpecifier entitlementSpecifierBase1 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null));
+ final EntitlementSpecifier entitlementSpecifierAO1 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Cabinet", BillingPeriod.TRIANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null));
+ final BaseEntitlementWithAddOnsSpecifier specifier1 = new DefaultBaseEntitlementWithAddOnsSpecifier(null, bundleKey1, ImmutableList.of(entitlementSpecifierBase1, entitlementSpecifierAO1), null, null, false);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.createBaseEntitlementsWithAddOns(account.getId(),
+ ImmutableList.of(specifier1),
+ false,
+ ImmutableList.<PluginProperty>of(),
+ callContext);
+ assertListenerStatus();
+
+ final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ Assert.assertEquals(entitlements.size(), 2);
+ Assert.assertEquals(entitlements.get(0).getLastActiveProduct().getName(), "Shotgun");
+ // See bug description at https://github.com/killbill/killbill/issues/865 and PR discussion at https://github.com/killbill/killbill/pull/1067/files/926ca68c32c8f8c93bd5eda94fba17f6e19e593d#r237981095
+ Assert.assertNull(entitlements.get(0).getBillCycleDayLocal());
+ Assert.assertEquals(entitlements.get(1).getLastActiveProduct().getName(), "Cabinet");
+ Assert.assertNull(entitlements.get(1).getBillCycleDayLocal());
+
+ // Account still has no BCD
+ final Account accountNoBCD = accountApi.getAccountById(account.getId(), callContext);
+ Assert.assertEquals(accountNoBCD.getBillCycleDayLocal(), (Integer) 0);
+
+ List<BillingEvent> events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext));
+ Assert.assertEquals(events.size(), 3);
+ for (final BillingEvent billingEvent : events) {
+ Assert.assertEquals(billingEvent.getBillCycleDayLocal(), 7);
+ }
+
+ // Verify BCD
+ final Account accountWithBCD = accountApi.getAccountById(account.getId(), callContext);
+ Assert.assertEquals(accountWithBCD.getBillCycleDayLocal(), (Integer) 7);
+
+ // Verify GET
+ final List<Entitlement> entitlementsUpdated = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ Assert.assertEquals(entitlementsUpdated.size(), 2);
+ Assert.assertEquals(entitlementsUpdated.get(0).getLastActiveProduct().getName(), "Shotgun");
+ Assert.assertEquals(entitlementsUpdated.get(0).getBillCycleDayLocal(), (Integer) 7);
+ Assert.assertEquals(entitlementsUpdated.get(1).getLastActiveProduct().getName(), "Cabinet");
+ Assert.assertEquals(entitlementsUpdated.get(1).getBillCycleDayLocal(), (Integer) 7);
+
+ events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext));
+ Assert.assertEquals(events.size(), 3);
+ for (final BillingEvent billingEvent : events) {
+ Assert.assertEquals(billingEvent.getBillCycleDayLocal(), 7);
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testBCDUpdateMultipleSubscriptionsAccountAndSubscriptionAligned() throws Exception {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ // Account with no BCD
+ final Account account = createAccount(getAccountData(0));
+ Assert.assertEquals(account.getBillCycleDayLocal(), (Integer) 0);
+
+ // Create 2 BASE entitlements
+ final String bundleKey1 = UUID.randomUUID().toString();
+ final String bundleKey2 = UUID.randomUUID().toString();
+ final EntitlementSpecifier entitlementSpecifierBase1 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Pistol", BillingPeriod.ANNUAL, "gunclubDiscountNoTrial", null));
+ final EntitlementSpecifier entitlementSpecifierBase2 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null));
+ final BaseEntitlementWithAddOnsSpecifier specifier1 = new DefaultBaseEntitlementWithAddOnsSpecifier(null, bundleKey1, ImmutableList.of(entitlementSpecifierBase1), null, null, false);
+ final BaseEntitlementWithAddOnsSpecifier specifier2 = new DefaultBaseEntitlementWithAddOnsSpecifier(null, bundleKey2, ImmutableList.of(entitlementSpecifierBase2), null, null, false);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.createBaseEntitlementsWithAddOns(account.getId(),
+ ImmutableList.of(specifier1, specifier2),
+ false,
+ ImmutableList.<PluginProperty>of(),
+ callContext);
+ assertListenerStatus();
+
+ final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ Assert.assertEquals(entitlements.size(), 2);
+ for (final Entitlement entitlement : entitlements) {
+ if ("pistol-annual-gunclub-discount-notrial".equals(entitlement.getLastActivePlan().getName())) {
+ // SUBSCRIPTION aligned
+ Assert.assertEquals(entitlement.getBillCycleDayLocal(), (Integer) 7);
+ } else {
+ // ACCOUNT aligned
+ Assert.assertNull(entitlement.getBillCycleDayLocal());
+ }
+ }
+
+ // Account still has no BCD
+ final Account accountNoBCD = accountApi.getAccountById(account.getId(), callContext);
+ Assert.assertEquals(accountNoBCD.getBillCycleDayLocal(), (Integer) 0);
+
+ List<BillingEvent> events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext));
+ Assert.assertEquals(events.size(), 4);
+ for (final BillingEvent billingEvent : events) {
+ if ("pistol-annual-gunclub-discount-notrial".equals(billingEvent.getPlan().getName())) {
+ Assert.assertEquals(billingEvent.getBillCycleDayLocal(), 7);
+ } else {
+ Assert.assertEquals(billingEvent.getBillCycleDayLocal(), 6);
+ }
+ }
+
+ // Verify BCD
+ final Account accountWithBCD = accountApi.getAccountById(account.getId(), callContext);
+ Assert.assertEquals(accountWithBCD.getBillCycleDayLocal(), (Integer) 6);
+
+ // Verify GET
+ final List<Entitlement> entitlementsUpdated = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ Assert.assertEquals(entitlementsUpdated.size(), 2);
+ for (final Entitlement entitlement : entitlementsUpdated) {
+ if ("pistol-annual-gunclub-discount-notrial".equals(entitlement.getLastActivePlan().getName())) {
+ Assert.assertEquals(entitlement.getBillCycleDayLocal(), (Integer) 7);
+ } else {
+ Assert.assertEquals(entitlement.getBillCycleDayLocal(), (Integer) 6);
+ }
+ }
+
+ events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext));
+ Assert.assertEquals(events.size(), 4);
+ for (final BillingEvent billingEvent : events) {
+ if ("pistol-annual-gunclub-discount-notrial".equals(billingEvent.getPlan().getName())) {
+ Assert.assertEquals(billingEvent.getBillCycleDayLocal(), 7);
+ } else {
+ Assert.assertEquals(billingEvent.getBillCycleDayLocal(), 6);
+ }
+ }
+ }
+
// This test was originally for https://github.com/killbill/killbill/issues/123.
// The invocationCount > 0 was to trigger an issue where events would come out-of-order randomly.
// While the bug shouldn't occur anymore, we're keeping it just in case (the test will also try to insert the events out-of-order manually).
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
index 3de7ad5..44b8794 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
@@ -214,7 +214,8 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
protected Subscription createSubscription(final UUID accountId, final String bundleExternalKey, final String productName,
final ProductCategory productCategory, final BillingPeriod billingPeriod, final boolean waitCompletion) throws Exception {
final Account account = accountApi.getAccount(accountId, requestOptions);
- if (account.getBillCycleDayLocal() == null || account.getBillCycleDayLocal() == 0) {
+ // ANNUAL subscriptions are SUBSCRIPTION aligned
+ if (billingPeriod == BillingPeriod.MONTHLY && (account.getBillCycleDayLocal() == null || account.getBillCycleDayLocal() == 0)) {
callbackServlet.pushExpectedEvent(ExtBusEventType.ACCOUNT_CHANGE);
}
callbackServlet.pushExpectedEvents(ExtBusEventType.ENTITLEMENT_CREATION, ExtBusEventType.SUBSCRIPTION_CREATION, ExtBusEventType.SUBSCRIPTION_CREATION, ExtBusEventType.INVOICE_CREATION);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
index 7ca4f03..02fce0d 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
@@ -96,7 +96,7 @@ public class TestCatalog extends TestJaxrsBase {
Assert.assertEquals(catalogsJson.get(0).getName(), "Firearms");
Assert.assertEquals(catalogsJson.get(0).getEffectiveDate().toLocalDate(), new LocalDate("2011-01-01"));
Assert.assertEquals(catalogsJson.get(0).getCurrencies().size(), 3);
- Assert.assertEquals(catalogsJson.get(0).getProducts().size(), 13);
+ Assert.assertEquals(catalogsJson.get(0).getProducts().size(), 15);
Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(), 7);
for (final Product productJson : catalogsJson.get(0).getProducts()) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
index 2b97be8..1ee196d 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
@@ -30,8 +30,6 @@ import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanChangeResult;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
-import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
@@ -52,10 +50,10 @@ public interface SubscriptionBaseApiService {
public boolean cancelWithRequestedDate(DefaultSubscriptionBase subscription, DateTime requestedDate, CallContext context)
throws SubscriptionBaseApiException;
- public boolean cancelWithPolicy(DefaultSubscriptionBase subscription, BillingActionPolicy policy, int accountBillCycleDayLocal, CallContext context)
+ public boolean cancelWithPolicy(DefaultSubscriptionBase subscription, BillingActionPolicy policy, CallContext context)
throws SubscriptionBaseApiException;
- public boolean cancelWithPolicyNoValidationAndCatalog(Iterable<DefaultSubscriptionBase> subscriptions, BillingActionPolicy policy, int accountBillCycleDayLocal, Catalog catalog, InternalCallContext context)
+ public boolean cancelWithPolicyNoValidationAndCatalog(Iterable<DefaultSubscriptionBase> subscriptions, BillingActionPolicy policy, Catalog catalog, InternalCallContext context)
throws SubscriptionBaseApiException;
public boolean uncancel(DefaultSubscriptionBase subscription, CallContext context)
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index 317f272..2680ba5 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -35,14 +35,11 @@ import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
-import org.killbill.billing.catalog.api.BillingAlignment;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogInternalApi;
import org.killbill.billing.catalog.api.Plan;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
-import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
@@ -71,7 +68,6 @@ import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleMode
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.bcd.BCDEvent;
import org.killbill.billing.subscription.events.bcd.BCDEventData;
-import org.killbill.billing.util.bcd.BillCycleDayCalculator;
import org.killbill.billing.util.cache.AccountIdFromBundleIdCacheLoader;
import org.killbill.billing.util.cache.BundleIdFromSubscriptionIdCacheLoader;
import org.killbill.billing.util.cache.Cachable.CacheType;
@@ -158,7 +154,7 @@ public class DefaultSubscriptionInternalApi extends DefaultSubscriptionBaseCreat
}
@Override
- public void cancelBaseSubscriptions(final Iterable<SubscriptionBase> subscriptions, final BillingActionPolicy policy, int accountBillCycleDayLocal, final InternalCallContext context) throws SubscriptionBaseApiException {
+ public void cancelBaseSubscriptions(final Iterable<SubscriptionBase> subscriptions, final BillingActionPolicy policy, final InternalCallContext context) throws SubscriptionBaseApiException {
try {
final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
@@ -174,7 +170,6 @@ public class DefaultSubscriptionInternalApi extends DefaultSubscriptionBaseCreat
}
}),
policy,
- accountBillCycleDayLocal,
catalog,
context);
} catch (CatalogApiException e) {
@@ -458,16 +453,6 @@ public class DefaultSubscriptionInternalApi extends DefaultSubscriptionBaseCreat
}
@Override
- public int getDefaultBillCycleDayLocal(final Map<UUID, Integer> bcdCache, final SubscriptionBase subscription, final SubscriptionBase baseSubscription, final PlanPhaseSpecifier planPhaseSpecifier, final int accountBillCycleDayLocal, final Catalog catalog, final InternalTenantContext context) throws SubscriptionBaseApiException {
- try {
- final BillingAlignment alignment = catalog.billingAlignment(planPhaseSpecifier, clock.getUTCNow(), subscription.getStartDate());
- return BillCycleDayCalculator.calculateBcdForAlignment(bcdCache, subscription, baseSubscription, alignment, context, accountBillCycleDayLocal);
- } catch (final CatalogApiException e) {
- throw new SubscriptionBaseApiException(e);
- }
- }
-
- @Override
public UUID getAccountIdFromBundleId(final UUID bundleId, final InternalTenantContext context) throws SubscriptionBaseApiException {
final CacheLoaderArgument arg = createAccountIdFromBundleIdCacheLoaderArgument(context);
return accountIdCacheController.get(bundleId, arg);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
index da5ae17..fc09a99 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
@@ -39,7 +39,6 @@ import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -262,8 +261,8 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
}
@Override
- public boolean cancelWithPolicy(final BillingActionPolicy policy, final int accountBillCycleDayLocal, final CallContext context) throws SubscriptionBaseApiException {
- return apiService.cancelWithPolicy(this, policy, accountBillCycleDayLocal, context);
+ public boolean cancelWithPolicy(final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
+ return apiService.cancelWithPolicy(this, policy, context);
}
@Override
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
index ac01a59..ff95732 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
@@ -32,6 +32,8 @@ import org.joda.time.DateTime;
import org.joda.time.ReadableInstant;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
@@ -87,16 +89,22 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
private final Clock clock;
private final SubscriptionDao dao;
+ private final AccountInternalApi accountInternalApi;
private final CatalogInternalApi catalogInternalApi;
private final PlanAligner planAligner;
private final AddonUtils addonUtils;
private final InternalCallContextFactory internalCallContextFactory;
@Inject
- public DefaultSubscriptionBaseApiService(final Clock clock, final SubscriptionDao dao, final CatalogInternalApi catalogInternalApi,
- final PlanAligner planAligner, final AddonUtils addonUtils,
+ public DefaultSubscriptionBaseApiService(final Clock clock,
+ final SubscriptionDao dao,
+ final AccountInternalApi accountInternalApi,
+ final CatalogInternalApi catalogInternalApi,
+ final PlanAligner planAligner,
+ final AddonUtils addonUtils,
final InternalCallContextFactory internalCallContextFactory) {
this.clock = clock;
+ this.accountInternalApi = accountInternalApi;
this.catalogInternalApi = catalogInternalApi;
this.planAligner = planAligner;
this.dao = dao;
@@ -207,7 +215,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
@Override
- public boolean cancelWithPolicy(final DefaultSubscriptionBase subscription, final BillingActionPolicy policy, int accountBillCycleDayLocal, final CallContext context) throws SubscriptionBaseApiException {
+ public boolean cancelWithPolicy(final DefaultSubscriptionBase subscription, final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
final EntitlementState currentState = subscription.getState();
if (currentState == EntitlementState.CANCELLED) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CANCEL_BAD_STATE, subscription.getId(), currentState);
@@ -217,14 +225,14 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final Catalog fullCatalog;
try {
fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
- return cancelWithPolicyNoValidationAndCatalog(ImmutableList.<DefaultSubscriptionBase>of(subscription), policy, accountBillCycleDayLocal, fullCatalog, internalCallContext);
- } catch (CatalogApiException e) {
+ return cancelWithPolicyNoValidationAndCatalog(ImmutableList.<DefaultSubscriptionBase>of(subscription), policy, fullCatalog, internalCallContext);
+ } catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
}
@Override
- public boolean cancelWithPolicyNoValidationAndCatalog(final Iterable<DefaultSubscriptionBase> subscriptions, final BillingActionPolicy policy, final int accountBillCycleDayLocal, final Catalog catalog, final InternalCallContext context) throws SubscriptionBaseApiException {
+ public boolean cancelWithPolicyNoValidationAndCatalog(final Iterable<DefaultSubscriptionBase> subscriptions, final BillingActionPolicy policy, final Catalog catalog, final InternalCallContext context) throws SubscriptionBaseApiException {
final Map<DefaultSubscriptionBase, DateTime> subscriptionsWithEffectiveDate = new HashMap<DefaultSubscriptionBase, DateTime>();
try {
@@ -232,12 +240,15 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final BillingAlignment billingAlignment = (subscription.getState() == EntitlementState.PENDING ? null : catalog.billingAlignment(new PlanPhaseSpecifier(subscription.getLastActivePlan().getName(), subscription.getLastActivePhase().getPhaseType()),
clock.getUTCNow(),
subscription.getStartDate()));
+ final Integer accountBillCycleDayLocal = accountInternalApi.getBCD(context);
final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, billingAlignment, accountBillCycleDayLocal, context);
subscriptionsWithEffectiveDate.put(subscription, effectiveDate);
}
return doCancelPlan(subscriptionsWithEffectiveDate, catalog, context);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
+ } catch (final AccountApiException e) {
+ throw new SubscriptionBaseApiException(e);
}
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
index b32981b..f1e5f39 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
@@ -383,7 +383,7 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
// Move ahead a bit abd cancel START_OF_TERM
clock.addDays(5);
testListener.pushExpectedEvent(NextEvent.CANCEL);
- subscription.cancelWithPolicy(BillingActionPolicy.START_OF_TERM, accountData.getBillCycleDayLocal(), callContext);
+ subscription.cancelWithPolicy(BillingActionPolicy.START_OF_TERM, callContext);
assertListenerStatus();
subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
@@ -408,7 +408,7 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
// Cancel / Uncancel a few times to make sure this works and we end up on a stable state
for (int i = 0; i < 3; i++) {
- subscription.cancelWithPolicy(BillingActionPolicy.IMMEDIATE, -1, callContext);
+ subscription.cancelWithPolicy(BillingActionPolicy.IMMEDIATE, callContext);
subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
assertEquals(subscription.getState(), EntitlementState.PENDING);
@@ -512,7 +512,7 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
assertEquals(subscription.getState(), Entitlement.EntitlementState.PENDING);
assertEquals(subscription.getStartDate().compareTo(startDate.toDateTime(accountData.getReferenceTime())), 0);
- subscription.cancelWithPolicy(BillingActionPolicy.IMMEDIATE, 1, callContext);
+ subscription.cancelWithPolicy(BillingActionPolicy.IMMEDIATE, callContext);
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.CANCEL);
clock.addDays(5);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
index 8b97f5d..96e7726 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
@@ -146,7 +146,7 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
subscription = subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
- subscription.cancelWithPolicy(BillingActionPolicy.END_OF_TERM, -1, callContext);
+ subscription.cancelWithPolicy(BillingActionPolicy.END_OF_TERM, callContext);
try {
final PlanPhaseSpecifier planPhaseSpecifier = new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
subscription.changePlanWithDate(new DefaultEntitlementSpecifier(planPhaseSpecifier), clock.getUTCNow(), callContext);
diff --git a/util/src/main/java/org/killbill/billing/util/bcd/BillCycleDayCalculator.java b/util/src/main/java/org/killbill/billing/util/bcd/BillCycleDayCalculator.java
index d53a0a9..59e00d7 100644
--- a/util/src/main/java/org/killbill/billing/util/bcd/BillCycleDayCalculator.java
+++ b/util/src/main/java/org/killbill/billing/util/bcd/BillCycleDayCalculator.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -31,6 +31,8 @@ import org.killbill.billing.subscription.api.SubscriptionBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Preconditions;
+
public abstract class BillCycleDayCalculator {
private static final Logger log = LoggerFactory.getLogger(BillCycleDayCalculator.class);
@@ -39,7 +41,8 @@ public abstract class BillCycleDayCalculator {
int result = 0;
switch (alignment) {
case ACCOUNT:
- result = accountBillCycleDayLocal != 0 ? accountBillCycleDayLocal : calculateOrRetrieveBcdFromSubscription(bcdCache, subscription, internalTenantContext);
+ Preconditions.checkState(accountBillCycleDayLocal != 0, "Account BCD should be set at this point");
+ result = accountBillCycleDayLocal;
break;
case BUNDLE:
result = calculateOrRetrieveBcdFromSubscription(bcdCache, baseSubscription, internalTenantContext);
diff --git a/util/src/test/java/org/killbill/billing/mock/MockSubscription.java b/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
index f686884..3017228 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
@@ -26,8 +26,6 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
-import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -73,9 +71,9 @@ public class MockSubscription implements SubscriptionBase {
}
@Override
- public boolean cancelWithPolicy(BillingActionPolicy policy, int accountBillCycleDayLocal, CallContext context)
+ public boolean cancelWithPolicy(final BillingActionPolicy policy, final CallContext context)
throws SubscriptionBaseApiException {
- return sub.cancelWithPolicy(policy, accountBillCycleDayLocal, context);
+ return sub.cancelWithPolicy(policy, context);
}
@Override