killbill-aplcache

Changes

Details

diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 4e1ffaa..3209c8c 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -128,7 +128,8 @@ public enum ErrorCode {
     */
     INVOICE_ACCOUNT_ID_INVALID(4001, "No account could be retrieved for id %s"),
     INVOICE_INVALID_TRANSITION(4002, "Transition did not contain a subscription id."),
-    INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID(4003, "No account id was retrieved for subscription id %s")  
+    INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID(4003, "No account id was retrieved for subscription id %s"),
+    INVOICE_INVALID_DATE_SEQUENCE(4004, "Date sequence was invalid. Start Date: %s; End Date: %s; Target Date: %s")
     ;
 
     private int code;
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
index 8cadd9a..b0c9961 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
@@ -32,25 +32,11 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     String getPhaseName();
 
-    DateTime getStartDate();
+    String getDescription();
 
-    DateTime getEndDate();
-
-    BigDecimal getRecurringAmount();
-
-    BigDecimal getRecurringRate();
-
-    BigDecimal getFixedAmount();
+    BigDecimal getAmount();
 
     Currency getCurrency();
 
-    InvoiceItem asCredit(UUID invoiceId);
-
-    int compareTo(InvoiceItem invoiceItem);
-
-    void subtract(InvoiceItem that);
-
-    boolean duplicates(InvoiceItem that);
-
-    boolean cancels(InvoiceItem that);
+    InvoiceItem asCredit();
 }
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 19eaad5..947c782 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,6 +22,7 @@ import java.util.List;
 import java.util.UUID;
 
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
@@ -41,7 +42,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
     private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
 
     private final InvoiceSqlDao invoiceSqlDao;
-    private final InvoiceItemSqlDao invoiceItemSqlDao;
+    private final RecurringInvoiceItemSqlDao recurringInvoiceItemSqlDao;
     private final InvoicePaymentSqlDao invoicePaymentSqlDao;
     private final NextBillingDateNotifier notifier;
     private final EntitlementBillingApi entitlementBillingApi;
@@ -52,7 +53,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
     public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
                              final NextBillingDateNotifier notifier, final EntitlementBillingApi entitlementBillingApi) {
         this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
-        this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
+        this.recurringInvoiceItemSqlDao = dbi.onDemand(RecurringInvoiceItemSqlDao.class);
         this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
         this.eventBus = eventBus;
         this.notifier = notifier;
@@ -91,7 +92,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     @Override
     public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
-        return invoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString());
+        return recurringInvoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString());
     }
 
     @Override
