killbill-memoizeit
Changes
beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java 2(+1 -1)
beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java 3(+0 -3)
invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java 62(+60 -2)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java 29(+20 -9)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java 59(+24 -35)
junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java 152(+152 -0)
Details
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
index 3c8e9d9..0b368c6 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
@@ -22,6 +22,7 @@ import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.BillCycleDay;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.Plan;
@@ -41,7 +42,7 @@ public interface BillingEvent extends Comparable<BillingEvent> {
* <p/>
* Note: The billCycleDay may come from the Account, or the bundle or the subscription itself
*/
- public int getBillCycleDay();
+ public BillCycleDay getBillCycleDay();
/**
* @return the subscription
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index 148fb04..3a7c1f0 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -187,7 +187,7 @@ public class TestOverdueIntegration extends TestIntegrationBase {
// Clear databases
}
- // We set the the property killbill.payment.retry.days=100,100,100 so that Payment retry logics does not end with an ABORTED state
+ // We set the the property killbill.payment.retry.days=8,8,8,8,8,8,8,8 so that Payment retry logics does not end with an ABORTED state
// preventing final instant payment to succeed.
//
@Test(groups = {"slow"}, enabled = true)
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
index 8083fdd..f83725d 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
@@ -24,8 +24,6 @@ import java.util.Collection;
import java.util.Map;
import java.util.UUID;
-import junit.framework.Assert;
-
import org.joda.time.DateTime;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Guice;
@@ -49,7 +47,6 @@ import com.ning.billing.util.api.TagUserApi;
import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.tag.ControlTagType;
import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDefinition;
@Guice(modules = {BeatrixModule.class})
public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
diff --git a/beatrix/src/test/resources/resource.properties b/beatrix/src/test/resources/resource.properties
index 1654b35..5002a5d 100644
--- a/beatrix/src/test/resources/resource.properties
+++ b/beatrix/src/test/resources/resource.properties
@@ -7,6 +7,6 @@ killbill.entitlement.engine.notifications.sleep=100
killbill.billing.util.persistent.bus.sleep=100
killbill.billing.util.persistent.bus.nbThreads=1
user.timezone=UTC
-killbill.payment.retry.days=100,100,100
+killbill.payment.retry.days=8,8,8,8,8,8,8,8
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index 4c2f636..3f94759 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -22,17 +22,15 @@ import java.util.Collection;
import java.util.List;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
-import com.ning.billing.account.api.BillCycleDay;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
@@ -58,7 +56,12 @@ import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.dao.TableName;
import com.ning.billing.util.tag.ControlTagType;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
+
public class DefaultInvoiceDao implements InvoiceDao {
+
private final InvoiceSqlDao invoiceSqlDao;
private final InvoicePaymentSqlDao invoicePaymentSqlDao;
private final TagUserApi tagUserApi;
@@ -258,7 +261,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
}
@Override
- public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final LocalDate upToDate) {
+ public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, @Nullable final LocalDate upToDate) {
return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
@Override
public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
@@ -267,7 +270,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
final Collection<Invoice> unpaidInvoices = Collections2.filter(invoices, new Predicate<Invoice>() {
@Override
public boolean apply(final Invoice in) {
- return (in.getBalance().compareTo(BigDecimal.ZERO) >= 1) && !in.getTargetDate().isAfter(upToDate);
+ return (in.getBalance().compareTo(BigDecimal.ZERO) >= 1) && (upToDate == null || !in.getTargetDate().isAfter(upToDate));
}
});
return new ArrayList<Invoice>(unpaidInvoices);
@@ -449,7 +452,6 @@ public class DefaultInvoiceDao implements InvoiceDao {
final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
transInvoiceItemDao.create(credit, context);
-
final Invoice invoice = transactional.getById(invoiceIdForRefund.toString());
if (invoice != null) {
populateChildren(invoice, transactional);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index 9e0d827..9b8c893 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -20,6 +20,8 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import org.joda.time.LocalDate;
import com.ning.billing.account.api.BillCycleDay;
@@ -57,7 +59,7 @@ public interface InvoiceDao {
public BigDecimal getAccountCBA(final UUID accountId);
- List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final LocalDate upToDate);
+ List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, @Nullable final LocalDate upToDate);
void test();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index ae23bd9..8842d3d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -323,11 +323,11 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
if (!startDate.isAfter(targetDate)) {
final LocalDate endDate = (nextEvent == null) ? null : new LocalDate(nextEvent.getEffectiveDate(), nextEvent.getTimeZone());
- final int billCycleDay = thisEvent.getBillCycleDay();
+ final int billCycleDayLocal = thisEvent.getBillCycleDay().getDayOfMonthLocal();
final List<RecurringInvoiceItemData> itemData;
try {
- itemData = billingMode.calculateInvoiceItemData(startDate, endDate, targetDate, accountTimeZone, billCycleDay, billingPeriod);
+ itemData = billingMode.calculateInvoiceItemData(startDate, endDate, targetDate, accountTimeZone, billCycleDayLocal, billingPeriod);
} catch (InvalidDateSequenceException e) {
throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
}
@@ -352,6 +352,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
}
+ log.info("Generated invoice items [{}] from event [{}]", items, thisEvent);
return items;
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java b/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
index a2f255f..e9f14b9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
@@ -69,10 +69,15 @@ public class InvoiceDateUtils {
if (proposedDate.dayOfMonth().get() < billingCycleDay) {
final int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
if (lastDayOfTheMonth < billingCycleDay) {
- return new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
+ proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
} else {
- return new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
+ proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
}
+ }
+
+ if (proposedDate.isBefore(previousBillCycleDate)) {
+ // Make sure not to go too far in the past
+ return previousBillCycleDate;
} else {
return proposedDate;
}
@@ -137,7 +142,7 @@ public class InvoiceDateUtils {
public static LocalDate calculateBillingCycleDateOnOrAfter(final LocalDate date, final DateTimeZone accountTimeZone,
final int billingCycleDayLocal) {
- final DateTime tmp = new DateTime(date.toDateTimeAtStartOfDay(), accountTimeZone);
+ final DateTime tmp = date.toDateTimeAtStartOfDay(accountTimeZone);
final DateTime proposedDateTime = calculateBillingCycleDateOnOrAfter(tmp, billingCycleDayLocal);
return new LocalDate(proposedDateTime, accountTimeZone);
@@ -145,7 +150,7 @@ public class InvoiceDateUtils {
public static LocalDate calculateBillingCycleDateAfter(final LocalDate date, final DateTimeZone accountTimeZone,
final int billingCycleDayLocal) {
- final DateTime tmp = new DateTime(date.toDateTimeAtStartOfDay(), accountTimeZone);
+ final DateTime tmp = date.toDateTimeAtStartOfDay(accountTimeZone);
final DateTime proposedDateTime = calculateBillingCycleDateAfter(tmp, billingCycleDayLocal);
return new LocalDate(proposedDateTime, accountTimeZone);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
index 7700c4e..39c5246 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
@@ -18,6 +18,8 @@ package com.ning.billing.invoice.model;
import java.util.List;
+import javax.annotation.Nullable;
+
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
@@ -25,9 +27,6 @@ import com.ning.billing.catalog.api.BillingPeriod;
public interface BillingMode {
- List<RecurringInvoiceItemData> calculateInvoiceItemData(LocalDate startDate, LocalDate endDate, LocalDate targetDate,
- DateTimeZone accountTimeZone, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
-
- List<RecurringInvoiceItemData> calculateInvoiceItemData(LocalDate startDate, LocalDate targetDate,
+ List<RecurringInvoiceItemData> calculateInvoiceItemData(LocalDate startDate, @Nullable LocalDate endDate, LocalDate targetDate,
DateTimeZone accountTimeZone, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
index 71a7cca..f2d641e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -93,7 +93,6 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
sb.append("planName = ").append(planName).append(", ");
sb.append("phaseName = ").append(phaseName).append(", ");
sb.append("startDate = ").append(startDate.toString()).append(", ");
- sb.append("endDate = ").append(endDate.toString()).append(", ");
sb.append("amount = ");
if (amount == null) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
index dd5061b..07e385b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
@@ -20,8 +20,12 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
+import javax.annotation.Nullable;
+
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.ning.billing.catalog.api.BillingPeriod;
@@ -34,15 +38,13 @@ import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateProRa
public class InAdvanceBillingMode implements BillingMode {
+ private static final Logger log = LoggerFactory.getLogger(InAdvanceBillingMode.class);
+
@Override
- public List<RecurringInvoiceItemData> calculateInvoiceItemData(final LocalDate startDate, final LocalDate endDate,
+ public List<RecurringInvoiceItemData> calculateInvoiceItemData(final LocalDate startDate, @Nullable final LocalDate endDate,
final LocalDate targetDate, final DateTimeZone accountTimeZone,
- final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
- if (endDate == null) {
- return calculateInvoiceItemData(startDate, targetDate, accountTimeZone, billingCycleDay, billingPeriod);
- }
-
- if (endDate.isBefore(startDate)) {
+ final int billingCycleDayLocal, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+ if (endDate != null && endDate.isBefore(startDate)) {
throw new InvalidDateSequenceException();
}
if (targetDate.isBefore(startDate)) {
@@ -52,80 +54,59 @@ public class InAdvanceBillingMode implements BillingMode {
final List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
// beginning from the start date, find the first billing date
- final LocalDate firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, accountTimeZone, billingCycleDay);
+ final LocalDate firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, accountTimeZone, billingCycleDayLocal);
// add pro-ration item if needed
if (firstBillingCycleDate.isAfter(startDate)) {
final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
- results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+ // Not common - add info in the logs for debugging purposes
+ final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods);
+ log.info("Adding pro-ration: {}", itemData);
+ results.add(itemData);
}
}
// add one item per billing period
- final LocalDate effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, endDate, billingPeriod);
- final LocalDate lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
+ final LocalDate effectiveEndDate;
+ if (endDate != null) {
+ effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, endDate, billingPeriod);
+ } else {
+ effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, billingPeriod);
+ }
+
+ final LocalDate lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDayLocal, billingPeriod);
final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
final int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
- results.add(new RecurringInvoiceItemData(firstBillingCycleDate.plusMonths(i * numberOfMonthsPerBillingPeriod),
- firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod), BigDecimal.ONE));
- }
-
- // check to see if a trailing pro-ration amount is needed
- if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
- final BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
- if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
- results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
- }
- }
- return results;
- }
-
- @Override
- public List<RecurringInvoiceItemData> calculateInvoiceItemData(final LocalDate startDate,
- final LocalDate targetDate,
- final DateTimeZone accountTimeZone,
- final int billingCycleDay,
- final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
- final List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
-
- if (targetDate.isBefore(startDate)) {
- // since the target date is before the start date of the event, this should result in no items being generated
- throw new InvalidDateSequenceException();
- }
-
- // beginning from the start date, find the first billing date
- final LocalDate firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, accountTimeZone, billingCycleDay);
-
- // add pro-ration item if needed
- if (firstBillingCycleDate.isAfter(startDate)) {
- final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
- if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
- results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+ final LocalDate servicePeriodStartDate;
+ if (results.size() > 0) {
+ // Make sure the periods align, especially with the pro-ration calculations above
+ servicePeriodStartDate = results.get(results.size() - 1).getEndDate();
+ } else if (i == 0) {
+ // Use the specified start date
+ servicePeriodStartDate = startDate;
+ } else {
+ throw new IllegalStateException("We should at least have one invoice item!");
}
- }
- // add one item per billing period
- final LocalDate effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, billingPeriod);
- final LocalDate lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
- final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
- final int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
+ // Make sure to align the end date with the BCD
+ final LocalDate servicePeriodEndDate = firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod);
- for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
- results.add(new RecurringInvoiceItemData(firstBillingCycleDate.plusMonths(i * numberOfMonthsPerBillingPeriod),
- firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod), BigDecimal.ONE));
+ results.add(new RecurringInvoiceItemData(servicePeriodStartDate, servicePeriodEndDate, BigDecimal.ONE));
}
// check to see if a trailing pro-ration amount is needed
if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
final BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
- results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
+ // Not common - add info in the logs for debugging purposes
+ final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods);
+ log.info("Adding trailing pro-ration: {}", itemData);
+ results.add(itemData);
}
}
-
return results;
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
index 4fa3aa6..90ae6be 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
@@ -43,4 +43,38 @@ public class RecurringInvoiceItemData {
public BigDecimal getNumberOfCycles() {
return numberOfCycles;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("RecurringInvoiceItemData");
+ sb.append("{startDate=").append(startDate);
+ sb.append(", endDate=").append(endDate);
+ sb.append(", numberOfCycles=").append(numberOfCycles);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final RecurringInvoiceItemData that = (RecurringInvoiceItemData) o;
+
+ if (endDate != null ? !endDate.equals(that.endDate) : that.endDate != null) return false;
+ if (numberOfCycles != null ? !numberOfCycles.equals(that.numberOfCycles) : that.numberOfCycles != null)
+ return false;
+ if (startDate != null ? !startDate.equals(that.startDate) : that.startDate != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = startDate != null ? startDate.hashCode() : 0;
+ result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+ result = 31 * result + (numberOfCycles != null ? numberOfCycles.hashCode() : 0);
+ return result;
+ }
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index a5bf42e..bb98c74 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -89,7 +89,7 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
@Override
public String getFormattedEndDate() {
- return Strings.nullToEmpty(item.getEndDate().toString(dateFormatter));
+ return Strings.nullToEmpty(item.getEndDate() == null ? null : item.getEndDate().toString(dateFormatter));
}
@Override
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index 16d2441..8d36804 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -159,6 +159,59 @@ public class TestDefaultInvoiceGenerator extends InvoicingTestBase {
}
@Test(groups = "fast")
+ public void testSimpleWithTimeZone() throws InvoiceApiException, CatalogApiException {
+ final UUID accountId = UUID.randomUUID();
+ final Subscription sub = createZombieSubscription();
+ final Plan plan = new MockPlan();
+ final BigDecimal rate = TEN;
+ final PlanPhase phase = createMockMonthlyPlanPhase(rate);
+
+ // Start date was the 16 local, but was the 17 UTC
+ final int bcdLocal = 16;
+ final int bcdUTC = 17;
+ final LocalDate startDate = buildDate(2012, 7, bcdLocal);
+
+ final BillingEventSet events = new MockBillingEventSet();
+ final BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, bcdUTC, bcdLocal);
+ events.add(event);
+
+ // Target date is the next BCD, in local time
+ final LocalDate targetDate = buildDate(2012, 8, bcdLocal);
+ final DateTimeZone accountTimeZone = DateTimeZone.forID("HST");
+ final Invoice invoice = generator.generateInvoice(accountId, events, null, targetDate, accountTimeZone, Currency.USD);
+
+ assertNotNull(invoice);
+ assertEquals(invoice.getNumberOfItems(), 2);
+ assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), buildDate(2012, 7, 16));
+ assertEquals(invoice.getInvoiceItems().get(0).getEndDate(), buildDate(2012, 8, 16));
+ assertEquals(invoice.getInvoiceItems().get(1).getStartDate(), buildDate(2012, 8, 16));
+ assertEquals(invoice.getInvoiceItems().get(1).getEndDate(), buildDate(2012, 9, 16));
+ }
+
+ @Test(groups = "fast")
+ public void testSimpleWithSingleDiscountEvent() throws Exception {
+ final UUID accountId = UUID.randomUUID();
+ final Subscription sub = createZombieSubscription();
+ final Plan plan = new MockPlan("Plan with a single discount phase");
+ final PlanPhase phaseEvergreen = createMockMonthlyPlanPhase(EIGHT, PhaseType.DISCOUNT);
+ final DateTimeZone accountTimeZone = DateTimeZone.UTC;
+ final int bcdUTC = 16;
+ final LocalDate startDate = buildDate(2012, 7, 16);
+
+ final BillingEventSet events = new MockBillingEventSet();
+ events.add(createBillingEvent(sub.getId(), startDate, plan, phaseEvergreen, bcdUTC));
+
+ // Set a target date of today (start date)
+ final LocalDate targetDate = startDate;
+ final Invoice invoice = generator.generateInvoice(accountId, events, null, targetDate, accountTimeZone, Currency.USD);
+
+ assertNotNull(invoice);
+ assertEquals(invoice.getNumberOfItems(), 1);
+ assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), buildDate(2012, 7, 16));
+ assertEquals(invoice.getInvoiceItems().get(0).getEndDate(), buildDate(2012, 8, 16));
+ }
+
+ @Test(groups = "fast")
public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiException, CatalogApiException {
final BillingEventSet events = new MockBillingEventSet();
@@ -709,7 +762,12 @@ public class TestDefaultInvoiceGenerator extends InvoicingTestBase {
}
private BillingEvent createBillingEvent(final UUID subscriptionId, final LocalDate startDate,
- final Plan plan, final PlanPhase planPhase, final int billCycleDay) throws CatalogApiException {
+ final Plan plan, final PlanPhase planPhase, final int billCycleDayUTC) throws CatalogApiException {
+ return createBillingEvent(subscriptionId, startDate, plan, planPhase, billCycleDayUTC, billCycleDayUTC);
+ }
+
+ private BillingEvent createBillingEvent(final UUID subscriptionId, final LocalDate startDate,
+ final Plan plan, final PlanPhase planPhase, final int billCycleDayUTC, final int billCycleDayLocal) throws CatalogApiException {
final Subscription sub = createZombieSubscription(subscriptionId);
final Currency currency = Currency.USD;
@@ -717,7 +775,7 @@ public class TestDefaultInvoiceGenerator extends InvoicingTestBase {
planPhase.getFixedPrice() == null ? null : planPhase.getFixedPrice().getPrice(currency),
planPhase.getRecurringPrice() == null ? null : planPhase.getRecurringPrice().getPrice(currency),
currency, planPhase.getBillingPeriod(),
- billCycleDay, BillingModeType.IN_ADVANCE, "Test", 1L, SubscriptionTransitionType.CREATE);
+ billCycleDayUTC, billCycleDayLocal, BillingModeType.IN_ADVANCE, "Test", 1L, SubscriptionTransitionType.CREATE);
}
private void testInvoiceGeneration(final UUID accountId, final BillingEventSet events, final List<Invoice> existingInvoices,
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java
index 30d602b..5d419a0 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java
@@ -28,6 +28,22 @@ import com.ning.billing.catalog.api.BillingPeriod;
public class TestInvoiceDateUtils {
@Test(groups = "fast")
+ public void testLastBCDShouldNotBeBeforePreviousBCD() throws Exception {
+ final LocalDate from = new LocalDate("2012-07-16");
+ final LocalDate previousBCD = new LocalDate("2012-08-15");
+ final int bcdLocal = 15;
+ final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(from, previousBCD, bcdLocal, BillingPeriod.MONTHLY);
+ Assert.assertEquals(lastBCD, new LocalDate("2012-08-15"));
+ }
+
+ @Test(groups = "fast")
+ public void testNextBCDShouldNotBeInThePast() throws Exception {
+ final LocalDate from = new LocalDate("2012-07-16");
+ final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, DateTimeZone.forID("Pacific/Pitcairn"), 15);
+ Assert.assertEquals(to, new LocalDate("2012-08-15"));
+ }
+
+ @Test(groups = "fast")
public void testProRationAfterLastBillingCycleDate() throws Exception {
final LocalDate endDate = new LocalDate("2012-06-02");
final LocalDate previousBillThroughDate = new LocalDate("2012-03-02");
@@ -76,4 +92,32 @@ public class TestInvoiceDateUtils {
final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, DateTimeZone.UTC, 3);
Assert.assertEquals(to, new LocalDate("2012-04-03"));
}
+
+ @Test(groups = "fast")
+ public void testEffectiveEndDate() throws Exception {
+ final LocalDate firstBCD = new LocalDate(2012, 7, 16);
+ final LocalDate targetDate = new LocalDate(2012, 8, 16);
+ final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+ final LocalDate effectiveEndDate = InvoiceDateUtils.calculateEffectiveEndDate(firstBCD, targetDate, billingPeriod);
+ // TODO should that be 2012-09-15?
+ Assert.assertEquals(effectiveEndDate, new LocalDate(2012, 9, 16));
+ }
+
+ @Test(groups = "fast")
+ public void testLastBCD() throws Exception {
+ final LocalDate firstBCD = new LocalDate(2012, 7, 16);
+ final LocalDate effectiveEndDate = new LocalDate(2012, 9, 15);
+ final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+ final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(effectiveEndDate, firstBCD, 16, billingPeriod);
+ Assert.assertEquals(lastBCD, new LocalDate(2012, 8, 16));
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateNbOfBillingPeriods() throws Exception {
+ final LocalDate firstBCD = new LocalDate(2012, 7, 16);
+ final LocalDate lastBCD = new LocalDate(2012, 9, 16);
+ final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+ final int numberOfWholeBillingPeriods = InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(firstBCD, lastBCD, billingPeriod);
+ Assert.assertEquals(numberOfWholeBillingPeriods, 2);
+ }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/model/TestInAdvanceBillingMode.java b/invoice/src/test/java/com/ning/billing/invoice/model/TestInAdvanceBillingMode.java
new file mode 100644
index 0000000..10ded27
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/model/TestInAdvanceBillingMode.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+
+public class TestInAdvanceBillingMode {
+
+ private static final DateTimeZone TIMEZONE = DateTimeZone.forID("Pacific/Pitcairn");
+ public static final BillingPeriod BILLING_PERIOD = BillingPeriod.MONTHLY;
+
+ @Test(groups = "fast")
+ public void testItemShouldNotStartInThePast() throws Exception {
+ final LocalDate startDate = new LocalDate(2012, 7, 16);
+ final LocalDate endDate = new LocalDate(2012, 7, 16);
+ final LocalDate targetDate = new LocalDate(2012, 7, 16);
+ final int billingCycleDayLocal = 15;
+
+ final LinkedHashMap<LocalDate, LocalDate> expectedDates = new LinkedHashMap<LocalDate, LocalDate>();
+ expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15));
+
+ verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateSimpleInvoiceItemWithNoEndDate() throws Exception {
+ final LocalDate startDate = new LocalDate(new DateTime("2012-07-17T02:25:33.000Z", DateTimeZone.UTC), TIMEZONE);
+ final LocalDate endDate = null;
+ final LocalDate targetDate = new LocalDate(2012, 7, 16);
+ final int billingCycleDayLocal = 15;
+
+ final LinkedHashMap<LocalDate, LocalDate> expectedDates = new LinkedHashMap<LocalDate, LocalDate>();
+ expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15));
+
+ verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateSimpleInvoiceItemWithBCDBeforeStartDay() throws Exception {
+ final LocalDate startDate = new LocalDate(2012, 7, 16);
+ final LocalDate endDate = new LocalDate(2012, 8, 16);
+ final LocalDate targetDate = new LocalDate(2012, 7, 16);
+ final int billingCycleDayLocal = 15;
+
+ final LinkedHashMap<LocalDate, LocalDate> expectedDates = new LinkedHashMap<LocalDate, LocalDate>();
+ expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15));
+
+ verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateSimpleInvoiceItemWithBCDEqualsStartDay() throws Exception {
+ final LocalDate startDate = new LocalDate(2012, 7, 16);
+ final LocalDate endDate = new LocalDate(2012, 8, 16);
+ final LocalDate targetDate = new LocalDate(2012, 7, 16);
+ final int billingCycleDayLocal = 16;
+
+ final LinkedHashMap<LocalDate, LocalDate> expectedDates = new LinkedHashMap<LocalDate, LocalDate>();
+ expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 16));
+
+ verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateSimpleInvoiceItemWithBCDAfterStartDay() throws Exception {
+ final LocalDate startDate = new LocalDate(2012, 7, 16);
+ final LocalDate endDate = new LocalDate(2012, 8, 16);
+ final LocalDate targetDate = new LocalDate(2012, 7, 16);
+ final int billingCycleDayLocal = 17;
+
+ final LinkedHashMap<LocalDate, LocalDate> expectedDates = new LinkedHashMap<LocalDate, LocalDate>();
+ expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 17));
+
+ verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateSimpleInvoiceItemWithBCDBeforeStartDayWithTargetDateIn3Months() throws Exception {
+ final LocalDate startDate = new LocalDate(2012, 7, 16);
+ final LocalDate endDate = null;
+ final LocalDate targetDate = new LocalDate(2012, 10, 16);
+ final int billingCycleDayLocal = 15;
+
+ final LinkedHashMap<LocalDate, LocalDate> expectedDates = new LinkedHashMap<LocalDate, LocalDate>();
+ expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15));
+ expectedDates.put(new LocalDate(2012, 8, 15), new LocalDate(2012, 9, 15));
+ expectedDates.put(new LocalDate(2012, 9, 15), new LocalDate(2012, 10, 15));
+ expectedDates.put(new LocalDate(2012, 10, 15), new LocalDate(2012, 11, 15));
+
+ verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateSimpleInvoiceItemWithBCDEqualsStartDayWithTargetDateIn3Months() throws Exception {
+ final LocalDate startDate = new LocalDate(2012, 7, 16);
+ final LocalDate endDate = null;
+ final LocalDate targetDate = new LocalDate(2012, 10, 16);
+ final int billingCycleDayLocal = 16;
+
+ final LinkedHashMap<LocalDate, LocalDate> expectedDates = new LinkedHashMap<LocalDate, LocalDate>();
+ expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 16));
+ expectedDates.put(new LocalDate(2012, 8, 16), new LocalDate(2012, 9, 16));
+ expectedDates.put(new LocalDate(2012, 9, 16), new LocalDate(2012, 10, 16));
+ expectedDates.put(new LocalDate(2012, 10, 16), new LocalDate(2012, 11, 16));
+
+ verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateSimpleInvoiceItemWithBCDAfterStartDayWithTargetDateIn3Months() throws Exception {
+ final LocalDate startDate = new LocalDate(2012, 7, 16);
+ final LocalDate endDate = null;
+ final LocalDate targetDate = new LocalDate(2012, 10, 16);
+ final int billingCycleDayLocal = 17;
+
+ final LinkedHashMap<LocalDate, LocalDate> expectedDates = new LinkedHashMap<LocalDate, LocalDate>();
+ expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 17));
+ expectedDates.put(new LocalDate(2012, 7, 17), new LocalDate(2012, 8, 17));
+ expectedDates.put(new LocalDate(2012, 8, 17), new LocalDate(2012, 9, 17));
+ expectedDates.put(new LocalDate(2012, 9, 17), new LocalDate(2012, 10, 17));
+
+ verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates);
+ }
+
+ private void verifyInvoiceItems(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate,
+ final DateTimeZone dateTimeZone, final int billingCycleDayLocal, final BillingPeriod billingPeriod,
+ final LinkedHashMap<LocalDate, LocalDate> expectedDates) throws InvalidDateSequenceException {
+ final InAdvanceBillingMode billingMode = new InAdvanceBillingMode();
+
+ final List<RecurringInvoiceItemData> invoiceItems = billingMode.calculateInvoiceItemData(startDate, endDate, targetDate, dateTimeZone, billingCycleDayLocal, billingPeriod);
+
+ int i = 0;
+ for (final LocalDate periodStartDate : expectedDates.keySet()) {
+ Assert.assertEquals(invoiceItems.get(i).getStartDate(), periodStartDate);
+ Assert.assertEquals(invoiceItems.get(i).getEndDate(), expectedDates.get(periodStartDate));
+ Assert.assertTrue(invoiceItems.get(0).getNumberOfCycles().compareTo(BigDecimal.ONE) <= 0);
+ i++;
+ }
+ Assert.assertEquals(invoiceItems.size(), i);
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
index a10d37d..69286f9 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
@@ -24,6 +24,7 @@ import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.BillCycleDay;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.Plan;
@@ -34,6 +35,7 @@ import com.ning.billing.entitlement.api.billing.BillingModeType;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
import com.ning.billing.invoice.model.InvoicingConfiguration;
+import com.ning.billing.mock.api.MockBillCycleDay;
public abstract class InvoicingTestBase extends InvoiceTestSuiteWithEmbeddedDB {
protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
@@ -90,7 +92,21 @@ public abstract class InvoicingTestBase extends InvoiceTestSuiteWithEmbeddedDB {
final DateTime effectiveDate,
final Plan plan, final PlanPhase planPhase,
@Nullable final BigDecimal fixedPrice, @Nullable final BigDecimal recurringPrice,
- final Currency currency, final BillingPeriod billingPeriod, final int billCycleDay,
+ final Currency currency, final BillingPeriod billingPeriod, final int billCycleDayUTC,
+ final BillingModeType billingModeType, final String description,
+ final long totalOrdering,
+ final SubscriptionTransitionType type) {
+ return createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase, fixedPrice, recurringPrice,
+ currency, billingPeriod, billCycleDayUTC, billCycleDayUTC, billingModeType, description,
+ totalOrdering, type);
+ }
+
+ protected BillingEvent createMockBillingEvent(@Nullable final Account account, final Subscription subscription,
+ final DateTime effectiveDate,
+ final Plan plan, final PlanPhase planPhase,
+ @Nullable final BigDecimal fixedPrice, @Nullable final BigDecimal recurringPrice,
+ final Currency currency, final BillingPeriod billingPeriod,
+ final int billCycleDayUTC, final int billCycleDayLocal,
final BillingModeType billingModeType, final String description,
final long totalOrdering,
final SubscriptionTransitionType type) {
@@ -101,8 +117,18 @@ public abstract class InvoicingTestBase extends InvoiceTestSuiteWithEmbeddedDB {
}
@Override
- public int getBillCycleDay() {
- return billCycleDay;
+ public BillCycleDay getBillCycleDay() {
+ return new BillCycleDay() {
+ @Override
+ public int getDayOfMonthUTC() {
+ return billCycleDayUTC;
+ }
+
+ @Override
+ public int getDayOfMonthLocal() {
+ return billCycleDayLocal;
+ }
+ };
}
@Override
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
index e191f92..fd5cf16 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
@@ -75,7 +75,7 @@ public abstract class ProRationTestBase extends InvoicingTestBase {
}
protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate targetDate, final int billingCycleDay) throws InvalidDateSequenceException {
- final List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, targetDate, DateTimeZone.UTC, billingCycleDay, getBillingPeriod());
+ final List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, null, targetDate, DateTimeZone.UTC, billingCycleDay, getBillingPeriod());
BigDecimal numberOfBillingCycles = ZERO;
for (final RecurringInvoiceItemData item : items) {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
index 866f64d..b8541b2 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
@@ -13,34 +13,35 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
+
package com.ning.billing.jaxrs.json;
-import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
import com.ning.billing.account.api.Account;
import com.ning.billing.entitlement.api.timeline.BundleTimeline;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.api.InvoicePayment;
import com.ning.billing.payment.api.Payment;
-import com.ning.billing.payment.api.PaymentStatus;
+import com.ning.billing.payment.api.Refund;
-public class AccountTimelineJson {
-
- private final List<PaymentJsonWithBundleKeys> payments;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.Multimap;
- private final List<InvoiceJsonWithBundleKeys> invoices;
+public class AccountTimelineJson {
private final AccountJsonSimple account;
-
-
private final List<BundleJsonWithSubscriptions> bundles;
+ private final List<InvoiceJsonWithBundleKeys> invoices;
+ private final List<PaymentJsonWithBundleKeys> payments;
@JsonCreator
public AccountTimelineJson(@JsonProperty("account") final AccountJsonSimple account,
@@ -84,59 +85,111 @@ public class AccountTimelineJson {
return tmp.toString();
}
- public AccountTimelineJson(final Account account, final List<Invoice> invoices, final List<Payment> payments, final List<BundleTimeline> bundles) {
+ public AccountTimelineJson(final Account account, final List<Invoice> invoices, final List<Payment> payments, final List<BundleTimeline> bundles,
+ final Multimap<UUID, Refund> refundsByPayment, final Multimap<UUID, InvoicePayment> chargebacksByPayment) {
this.account = new AccountJsonSimple(account.getId().toString(), account.getExternalKey());
this.bundles = new LinkedList<BundleJsonWithSubscriptions>();
for (final BundleTimeline cur : bundles) {
this.bundles.add(new BundleJsonWithSubscriptions(account.getId(), cur));
}
+
this.invoices = new LinkedList<InvoiceJsonWithBundleKeys>();
- for (final Invoice cur : invoices) {
- this.invoices.add(new InvoiceJsonWithBundleKeys(cur.getPaidAmount(),
- cur.getCBAAmount(),
- cur.getCreditAdjAmount(),
- cur.getRefundAdjAmount(),
- cur.getId().toString(),
- cur.getInvoiceDate(),
- cur.getTargetDate(),
- Integer.toString(cur.getInvoiceNumber()),
- cur.getBalance(),
- cur.getAccountId().toString(),
- getBundleExternalKey(cur, bundles)));
+ // Extract the credits from the invoices first
+ final List<CreditJson> credits = new ArrayList<CreditJson>();
+ for (final Invoice invoice : invoices) {
+ for (final InvoiceItem invoiceItem : invoice.getInvoiceItems()) {
+ if (InvoiceItemType.CREDIT_ADJ.equals(invoiceItem.getInvoiceItemType())) {
+ credits.add(new CreditJson(invoiceItem, account.getTimeZone()));
+ }
+ }
+ }
+ // Create now the invoice json objects
+ for (final Invoice invoice : invoices) {
+ this.invoices.add(new InvoiceJsonWithBundleKeys(invoice.getPaidAmount(),
+ invoice.getCBAAmount(),
+ invoice.getCreditAdjAmount(),
+ invoice.getRefundAdjAmount(),
+ invoice.getId().toString(),
+ invoice.getInvoiceDate(),
+ invoice.getTargetDate(),
+ Integer.toString(invoice.getInvoiceNumber()),
+ invoice.getBalance(),
+ invoice.getAccountId().toString(),
+ getBundleExternalKey(invoice, bundles),
+ credits));
}
this.payments = new LinkedList<PaymentJsonWithBundleKeys>();
- for (final Payment cur : payments) {
-
- final String status = cur.getPaymentStatus().toString();
- this.payments.add(new PaymentJsonWithBundleKeys(cur.getAmount(), cur.getPaidAmount(), account.getId().toString(),
- cur.getInvoiceId().toString(), cur.getId().toString(),
- cur.getEffectiveDate(), cur.getEffectiveDate(),
- cur.getAttempts().size(), cur.getCurrency().toString(), status,
- getBundleExternalKey(cur.getInvoiceId(), invoices, bundles)));
+ for (final Payment payment : payments) {
+ final List<RefundJson> refunds = new ArrayList<RefundJson>();
+ for (final Refund refund : refundsByPayment.get(payment.getId())) {
+ refunds.add(new RefundJson(refund));
+ }
+
+ final List<ChargebackJson> chargebacks = new ArrayList<ChargebackJson>();
+ for (final InvoicePayment chargeback : chargebacksByPayment.get(payment.getId())) {
+ chargebacks.add(new ChargebackJson(chargeback));
+ }
+
+ final String status = payment.getPaymentStatus().toString();
+ this.payments.add(new PaymentJsonWithBundleKeys(payment.getAmount(), payment.getPaidAmount(), account.getId().toString(),
+ payment.getInvoiceId().toString(), payment.getId().toString(),
+ payment.getEffectiveDate(), payment.getEffectiveDate(),
+ payment.getAttempts().size(), payment.getCurrency().toString(), status,
+ getBundleExternalKey(payment.getInvoiceId(), invoices, bundles),
+ refunds, chargebacks));
}
}
- public AccountTimelineJson() {
- this.account = null;
- this.bundles = null;
- this.invoices = null;
- this.payments = null;
+ public AccountJsonSimple getAccount() {
+ return account;
}
- public List<PaymentJsonWithBundleKeys> getPayments() {
- return payments;
+ public List<BundleJsonWithSubscriptions> getBundles() {
+ return bundles;
}
public List<InvoiceJsonWithBundleKeys> getInvoices() {
return invoices;
}
- public AccountJsonSimple getAccount() {
- return account;
+ public List<PaymentJsonWithBundleKeys> getPayments() {
+ return payments;
}
- public List<BundleJsonWithSubscriptions> getBundles() {
- return bundles;
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("AccountTimelineJson");
+ sb.append("{account=").append(account);
+ sb.append(", bundles=").append(bundles);
+ sb.append(", invoices=").append(invoices);
+ sb.append(", payments=").append(payments);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final AccountTimelineJson that = (AccountTimelineJson) o;
+
+ if (account != null ? !account.equals(that.account) : that.account != null) return false;
+ if (bundles != null ? !bundles.equals(that.bundles) : that.bundles != null) return false;
+ if (invoices != null ? !invoices.equals(that.invoices) : that.invoices != null) return false;
+ if (payments != null ? !payments.equals(that.payments) : that.payments != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = account != null ? account.hashCode() : 0;
+ result = 31 * result + (bundles != null ? bundles.hashCode() : 0);
+ result = 31 * result + (invoices != null ? invoices.hashCode() : 0);
+ result = 31 * result + (payments != null ? payments.hashCode() : 0);
+ return result;
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java
index 4c27655..5279507 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java
@@ -1,16 +1,5 @@
-package com.ning.billing.jaxrs.json;
-
-import java.math.BigDecimal;
-
-import org.joda.time.LocalDate;
-
-import com.ning.billing.invoice.api.Invoice;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2012 Ning, Inc.
*
* Ning 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
@@ -25,13 +14,28 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* under the License.
*/
+package com.ning.billing.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.invoice.api.Invoice;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+
public class InvoiceJsonWithBundleKeys extends InvoiceJsonSimple {
private final String bundleKeys;
+ private final List<CreditJson> credits;
public InvoiceJsonWithBundleKeys() {
super();
this.bundleKeys = null;
+ this.credits = ImmutableList.<CreditJson>of();
}
@JsonCreator
@@ -45,37 +49,47 @@ public class InvoiceJsonWithBundleKeys extends InvoiceJsonSimple {
@JsonProperty("invoiceNumber") final String invoiceNumber,
@JsonProperty("balance") final BigDecimal balance,
@JsonProperty("accountId") final String accountId,
- @JsonProperty("externalBundleKeys") final String bundleKeys) {
+ @JsonProperty("externalBundleKeys") final String bundleKeys,
+ @JsonProperty("credits") final List<CreditJson> credits) {
super(amount, cba, creditAdj, refundAdj, invoiceId, invoiceDate, targetDate, invoiceNumber, balance, accountId);
this.bundleKeys = bundleKeys;
+ this.credits = credits;
}
- public InvoiceJsonWithBundleKeys(final Invoice input, final String bundleKeys) {
+ public InvoiceJsonWithBundleKeys(final Invoice input, final String bundleKeys, final List<CreditJson> credits) {
super(input);
this.bundleKeys = bundleKeys;
+ this.credits = credits;
}
public String getBundleKeys() {
return bundleKeys;
}
+ public List<CreditJson> getCredits() {
+ return credits;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("InvoiceJsonWithBundleKeys");
+ sb.append("{bundleKeys='").append(bundleKeys).append('\'');
+ sb.append(", credits=").append(credits);
+ sb.append('}');
+ return sb.toString();
+ }
+
@Override
public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- if (!super.equals(o)) {
- return false;
- }
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
final InvoiceJsonWithBundleKeys that = (InvoiceJsonWithBundleKeys) o;
- if (bundleKeys != null ? !bundleKeys.equals(that.bundleKeys) : that.bundleKeys != null) {
- return false;
- }
+ if (bundleKeys != null ? !bundleKeys.equals(that.bundleKeys) : that.bundleKeys != null) return false;
+ if (credits != null ? !credits.equals(that.credits) : that.credits != null) return false;
return true;
}
@@ -84,6 +98,7 @@ public class InvoiceJsonWithBundleKeys extends InvoiceJsonSimple {
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (bundleKeys != null ? bundleKeys.hashCode() : 0);
+ result = 31 * result + (credits != null ? credits.hashCode() : 0);
return result;
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java
index 2d71277..255cef2 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java
@@ -17,19 +17,25 @@
package com.ning.billing.jaxrs.json;
import java.math.BigDecimal;
+import java.util.List;
import org.joda.time.DateTime;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
public class PaymentJsonWithBundleKeys extends PaymentJsonSimple {
private final String bundleKeys;
+ private final List<RefundJson> refunds;
+ private final List<ChargebackJson> chargebacks;
public PaymentJsonWithBundleKeys() {
super();
this.bundleKeys = null;
+ this.refunds = ImmutableList.<RefundJson>of();
+ this.chargebacks = ImmutableList.<ChargebackJson>of();
}
@JsonCreator
@@ -43,12 +49,59 @@ public class PaymentJsonWithBundleKeys extends PaymentJsonSimple {
@JsonProperty("retryCount") final Integer retryCount,
@JsonProperty("currency") final String currency,
@JsonProperty("status") final String status,
- @JsonProperty("externalBundleKeys") final String bundleKeys) {
+ @JsonProperty("externalBundleKeys") final String bundleKeys,
+ @JsonProperty("refunds") final List<RefundJson> refunds,
+ @JsonProperty("chargebacks") final List<ChargebackJson> chargebacks) {
super(amount, paidAmount, accountId, invoiceId, paymentId, requestedDate, effectiveDate, retryCount, currency, status);
this.bundleKeys = bundleKeys;
+ this.refunds = refunds;
+ this.chargebacks = chargebacks;
}
public String getBundleKeys() {
return bundleKeys;
}
+
+ public List<RefundJson> getRefunds() {
+ return refunds;
+ }
+
+ public List<ChargebackJson> getChargebacks() {
+ return chargebacks;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("PaymentJsonWithBundleKeys");
+ sb.append("{bundleKeys='").append(bundleKeys).append('\'');
+ sb.append(", refunds=").append(refunds);
+ sb.append(", chargebacks=").append(chargebacks);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ final PaymentJsonWithBundleKeys that = (PaymentJsonWithBundleKeys) o;
+
+ if (bundleKeys != null ? !bundleKeys.equals(that.bundleKeys) : that.bundleKeys != null) return false;
+ if (chargebacks != null ? !chargebacks.equals(that.chargebacks) : that.chargebacks != null) return false;
+ if (refunds != null ? !refunds.equals(that.refunds) : that.refunds != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (bundleKeys != null ? bundleKeys.hashCode() : 0);
+ result = 31 * result + (refunds != null ? refunds.hashCode() : 0);
+ result = 31 * result + (chargebacks != null ? chargebacks.hashCode() : 0);
+ return result;
+ }
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index 957e018..d251e80 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -16,8 +16,6 @@
package com.ning.billing.jaxrs.resources;
-import static com.ning.billing.jaxrs.resources.JaxrsResource.CUSTOM_FIELDS;
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import java.util.ArrayList;
import java.util.Collection;
@@ -43,10 +41,6 @@ import javax.ws.rs.core.UriInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
@@ -60,6 +54,8 @@ import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.jaxrs.json.AccountEmailJson;
import com.ning.billing.jaxrs.json.AccountJson;
@@ -81,9 +77,20 @@ import com.ning.billing.util.api.CustomFieldUserApi;
import com.ning.billing.util.api.TagUserApi;
import com.ning.billing.util.dao.ObjectType;
+import com.google.common.base.Function;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
@Singleton
@Path(JaxrsResource.ACCOUNTS_PATH)
public class AccountResource extends JaxRsResourceBase {
+
private static final Logger log = LoggerFactory.getLogger(AccountResource.class);
private static final String ID_PARAM_NAME = "accountId";
@@ -91,6 +98,7 @@ public class AccountResource extends JaxRsResourceBase {
private final EntitlementUserApi entitlementApi;
private final EntitlementTimelineApi timelineApi;
private final InvoiceUserApi invoiceApi;
+ private final InvoicePaymentApi invoicePaymentApi;
private final PaymentApi paymentApi;
private final Context context;
private final JaxrsUriBuilder uriBuilder;
@@ -100,6 +108,7 @@ public class AccountResource extends JaxRsResourceBase {
final AccountUserApi accountApi,
final EntitlementUserApi entitlementApi,
final InvoiceUserApi invoiceApi,
+ final InvoicePaymentApi invoicePaymentApi,
final PaymentApi paymentApi,
final EntitlementTimelineApi timelineApi,
final CustomFieldUserApi customFieldUserApi,
@@ -110,6 +119,7 @@ public class AccountResource extends JaxRsResourceBase {
this.accountApi = accountApi;
this.entitlementApi = entitlementApi;
this.invoiceApi = invoiceApi;
+ this.invoicePaymentApi = invoicePaymentApi;
this.paymentApi = paymentApi;
this.timelineApi = timelineApi;
this.context = context;
@@ -248,20 +258,41 @@ public class AccountResource extends JaxRsResourceBase {
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + TIMELINE)
@Produces(APPLICATION_JSON)
- public Response getAccountTimeline(@PathParam("accountId") final String accountId) {
+ public Response getAccountTimeline(@PathParam("accountId") final String accountIdString) {
try {
+ final UUID accountId = UUID.fromString(accountIdString);
+ final Account account = accountApi.getAccountById(accountId);
- final Account account = accountApi.getAccountById(UUID.fromString(accountId));
-
+ // Get the invoices
final List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId());
- final List<Payment> payments = paymentApi.getAccountPayments(UUID.fromString(accountId));
+ // Get the payments
+ final List<Payment> payments = paymentApi.getAccountPayments(accountId);
+
+ // Get the refunds
+ final List<Refund> refunds = paymentApi.getAccountRefunds(account);
+ final Multimap<UUID, Refund> refundsByPayment = ArrayListMultimap.<UUID, Refund>create();
+ for (final Refund refund : refunds) {
+ refundsByPayment.put(refund.getPaymentId(), refund);
+ }
+
+ // Get the chargebacks
+ final List<InvoicePayment> chargebacks = invoicePaymentApi.getChargebacksByAccountId(accountId);
+ final Multimap<UUID, InvoicePayment> chargebacksByPayment = ArrayListMultimap.<UUID, InvoicePayment>create();
+ for (final InvoicePayment chargeback : chargebacks) {
+ chargebacksByPayment.put(chargeback.getPaymentId(), chargeback);
+ }
+
+ // Get the bundles
final List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(account.getId());
final List<BundleTimeline> bundlesTimeline = new LinkedList<BundleTimeline>();
for (final SubscriptionBundle cur : bundles) {
bundlesTimeline.add(timelineApi.getBundleRepair(cur.getId()));
}
- final AccountTimelineJson json = new AccountTimelineJson(account, invoices, payments, bundlesTimeline);
+
+ final AccountTimelineJson json = new AccountTimelineJson(account, invoices, payments, bundlesTimeline,
+ refundsByPayment, chargebacksByPayment);
+
return Response.status(Status.OK).entity(json).build();
} catch (AccountApiException e) {
if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
@@ -436,7 +467,6 @@ public class AccountResource extends JaxRsResourceBase {
}
}
-
/*
* ************************** REFUNDS ********************************
*/
@@ -447,7 +477,7 @@ public class AccountResource extends JaxRsResourceBase {
try {
final Account account = accountApi.getAccountById(UUID.fromString(accountId));
- List<Refund> refunds = paymentApi.getAccountRefunds(account);
+ List<Refund> refunds = paymentApi.getAccountRefunds(account);
List<RefundJson> result = new ArrayList<RefundJson>(Collections2.transform(refunds, new Function<Refund, RefundJson>() {
@Override
public RefundJson apply(Refund input) {
@@ -466,8 +496,6 @@ public class AccountResource extends JaxRsResourceBase {
}
}
-
-
/*
* ************************* CUSTOM FIELDS *****************************
*/
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
index 642313a..5c2ca0b 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
@@ -17,8 +17,10 @@
package com.ning.billing.jaxrs.json;
import java.math.BigDecimal;
+import java.util.List;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.mockito.Mockito;
import org.testng.Assert;
@@ -32,6 +34,7 @@ import com.ning.billing.util.clock.DefaultClock;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
+import com.google.common.collect.ImmutableList;
public class TestInvoiceJsonWithBundleKeys extends JaxrsTestSuite {
@@ -57,8 +60,11 @@ public class TestInvoiceJsonWithBundleKeys extends JaxrsTestSuite {
final BigDecimal balance = BigDecimal.ZERO;
final String accountId = UUID.randomUUID().toString();
final String bundleKeys = UUID.randomUUID().toString();
+ CreditJson creditJson = createCreditJson();
+ final List<CreditJson> credits = ImmutableList.<CreditJson>of(creditJson);
final InvoiceJsonWithBundleKeys invoiceJsonSimple = new InvoiceJsonWithBundleKeys(amount, cba, creditAdj, refundAdj, invoiceId, invoiceDate,
- targetDate, invoiceNumber, balance, accountId, bundleKeys);
+ targetDate, invoiceNumber, balance, accountId, bundleKeys,
+ credits);
Assert.assertEquals(invoiceJsonSimple.getAmount(), amount);
Assert.assertEquals(invoiceJsonSimple.getCBA(), cba);
Assert.assertEquals(invoiceJsonSimple.getCreditAdj(), creditAdj);
@@ -70,6 +76,7 @@ public class TestInvoiceJsonWithBundleKeys extends JaxrsTestSuite {
Assert.assertEquals(invoiceJsonSimple.getBalance(), balance);
Assert.assertEquals(invoiceJsonSimple.getAccountId(), accountId);
Assert.assertEquals(invoiceJsonSimple.getBundleKeys(), bundleKeys);
+ Assert.assertEquals(invoiceJsonSimple.getCredits(), credits);
final String asJson = mapper.writeValueAsString(invoiceJsonSimple);
Assert.assertEquals(asJson, "{\"amount\":" + invoiceJsonSimple.getAmount().toString() + "," +
@@ -82,6 +89,14 @@ public class TestInvoiceJsonWithBundleKeys extends JaxrsTestSuite {
"\"invoiceNumber\":\"" + invoiceJsonSimple.getInvoiceNumber() + "\"," +
"\"balance\":" + invoiceJsonSimple.getBalance().toString() + "," +
"\"accountId\":\"" + invoiceJsonSimple.getAccountId() + "\"," +
+ "\"credits\":[" +
+ "{\"creditAmount\":" + creditJson.getCreditAmount() + "," +
+ "\"invoiceId\":\"" + creditJson.getInvoiceId().toString() + "\"," +
+ "\"invoiceNumber\":\"" + creditJson.getInvoiceNumber() + "\"," +
+ "\"requestedDate\":\"" + creditJson.getRequestedDate().toDateTimeISO().toString() + "\"," +
+ "\"effectiveDate\":\"" + creditJson.getEffectiveDate().toDateTimeISO().toString() + "\"," +
+ "\"reason\":\"" + creditJson.getReason() + "\"," +
+ "\"accountId\":\"" + creditJson.getAccountId().toString() + "\"}]," +
"\"bundleKeys\":\"" + invoiceJsonSimple.getBundleKeys() + "\"}");
final InvoiceJsonWithBundleKeys fromJson = mapper.readValue(asJson, InvoiceJsonWithBundleKeys.class);
@@ -103,8 +118,9 @@ public class TestInvoiceJsonWithBundleKeys extends JaxrsTestSuite {
Mockito.when(invoice.getAccountId()).thenReturn(UUID.randomUUID());
final String bundleKeys = UUID.randomUUID().toString();
+ final List<CreditJson> credits = ImmutableList.<CreditJson>of(createCreditJson());
- final InvoiceJsonWithBundleKeys invoiceJsonWithBundleKeys = new InvoiceJsonWithBundleKeys(invoice, bundleKeys);
+ final InvoiceJsonWithBundleKeys invoiceJsonWithBundleKeys = new InvoiceJsonWithBundleKeys(invoice, bundleKeys, credits);
Assert.assertEquals(invoiceJsonWithBundleKeys.getAmount(), invoice.getChargedAmount());
Assert.assertEquals(invoiceJsonWithBundleKeys.getCBA(), invoice.getCBAAmount());
Assert.assertEquals(invoiceJsonWithBundleKeys.getCreditAdj(), invoice.getCreditAdjAmount());
@@ -116,5 +132,17 @@ public class TestInvoiceJsonWithBundleKeys extends JaxrsTestSuite {
Assert.assertEquals(invoiceJsonWithBundleKeys.getBalance(), invoice.getBalance());
Assert.assertEquals(invoiceJsonWithBundleKeys.getAccountId(), invoice.getAccountId().toString());
Assert.assertEquals(invoiceJsonWithBundleKeys.getBundleKeys(), bundleKeys);
+ Assert.assertEquals(invoiceJsonWithBundleKeys.getCredits(), credits);
+ }
+
+ private CreditJson createCreditJson() {
+ final BigDecimal creditAmount = BigDecimal.TEN;
+ final UUID invoiceId = UUID.randomUUID();
+ final String invoiceNumber = UUID.randomUUID().toString();
+ final DateTime requestedDate = clock.getUTCNow();
+ final DateTime effectiveDate = clock.getUTCNow();
+ final String reason = UUID.randomUUID().toString();
+ final UUID accountId = UUID.randomUUID();
+ return new CreditJson(creditAmount, invoiceId, invoiceNumber, requestedDate, effectiveDate, reason, accountId);
}
}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
index baf703f..fb8998e 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
@@ -40,6 +40,7 @@ import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
public class BillCycleDayCalculator {
@@ -51,7 +52,6 @@ public class BillCycleDayCalculator {
@Inject
public BillCycleDayCalculator(final CatalogService catalogService, final EntitlementUserApi entitlementApi) {
- super();
this.catalogService = catalogService;
this.entitlementApi = entitlementApi;
}
@@ -80,11 +80,17 @@ public class BillCycleDayCalculator {
phase.getPhaseType()),
transition.getRequestedTransitionTime());
+ return calculateBcdForAlignment(alignment, bundle, subscription, account, catalog, plan);
+ }
+
+ @VisibleForTesting
+ BillCycleDay calculateBcdForAlignment(final BillingAlignment alignment, final SubscriptionBundle bundle, final Subscription subscription,
+ final Account account, final Catalog catalog, final Plan plan) throws AccountApiException, EntitlementUserApiException, CatalogApiException {
BillCycleDay result = null;
switch (alignment) {
case ACCOUNT:
result = account.getBillCycleDay();
- if (result.getDayOfMonthUTC() == 0) {
+ if (result == null || result.getDayOfMonthUTC() == 0) {
result = calculateBcdFromSubscription(subscription, plan, account);
}
break;
@@ -110,33 +116,38 @@ public class BillCycleDayCalculator {
return result;
}
- private BillCycleDay calculateBcdFromSubscription(final Subscription subscription, final Plan plan, final Account account) throws AccountApiException {
+ @VisibleForTesting
+ BillCycleDay calculateBcdFromSubscription(final Subscription subscription, final Plan plan, final Account account) throws AccountApiException {
final DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate());
// There are really two kinds of billCycleDay:
// - a System billingCycleDay which should be computed from UTC time (in order to get the correct notification time at
// the end of each service period)
// - a User billingCycleDay which should align with the account timezone
- return new CalculatedBillCycleDay(account.getTimeZone(), date);
+ final CalculatedBillCycleDay calculatedBillCycleDay = new CalculatedBillCycleDay(account.getTimeZone(), date);
+ log.info("Calculated BCD: subscription id {}, subscription start {}, timezone {}, bcd UTC {}, bcd local {}",
+ new Object[]{subscription.getId(), date.toDateTimeISO(), account.getTimeZone(),
+ calculatedBillCycleDay.getDayOfMonthUTC(), calculatedBillCycleDay.getDayOfMonthLocal()});
+ return calculatedBillCycleDay;
}
private static final class CalculatedBillCycleDay implements BillCycleDay {
- private final DateTime bcdTimeUTC;
+ private final DateTime bcdTime;
private final DateTimeZone accountTimeZone;
- private CalculatedBillCycleDay(final DateTimeZone accountTimeZone, final DateTime bcdTimeUTC) {
+ private CalculatedBillCycleDay(final DateTimeZone accountTimeZone, final DateTime bcdTime) {
this.accountTimeZone = accountTimeZone;
- this.bcdTimeUTC = bcdTimeUTC;
+ this.bcdTime = bcdTime;
}
@Override
public int getDayOfMonthUTC() {
- return bcdTimeUTC.getDayOfMonth();
+ return bcdTime.toDateTime(DateTimeZone.UTC).getDayOfMonth();
}
@Override
public int getDayOfMonthLocal() {
- return bcdTimeUTC.toDateTime(accountTimeZone).getDayOfMonth();
+ return bcdTime.toDateTime(accountTimeZone).getDayOfMonth();
}
}
}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
index 07c96b7..af3d412 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
@@ -30,6 +30,7 @@ import org.joda.time.DateTimeZone;
import com.google.inject.Inject;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.BillCycleDay;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.Plan;
@@ -191,7 +192,7 @@ public class BlockingCalculator {
protected BillingEvent createNewDisableEvent(final DateTime odEventTime, final BillingEvent previousEvent) {
final Account account = previousEvent.getAccount();
- final int billCycleDay = previousEvent.getBillCycleDay();
+ final BillCycleDay billCycleDay = previousEvent.getBillCycleDay();
final Subscription subscription = previousEvent.getSubscription();
final DateTime effectiveDate = odEventTime;
final PlanPhase planPhase = previousEvent.getPlanPhase();
@@ -214,7 +215,7 @@ public class BlockingCalculator {
protected BillingEvent createNewReenableEvent(final DateTime odEventTime, final BillingEvent previousEvent) {
final Account account = previousEvent.getAccount();
- final int billCycleDay = previousEvent.getBillCycleDay();
+ final BillCycleDay billCycleDay = previousEvent.getBillCycleDay();
final Subscription subscription = previousEvent.getSubscription();
final DateTime effectiveDate = odEventTime;
final PlanPhase planPhase = previousEvent.getPlanPhase();
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
index d8b1bc3..9777935 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
@@ -156,7 +156,7 @@ public class DefaultBillingApi implements BillingApi {
accountApi.updateAccount(account.getExternalKey(), modifiedData, context);
}
- final BillingEvent event = new DefaultBillingEvent(account, transition, subscription, bcd.getDayOfMonthUTC(), account.getCurrency(), catalogService.getFullCatalog());
+ final BillingEvent event = new DefaultBillingEvent(account, transition, subscription, bcd, account.getCurrency(), catalogService.getFullCatalog());
result.add(event);
} catch (CatalogApiException e) {
log.error("Failing to identify catalog components while creating BillingEvent from transition: " +
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java
index d35f4fe..a51f874 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java
@@ -22,6 +22,7 @@ import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.BillCycleDay;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Catalog;
import com.ning.billing.catalog.api.CatalogApiException;
@@ -36,7 +37,7 @@ import com.ning.billing.entitlement.api.user.Subscription;
public class DefaultBillingEvent implements BillingEvent {
private final Account account;
- private final int billCycleDay;
+ private final BillCycleDay billCycleDay;
private final Subscription subscription;
private final DateTime effectiveDate;
private final PlanPhase planPhase;
@@ -51,10 +52,10 @@ public class DefaultBillingEvent implements BillingEvent {
private final Long totalOrdering;
private final DateTimeZone timeZone;
- public DefaultBillingEvent(final Account account, final EffectiveSubscriptionEvent transition, final Subscription subscription, final int billCycleDayUTC, final Currency currency, final Catalog catalog) throws CatalogApiException {
+ public DefaultBillingEvent(final Account account, final EffectiveSubscriptionEvent transition, final Subscription subscription, final BillCycleDay billCycleDay, final Currency currency, final Catalog catalog) throws CatalogApiException {
this.account = account;
- this.billCycleDay = billCycleDayUTC;
+ this.billCycleDay = billCycleDay;
this.subscription = subscription;
effectiveDate = transition.getEffectiveTransitionTime();
final String planPhaseName = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ?
@@ -87,7 +88,7 @@ public class DefaultBillingEvent implements BillingEvent {
public DefaultBillingEvent(final Account account, final Subscription subscription, final DateTime effectiveDate, final Plan plan, final PlanPhase planPhase,
final BigDecimal fixedPrice, final BigDecimal recurringPrice, final Currency currency,
- final BillingPeriod billingPeriod, final int billCycleDay, final BillingModeType billingModeType,
+ final BillingPeriod billingPeriod, final BillCycleDay billCycleDay, final BillingModeType billingModeType,
final String description, final long totalOrdering, final SubscriptionTransitionType type, final DateTimeZone timeZone) {
this.account = account;
this.subscription = subscription;
@@ -126,7 +127,7 @@ public class DefaultBillingEvent implements BillingEvent {
}
@Override
- public int getBillCycleDay() {
+ public BillCycleDay getBillCycleDay() {
return billCycleDay;
}
@@ -193,35 +194,23 @@ public class DefaultBillingEvent implements BillingEvent {
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
- sb.append("BillingEvent {subscriptionId = ").append(subscription.getId().toString()).append(", ");
- sb.append("plan = ").append(plan.getName()).append(", ");
- sb.append("phase = ").append(planPhase.getName()).append(", ");
- sb.append("effectiveDate = ").append(effectiveDate.toString()).append(", ");
- sb.append("billCycleDay = ").append(billCycleDay).append(", ");
- sb.append("recurringPrice = ");
-
- try {
- sb.append(recurringPrice.toString());
- } catch (Exception e) {
- sb.append("null");
- }
-
- sb.append(", ");
- sb.append("fixedPrice = ");
-
- try {
- sb.append(fixedPrice.toString());
- } catch (Exception e) {
- sb.append("null");
- }
- sb.append(", ");
-
- sb.append("currency = ").append(currency.toString()).append(", ");
- sb.append("billingPeriod = ").append(billingPeriod.toString());
- sb.append(", ");
- sb.append("totalOrdering = ").append(getTotalOrdering().toString());
- sb.append("}");
-
+ sb.append("DefaultBillingEvent");
+ sb.append("{account=").append(account);
+ sb.append(", billCycleDay=").append(billCycleDay);
+ sb.append(", subscription=").append(subscription);
+ sb.append(", effectiveDate=").append(effectiveDate);
+ sb.append(", planPhase=").append(planPhase);
+ sb.append(", plan=").append(plan);
+ sb.append(", fixedPrice=").append(fixedPrice);
+ sb.append(", recurringPrice=").append(recurringPrice);
+ sb.append(", currency=").append(currency);
+ sb.append(", description='").append(description).append('\'');
+ sb.append(", billingModeType=").append(billingModeType);
+ sb.append(", billingPeriod=").append(billingPeriod);
+ sb.append(", type=").append(type);
+ sb.append(", totalOrdering=").append(totalOrdering);
+ sb.append(", timeZone=").append(timeZone);
+ sb.append('}');
return sb.toString();
}
@@ -281,7 +270,7 @@ public class DefaultBillingEvent implements BillingEvent {
@Override
public int hashCode() {
- int result = billCycleDay;
+ int result = billCycleDay.hashCode();
result = 31 * result + subscription.hashCode();
result = 31 * result + effectiveDate.hashCode();
result = 31 * result + planPhase.hashCode();
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
new file mode 100644
index 0000000..b5b327a
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.junction.plumbing.billing;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.BillCycleDay;
+import com.ning.billing.catalog.api.BillingAlignment;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public class TestBillCycleDayCalculator {
+
+ @Test(groups = "fast")
+ public void testCalculateBCDForAOWithBPCancelledBundleAligned() throws Exception {
+ final DateTimeZone accountTimeZone = DateTimeZone.UTC;
+ final DateTime bpStartDateUTC = new DateTime(2012, 7, 16, 21, 0, 0, DateTimeZone.UTC);
+ final int expectedBCDUTC = 16;
+
+ // Create the calculator
+ final CatalogService catalogService = Mockito.mock(CatalogService.class);
+ final EntitlementUserApi entitlementUserApi = Mockito.mock(EntitlementUserApi.class);
+ final BillCycleDayCalculator billCycleDayCalculator = new BillCycleDayCalculator(catalogService, entitlementUserApi);
+
+ // Create a Bundle associated with a subscription
+ final SubscriptionBundle bundle = Mockito.mock(SubscriptionBundle.class);
+ final EffectiveSubscriptionEvent previousTransition = Mockito.mock(EffectiveSubscriptionEvent.class);
+ final Subscription subscription = Mockito.mock(Subscription.class);
+ Mockito.when(subscription.getStartDate()).thenReturn(bpStartDateUTC);
+ Mockito.when(subscription.getPreviousTransition()).thenReturn(previousTransition);
+ // subscription.getCurrentPlan() will return null as expected (cancelled BP)
+ Mockito.when(entitlementUserApi.getBaseSubscription(Mockito.<UUID>any())).thenReturn(subscription);
+
+ // Create a the base plan associated with that subscription
+ final Plan plan = Mockito.mock(Plan.class);
+ Mockito.when(plan.dateOfFirstRecurringNonZeroCharge(bpStartDateUTC)).thenReturn(bpStartDateUTC);
+ final Catalog catalog = Mockito.mock(Catalog.class);
+ Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(plan);
+
+ final Account account = Mockito.mock(Account.class);
+ Mockito.when(account.getTimeZone()).thenReturn(accountTimeZone);
+ final BillCycleDay billCycleDay = billCycleDayCalculator.calculateBcdForAlignment(BillingAlignment.BUNDLE, bundle, subscription,
+ account, catalog, null);
+
+ Assert.assertEquals(billCycleDay.getDayOfMonthUTC(), expectedBCDUTC);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateBCDWithTimeZoneHST() throws Exception {
+ final DateTimeZone accountTimeZone = DateTimeZone.forID("HST");
+ final DateTime startDateUTC = new DateTime("2012-07-16T21:17:03.000Z", DateTimeZone.UTC);
+ final int bcdUTC = 16;
+ final int bcdLocal = 16;
+
+ verifyBCDCalculation(accountTimeZone, startDateUTC, bcdUTC, bcdLocal);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateBCDWithTimeZoneCEST() throws Exception {
+ final DateTimeZone accountTimeZone = DateTimeZone.forID("Europe/Paris");
+ final DateTime startDateUTC = new DateTime("2012-07-16T21:17:03.000Z", DateTimeZone.UTC);
+ final int bcdUTC = 16;
+ final int bcdLocal = 16;
+
+ verifyBCDCalculation(accountTimeZone, startDateUTC, bcdUTC, bcdLocal);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateBCDWithTimeZoneUTC() throws Exception {
+ final DateTimeZone accountTimeZone = DateTimeZone.UTC;
+ final DateTime startDateUTC = new DateTime("2012-07-16T21:17:03.000Z", DateTimeZone.UTC);
+ final int bcdUTC = 16;
+ final int bcdLocal = 16;
+
+ verifyBCDCalculation(accountTimeZone, startDateUTC, bcdUTC, bcdLocal);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateBCDWithTimeZoneEEST() throws Exception {
+ final DateTimeZone accountTimeZone = DateTimeZone.forID("+0300");
+ final DateTime startDateUTC = new DateTime("2012-07-16T21:17:03.000Z", DateTimeZone.UTC);
+ final int bcdUTC = 16;
+ final int bcdLocal = 17;
+
+ verifyBCDCalculation(accountTimeZone, startDateUTC, bcdUTC, bcdLocal);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateBCDWithTimeZoneJST() throws Exception {
+ final DateTimeZone accountTimeZone = DateTimeZone.forID("Asia/Tokyo");
+ final DateTime startDateUTC = new DateTime("2012-07-16T21:17:03.000Z", DateTimeZone.UTC);
+ final int bcdUTC = 16;
+ final int bcdLocal = 17;
+
+ verifyBCDCalculation(accountTimeZone, startDateUTC, bcdUTC, bcdLocal);
+ }
+
+ @Test(groups = "fast")
+ public void testCalculateBCDWithSubscriptionDateNotInUTC() throws Exception {
+ // Test to verify the computations don't rely implicitly on UTC
+ final DateTimeZone accountTimeZone = DateTimeZone.forID("Asia/Tokyo");
+ final DateTime startDate = new DateTime("2012-07-16T21:17:03.000Z", DateTimeZone.forID("HST"));
+ final int bcdUTC = 16;
+ final int bcdLocal = 17;
+
+ verifyBCDCalculation(accountTimeZone, startDate, bcdUTC, bcdLocal);
+ }
+
+ private void verifyBCDCalculation(final DateTimeZone accountTimeZone, final DateTime startDateUTC, final int bcdUTC, final int bcdLocal) throws AccountApiException {
+ final BillCycleDayCalculator billCycleDayCalculator = new BillCycleDayCalculator(Mockito.mock(CatalogService.class), Mockito.mock(EntitlementUserApi.class));
+
+ final Subscription subscription = Mockito.mock(Subscription.class);
+ Mockito.when(subscription.getStartDate()).thenReturn(startDateUTC);
+
+ final Plan plan = Mockito.mock(Plan.class);
+ Mockito.when(plan.dateOfFirstRecurringNonZeroCharge(startDateUTC)).thenReturn(startDateUTC);
+
+ final Account account = Mockito.mock(Account.class);
+ Mockito.when(account.getTimeZone()).thenReturn(accountTimeZone);
+
+ final BillCycleDay bcd = billCycleDayCalculator.calculateBcdFromSubscription(subscription, plan, account);
+ Assert.assertEquals(bcd.getDayOfMonthUTC(), bcdUTC);
+ Assert.assertEquals(bcd.getDayOfMonthLocal(), bcdLocal);
+ }
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java
index caccc9c..ef9f1b4 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java
@@ -517,7 +517,7 @@ public class TestBillingApi extends JunctionTestSuite {
assertNull(event.getRecurringPrice());
}
- Assert.assertEquals(BCD, event.getBillCycleDay());
+ Assert.assertEquals(BCD, event.getBillCycleDay().getDayOfMonthUTC());
Assert.assertEquals(id, event.getSubscription().getId());
Assert.assertEquals(time.getDayOfMonth(), event.getEffectiveDate().getDayOfMonth());
Assert.assertEquals(nextPhase, event.getPlanPhase());
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
index f17f681..e6dd137 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -35,6 +35,7 @@ import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.BillCycleDay;
import com.ning.billing.catalog.MockPlan;
import com.ning.billing.catalog.MockPlanPhase;
import com.ning.billing.catalog.api.BillingPeriod;
@@ -53,6 +54,7 @@ import com.ning.billing.junction.api.BlockingState;
import com.ning.billing.junction.api.DefaultBlockingState;
import com.ning.billing.junction.dao.BlockingStateDao;
import com.ning.billing.junction.plumbing.billing.BlockingCalculator.DisabledDuration;
+import com.ning.billing.mock.api.MockBillCycleDay;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
@@ -532,7 +534,7 @@ public class TestBlockingCalculator extends JunctionTestSuite {
protected BillingEvent createRealEvent(final DateTime effectiveDate, final Subscription subscription) {
final Account account = this.account;
- final int billCycleDay = 1;
+ final BillCycleDay billCycleDay = new MockBillCycleDay(1);
final PlanPhase planPhase = new MockPlanPhase();
final Plan plan = new MockPlan();
final BigDecimal fixedPrice = BigDecimal.TEN;
@@ -615,7 +617,7 @@ public class TestBlockingCalculator extends JunctionTestSuite {
private class MockBillingEvent extends DefaultBillingEvent {
public MockBillingEvent() {
super(account, subscription1, clock.getUTCNow(), null, null, BigDecimal.ZERO, BigDecimal.TEN, Currency.USD, BillingPeriod.ANNUAL,
- 4, BillingModeType.IN_ADVANCE, "", 3L, SubscriptionTransitionType.CREATE, DateTimeZone.UTC);
+ new MockBillCycleDay(4), BillingModeType.IN_ADVANCE, "", 3L, SubscriptionTransitionType.CREATE, DateTimeZone.UTC);
}
}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultBillingEvent.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultBillingEvent.java
index 6f8ea89..b760d0b 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultBillingEvent.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultBillingEvent.java
@@ -43,6 +43,7 @@ import com.ning.billing.entitlement.api.billing.BillingEvent;
import com.ning.billing.entitlement.api.billing.BillingModeType;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.junction.JunctionTestSuite;
+import com.ning.billing.mock.api.MockBillCycleDay;
public class TestDefaultBillingEvent extends JunctionTestSuite {
public static final UUID ID_ZERO = new UUID(0L, 0L);
@@ -134,7 +135,7 @@ public class TestDefaultBillingEvent extends JunctionTestSuite {
return new DefaultBillingEvent(null, sub, effectiveDate,
shotgun, shotgunMonthly,
- BigDecimal.ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, billCycleDay,
+ BigDecimal.ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, new MockBillCycleDay(billCycleDay),
BillingModeType.IN_ADVANCE, "Test Event 1", totalOrdering, type, DateTimeZone.UTC);
}