killbill-memoizeit

Changes

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);
     }