@@ -117,8 +118,8 @@ public class DefaultInvoiceDao implements InvoiceDao {
                  Invoice invoice = invoiceDao.getById(invoiceId.toString());
 
                  if (invoice != null) {
-                     InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                     List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
+                     RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+                     List<InvoiceItem> invoiceItems = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
                      invoice.addInvoiceItems(invoiceItems);
 
                      InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
@@ -144,8 +145,8 @@ public class DefaultInvoiceDao implements InvoiceDao {
                     invoiceDao.create(invoice);
 
                     List<InvoiceItem> invoiceItems = invoice.getInvoiceItems();
-                    InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                    invoiceItemDao.create(invoiceItems);
+                    RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+                    recurringInvoiceItemDao.create(invoiceItems);
 
                     notifyOfFutureBillingEvents(invoiceSqlDao, invoiceItems);
                     setChargedThroughDates(invoiceSqlDao, invoiceItems);
@@ -256,9 +257,9 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     private void getInvoiceItemsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
-        InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
+        RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
         for (final Invoice invoice : invoices) {
-            List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
+            List<InvoiceItem> invoiceItems = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
             invoice.addInvoiceItems(invoiceItems);
         }
     }
@@ -274,20 +275,30 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     private void notifyOfFutureBillingEvents(final InvoiceSqlDao dao, final List<InvoiceItem> invoiceItems) {
         for (final InvoiceItem item : invoiceItems) {
-            if ((item.getEndDate() != null) &&
-                    (item.getRecurringAmount() == null || item.getRecurringAmount().compareTo(BigDecimal.ZERO) >= 0)) {
-                notifier.insertNextBillingNotification(dao, item.getSubscriptionId(), item.getEndDate());
+            if (item instanceof RecurringInvoiceItem) {
+                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+                if ((recurringInvoiceItem.getEndDate() != null) &&
+                        (recurringInvoiceItem.getAmount() == null ||
+                                recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+                notifier.insertNextBillingNotification(dao, item.getSubscriptionId(), recurringInvoiceItem.getEndDate());
             }
+            }
+
         }
     }
 
     private void setChargedThroughDates(final InvoiceSqlDao dao, final Collection<InvoiceItem> invoiceItems) {
         for (InvoiceItem item : invoiceItems) {
-            if ((item.getEndDate() != null) &&
-                    (item.getRecurringAmount() == null || item.getRecurringAmount().compareTo(BigDecimal.ZERO) >= 0)) {
-                log.info("Setting CTD for invoice item {} to {}", item.getId().toString(), item.getEndDate().toString());
-                entitlementBillingApi.setChargedThroughDateFromTransaction(dao, item.getSubscriptionId(), item.getEndDate());
+            if (item instanceof RecurringInvoiceItem) {
+                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+                if ((recurringInvoiceItem.getEndDate() != null) &&
+                        (recurringInvoiceItem.getAmount() == null ||
+                                recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+                    log.info("Setting CTD for invoice item {} to {}", recurringInvoiceItem.getId().toString(), recurringInvoiceItem.getEndDate().toString());
+                    entitlementBillingApi.setChargedThroughDateFromTransaction(dao, recurringInvoiceItem.getSubscriptionId(), recurringInvoiceItem.getEndDate());
+                }
             }
+
         }
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
new file mode 100644
index 0000000..ed6ffe0
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
@@ -0,0 +1,93 @@
+package com.ning.billing.invoice.dao;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.util.entity.EntityDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(FixedPriceInvoiceItemSqlDao.FixedPriceInvoiceItemMapper.class)
+public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
+    @SqlQuery
+    List<InvoiceItem> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId);
+
+    @SqlQuery
+    List<InvoiceItem> getInvoiceItemsByAccount(@Bind("accountId") final String accountId);
+
+    @SqlQuery
+    List<InvoiceItem> getInvoiceItemsBySubscription(@Bind("subscriptionId") final String subscriptionId);
+
+    @Override
+    @SqlUpdate
+    void create(@FixedPriceInvoiceItemBinder final InvoiceItem invoiceItem);
+
+    @Override
+    @SqlUpdate
+    void update(@FixedPriceInvoiceItemBinder final InvoiceItem invoiceItem);
+
+    @SqlBatch
+    void create(@FixedPriceInvoiceItemBinder final List<FixedPriceInvoiceItem> items);
+
+    @BindingAnnotation(FixedPriceInvoiceItemBinder.FixedPriceInvoiceItemBinderFactory.class)
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.PARAMETER})
+    public @interface FixedPriceInvoiceItemBinder {
+        public static class FixedPriceInvoiceItemBinderFactory implements BinderFactory {
+            public Binder build(Annotation annotation) {
+                return new Binder<FixedPriceInvoiceItemBinder, FixedPriceInvoiceItem>() {
+                    public void bind(SQLStatement q, FixedPriceInvoiceItemBinder bind, FixedPriceInvoiceItem item) {
+                        q.bind("id", item.getId().toString());
+                        q.bind("invoiceId", item.getInvoiceId().toString());
+                        q.bind("subscriptionId", item.getSubscriptionId().toString());
+                        q.bind("planName", item.getPlanName());
+                        q.bind("phaseName", item.getPhaseName());
+                        q.bind("date", item.getDate().toDate());
+                        q.bind("amount", item.getAmount());
+                        q.bind("currency", item.getCurrency().toString());
+                    }
+                };
+            }
+        }
+    }
+
+    public static class FixedPriceInvoiceItemMapper implements ResultSetMapper<FixedPriceInvoiceItem> {
+        @Override
+        public FixedPriceInvoiceItem map(int index, ResultSet result, StatementContext context) throws SQLException {
+            UUID id = UUID.fromString(result.getString("id"));
+            UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
+            UUID subscriptionId = UUID.fromString(result.getString("subscription_id"));
+            String planName = result.getString("plan_name");
+            String phaseName = result.getString("phase_name");
+            DateTime date = new DateTime(result.getTimestamp("date"));
+            BigDecimal amount = result.getBigDecimal("amount");
+            Currency currency = Currency.valueOf(result.getString("currency"));
+
+            return new FixedPriceInvoiceItem(id, invoiceId, subscriptionId, planName, phaseName, date,
+                                            amount, currency);
+        }
+    }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
index 174b44d..8cf0195 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -69,16 +69,24 @@ public class InvoiceListener {
 
     @Subscribe
     public void handleSubscriptionTransition(final SubscriptionTransition transition) {
-        processSubscription(transition);
+        try {
+            processSubscription(transition);
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
+        }
     }
 
     @Subscribe
     public void handleNextBillingDateEvent(final NextBillingDateEvent event) {
         // STEPH should we use the date of the event instead?
-        processSubscription(event.getSubscriptionId(), clock.getUTCNow());
+        try {
+            processSubscription(event.getSubscriptionId(), clock.getUTCNow());
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
+        }
     }
 
-    private void processSubscription(final SubscriptionTransition transition) {
+    private void processSubscription(final SubscriptionTransition transition) throws InvoiceApiException {
         UUID subscriptionId = transition.getSubscriptionId();
         DateTime targetDate = transition.getEffectiveTransitionTime();
         log.info("Got subscription transition from InvoiceListener. id: " + subscriptionId.toString() + "; targetDate: " + targetDate.toString());
@@ -86,7 +94,7 @@ public class InvoiceListener {
         processSubscription(subscriptionId, targetDate);
     }
 
-    private void processSubscription(final UUID subscriptionId, final DateTime targetDate) {
+    private void processSubscription(final UUID subscriptionId, final DateTime targetDate) throws InvoiceApiException {
         if (subscriptionId == null) {
             log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
             return;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
index 0ce3b52..da95559 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
@@ -31,9 +31,7 @@ public class BillingEventSet extends ArrayList<BillingEvent> {
         addAll(events);
     }
 
-    public BillingEvent getLast() {
-        if (this.size() == 0) {return null;}
-
-        return this.get(this.size() - 1);
+    public boolean isLast(final BillingEvent event) {
+        return (super.indexOf(event) == size() - 1);
     }
 }
\ No newline at end of file
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 71c4765..e197feb 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
@@ -19,14 +19,17 @@ package com.ning.billing.invoice.model;
 import com.ning.billing.catalog.api.BillingPeriod;
 import org.joda.time.DateTime;
 
-import java.math.BigDecimal;
+import java.util.List;
 
 public interface BillingMode {
-    BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+    List<RecurringInvoiceItemData> calculateInvoiceItemData(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+    List<RecurringInvoiceItemData> calculateInvoiceItemData(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
 
-    BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
-
-    DateTime calculateEffectiveEndDate(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
-
-    DateTime calculateEffectiveEndDate(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
+//    BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+//
+//    BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+//
+//    DateTime calculateEffectiveEndDate(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
+//
+//    DateTime calculateEffectiveEndDate(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
 }
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
index ffdf806..55c646c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
@@ -21,64 +21,62 @@ import org.joda.time.DateTime;
 
 import java.math.BigDecimal;
 
-public abstract class BillingModeBase implements BillingMode {
-    @Override
-    public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
-        if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-
-        if (billingPeriod == BillingPeriod.NO_BILLING_PERIOD) {
-            return BigDecimal.ZERO;
-        }
-
-        BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
-
-        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
-        DateTime endBillCycleDate;
-        BigDecimal trailingProRation;
-        BigDecimal numberOfBillingPeriods;
-
-        DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
-        endBillCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillCycleDate, billingCycleDay, billingPeriod);
-        numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
-
-        trailingProRation = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, endBillCycleDate, billingPeriod);
-
-        return precedingProRation.add(numberOfBillingPeriods).add(trailingProRation);
-    }
-
-    @Override
-    public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
-        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-
-        BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
-
-        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
-        DateTime endBillCycleDate = calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
-        BigDecimal numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
-
-        return precedingProRation.add(numberOfBillingPeriods);
-    }
-
-    boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
-        return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
-    }
-
-    public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    protected abstract BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod);
-
-    protected abstract DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay);
-
-    protected abstract DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    protected abstract DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    protected abstract BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    protected abstract BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod);
-
-    protected abstract DateTime calculateEffectiveEndDate(final DateTime billCycleDate, final DateTime targetDate, final DateTime endDate, final BillingPeriod billingPeriod);
+public abstract class BillingModeBase {
+//    public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+//        if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//
+//        if (billingPeriod == BillingPeriod.NO_BILLING_PERIOD) {
+//            return BigDecimal.ZERO;
+//        }
+//
+//        BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
+//
+//        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+//        DateTime endBillCycleDate;
+//        BigDecimal trailingProRation;
+//        BigDecimal numberOfBillingPeriods;
+//
+//        DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
+//        endBillCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillCycleDate, billingCycleDay, billingPeriod);
+//        numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
+//
+//        trailingProRation = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, endBillCycleDate, billingPeriod);
+//
+//        return precedingProRation.add(numberOfBillingPeriods).add(trailingProRation);
+//    }
+//
+//    public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+//        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//
+//        BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
+//
+//        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+//        DateTime endBillCycleDate = calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
+//        BigDecimal numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
+//
+//        return precedingProRation.add(numberOfBillingPeriods);
+//    }
+//
+//    boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
+//        return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
+//    }
+//
+//    public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    protected abstract BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod);
+//
+//    protected abstract DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay);
+//
+//    protected abstract DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    protected abstract DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    protected abstract BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    protected abstract BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod);
+//
+//    protected abstract DateTime calculateEffectiveEndDate(final DateTime billCycleDate, final DateTime targetDate, final DateTime endDate, final BillingPeriod billingPeriod);
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
index c83be80..b2e3e6e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
@@ -17,243 +17,234 @@
 package com.ning.billing.invoice.model;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.UUID;
-import org.joda.time.DateTime;
-import org.joda.time.Days;
-import org.joda.time.Duration;
-import org.joda.time.Period;
-import org.joda.time.format.ISODateTimeFormat;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import org.joda.time.DateTime;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
-import com.ning.billing.entitlement.api.billing.BillingModeType;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.MutableDateTime;
 
 import javax.annotation.Nullable;
 
 public class DefaultInvoiceGenerator implements InvoiceGenerator {
-    private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
+    private static final int ROUNDING_MODE = InvoicingConfiguration.getRoundingMode();
+    private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+    //private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
 
     @Override
     public Invoice generateInvoice(final UUID accountId, final BillingEventSet events,
-                                   @Nullable final InvoiceItemList existingItems, final DateTime targetDate,
-                                   final Currency targetCurrency) {
-        if (events == null) {
+                                   @Nullable final List<InvoiceItem> items, final DateTime targetDate,
+                                   final Currency targetCurrency) throws InvoiceApiException {
+        if ((events == null) || (events.size() == 0)) {
             return null;
         }
 
-        if (events.size() == 0) {
-            return null;
+        Collections.sort(events);
+
+        List<InvoiceItem> existingItems = null;
+        if (items != null) {
+            existingItems = new ArrayList<InvoiceItem>(items);
+            Collections.sort(existingItems);
         }
 
         DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency);
-        InvoiceItemList currentItems = generateInvoiceItems(events, invoice.getId(), targetDate, targetCurrency);
-        InvoiceItemList itemsToPost = reconcileInvoiceItems(invoice.getId(), currentItems, existingItems);
+        UUID invoiceId = invoice.getId();
+        List<InvoiceItem> proposedItems = generateInvoiceItems(invoiceId, events, targetDate, targetCurrency);
 
-        if (itemsToPost.size() == 0) {
+        if (existingItems != null) {
+            removeCancellingInvoiceItems(existingItems);
+            removeDuplicatedInvoiceItems(proposedItems, existingItems);
+
+            for (InvoiceItem existingItem : existingItems) {
+                if (existingItem instanceof RecurringInvoiceItem) {
+                    RecurringInvoiceItem recurringItem = (RecurringInvoiceItem) existingItem;
+                    proposedItems.add(recurringItem.asCredit());
+                }
+            }
+        }
+
+        if (proposedItems == null || proposedItems.size()  == 0) {
             return null;
         } else {
-            invoice.addInvoiceItems(itemsToPost);
+            invoice.addInvoiceItems(proposedItems);
             return invoice;
         }
     }
 
-    private InvoiceItemList reconcileInvoiceItems(final UUID invoiceId, final InvoiceItemList currentInvoiceItems,
-                                                  final InvoiceItemList existingInvoiceItems) {
-        if ((existingInvoiceItems == null) || (existingInvoiceItems.size() == 0)) {
-            return currentInvoiceItems;
-        }
-
-        InvoiceItemList currentItems = new InvoiceItemList();
-        for (final InvoiceItem item : currentInvoiceItems) {
-            currentItems.add(new DefaultInvoiceItem(item, invoiceId));
+   /*
+    * removes all matching items from both submitted collections
+    */
+    private void removeDuplicatedInvoiceItems(final List<InvoiceItem> proposedItems,
+                                              final List<InvoiceItem> existingInvoiceItems) {
+        Iterator<InvoiceItem> proposedItemIterator = proposedItems.iterator();
+        while (proposedItemIterator.hasNext()) {
+            InvoiceItem proposedItem = proposedItemIterator.next();
+
+            if (existingInvoiceItems.contains(proposedItem)) {
+                existingInvoiceItems.remove(proposedItem);
+                proposedItemIterator.remove();
+            }
         }
+    }
 
-        // STEPH why clone? Why cast?
-        InvoiceItemList existingItems = (InvoiceItemList) existingInvoiceItems.clone();
-
-        Collections.sort(currentItems);
-        Collections.sort(existingItems);
-
-        for (final InvoiceItem currentItem : currentItems) {
-            Iterator<InvoiceItem> it = existingItems.iterator();
+    private void removeCancellingInvoiceItems(final List<InvoiceItem> items) {
+        List<UUID> itemsToRemove = new ArrayList<UUID>();
 
-            // see if there are any existing items that are covered by the current item
-            while (it.hasNext()) {
-                InvoiceItem existingItem = it.next();
-                // STEPH this is more like 'contained' that 'duplicates'
-                if (currentItem.duplicates(existingItem)) {
-                    currentItem.subtract(existingItem);
-                    it.remove();
+        for (InvoiceItem item1 : items) {
+            if (item1 instanceof RecurringInvoiceItem) {
+                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item1;
+                if (recurringInvoiceItem.reversesItem()) {
+                    itemsToRemove.add(recurringInvoiceItem.getId());
+                    itemsToRemove.add(recurringInvoiceItem.getReversedItemId());
                 }
             }
         }
 
-        // remove cancelling pairs of invoice items
-        existingItems.removeCancellingPairs();
-
-        // add existing items that aren't covered by current items as credit items
-        for (final InvoiceItem existingItem : existingItems) {
-            // STEPH do we really want to credit if that has not been paid yet?
-            currentItems.add(existingItem.asCredit(existingItem.getInvoiceId()));
+        Iterator<InvoiceItem> iterator = items.iterator();
+        while (iterator.hasNext()) {
+            InvoiceItem item = iterator.next();
+            if (itemsToRemove.contains(item.getId())) {
+                iterator.remove();
+            }
         }
-
-        currentItems.cleanupDuplicatedItems();
-
-        return currentItems;
     }
 
-    private InvoiceItemList generateInvoiceItems(final BillingEventSet events, final UUID invoiceId,
-                                                 final DateTime targetDate, final Currency targetCurrency) {
-        InvoiceItemList items = new InvoiceItemList();
+    private List<InvoiceItem> generateInvoiceItems(final UUID invoiceId, final BillingEventSet events,
+                                                   final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+        List<InvoiceItem> items = new ArrayList<InvoiceItem>();
 
-        // sort events; this relies on the sort order being by subscription id then start date
-        Collections.sort(events);
-
-        // for each event, process it either as a terminated event (if there's a subsequent event)
-        // ...or as a non-terminated event (if no subsequent event exists)
-        for (int i = 0; i < (events.size() - 1); i++) {
+        for (int i = 0; i < events.size(); i++) {
             BillingEvent thisEvent = events.get(i);
-            BillingEvent nextEvent = events.get(i + 1);
-
-
-            if (thisEvent.getSubscription().getId().equals(nextEvent.getSubscription().getId())) {
-                processEvents(invoiceId, thisEvent, nextEvent, items, targetDate, targetCurrency);
-            } else {
-                processEvent(invoiceId, thisEvent, items, targetDate, targetCurrency);
+            BillingEvent nextEvent = events.isLast(thisEvent) ? null : events.get(i + 1);
+            if (nextEvent != null) {
+                nextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null;
             }
-        }
 
-        // process the last item in the event set
-        if (events.size() > 0) {
-            processEvent(invoiceId, events.getLast(), items, targetDate, targetCurrency);
+            items.addAll(processEvents(invoiceId, thisEvent, nextEvent, targetDate, currency));
         }
 
         return items;
     }
 
-    private void processEvent(final UUID invoiceId, final BillingEvent event, final InvoiceItemList items,
-                              final DateTime targetDate, final Currency targetCurrency) {
-    	try {
-    	    // STEPH should not that check apply to next method as well?
-            if (event.getEffectiveDate().compareTo(targetDate) > 0) {
-                return;
-            }
-
-            BigDecimal recurringRate = event.getRecurringPrice() == null ? null : event.getRecurringPrice().getPrice(targetCurrency);
-            BigDecimal fixedPrice = event.getFixedPrice() == null ? null : event.getFixedPrice().getPrice(targetCurrency);
+    private List<InvoiceItem> processEvents(final UUID invoiceId, final BillingEvent thisEvent, final BillingEvent nextEvent,
+                                            final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+        List<InvoiceItem> items = new ArrayList<InvoiceItem>();
+        InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, thisEvent, targetDate, currency);
+        if (fixedPriceInvoiceItem != null) {
+            items.add(fixedPriceInvoiceItem);
+        }
 
-    		BigDecimal numberOfBillingPeriods;
-            BigDecimal recurringAmount = null;
-            DateTime billThroughDate;
+        BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
+        if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+            BillingMode billingMode = instantiateBillingMode(thisEvent.getBillingMode());
+            DateTime startDate = thisEvent.getEffectiveDate();
+            DateTime endDate = (nextEvent == null) ? null : nextEvent.getEffectiveDate();
+            int billCycleDay = thisEvent.getBillCycleDay();
+
+            List<RecurringInvoiceItemData> itemData;
+            try {
+                itemData = billingMode.calculateInvoiceItemData(startDate, endDate, targetDate, billCycleDay, billingPeriod);
+            } catch (InvalidDateSequenceException e) {
+                throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
+            }
 
-            if (recurringRate == null) {
-                billThroughDate = event.getPlanPhase().getDuration().addToDateTime(event.getEffectiveDate());
-            } else {
-                numberOfBillingPeriods = calculateNumberOfBillingPeriods(event, targetDate);
-                recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
-                BillingMode billingMode = getBillingMode(event.getBillingMode());
-                billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
+            for (RecurringInvoiceItemData itemDatum : itemData) {
+                InternationalPrice price = thisEvent.getRecurringPrice();
+                if (price != null) {
+                    BigDecimal rate;
+
+                    try {
+                        rate = thisEvent.getRecurringPrice().getPrice(currency);
+                    } catch (CatalogApiException e) {
+                        throw new InvoiceApiException(e, ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency.toString());
+                    }
+
+                    BigDecimal amount = itemDatum.getNumberOfCycles().multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
+
+                    RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, thisEvent.getSubscription().getId(),
+                                                                                  thisEvent.getPlan().getName(),
+                                                                                  thisEvent.getPlanPhase().getName(),
+                                                                                  itemDatum.getStartDate(), itemDatum.getEndDate(),
+                                                                                  amount, rate, currency);
+                    items.add(recurringItem);
+                }
             }
+        }
 
-            BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(event.getPlanPhase().getName()) ? null : fixedPrice;
+        return items;
+    }
 
-            // STEPH don't we also need to check for if (Days.daysBetween(firstEvent.getEffectiveDate(), billThroughDate).getDays() > 0)
-            addInvoiceItem(invoiceId, items, event, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
-    	} catch (CatalogApiException e) {
-    	    // STEPH same remark for catalog exception.
-            log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
-                    invoiceId.toString(),
-                    ISODateTimeFormat.basicDateTime().print(event.getEffectiveDate())), e);
+    private BillingMode instantiateBillingMode(BillingModeType billingMode) {
+        switch (billingMode) {
+            case IN_ADVANCE:
+                return new InAdvanceBillingMode();
+            default:
+                throw new UnsupportedOperationException();
         }
     }
 
-    private void processEvents(final UUID invoiceId, final BillingEvent firstEvent, final BillingEvent secondEvent,
-                               final InvoiceItemList items, final DateTime targetDate, final Currency targetCurrency) {
-    	try {
-            BigDecimal recurringRate = firstEvent.getRecurringPrice() == null ? null : firstEvent.getRecurringPrice().getPrice(targetCurrency);
-            BigDecimal fixedPrice = firstEvent.getFixedPrice() == null ? null : firstEvent.getFixedPrice().getPrice(targetCurrency);
-
-            BigDecimal numberOfBillingPeriods;
-            BigDecimal recurringAmount = null;
-            DateTime billThroughDate;
-
-            if (recurringRate == null) {
-                // since it's fixed price only, the following event dictates the end date, regardless of when it takes place
-                billThroughDate = secondEvent.getEffectiveDate();
-            } else {
-                numberOfBillingPeriods = calculateNumberOfBillingPeriods(firstEvent, secondEvent, targetDate);
-                recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
-                BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
-                billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
-            }
-
-            if (Days.daysBetween(firstEvent.getEffectiveDate(), billThroughDate).getDays() > 0) {
-                BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(firstEvent.getPlanPhase().getName()) ? null : fixedPrice;
-                addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
+    private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final BillingEvent thisEvent,
+                                               final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+        if (thisEvent.getEffectiveDate().isAfter(targetDate)) {
+            return null;
+        } else {
+            FixedPriceInvoiceItem fixedPriceInvoiceItem = null;
+
+            if (thisEvent.getFixedPrice() != null) {
+                try {
+                    BigDecimal fixedPrice = thisEvent.getFixedPrice().getPrice(currency);
+                    fixedPriceInvoiceItem = new FixedPriceInvoiceItem(invoiceId, thisEvent.getSubscription().getId(),
+                                                                      thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
+                                                                      thisEvent.getEffectiveDate(), fixedPrice, currency);
+                } catch (CatalogApiException e) {
+                    throw new InvoiceApiException(e, ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency.toString());
+                }
             }
-    	} catch (CatalogApiException e) {
 
-    	    // STEPH That needs to be thrown so we stop that invoice generation
-    		log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
-                    invoiceId.toString(),
-                    ISODateTimeFormat.basicDateTime().print(firstEvent.getEffectiveDate())), e);
+            return fixedPriceInvoiceItem;
         }
     }
 
-    private void addInvoiceItem(final UUID invoiceId, final InvoiceItemList items, final BillingEvent event,
-                                final DateTime billThroughDate, final BigDecimal amount, final BigDecimal rate,
-                                final BigDecimal fixedAmount, final Currency currency) {
-        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(),
-                                  event.getPlan().getName(), event.getPlanPhase().getName(), event.getEffectiveDate(),
-                                  billThroughDate, amount, rate, fixedAmount, currency);
-        items.add(item);
-        System.out.println(item);
-    }
-
-    private BigDecimal calculateNumberOfBillingPeriods(final BillingEvent event, final DateTime targetDate){
-        BillingMode billingMode = getBillingMode(event.getBillingMode());
-        DateTime startDate = event.getEffectiveDate();
-        int billingCycleDay = event.getBillCycleDay();
-        BillingPeriod billingPeriod = event.getBillingPeriod();
-
-        try {
-            return billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod);
-        } catch (InvalidDateSequenceException e) {
-            // TODO: Jeff -- log issue
-            return BigDecimal.ZERO;
-        }
-    }
+    // assumption: startDate is in the user's time zone
+    private DateTime calculateSegmentEndDate(final DateTime startDate, final DateTime nextEndDate,
+                                             final int billCycleDay, final BillingPeriod billingPeriod) {
+        int dayOfMonth = startDate.getDayOfMonth();
+        int maxDayOfMonth = startDate.dayOfMonth().getMaximumValue();
 
-    private BigDecimal calculateNumberOfBillingPeriods(final BillingEvent firstEvent, final BillingEvent secondEvent,
-                                                  final DateTime targetDate) {
-        BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
-        DateTime startDate = firstEvent.getEffectiveDate();
-        int billingCycleDay = firstEvent.getBillCycleDay();
-        BillingPeriod billingPeriod = firstEvent.getBillingPeriod();
+        DateTime nextBillingDate;
 
-        DateTime endDate = secondEvent.getEffectiveDate();
+        // if the start date is not on the bill cycle day, move it to the nearest following date that works
+        if ((billCycleDay > maxDayOfMonth) || (dayOfMonth == billCycleDay)) {
+            nextBillingDate = startDate.plusMonths(billingPeriod.getNumberOfMonths());
+        } else {
+            MutableDateTime proposedDate = startDate.toMutableDateTime();
 
-        try {
-            return billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
-        } catch (InvalidDateSequenceException e) {
-            // TODO: Jeff -- log issue
-            return BigDecimal.ZERO;
+            if (dayOfMonth < billCycleDay) {
+                // move the end date forward to the bill cycle date (same month)
+                int effectiveBillCycleDay = (billCycleDay > maxDayOfMonth) ? maxDayOfMonth : billCycleDay;
+                nextBillingDate = proposedDate.dayOfMonth().set(effectiveBillCycleDay).toDateTime();
+            } else {
+                // go to the next month
+                proposedDate = proposedDate.monthOfYear().add(1);
+                maxDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
+                int effectiveBillCycleDay = (billCycleDay > maxDayOfMonth) ? maxDayOfMonth : billCycleDay;
+                nextBillingDate = proposedDate.dayOfMonth().set(effectiveBillCycleDay).toDateTime();
+            }
         }
-    }
 
-    private BillingMode getBillingMode(final BillingModeType billingModeType) {
-        switch (billingModeType) {
-            case IN_ADVANCE:
-                return new InAdvanceBillingMode();
-            default:
-                return null;
-        }
+        return nextBillingDate.isAfter(nextEndDate) ? nextEndDate : nextBillingDate;
     }
 }
\ No newline at end of file
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
new file mode 100644
index 0000000..2e20fce
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -0,0 +1,240 @@
+/*
+* Copyright 2010-2011 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 com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class FixedPriceInvoiceItem implements InvoiceItem {
+    private final UUID id;
+    private final UUID invoiceId;
+    private final UUID subscriptionId;
+    private final String planName;
+    private final String phaseName;
+    private DateTime date;
+    private BigDecimal amount;
+    private final Currency currency;
+
+    public FixedPriceInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime date, BigDecimal amount, Currency currency) {
+        this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName,
+             date, amount, currency);
+    }
+
+    public FixedPriceInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime date, BigDecimal amount, Currency currency) {
+        this.id = id;
+        this.invoiceId = invoiceId;
+        this.subscriptionId = subscriptionId;
+        this.planName = planName;
+        this.phaseName = phaseName;
+        this.date = date;
+        this.amount = amount;
+        this.currency = currency;
+    }
+
+//    public FixedPriceInvoiceItem(FixedPriceInvoiceItem that, UUID invoiceId) {
+//        this.id = UUID.randomUUID();
+//        this.invoiceId = invoiceId;
+//        this.subscriptionId = that.getSubscriptionId();
+//        this.planName = that.getPlanName();
+//        this.phaseName = that.getPhaseName();
+//        this.date = that.getDate();
+//        this.amount = that.getAmount();
+//        this.currency = that.getCurrency();
+//    }
+
+    @Override
+    public InvoiceItem asCredit() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public UUID getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    @Override
+    public String getPlanName() {
+        return planName;
+    }
+
+    @Override
+    public String getPhaseName() {
+        return phaseName;
+    }
+
+    @Override
+    public String getDescription() {
+        return String.format("%s (fixed price) on %s", getPhaseName(), getDate().toString());
+    }
+
+    @Override
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = subscriptionId != null ? subscriptionId.hashCode() : 0;
+        result = 31 * result + (planName != null ? planName.hashCode() : 0);
+        result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
+        result = 31 * result + (date != null ? date.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        return result;
+    }
+
+    public DateTime getDate() {
+        return date;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public int compareTo(InvoiceItem item) {
+        if (!(item instanceof FixedPriceInvoiceItem)) {
+            return 1;
+        }
+
+        FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
+        int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+
+        if (compareSubscriptions == 0) {
+            return getDate().compareTo(that.getDate());
+        } else {
+            return compareSubscriptions;
+        }
+    }
+
+//    @Override
+//    public void subtract(InvoiceItem that) {
+//        // for now, do nothing -- fixed price items aren't subtracted
+//        return;
+//    }
+//
+//    @Override
+//    public boolean duplicates(InvoiceItem item) {
+//        if (!(item instanceof FixedPriceInvoiceItem)) {return false;}
+//
+//        FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
+//        if (!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
+//
+//        if (!this.planName.equals(that.getPlanName())) {return false;}
+//        if (!this.phaseName.equals(that.getPhaseName())) {return false;}
+//
+//        if (!compareNullableBigDecimal(this.getAmount(), that.getAmount())) {return false;}
+//
+//        if (!this.getCurrency().equals(that.getCurrency())) {return false;}
+//
+//        return (this.date.compareTo(that.getDate()) == 0);
+//    }
+//
+//    private boolean compareNullableBigDecimal(@Nullable BigDecimal value1, @Nullable BigDecimal value2) {
+//        if ((value1 == null) && (value2 != null)) {return false;}
+//        if ((value1 != null) && (value2 == null)) {return false;}
+//
+//        if ((value1 != null) && (value2 != null)) {
+//            if (value1.compareTo(value2) != 0) {return false;}
+//        }
+//
+//        return true;
+//    }
+//
+//    /**
+//     * indicates whether the supplied item is a cancelling item for this item
+//     * @param item  the InvoiceItem to be examined
+//     * @return true if the two invoice items cancel each other out (same subscription, same date range, sum of amounts = 0)
+//     */
+//    @Override
+//    public boolean cancels(InvoiceItem item) {
+//        if (!(item instanceof FixedPriceInvoiceItem)) {return false;}
+//
+//        FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
+//
+//        if(!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
+//        if(!this.getDate().equals(that.getDate())) {return false;}
+//
+//        if (!safeCheckForZeroSum(this.getAmount(), that.getAmount())) {return false;}
+//
+//        if(!this.getCurrency().equals(that.getCurrency())) {return false;}
+//
+//        return true;
+//    }
+//
+//    private boolean safeCheckForZeroSum(final BigDecimal value1, final BigDecimal value2) {
+//        if ((value1 == null) && (value2 == null)) {return true;}
+//        if ((value1 == null) ^ (value2 == null)) {return false;}
+//        return (value1.add(value2).compareTo(BigDecimal.ZERO) == 0);
+//    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+        sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+        sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+        sb.append("planName = ").append(planName).append(", ");
+        sb.append("phaseName = ").append(phaseName).append(", ");
+        sb.append("date = ").append(date.toString()).append(", ");
+
+        sb.append("amount = ");
+        if (amount == null) {
+            sb.append("null");
+        } else {
+            sb.append(amount.toString());
+        }
+
+        sb.append("}");
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) o;
+
+        if (amount != null ? !amount.equals(that.amount) : that.amount != null) return false;
+        if (currency != that.currency) return false;
+        if (date != null ? !date.equals(that.date) : that.date != null) return false;
+        if (phaseName != null ? !phaseName.equals(that.phaseName) : that.phaseName != null) return false;
+        if (planName != null ? !planName.equals(that.planName) : that.planName != null) return false;
+        if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
+            return false;
+
+        return true;
+    }
+}
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 b8f5c42..864aecf 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
@@ -23,111 +23,123 @@ import org.joda.time.Months;
 import org.joda.time.MutableDateTime;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
 
-public class InAdvanceBillingMode extends BillingModeBase {
-    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+public class InAdvanceBillingMode implements BillingMode {
+    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
     private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
 
     @Override
-    public DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
-        return calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
-    }
+    public List<RecurringInvoiceItemData> calculateInvoiceItemData(final DateTime startDate, final DateTime endDate,
+                                                                   final DateTime targetDate, final int billingCycleDay,
+                                                                   final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+        if (endDate == null) {
+            return calculateInvoiceItemData(startDate, targetDate, billingCycleDay, billingPeriod);
+        }
 
-    @Override
-    public DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
-        return calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
-    }
+        if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
 
-    @Override
-    protected BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod) {
-        int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
-        BigDecimal numberOfMonthsInPeriod = new BigDecimal(billingPeriod.getNumberOfMonths());
-        return new BigDecimal(numberOfMonths).divide(numberOfMonthsInPeriod, 0, ROUNDING_METHOD);
-    }
-
-    @Override
-    protected DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
-        int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+        List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
 
+        // beginning from the start date, find the first billing date
+        DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
 
-        MutableDateTime tmp = date.toMutableDateTime();
-        if (billingCycleDay > lastDayOfMonth) {
-            tmp.setDayOfMonth(lastDayOfMonth);
-        } else {
-            tmp.setDayOfMonth(billingCycleDay);
+        // add pro-ration item if needed
+        if (firstBillingCycleDate.isAfter(startDate)) {
+            BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
+            if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+                results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+            }
         }
-        DateTime proposedDate = tmp.toDateTime();
 
-        while (proposedDate.isBefore(date)) {
-            // STEPH could be an annual ?
-            proposedDate = proposedDate.plusMonths(1);
-        }
-        return proposedDate;
-    }
+        // add one item per billing period
+        DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, endDate, billingPeriod);
+        DateTime lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
+        int numberOfWholeBillingPeriods =  calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
+        int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
 
-    @Override
-    protected DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime proposedDate = billingCycleDate;
+        for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
+            results.add(new RecurringInvoiceItemData(firstBillingCycleDate.plusMonths(i * numberOfMonthsPerBillingPeriod),
+                                                     firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod), BigDecimal.ONE));
+        }
 
-        while (!proposedDate.isAfter(date)) {
-            proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
-
-            if (proposedDate.dayOfMonth().get() != billingCycleDay) {
-                int lastDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
-
-                if (lastDayOfMonth < billingCycleDay) {
-                    proposedDate = new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfMonth,
-                                                proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
-                                                proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
-                } else {
-                    proposedDate = new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay,
-                                                proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
-                                                proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
-                }
+        // check to see if a trailing pro-ration amount is needed
+        if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
+            BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
+            if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+                results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
             }
         }
 
-        return proposedDate;
+        return results;
     }
 
     @Override
-    protected DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime proposedDate = previousBillCycleDate;
-        proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+    public List<RecurringInvoiceItemData> calculateInvoiceItemData(final DateTime startDate,
+                                                                   final DateTime targetDate, final int billingCycleDay,
+                                                                   final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
 
-        if (!proposedDate.isBefore(date)) {return previousBillCycleDate;}
+        List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
 
-        while (proposedDate.isBefore(date)) {
-            proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+        // beginning from the start date, find the first billing date
+        DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+
+        // add pro-ration item if needed
+        if (firstBillingCycleDate.isAfter(startDate)) {
+            BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
+            if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+                results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+            }
         }
 
-        proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+        // add one item per billing period
+        DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, billingPeriod);
+        DateTime lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
+        int numberOfWholeBillingPeriods =  calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
+        int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
 
-        if (proposedDate.dayOfMonth().get() < billingCycleDay) {
-            int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
-            if (lastDayOfTheMonth < billingCycleDay) {
-                return new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth,
-                                    proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
-                                    proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
-            } else {
-                return new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay,
-                                    proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
-                                    proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
+        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)) {
+            BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
+            if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+                results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
             }
+        }
+
+        return results;
+    }
+
+    private DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
+        int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+
+        MutableDateTime tmp = date.toMutableDateTime();
+        if (billingCycleDay > lastDayOfMonth) {
+            tmp.setDayOfMonth(lastDayOfMonth);
         } else {
-            return proposedDate;
+            tmp.setDayOfMonth(billingCycleDay);
         }
+        DateTime proposedDate = tmp.toDateTime();
+
+        while (proposedDate.isBefore(date)) {
+            // STEPH could be an annual ?
+            proposedDate = proposedDate.plusMonths(1);
+        }
+        return proposedDate;
     }
 
-    @Override
-    protected BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime nextBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+    private BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, DateTime nextBillingCycleDate, final BillingPeriod billingPeriod) {
         DateTime previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
 
         int daysBetween = Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays();
-        if (daysBetween == 0) {
+        if (daysBetween <= 0) {
             return BigDecimal.ZERO;
         }
 
@@ -137,42 +149,86 @@ public class InAdvanceBillingMode extends BillingModeBase {
         return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
     }
 
-    @Override
-    protected BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod) {
-        // note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
-        DateTime nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
-        BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillThroughDate, nextBillThroughDate).getDays());
-
-        BigDecimal days = new BigDecimal(Days.daysBetween(previousBillThroughDate, endDate).getDays());
-
-        return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+    private int calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod) {
+        int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
+        int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+        return numberOfMonths / numberOfMonthsInPeriod;
     }
 
-    @Override
-    protected DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod) {
+    private DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod) {
         if (targetDate.isBefore(endDate)) {
             if (targetDate.isBefore(billCycleDate)) {
                 return billCycleDate;
             }
 
             int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-            DateTime startOfPeriod = billCycleDate;
-            DateTime startOfNextPeriod = billCycleDate.plusMonths(numberOfMonthsInPeriod);
+            int numberOfPeriods = 0;
+            DateTime proposedDate = billCycleDate;
 
-            while (isNotBetween(targetDate, startOfPeriod, startOfNextPeriod)) {
-                startOfPeriod = startOfNextPeriod;
-                startOfNextPeriod = startOfPeriod.plusMonths(numberOfMonthsInPeriod);
+            while (!proposedDate.isAfter(targetDate)) {
+                proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+                numberOfPeriods += 1;
             }
 
             // the current period includes the target date
             // check to see whether the end date truncates the period
-            if (endDate.isBefore(startOfNextPeriod)) {
+            if (endDate.isBefore(proposedDate)) {
                 return endDate;
             } else {
-                return startOfNextPeriod;
+                return proposedDate;
             }
         } else {
             return endDate;
         }
     }
+
+    private DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, BillingPeriod billingPeriod) {
+        if (targetDate.isBefore(billCycleDate)) {
+            return billCycleDate;
+        }
+
+        int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+        int numberOfPeriods = 0;
+        DateTime proposedDate = billCycleDate;
+
+        while (!proposedDate.isAfter(targetDate)) {
+            proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+            numberOfPeriods += 1;
+        }
+
+        return proposedDate;
+    }
+
+    private DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
+        DateTime proposedDate = previousBillCycleDate;
+
+        int numberOfPeriods = 0;
+        while (!proposedDate.isAfter(date)) {
+            proposedDate = previousBillCycleDate.plusMonths(numberOfPeriods * billingPeriod.getNumberOfMonths());
+            numberOfPeriods += 1;
+        }
+
+        proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+
+        if (proposedDate.dayOfMonth().get() < billingCycleDay) {
+            int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
+            if (lastDayOfTheMonth < billingCycleDay) {
+                return new MutableDateTime(proposedDate).dayOfMonth().set(lastDayOfTheMonth).toDateTime();
+            } else {
+                return new MutableDateTime(proposedDate).dayOfMonth().set(billingCycleDay).toDateTime();
+            }
+        } else {
+            return proposedDate;
+        }
+    }
+
+    private BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod) {
+        // note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
+        DateTime nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
+        BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillThroughDate, nextBillThroughDate).getDays());
+
+        BigDecimal days = new BigDecimal(Days.daysBetween(previousBillThroughDate, endDate).getDays());
+
+        return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+    }
 }
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
index 626993e..6bc6c9d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
@@ -18,11 +18,14 @@ package com.ning.billing.invoice.model;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
 import org.joda.time.DateTime;
 
 import javax.annotation.Nullable;
+import java.util.List;
 import java.util.UUID;
 
 public interface InvoiceGenerator {
-    public Invoice generateInvoice(UUID accountId, BillingEventSet events, @Nullable InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
+    public Invoice generateInvoice(UUID accountId, BillingEventSet events, @Nullable List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
index f35f8b1..683bbaf 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
@@ -18,13 +18,12 @@ package com.ning.billing.invoice.model;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import com.ning.billing.invoice.api.InvoiceItem;
 
 public class InvoiceItemList extends ArrayList<InvoiceItem> {
     private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
-    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
 
     public InvoiceItemList() {
         super();
@@ -40,64 +39,50 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
         BigDecimal total = BigDecimal.ZERO.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
 
         for (final InvoiceItem item : this) {
-            if (item.getRecurringAmount() != null) {
-                total = total.add(item.getRecurringAmount());
-            }
-
-            if (item.getFixedAmount() != null) {
-                total = total.add(item.getFixedAmount());
+            if (item.getAmount() != null) {
+                total = total.add(item.getAmount());
             }
         }
 
         return total.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
     }
 
-    public void removeCancellingPairs() {
-        List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
-
-        for (int firstItemIndex = 0; firstItemIndex < this.size(); firstItemIndex++) {
-            for (int secondItemIndex = firstItemIndex + 1; secondItemIndex < this.size(); secondItemIndex++) {
-                InvoiceItem firstItem = this.get(firstItemIndex);
-                InvoiceItem secondItem = this.get(secondItemIndex);
-                if (firstItem.cancels(secondItem)) {
-                    itemsToRemove.add(firstItem);
-                    itemsToRemove.add(secondItem);
-                }
-            }
-        }
-
-        this.removeAll(itemsToRemove);
-    }
-
-   /*
-    * removes items from the list that have a recurring amount of zero, but a recurring rate that is not zero
-    */
-    public void cleanupDuplicatedItems() {
-        Iterator<InvoiceItem> iterator = this.iterator();
-        while (iterator.hasNext()) {
-            InvoiceItem item = iterator.next();
-
-            boolean fixedAmountNull = (item.getFixedAmount() == null);
-            boolean recurringRateNull = (item.getRecurringRate() == null);
-            boolean recurringAmountZero = (item.getRecurringRate() !=null) && (item.getRecurringAmount().compareTo(BigDecimal.ZERO) == 0);
-
-            if (fixedAmountNull && (recurringRateNull || recurringAmountZero)) {
-                iterator.remove();
-            } else if (item.getEndDate() != null && item.getStartDate().compareTo(item.getEndDate()) == 0) {
-                iterator.remove();
-            } else if (item.getFixedAmount() == null && item.getRecurringAmount() == null) {
-                iterator.remove();
-            }
-        }
-    }
-
-    public boolean hasInvoiceItemForPhase(final String phaseName) {
-        for (final InvoiceItem item : this) {
-            if (item.getPhaseName().equals(phaseName)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
+//    public void removeCancellingPairs() {
+//        List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
+//
+//        for (int firstItemIndex = 0; firstItemIndex < this.size(); firstItemIndex++) {
+//            for (int secondItemIndex = firstItemIndex + 1; secondItemIndex < this.size(); secondItemIndex++) {
+//                InvoiceItem firstItem = this.get(firstItemIndex);
+//                InvoiceItem secondItem = this.get(secondItemIndex);
+//                if (firstItem.cancels(secondItem)) {
+//                    itemsToRemove.add(firstItem);
+//                    itemsToRemove.add(secondItem);
+//                }
+//            }
+//        }
+//
+//        this.removeAll(itemsToRemove);
+//    }
+
+//   /*
+//    * removes recurring items from the list that have a recurring amount of zero, but a recurring rate that is not zero
+//    */
+//    public void cleanupDuplicatedItems() {
+//        Iterator<InvoiceItem> iterator = this.iterator();
+//        while (iterator.hasNext()) {
+//            InvoiceItem item = iterator.next();
+//
+//            if (item instanceof RecurringInvoiceItem) {
+//                RecurringInvoiceItem that = (RecurringInvoiceItem) item;
+//                boolean recurringRateNull = (that.getRate() == null);
+//                boolean recurringAmountZero = (that.getAmount() !=null) && (that.getAmount().compareTo(BigDecimal.ZERO) == 0);
+//
+//                if (recurringRateNull || recurringAmountZero) {
+//                    iterator.remove();
+//                } else if (that.getEndDate() != null && that.getStartDate().compareTo(that.getEndDate()) == 0) {
+//                    iterator.remove();
+//                }
+//            }
+//        }
+//    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
index 7482465..554dc62 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
@@ -22,7 +22,7 @@ public class InvoicingConfiguration {
     private final static int roundingMethod = BigDecimal.ROUND_HALF_UP;
     private final static int numberOfDecimals = 4;
 
-    public static int getRoundingMethod() {
+    public static int getRoundingMode() {
         return roundingMethod;
     }
 
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
new file mode 100644
index 0000000..1c75112
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
@@ -0,0 +1,29 @@
+package com.ning.billing.invoice.model;
+
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+
+public class RecurringInvoiceItemData {
+    private final DateTime startDate;
+    private final DateTime endDate;
+    private final BigDecimal numberOfCycles;
+
+    public RecurringInvoiceItemData(DateTime startDate, DateTime endDate, BigDecimal numberOfCycles) {
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.numberOfCycles = numberOfCycles;
+    }
+
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    public DateTime getEndDate() {
+        return endDate;
+    }
+
+    public BigDecimal getNumberOfCycles() {
+        return numberOfCycles;
+    }
+}
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
new file mode 100644
index 0000000..5fc1118
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
@@ -0,0 +1,56 @@
+group FixedPriceInvoiceItemSqlDao;
+
+fields(prefix) ::= <<
+  <prefix>id,
+  <prefix>invoice_id,
+  <prefix>subscription_id,
+  <prefix>plan_name,
+  <prefix>phase_name,
+  <prefix>date,
+  <prefix>amount,
+  <prefix>currency
+>>
+
+getById() ::= <<
+  SELECT <fields()>
+  FROM fixed_invoice_items
+  WHERE id = :id;
+>>
+
+getInvoiceItemsByInvoice() ::= <<
+  SELECT <fields()>
+  FROM fixed_invoice_items
+  WHERE invoice_id = :invoiceId;
+>>
+
+getInvoiceItemsByAccount() ::= <<
+  SELECT <fields("rii.")>
+  FROM fixed_invoice_items rii
+  INNER JOIN invoices i ON i.id = rii.invoice_id
+  WHERE i.account_id = :accountId;
+>>
+
+getInvoiceItemsBySubscription() ::= <<
+  SELECT <fields()>
+  FROM fixed_invoice_items
+  WHERE subscription_id = :subscriptionId;
+>>
+
+create() ::= <<
+  INSERT INTO fixed_invoice_items(<fields()>)
+  VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName,
+         :date, :amount, :currency);
+>>
+
+update() ::= <<
+  UPDATE fixed_invoice_items
+  SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+      date = :date, amount = :amount, currency = :currency
+  WHERE id = :id;
+>>
+
+test() ::= <<
+  SELECT 1
+  FROM fixed_invoice_items;
+>>
+;
\ No newline at end of file
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
index 8a6fe38..a7aa135 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -31,8 +31,8 @@ getInvoicesByAccountAfterDate() ::= <<
 getInvoicesBySubscription() ::= <<
   SELECT <invoiceFields("i.")>
   FROM invoices i
-  LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
-  WHERE ii.subscription_id = :subscriptionId
+  LEFT JOIN recurring_invoice_items rii ON i.id = rii.invoice_id
+  WHERE rii.subscription_id = :subscriptionId
   GROUP BY <invoiceFields("i.")>;
 >>
 
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
index 28f3c89..f00e06c 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -1,5 +1,6 @@
 DROP TABLE IF EXISTS invoice_items;
-CREATE TABLE invoice_items (
+DROP TABLE IF EXISTS recurring_invoice_items;
+CREATE TABLE recurring_invoice_items (
   id char(36) NOT NULL,
   invoice_id char(36) NOT NULL,
   subscription_id char(36) NOT NULL,
@@ -7,13 +8,29 @@ CREATE TABLE invoice_items (
   phase_name varchar(50) NOT NULL,
   start_date datetime NOT NULL,
   end_date datetime NOT NULL,
-  recurring_amount numeric(10,4) NULL,
-  recurring_rate numeric(10,4) NULL,
-  fixed_amount numeric(10,4) NULL,
+  amount numeric(10,4) NULL,
+  rate numeric(10,4) NULL,
   currency char(3) NOT NULL,
+  reversed_item_id char(36),
   PRIMARY KEY(id)
 ) ENGINE=innodb;
-CREATE INDEX invoice_items_subscription_id ON invoice_items(subscription_id ASC);
+CREATE INDEX recurring_invoice_items_subscription_id ON recurring_invoice_items(subscription_id ASC);
+CREATE INDEX recurring_invoice_items_invoice_id ON recurring_invoice_items(invoice_id ASC);
+
+DROP TABLE IF EXISTS fixed_invoice_items;
+CREATE TABLE fixed_invoice_items (
+  id char(36) NOT NULL,
+  invoice_id char(36) NOT NULL,
+  subscription_id char(36) NOT NULL,
+  plan_name varchar(50) NOT NULL,
+  phase_name varchar(50) NOT NULL,
+  date datetime NOT NULL,
+  amount numeric(10,4) NULL,
+  currency char(3) NOT NULL,
+  PRIMARY KEY(id)
+) ENGINE=innodb;
+CREATE INDEX fixed_invoice_items_subscription_id ON fixed_invoice_items(subscription_id ASC);
+CREATE INDEX fixed_invoice_items_invoice_id ON fixed_invoice_items(invoice_id ASC);
 
 DROP TABLE IF EXISTS invoice_locking;
 CREATE TABLE invoice_locking (
@@ -56,7 +73,6 @@ GROUP BY invoice_id;
 DROP VIEW IF EXISTS invoice_item_summary;
 CREATE VIEW invoice_item_summary AS
 SELECT invoice_id,
-       CASE WHEN SUM(recurring_amount) IS NULL THEN 0 ELSE SUM(recurring_amount) END
-       + CASE WHEN SUM(fixed_amount) IS NULL THEN 0 ELSE SUM(fixed_amount) END AS amount_invoiced
-FROM invoice_items
+       CASE WHEN SUM(amount) IS NULL THEN 0 ELSE SUM(amount) END AS amount_invoiced
+FROM recurring_invoice_items
 GROUP BY invoice_id;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
index 4779495..6957ba0 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
@@ -35,7 +35,7 @@ import com.ning.billing.util.bus.DefaultBusService;
 
 public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
     protected InvoiceDao invoiceDao;
-    protected InvoiceItemSqlDao invoiceItemDao;
+    protected RecurringInvoiceItemSqlDao recurringInvoiceItemDao;
     protected InvoicePaymentSqlDao invoicePaymentDao;
     protected InvoiceModuleWithEmbeddedDb module;
 
@@ -56,7 +56,7 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
             invoiceDao = injector.getInstance(InvoiceDao.class);
             invoiceDao.test();
 
-            invoiceItemDao = module.getInvoiceItemSqlDao();
+            recurringInvoiceItemDao = module.getInvoiceItemSqlDao();
 
             invoicePaymentDao = module.getInvoicePaymentSqlDao();
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index 2369467..90c1eb8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -29,12 +29,13 @@ import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.model.BillingEventSet;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceItemList;
@@ -84,7 +85,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         UUID subscriptionId = UUID.randomUUID();
         DateTime startDate = new DateTime(2010, 1, 1, 0, 0, 0, 0);
         DateTime endDate = new DateTime(2010, 4, 1, 0, 0, 0, 0);
-        InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, new BigDecimal("21.00"), new BigDecimal("7.00"), null, Currency.USD);
+        InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
         invoice.addInvoiceItem(invoiceItem);
         invoiceDao.create(invoice);
 
@@ -181,7 +182,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate = new BigDecimal("9.0");
         BigDecimal amount = rate.multiply(new BigDecimal("3.0"));
 
-        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", targetDate, endDate, amount, rate, null, Currency.USD);
+        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", targetDate, endDate, amount, rate, Currency.USD);
         invoice.addInvoiceItem(item);
         invoiceDao.create(invoice);
 
@@ -276,17 +277,17 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
         DateTime endDate = startDate.plusMonths(1);
 
-        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, "test plan", "test A", startDate, endDate, rate1, rate1, null, Currency.USD);
-        invoiceItemDao.create(item1);
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, subscriptionId1, "test plan", "test A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, "test plan", "test B", startDate, endDate, rate2, rate2, null, Currency.USD);
-        invoiceItemDao.create(item2);
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, subscriptionId2, "test plan", "test B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item2);
 
-        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, "test plan", "test C", startDate, endDate, rate3, rate3, null, Currency.USD);
-        invoiceItemDao.create(item3);
+        RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, subscriptionId3, "test plan", "test C", startDate, endDate, rate3, rate3, Currency.USD);
+        recurringInvoiceItemDao.create(item3);
 
-        DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, "test plan", "test D", startDate, endDate, rate4, rate4, null, Currency.USD);
-        invoiceItemDao.create(item4);
+        RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, subscriptionId4, "test plan", "test D", startDate, endDate, rate4, rate4, Currency.USD);
+        recurringInvoiceItemDao.create(item4);
 
         // create invoice 2 (subscriptions 1-3)
         DefaultInvoice invoice2 = new DefaultInvoice(accountId, targetDate, Currency.USD);
@@ -297,14 +298,14 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         startDate = endDate;
         endDate = startDate.plusMonths(1);
 
-        DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
-        invoiceItemDao.create(item5);
+        RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, subscriptionId1, "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item5);
 
-        DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
-        invoiceItemDao.create(item6);
+        RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, subscriptionId2, "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item6);
 
-        DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, "test plan", "test phase C", startDate, endDate, rate3, rate3, null, Currency.USD);
-        invoiceItemDao.create(item7);
+        RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, subscriptionId3, "test plan", "test phase C", startDate, endDate, rate3, rate3, Currency.USD);
+        recurringInvoiceItemDao.create(item7);
 
         // check that each subscription returns the correct number of invoices
         List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1);
@@ -362,11 +363,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate1 = new BigDecimal("17.0");
         BigDecimal rate2 = new BigDecimal("42.0");
 
-        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
-        invoiceItemDao.create(item1);
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
-        invoiceItemDao.create(item2);
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item2);
 
         BigDecimal payment1 = new BigDecimal("48.0");
         InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
@@ -389,11 +390,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate1 = new BigDecimal("17.0");
         BigDecimal rate2 = new BigDecimal("42.0");
 
-        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
-        invoiceItemDao.create(item1);
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
-        invoiceItemDao.create(item2);
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item2);
 
         BigDecimal balance = invoiceDao.getAccountBalance(accountId);
         assertEquals(balance.compareTo(rate1.add(rate2)), 0);
@@ -427,11 +428,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate1 = new BigDecimal("17.0");
         BigDecimal rate2 = new BigDecimal("42.0");
 
-        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
-        invoiceItemDao.create(item1);
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
-        invoiceItemDao.create(item2);
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item2);
 
         DateTime upToDate;
         Collection<Invoice> invoices;
@@ -453,8 +454,8 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         BigDecimal rate3 = new BigDecimal("21.0");
 
-        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoice2.getId(), UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2, rate3, rate3, null, Currency.USD);
-        invoiceItemDao.create(item3);
+        RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2, rate3, rate3, Currency.USD);
+        recurringInvoiceItemDao.create(item3);
 
         upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
         invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
@@ -471,7 +472,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
      *
      */
     @Test
-    public void testInvoiceGenerationForImmediateChanges() {
+    public void testInvoiceGenerationForImmediateChanges() throws InvoiceApiException {
         InvoiceGenerator generator = new DefaultInvoiceGenerator();
 
         UUID accountId = UUID.randomUUID();
@@ -526,7 +527,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     }
 
     @Test
-    public void testInvoiceForFreeTrial() {
+    public void testInvoiceForFreeTrial() throws InvoiceApiException {
         DefaultPrice price = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
         MockInternationalPrice recurringPrice = new MockInternationalPrice(price);
         MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
@@ -544,12 +545,14 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime targetDate = buildDateTime(2011, 1, 15);
         InvoiceGenerator generator = new DefaultInvoiceGenerator();
         Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
-        assertEquals(invoice.getNumberOfItems(), 1);
+
+        // expect one pro-ration it and one full-period item
+        assertEquals(invoice.getNumberOfItems(), 2);
         assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
     }
 
     @Test
-    public void testInvoiceForEmptyEventSet() {
+    public void testInvoiceForEmptyEventSet() throws InvoiceApiException {
         InvoiceGenerator generator = new DefaultInvoiceGenerator();
         BillingEventSet events = new BillingEventSet();
         Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new DateTime(), Currency.USD);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
index b2c2f23..dae0725 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
@@ -16,25 +16,20 @@
 
 package com.ning.billing.invoice.dao;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
 import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
-import org.apache.commons.io.IOUtils;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import org.joda.time.DateTime;
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
-import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
 
 public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
     @Test
@@ -45,19 +40,18 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         DateTime endDate = new DateTime(2011, 11, 1, 0, 0, 0, 0);
         BigDecimal rate = new BigDecimal("20.00");
 
-        InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, rate, rate, rate, Currency.USD);
-        invoiceItemDao.create(item);
+        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, rate, rate, Currency.USD);
+        recurringInvoiceItemDao.create(item);
 
-        InvoiceItem thisItem = invoiceItemDao.getById(item.getId().toString());
+        RecurringInvoiceItem thisItem = (RecurringInvoiceItem) recurringInvoiceItemDao.getById(item.getId().toString());
         assertNotNull(thisItem);
         assertEquals(thisItem.getId(), item.getId());
         assertEquals(thisItem.getInvoiceId(), item.getInvoiceId());
         assertEquals(thisItem.getSubscriptionId(), item.getSubscriptionId());
         assertEquals(thisItem.getStartDate(), item.getStartDate());
         assertEquals(thisItem.getEndDate(), item.getEndDate());
-        assertEquals(thisItem.getRecurringAmount().compareTo(item.getRecurringAmount()), 0);
-        assertEquals(thisItem.getRecurringRate().compareTo(item.getRecurringRate()), 0);
-        assertEquals(thisItem.getFixedAmount().compareTo(item.getFixedAmount()), 0);
+        assertEquals(thisItem.getAmount().compareTo(item.getRate()), 0);
+        assertEquals(thisItem.getRate().compareTo(item.getRate()), 0);
         assertEquals(thisItem.getCurrency(), item.getCurrency());
     }
 
@@ -69,11 +63,11 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
 
         for (int i = 0; i < 3; i++) {
             UUID invoiceId = UUID.randomUUID();
-            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1), rate, rate, null, Currency.USD);
-            invoiceItemDao.create(item);
+            RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1), rate, rate, Currency.USD);
+            recurringInvoiceItemDao.create(item);
         }
 
-        List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsBySubscription(subscriptionId.toString());
+        List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsBySubscription(subscriptionId.toString());
         assertEquals(items.size(), 3);
     }
 
@@ -86,11 +80,11 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         for (int i = 0; i < 5; i++) {
             UUID subscriptionId = UUID.randomUUID();
             BigDecimal amount = rate.multiply(new BigDecimal(i + 1));
-            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), amount, amount, amount, Currency.USD);
-            invoiceItemDao.create(item);
+            RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), amount, amount, Currency.USD);
+            recurringInvoiceItemDao.create(item);
         }
 
-        List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
+        List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
         assertEquals(items.size(), 5);
     }
 
@@ -107,10 +101,10 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate = new BigDecimal("20.00");
 
         UUID subscriptionId = UUID.randomUUID();
-        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), rate, rate, rate, Currency.USD);
-        invoiceItemDao.create(item);
+        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), rate, rate, Currency.USD);
+        recurringInvoiceItemDao.create(item);
 
-        List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
+        List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
         assertEquals(items.size(), 1);
     }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
index 53eca25..c18e1bf 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -21,13 +21,12 @@ import java.io.IOException;
 import com.ning.billing.invoice.api.test.InvoiceTestApi;
 import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
 import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
-import com.ning.billing.util.glue.GlobalLockerModule;
+import com.ning.billing.invoice.dao.RecurringInvoiceItemSqlDao;
 import org.skife.jdbi.v2.IDBI;
 import com.ning.billing.account.glue.AccountModule;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.glue.EntitlementModule;
-import com.ning.billing.invoice.dao.InvoiceItemSqlDao;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 import com.ning.billing.util.glue.BusModule;
@@ -50,8 +49,8 @@ public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
         helper.stopMysql();
     }
 
-    public InvoiceItemSqlDao getInvoiceItemSqlDao() {
-        return dbi.onDemand(InvoiceItemSqlDao.class);
+    public RecurringInvoiceItemSqlDao getInvoiceItemSqlDao() {
+        return dbi.onDemand(RecurringInvoiceItemSqlDao.class);
     }
 
     public InvoicePaymentSqlDao getInvoicePaymentSqlDao() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index 11f8d62..f24e173 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
@@ -22,7 +22,6 @@ import com.ning.billing.catalog.MockPlan;
 import com.ning.billing.catalog.MockPlanPhase;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.InternationalPrice;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
@@ -34,11 +33,12 @@ import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.dao.MockSubscription;
 import com.ning.billing.invoice.model.BillingEventSet;
 import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceItemList;
 import org.joda.time.DateTime;
@@ -51,14 +51,13 @@ import java.util.UUID;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
 
 @Test(groups = {"fast", "invoicing", "invoiceGenerator"})
 public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     private final InvoiceGenerator generator = new DefaultInvoiceGenerator();
 
     @Test
-    public void testWithNullEventSetAndNullInvoiceSet() {
+    public void testWithNullEventSetAndNullInvoiceSet() throws InvoiceApiException {
         UUID accountId = UUID.randomUUID();
         Invoice invoice = generator.generateInvoice(accountId, new BillingEventSet(), new InvoiceItemList(), new DateTime(), Currency.USD);
 
@@ -66,7 +65,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testWithEmptyEventSet() {
+    public void testWithEmptyEventSet() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -77,7 +76,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testWithSingleMonthlyEvent() {
+    public void testWithSingleMonthlyEvent() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
@@ -97,13 +96,13 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
         assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 1);
+        assertEquals(invoice.getNumberOfItems(), 2);
         assertEquals(invoice.getTotalAmount(), TWENTY);
         assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
     }
 
     @Test
-    public void testWithSingleMonthlyEventWithLeadingProRation() {
+    public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
@@ -122,7 +121,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
         assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 1);
+        assertEquals(invoice.getNumberOfItems(), 2);
 
         BigDecimal expectedNumberOfBillingCycles;
         expectedNumberOfBillingCycles = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
@@ -131,7 +130,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testTwoMonthlySubscriptionsWithAlignedBillingDates() {
+    public void testTwoMonthlySubscriptionsWithAlignedBillingDates() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Plan plan1 = new MockPlan();
@@ -161,14 +160,13 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() {
+    public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Plan plan1 = new MockPlan();
         BigDecimal rate1 = FIVE;
         PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
-        
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
         BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1,phase1, 1);
         events.add(event1);
@@ -184,7 +182,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
         assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 2);
+        assertEquals(invoice.getNumberOfItems(), 4);
 
         BigDecimal numberOfCyclesEvent1;
         numberOfCyclesEvent1 = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
@@ -200,7 +198,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() {
+    public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Plan plan1 = new MockPlan();
@@ -227,12 +225,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
         assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 3);
+        assertEquals(invoice.getNumberOfItems(), 4);
         assertEquals(invoice.getTotalAmount(), rate1.add(rate2).add(TWO.multiply(rate3)).setScale(NUMBER_OF_DECIMALS));
     }
 
     @Test
-    public void testSingleEventWithExistingInvoice() {
+    public void testSingleEventWithExistingInvoice() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
@@ -258,7 +256,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testMultiplePlansWithUtterChaos() {
+    public void testMultiplePlansWithUtterChaos() throws InvoiceApiException {
         // plan 1: change of phase from trial to discount followed by immediate cancellation; (covers phase change, cancel, pro-ration)
         // plan 2: single plan that moves from trial to discount to evergreen; BCD = 10 (covers phase change)
         // plan 3: change of term from monthly (BCD = 20) to annual (BCD = 31; immediate)
@@ -419,7 +417,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testZeroDollarEvents() {
+    public void testZeroDollarEvents() throws InvoiceApiException {
         Plan plan = new MockPlan();
         PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
         BillingEventSet events = new BillingEventSet();
@@ -433,7 +431,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testEndDateIsCorrect() {
+    public void testEndDateIsCorrect() throws InvoiceApiException {
         Plan plan = new MockPlan();
         PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
         BillingEventSet events = new BillingEventSet();
@@ -442,71 +440,14 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         InvoiceGenerator invoiceGenerator = new DefaultInvoiceGenerator();
         Invoice invoice = invoiceGenerator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
-        InvoiceItem item = invoice.getInvoiceItems().get(0);
+        RecurringInvoiceItem item = (RecurringInvoiceItem) invoice.getInvoiceItems().get(0);
 
         // end date of the invoice item should be equal to exactly one month later
         assertEquals(item.getEndDate().compareTo(targetDate.plusMonths(1)), 0);
     }
 
     @Test
-    public void testImmediateChange() {
-        UUID accountId = UUID.randomUUID();
-        Subscription subscription = new MockSubscription();
-        Plan plan1 = new MockPlan("plan 1");
-        PlanPhase plan1phase1 = new MockPlanPhase(plan1, PhaseType.TRIAL);
-        PlanPhase plan1phase2 = new MockPlanPhase(plan1, PhaseType.DISCOUNT);
-
-        Plan plan2 = new MockPlan("plan 2");
-        PlanPhase plan2phase1 = new MockPlanPhase(plan2, PhaseType.TRIAL);
-        PlanPhase plan2phase2 = new MockPlanPhase(plan2, PhaseType.DISCOUNT);
-
-        InternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
-        InternationalPrice cheapPrice = new MockInternationalPrice(new DefaultPrice(ONE, Currency.USD));
-        InternationalPrice lessCheapPrice = new MockInternationalPrice(new DefaultPrice(FOUR, Currency.USD));
-
-        BillingEventSet events = new BillingEventSet();
-
-        BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-31T00:02:04.000Z"),
-                                                      plan1, plan1phase1,
-                                                      zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
-                                                      BillingModeType.IN_ADVANCE, "Test Event 1",
-                                                      SubscriptionTransitionType.CREATE);
-        BillingEvent event2 = new DefaultBillingEvent(subscription, new DateTime("2012-04-30T00:02:04.000Z"),
-                                                      plan1, plan1phase2,
-                                                      null, cheapPrice, BillingPeriod.MONTHLY, 1,
-                                                      BillingModeType.IN_ADVANCE, "Test Event 2",
-                                                      SubscriptionTransitionType.PHASE);
-        events.add(event1);
-        events.add(event2);
-
-        Invoice invoice1 = generator.generateInvoice(accountId, events, null, new DateTime("2012-01-31T00:02:04.000Z"), Currency.USD);
-        assertNotNull(invoice1);
-        assertEquals(invoice1.getNumberOfItems(), 1);
-
-        BillingEvent event3 = new DefaultBillingEvent(subscription, new DateTime("2012-01-31T00:02:04.000Z"),
-                                                      plan2, plan2phase1,
-                                                      zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
-                                                      BillingModeType.IN_ADVANCE, "Test Event 3",
-                                                      SubscriptionTransitionType.CHANGE);
-        BillingEvent event4 = new DefaultBillingEvent(subscription, new DateTime("2012-04-30T00:02:04.000Z"),
-                                                      plan2, plan2phase2,
-                                                      null, lessCheapPrice, BillingPeriod.MONTHLY, 1,
-                                                      BillingModeType.IN_ADVANCE, "Test Event 4",
-                                                      SubscriptionTransitionType.PHASE);
-
-        events.add(event3);
-        events.add(event4);
-
-        InvoiceItemList items = new InvoiceItemList(invoice1.getInvoiceItems());
-        Invoice invoice2 = generator.generateInvoice(accountId, events, items, new DateTime("2012-01-31T00:02:04.000Z"), Currency.USD);
-
-        assertNotNull(invoice2);
-        assertEquals(invoice2.getNumberOfItems(), 2);
-        assertEquals(invoice2.getInvoiceItems().get(0).getPlanName(), plan2.getName());
-    }
-
-    @Test
-    private void testFixedPriceLifeCycle() {
+    public void testFixedPriceLifeCycle() throws InvoiceApiException {
         UUID accountId = UUID.randomUUID();
         Subscription subscription = new MockSubscription();
         Plan plan = new MockPlan("plan 1");
@@ -537,7 +478,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Invoice invoice1 = generator.generateInvoice(accountId, events, null, new DateTime("2012-02-01T00:01:00.000-08:00"), Currency.USD);
         assertNotNull(invoice1);
         assertEquals(invoice1.getNumberOfItems(), 1);
-        assertEquals(invoice1.getInvoiceItems().get(0).getEndDate().compareTo(changeDate), 0);
+
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoice1.getInvoiceItems(), new DateTime("2012-04-05T00:01:00.000-08:00"), Currency.USD);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 1);
+        FixedPriceInvoiceItem item = (FixedPriceInvoiceItem) invoice2.getInvoiceItems().get(0);
+        assertEquals(item.getDate().compareTo(changeDate), 0);
    }
 
     private MockPlanPhase createMockMonthlyPlanPhase() {
@@ -549,13 +495,6 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
                                  null, BillingPeriod.MONTHLY);
     }
 
-    private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate,
-                                                     @Nullable final BigDecimal fixedRate, PhaseType phaseType) {
-        return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
-                                 new MockInternationalPrice(new DefaultPrice(fixedRate, Currency.USD)),
-                                 BillingPeriod.MONTHLY, phaseType);
-    }
-
     private MockPlanPhase createMockMonthlyPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
         return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
                                  null, BillingPeriod.MONTHLY, phaseType);
@@ -578,7 +517,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
     private void testInvoiceGeneration(final BillingEventSet events, final InvoiceItemList existingInvoiceItems,
                                        final DateTime targetDate, final int expectedNumberOfItems,
-                                       final BigDecimal expectedAmount) {
+                                       final BigDecimal expectedAmount) throws InvoiceApiException {
         Currency currency = Currency.USD;
         UUID accountId = UUID.randomUUID();
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, currency);
@@ -588,5 +527,6 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         existingInvoiceItems.addAll(invoice.getInvoiceItems());
         assertEquals(invoice.getTotalAmount(), expectedAmount);
     }
+
     // TODO: Jeff C -- how do we ensure that an annual add-on is properly aligned *at the end* with the base plan?
 }                      
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
index b38d076..bd8a38d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
@@ -39,14 +39,6 @@ public class ValidationProRationTests extends ProRationTestBase {
         return new InAdvanceBillingMode();
     }
 
-    protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
-        return getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod());
-    }
-
-    protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
-        return getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
-    }
-
     @Test(expectedExceptions = InvalidDateSequenceException.class)
     public void testTargetStartEnd() throws InvalidDateSequenceException {
         DateTime startDate = buildDateTime(2011, 1, 30);
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 afc9d1c..afe95fd 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
@@ -17,6 +17,7 @@
 package com.ning.billing.invoice.tests;
 
 
+import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.invoice.model.InvoicingConfiguration;
 import org.joda.time.DateTime;
 
@@ -24,7 +25,7 @@ import java.math.BigDecimal;
 
 public abstract class InvoicingTestBase {
     protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
-    protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+    protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
 
     protected static final BigDecimal ZERO = new BigDecimal("0.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal ONE_HALF = new BigDecimal("0.5").setScale(NUMBER_OF_DECIMALS);
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 174017b..a24b0c0 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
@@ -19,23 +19,25 @@ package com.ning.billing.invoice.tests;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.invoice.model.BillingMode;
 import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.model.RecurringInvoiceItemData;
 import org.joda.time.DateTime;
 
 import java.math.BigDecimal;
+import java.util.List;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.fail;
 
-public abstract class ProRationTestBase extends InvoicingTestBase{
+public abstract class ProRationTestBase extends InvoicingTestBase {
     protected abstract BillingMode getBillingMode();
     protected abstract BillingPeriod getBillingPeriod();
 
     protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException {
         try {
             BigDecimal numberOfBillingCycles;
-            numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod());
+            numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay);
 
-            assertEquals(numberOfBillingCycles, expectedValue);
+            assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0, "Actual: " + numberOfBillingCycles.toString() + "; expected: " + expectedValue.toString());
         } catch (InvalidDateSequenceException idse) {
             throw idse;
         } catch (Exception e) {
@@ -46,13 +48,35 @@ public abstract class ProRationTestBase extends InvoicingTestBase{
     protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException {
         try {
             BigDecimal numberOfBillingCycles;
-            numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
+            numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay);
 
-            assertEquals(numberOfBillingCycles, expectedValue);
+            assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0);
         } catch (InvalidDateSequenceException idse) {
             throw idse;
         } catch (Exception e) {
             fail("Unexpected exception: " + e.getMessage());
         }
     }
+
+    protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
+        List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
+
+        BigDecimal numberOfBillingCycles = ZERO;
+        for (RecurringInvoiceItemData item : items) {
+            numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
+        }
+
+        return numberOfBillingCycles;
+    }
+
+    protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
+        List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, targetDate, billingCycleDay, getBillingPeriod());
+
+        BigDecimal numberOfBillingCycles = ZERO;
+        for (RecurringInvoiceItemData item : items) {
+            numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
+        }
+
+        return numberOfBillingCycles;
+    }
 }
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index b722567..01bb03f 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -35,7 +35,7 @@ import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.payment.TestHelper;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.Bus.EventBusException;
@@ -67,14 +67,13 @@ public abstract class TestPaymentApi {
         final BigDecimal amount = new BigDecimal("10.00");
         final UUID subscriptionId = UUID.randomUUID();
 
-        invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
+        invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
                                                        subscriptionId,
                                                        "test plan", "test phase",
                                                        now,
                                                        now.plusMonths(1),
                                                        amount,
                                                        new BigDecimal("1.0"),
-                                                       null,
                                                        Currency.USD));
 
         List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()));
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
index 2bae34b..87ff44b 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -33,7 +33,7 @@ import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
 
 public class TestHelper {
     protected final AccountDao accountDao;
@@ -66,16 +66,18 @@ public class TestHelper {
         Invoice invoice = new DefaultInvoice(UUID.randomUUID(), account.getId(), new DateTime(), targetDate, currency);
 
         for (InvoiceItem item : items) {
-            invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
-                                               item.getSubscriptionId(),
-                                               item.getPlanName(),
-                                               item.getPhaseName(),
-                                               item.getStartDate(),
-                                               item.getEndDate(),
-                                               item.getRecurringAmount(),
-                                               item.getRecurringRate(),
-                                               item.getFixedAmount(),
-                                               item.getCurrency()));
+            if (item instanceof RecurringInvoiceItem) {
+                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+                invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+                                                               recurringInvoiceItem.getSubscriptionId(),
+                                                               recurringInvoiceItem.getPlanName(),
+                                                               recurringInvoiceItem.getPhaseName(),
+                                                               recurringInvoiceItem.getStartDate(),
+                                                               recurringInvoiceItem.getEndDate(),
+                                                               recurringInvoiceItem.getAmount(),
+                                                               recurringInvoiceItem.getRate(),
+                                                               recurringInvoiceItem.getCurrency()));
+            }
         }
         invoiceDao.create(invoice);
         return invoice;
@@ -85,7 +87,7 @@ public class TestHelper {
         final DateTime now = new DateTime(DateTimeZone.UTC);
         final UUID subscriptionId = UUID.randomUUID();
         final BigDecimal amount = new BigDecimal("10.00");
-        final InvoiceItem item = new DefaultInvoiceItem(null, subscriptionId, "test plan", "test phase", now, now.plusMonths(1), amount, new BigDecimal("1.0"), null, Currency.USD);
+        final InvoiceItem item = new RecurringInvoiceItem(null, subscriptionId, "test plan", "test phase", now, now.plusMonths(1), amount, new BigDecimal("1.0"), Currency.USD);
 
         return createTestInvoice(account, now, Currency.USD, item);
     }